From 2d56e80ed22c041ceb7d84544d7f37ae9f8ccc50 Mon Sep 17 00:00:00 2001 From: chankyin Date: Fri, 6 Oct 2023 18:01:23 +0800 Subject: [PATCH 1/6] feat(linker): add LinkRule linker --- .gitattributes | 3 + Makefile | 35 ++- charts/kelemetry/crds | 1 + .../kelemetry.kubewharf.io_linkrules.yaml | 163 ++++++++++ crds/samples/helm-release-link-rule.yaml | 17 ++ go.mod | 11 +- go.sum | 25 ++ hack/boilerplate.txt | 14 + hack/typos.toml | 4 +- .../linker/annotation}/linker.go | 0 .../linker/annotation}/schema.go | 0 .../linker/owner}/linker.go | 0 pkg/aggregator/linker/rule/linker.go | 278 ++++++++++++++++++ pkg/crds/apis/register.go | 15 + pkg/crds/apis/v1alpha1/doc.go | 16 + pkg/crds/apis/v1alpha1/register.go | 46 +++ pkg/crds/apis/v1alpha1/types_linkrule.go | 114 +++++++ pkg/crds/apis/v1alpha1/util/util.go | 104 +++++++ .../apis/v1alpha1/zz_generated.deepcopy.go | 146 +++++++++ .../client/clientset/versioned/clientset.go | 119 ++++++++ .../versioned/fake/clientset_generated.go | 84 ++++++ .../client/clientset/versioned/fake/doc.go | 18 ++ .../clientset/versioned/fake/register.go | 57 ++++ .../client/clientset/versioned/scheme/doc.go | 18 ++ .../clientset/versioned/scheme/register.go | 57 ++++ .../typed/apis/v1alpha1/apis_client.go | 106 +++++++ .../versioned/typed/apis/v1alpha1/doc.go | 18 ++ .../versioned/typed/apis/v1alpha1/fake/doc.go | 18 ++ .../apis/v1alpha1/fake/fake_apis_client.go | 39 +++ .../typed/apis/v1alpha1/fake/fake_linkrule.go | 135 +++++++++ .../apis/v1alpha1/generated_expansion.go | 19 ++ .../versioned/typed/apis/v1alpha1/linkrule.go | 181 ++++++++++++ .../externalversions/apis/interface.go | 44 +++ .../apis/v1alpha1/interface.go | 43 +++ .../apis/v1alpha1/linkrule.go | 98 ++++++ .../informers/externalversions/factory.go | 259 ++++++++++++++++ .../informers/externalversions/generic.go | 60 ++++ .../internalinterfaces/factory_interfaces.go | 39 +++ .../apis/v1alpha1/expansion_generated.go | 21 ++ .../client/listers/apis/v1alpha1/linkrule.go | 67 +++++ pkg/imports.go | 5 +- pkg/k8s/k8s.go | 13 + pkg/k8s/k8smock.go | 15 + pkg/util/cache/ttl.go | 14 +- pkg/util/reflect/reflect.go | 2 + pkg/util/zconstants/link.go | 17 ++ 46 files changed, 2549 insertions(+), 9 deletions(-) create mode 100644 .gitattributes create mode 120000 charts/kelemetry/crds create mode 100644 crds/config/kelemetry.kubewharf.io_linkrules.yaml create mode 100644 crds/samples/helm-release-link-rule.yaml create mode 100644 hack/boilerplate.txt rename pkg/{annotationlinker => aggregator/linker/annotation}/linker.go (100%) rename pkg/{annotationlinker => aggregator/linker/annotation}/schema.go (100%) rename pkg/{ownerlinker => aggregator/linker/owner}/linker.go (100%) create mode 100644 pkg/aggregator/linker/rule/linker.go create mode 100644 pkg/crds/apis/register.go create mode 100644 pkg/crds/apis/v1alpha1/doc.go create mode 100644 pkg/crds/apis/v1alpha1/register.go create mode 100644 pkg/crds/apis/v1alpha1/types_linkrule.go create mode 100644 pkg/crds/apis/v1alpha1/util/util.go create mode 100644 pkg/crds/apis/v1alpha1/zz_generated.deepcopy.go create mode 100644 pkg/crds/client/clientset/versioned/clientset.go create mode 100644 pkg/crds/client/clientset/versioned/fake/clientset_generated.go create mode 100644 pkg/crds/client/clientset/versioned/fake/doc.go create mode 100644 pkg/crds/client/clientset/versioned/fake/register.go create mode 100644 pkg/crds/client/clientset/versioned/scheme/doc.go create mode 100644 pkg/crds/client/clientset/versioned/scheme/register.go create mode 100644 pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/apis_client.go create mode 100644 pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/doc.go create mode 100644 pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/fake/doc.go create mode 100644 pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_apis_client.go create mode 100644 pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_linkrule.go create mode 100644 pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/generated_expansion.go create mode 100644 pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/linkrule.go create mode 100644 pkg/crds/client/informers/externalversions/apis/interface.go create mode 100644 pkg/crds/client/informers/externalversions/apis/v1alpha1/interface.go create mode 100644 pkg/crds/client/informers/externalversions/apis/v1alpha1/linkrule.go create mode 100644 pkg/crds/client/informers/externalversions/factory.go create mode 100644 pkg/crds/client/informers/externalversions/generic.go create mode 100644 pkg/crds/client/informers/externalversions/internalinterfaces/factory_interfaces.go create mode 100644 pkg/crds/client/listers/apis/v1alpha1/expansion_generated.go create mode 100644 pkg/crds/client/listers/apis/v1alpha1/linkrule.go diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..8d0d4925 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +* text=auto +pkg/crds/client linguist-generated=true +zz_generated*.go linguist-generated=true diff --git a/Makefile b/Makefile index 5b928d5a..1daa3070 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ endif LINKER_WORKER_COUNT ?= 1 -CONTROLLERS ?= audit-consumer,audit-producer,audit-webhook,event-informer,annotation-linker,owner-linker,resource-object-tag,resource-event-tag,diff-decorator,diff-controller,diff-api,pprof,jaeger-storage-plugin,jaeger-redirect-server,kelemetrix +CONTROLLERS ?= audit-consumer,audit-producer,audit-webhook,event-informer,annotation-linker,owner-linker,rule-linker,resource-object-tag,resource-event-tag,diff-decorator,diff-controller,diff-api,pprof,jaeger-storage-plugin,jaeger-redirect-server,kelemetrix ifeq ($(CONTROLLERS),) ENABLE_ARGS ?= else @@ -181,3 +181,36 @@ fmt: golines -m140 --base-formatter=gofumpt -w . goimports -l -w . gci write -s standard -s default -s 'prefix(github.com/kubewharf/kelemetry)' . + +generate: + go run sigs.k8s.io/controller-tools/cmd/controller-gen \ + crd \ + paths=./pkg/crds/apis/... \ + output:crd:dir=./crds/config + go run k8s.io/code-generator/cmd/deepcopy-gen \ + -o /tmp/kelemetry-gen/github.com/kubewharf/kelemetry \ + --input-dirs=./pkg/crds/apis/v1alpha1 \ + --output-file-base=zz_generated.deepcopy \ + -h ./hack/boilerplate.txt + go run k8s.io/code-generator/cmd/client-gen \ + -o /tmp/kelemetry-gen \ + --input=github.com/kubewharf/kelemetry/pkg/crds/apis/v1alpha1 \ + --input-base= \ + --output-package=github.com/kubewharf/kelemetry/pkg/crds/client/clientset \ + --clientset-name=versioned \ + -h ./hack/boilerplate.txt + go run k8s.io/code-generator/cmd/lister-gen \ + -o /tmp/kelemetry-gen \ + --input-dirs=github.com/kubewharf/kelemetry/pkg/crds/apis/v1alpha1 \ + --output-package=github.com/kubewharf/kelemetry/pkg/crds/client/listers \ + -h ./hack/boilerplate.txt + go run k8s.io/code-generator/cmd/informer-gen \ + -o /tmp/kelemetry-gen \ + --input-dirs=github.com/kubewharf/kelemetry/pkg/crds/apis/v1alpha1 \ + --output-package=github.com/kubewharf/kelemetry/pkg/crds/client/informers \ + --versioned-clientset-package=github.com/kubewharf/kelemetry/pkg/crds/client/clientset/versioned \ + --listers-package=github.com/kubewharf/kelemetry/pkg/crds/client/listers \ + -h ./hack/boilerplate.txt + cp -r /tmp/kelemetry-gen/github.com/kubewharf/kelemetry/pkg/crds -T pkg/crds + rm -r /tmp/kelemetry-gen + $(MAKE) fmt diff --git a/charts/kelemetry/crds b/charts/kelemetry/crds new file mode 120000 index 00000000..17e154c7 --- /dev/null +++ b/charts/kelemetry/crds @@ -0,0 +1 @@ +../../crds/config \ No newline at end of file diff --git a/crds/config/kelemetry.kubewharf.io_linkrules.yaml b/crds/config/kelemetry.kubewharf.io_linkrules.yaml new file mode 100644 index 00000000..4aecc644 --- /dev/null +++ b/crds/config/kelemetry.kubewharf.io_linkrules.yaml @@ -0,0 +1,163 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.0 + name: linkrules.kelemetry.kubewharf.io +spec: + group: kelemetry.kubewharf.io + names: + kind: LinkRule + listKind: LinkRuleList + plural: linkrules + singular: linkrule + scope: Cluster + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: "LinkRule instructs Kelemetry to display multiple objects (the + \"source\" and the \"target\") in the same trace by looking up the \"target\" + when the span of the \"source\" object gets created. \n LinkRule is bidirectional. + Once the link is recorded, searching \"source\" or \"target\" would both + display the other trace in the link as long as the link is not filtered + out by tfconfig." + 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 + link: + description: Link specifies how the two objects are linked together. + properties: + class: + description: "If Class is non empty, a pseudospan named the given + value is inserted in the hierarchy between the target and source + objects. \n Multiple links with the same nonempty Class share the + same pseudospan." + type: string + targetRole: + default: Child + description: "TargetRole selects the display position of the target + object. \n One of the target objects will be arbitrarily selected + if there are multiple links with preferTargetParent=true." + type: string + required: + - targetRole + type: object + metadata: + type: object + sourceFilter: + description: SourceFilter determines whether a source object matches this + rule. + properties: + resources: + description: Resources are the possible resource types that a child + object may belong to. + items: + description: GroupResource specifies a Group and a Resource, but + does not force a version. This is useful for identifying concepts + during lookup stages without having partially valid types + properties: + group: + type: string + resource: + type: string + required: + - group + - resource + type: object + type: array + selector: + description: Selector selects matching child objects by label. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the key + and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship to + a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + targetTemplate: + description: TargetTemplate indicates how to find the target object from + a matched source object. + properties: + clusterTemplate: + description: "ClusterTemplate is a Go text template string to compute + the cluster name of the target object. \n The context of the template + is the child object that matched the rule. \n If empty or unspecified, + uses the cluster of the child object." + type: string + group: + type: string + nameTemplate: + description: "NameTemplate is a Go text template string to compute + the target object name. \n The context of the template is the child + object that matched the rule." + type: string + namespaceTemplate: + description: "NamespaceTemplate is a Go text template string to compute + the target object name. \n If the namespace is empty, the target + object is expected to be cluster-scoped." + type: string + resource: + type: string + version: + type: string + required: + - group + - nameTemplate + - namespaceTemplate + - resource + - version + type: object + required: + - link + - sourceFilter + - targetTemplate + type: object + served: true + storage: true diff --git a/crds/samples/helm-release-link-rule.yaml b/crds/samples/helm-release-link-rule.yaml new file mode 100644 index 00000000..f7b66e3c --- /dev/null +++ b/crds/samples/helm-release-link-rule.yaml @@ -0,0 +1,17 @@ +apiVersion: kelemetry.kubewharf.io/v1alpha1 +kind: LinkRule +metadata: + name: helm-release +sourceFilter: + selector: + matchLabels: + app.kubernetes.io/managed-by: Helm +targetTemplate: + group: "" + version: v1 + resource: secrets + namespaceTemplate: '{{.metadata.annotations["meta.helm.sh/release-namespace"]}}' + nameTemplate: '{{.metadata.annotations["meta.helm.sh/release-name"]}}' +link: + targetRole: Parent + class: templates diff --git a/go.mod b/go.mod index 8f84b9dc..0be87e26 100644 --- a/go.mod +++ b/go.mod @@ -50,7 +50,7 @@ require ( github.com/eapache/queue v1.1.0 // indirect github.com/emicklei/go-restful/v3 v3.10.1 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect - github.com/fatih/color v1.14.1 // indirect + github.com/fatih/color v1.15.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect @@ -61,6 +61,7 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/gobuffalo/flect v1.0.2 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/gocql/gocql v1.3.2 // indirect github.com/gogo/googleapis v1.4.1 // indirect @@ -84,6 +85,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/imdario/mergo v0.3.13 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect github.com/jcmturner/gofork v1.7.6 // indirect @@ -115,6 +117,7 @@ require ( github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/spf13/afero v1.9.5 // indirect github.com/spf13/cast v1.5.1 // indirect + github.com/spf13/cobra v1.7.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect @@ -132,12 +135,14 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.3.0 // indirect golang.org/x/crypto v0.13.0 // indirect + golang.org/x/mod v0.10.0 // indirect golang.org/x/net v0.15.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect golang.org/x/sys v0.12.0 // indirect golang.org/x/term v0.12.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.8.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect @@ -147,7 +152,11 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.27.1 // indirect + k8s.io/code-generator v0.27.3 // indirect + k8s.io/gengo v0.0.0-20220902162205-c0856e24416d // indirect k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect + sigs.k8s.io/controller-tools v0.12.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect diff --git a/go.sum b/go.sum index 4011b2f8..37ea7ccf 100644 --- a/go.sum +++ b/go.sum @@ -90,6 +90,7 @@ github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03V github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/crossdock/crossdock-go v0.0.0-20160816171116-049aabb0122b h1:WR1qVJzbvrVywhAk4kMQKRPx09AZVI0NdEdYs59iHcA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -126,6 +127,8 @@ github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -140,6 +143,7 @@ github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SU github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= @@ -160,6 +164,8 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91 github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA= +github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gocql/gocql v1.3.2 h1:ox3T+R7VFibHSIGxRkuUi1uIvAv8jBHCWxc+9aFQ/LA= @@ -225,6 +231,7 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -278,6 +285,7 @@ github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jaegertracing/jaeger v1.49.0 h1:3XI8ZOK6oncyoAxCiKakC9sRaeDTdDwmxmaSN+KQBo8= github.com/jaegertracing/jaeger v1.49.0/go.mod h1:gU5wCSJSwk5MJHO/uWEBYKklT80oqEc2ixj3ssbwloQ= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= @@ -309,6 +317,7 @@ github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZX github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -377,6 +386,7 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqn github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -390,6 +400,7 @@ github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -520,6 +531,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -688,6 +701,7 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -705,6 +719,7 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= +golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -837,12 +852,19 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.28.1 h1:i+0O8k2NPBCPYaMB+uCkseEbawEt/eFaiRqUx8aB108= k8s.io/api v0.28.1/go.mod h1:uBYwID+66wiL28Kn2tBjBYQdEU0Xk0z5qF8bIBqk/Dg= +k8s.io/apiextensions-apiserver v0.27.1 h1:Hp7B3KxKHBZ/FxmVFVpaDiXI6CCSr49P1OJjxKO6o4g= +k8s.io/apiextensions-apiserver v0.27.1/go.mod h1:8jEvRDtKjVtWmdkhOqE84EcNWJt/uwF8PC4627UZghY= k8s.io/apimachinery v0.28.2 h1:KCOJLrc6gu+wV1BYgwik4AF4vXOlVJPdiqn0yAWWwXQ= k8s.io/apimachinery v0.28.2/go.mod h1:RdzF87y/ngqk9H4z3EL2Rppv5jj95vGS/HaFXrLDApU= k8s.io/apiserver v0.28.1 h1:dw2/NKauDZCnOUAzIo2hFhtBRUo6gQK832NV8kuDbGM= k8s.io/apiserver v0.28.1/go.mod h1:d8aizlSRB6yRgJ6PKfDkdwCy2DXt/d1FDR6iJN9kY1w= k8s.io/client-go v0.28.1 h1:pRhMzB8HyLfVwpngWKE8hDcXRqifh1ga2Z/PU9SXVK8= k8s.io/client-go v0.28.1/go.mod h1:pEZA3FqOsVkCc07pFVzK076R+P/eXqsgx5zuuRWukNE= +k8s.io/code-generator v0.27.3 h1:JRhRQkzKdQhHmv9s5f7vuqveL8qukAQ2IqaHm6MFspM= +k8s.io/code-generator v0.27.3/go.mod h1:DPung1sI5vBgn4AGKtlPRQAyagj/ir/4jI55ipZHVww= +k8s.io/gengo v0.0.0-20220902162205-c0856e24416d h1:U9tB195lKdzwqicbJvyJeOXV7Klv+wNAWENRnXEGi08= +k8s.io/gengo v0.0.0-20220902162205-c0856e24416d/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= @@ -853,9 +875,12 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/controller-tools v0.12.0 h1:TY6CGE6+6hzO7hhJFte65ud3cFmmZW947jajXkuDfBw= +sigs.k8s.io/controller-tools v0.12.0/go.mod h1:rXlpTfFHZMpZA8aGq9ejArgZiieHd+fkk/fTatY8A2M= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/hack/boilerplate.txt b/hack/boilerplate.txt new file mode 100644 index 00000000..143af851 --- /dev/null +++ b/hack/boilerplate.txt @@ -0,0 +1,14 @@ +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + diff --git a/hack/typos.toml b/hack/typos.toml index c9406868..c2ab504e 100644 --- a/hack/typos.toml +++ b/hack/typos.toml @@ -1,5 +1,5 @@ [files] -extend-exclude = ["go.mod", "go.sum"] +extend-exclude = ["go.mod", "go.sum", "crds/config/*.yaml"] [default.extend-identifiers] -O_WRONLY = "O_WRONLY" +ANDed = "ANDed" diff --git a/pkg/annotationlinker/linker.go b/pkg/aggregator/linker/annotation/linker.go similarity index 100% rename from pkg/annotationlinker/linker.go rename to pkg/aggregator/linker/annotation/linker.go diff --git a/pkg/annotationlinker/schema.go b/pkg/aggregator/linker/annotation/schema.go similarity index 100% rename from pkg/annotationlinker/schema.go rename to pkg/aggregator/linker/annotation/schema.go diff --git a/pkg/ownerlinker/linker.go b/pkg/aggregator/linker/owner/linker.go similarity index 100% rename from pkg/ownerlinker/linker.go rename to pkg/aggregator/linker/owner/linker.go diff --git a/pkg/aggregator/linker/rule/linker.go b/pkg/aggregator/linker/rule/linker.go new file mode 100644 index 00000000..3ea05080 --- /dev/null +++ b/pkg/aggregator/linker/rule/linker.go @@ -0,0 +1,278 @@ +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rulelinker + +import ( + "context" + "fmt" + "sync" + "sync/atomic" + "time" + + "github.com/sirupsen/logrus" + "github.com/spf13/pflag" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + "k8s.io/utils/clock" + + "github.com/kubewharf/kelemetry/pkg/aggregator/linker" + kelemetryv1a1util "github.com/kubewharf/kelemetry/pkg/crds/apis/v1alpha1/util" + "github.com/kubewharf/kelemetry/pkg/crds/client/informers/externalversions/apis/v1alpha1" + kelemetryv1a1listers "github.com/kubewharf/kelemetry/pkg/crds/client/listers/apis/v1alpha1" + "github.com/kubewharf/kelemetry/pkg/k8s" + "github.com/kubewharf/kelemetry/pkg/k8s/discovery" + "github.com/kubewharf/kelemetry/pkg/k8s/objectcache" + "github.com/kubewharf/kelemetry/pkg/manager" + utilobject "github.com/kubewharf/kelemetry/pkg/util/object" + reflectutil "github.com/kubewharf/kelemetry/pkg/util/reflect" + "github.com/kubewharf/kelemetry/pkg/util/zconstants" +) + +func init() { + manager.Global.ProvideListImpl("rule-linker", manager.Ptr(&Controller{}), &manager.List[linker.Linker]{}) +} + +type Options struct { + Enable bool + InformerTtl time.Duration + CacheSyncTimeout time.Duration +} + +func (options *Options) Setup(fs *pflag.FlagSet) { + fs.BoolVar(&options.Enable, "rule-linker-enable", false, "enable rule linker") + fs.DurationVar( + &options.InformerTtl, + "rule-linker-informer-ttl", + time.Hour, + "duration for which the LinkRule lister is unused before the reflector is closed", + ) + fs.DurationVar( + &options.CacheSyncTimeout, + "rule-linker-sync-timeout", + time.Second*5, + "duration to wait for the initial LinkRule list to become ready; timeout has no effect other than potentially missing link rules in some objects", + ) +} + +func (options *Options) EnableFlag() *bool { return &options.Enable } + +type Controller struct { + options Options + Logger logrus.FieldLogger + Clock clock.Clock + Clients k8s.Clients + Discovery discovery.DiscoveryCache + ObjectCache *objectcache.ObjectCache + + listersMu sync.RWMutex + listers map[string]*listerEntry +} + +type listerEntry struct { + readyCh <-chan struct{} + lister kelemetryv1a1listers.LinkRuleLister + err error + resetTimer func(time.Duration) +} + +var _ manager.Component = &Controller{} + +func (ctrl *Controller) Options() manager.Options { return &ctrl.options } +func (ctrl *Controller) Init() error { + ctrl.listers = make(map[string]*listerEntry) + return nil +} +func (ctrl *Controller) Start(ctx context.Context) error { return nil } +func (ctrl *Controller) Close(ctx context.Context) error { return nil } + +var _ linker.Linker = &Controller{} + +func (ctrl *Controller) LinkerName() string { return "rule-linker" } + +func (ctrl *Controller) Lookup(ctx context.Context, object utilobject.Rich) ([]linker.LinkerResult, error) { + logger := ctrl.Logger.WithFields(object.AsFields("object")) + + raw := object.Raw + if raw == nil { + logger.Debug("Fetching dynamic object") + + var err error + raw, err = ctrl.ObjectCache.Get(ctx, object.VersionedKey) + if err != nil { + return nil, fmt.Errorf("cannot fetch object value: %w", err) + } + + if raw == nil { + return nil, fmt.Errorf("object does not exist") + } + } + + lister, err := ctrl.getLister(ctx, object.Cluster) + if err != nil { + return nil, fmt.Errorf("lister is unavailable for cluster %q", object.Cluster) + } + + var results []linker.LinkerResult + + rules, err := lister.List(labels.Everything()) + if err != nil { + return nil, fmt.Errorf("cannot list rules") + } + + for _, rule := range rules { + matched, err := kelemetryv1a1util.TestObject(object.Key, raw, &rule.SourceFilter) + if err != nil { + logger.WithField("rule", rule.Name).WithError(err).Error("error testing object for rule match") + continue + } + + if !matched { + continue + } + + parentRef, err := kelemetryv1a1util.GenerateTarget(&rule.TargetTemplate, object, raw) + if err != nil { + return nil, fmt.Errorf("cannot infer parent object: %w", err) + } + + logger.WithField("rule", rule.Name).WithFields(parentRef.AsFields("parent")).Debug("Testing if parent exists") + parentRaw, err := ctrl.ObjectCache.Get(ctx, parentRef) + if err != nil { + return nil, fmt.Errorf("cannot get parent specified by rule: %w", err) + } + + role, err := zconstants.LinkRoleValueFromTargetRole(rule.Link.TargetRole) + if err != nil { + return nil, fmt.Errorf("cannot parse target role: %w", err) + } + + results = append(results, linker.LinkerResult{ + Object: utilobject.Rich{ + VersionedKey: parentRef, + Uid: parentRaw.GetUID(), + Raw: parentRaw, + }, + Role: role, + }) + } + + return results, nil +} + +func (ctrl *Controller) getLister(ctx context.Context, cluster string) (kelemetryv1a1listers.LinkRuleLister, error) { + if lister, exists := ctrl.tryGetLister(cluster); exists { + return lister.touch(ctrl.options.InformerTtl) + } + + entry, readyCh := ctrl.setPendingLister(cluster) + if readyCh == nil { + return entry.touch(ctrl.options.InformerTtl) + } + + defer close(readyCh) + + client, err := ctrl.Clients.Cluster(cluster) + if err != nil { + err := fmt.Errorf("cannot initialize clients for cluster %q: %w", cluster, err) + entry.err = err + return nil, err + } + + informer := v1alpha1.NewLinkRuleInformer(client.KelemetryClient(), 0, cache.Indexers{}) + + entry.lister = kelemetryv1a1listers.NewLinkRuleLister(informer.GetIndexer()) + + timerCh, resetFunc := startTimer(ctrl.Clock, ctrl.options.InformerTtl) + go informer.Run(timerCh) + + waitCtx, waitCtxCancelFunc := context.WithTimeout(ctx, ctrl.options.CacheSyncTimeout) + defer waitCtxCancelFunc() + populated := cache.WaitForCacheSync(waitCtx.Done(), informer.HasSynced) + if !populated { + ctrl.Logger.WithField("cluster", cluster).Warn("LinkRule lister has not synced, will continue as usual") + } + + entry.resetTimer = resetFunc + + return entry.lister, nil +} + +func (ctrl *Controller) tryGetLister(cluster string) (*listerEntry, bool) { + ctrl.listersMu.RLock() + defer ctrl.listersMu.RUnlock() + lister, exists := ctrl.listers[cluster] + return lister, exists +} + +func (ctrl *Controller) setPendingLister(cluster string) (_ *listerEntry, _readyCh chan<- struct{}) { + ctrl.listersMu.Lock() + defer ctrl.listersMu.Unlock() + + lister, exists := ctrl.listers[cluster] + if exists { + return lister, nil + } + + readyCh := make(chan struct{}) + + entry := &listerEntry{ + readyCh: readyCh, + } + ctrl.listers[cluster] = entry + + return entry, readyCh +} + +func (entry *listerEntry) touch(ttl time.Duration) (kelemetryv1a1listers.LinkRuleLister, error) { + <-entry.readyCh + if entry.err != nil { + return nil, entry.err + } + + entry.resetTimer(ttl) + return entry.lister, nil +} + +// a specialized timer optimized for frequent resets and only supports strictly increasing resets. +func startTimer(clock clock.Clock, initialTtl time.Duration) (<-chan struct{}, func(time.Duration)) { + timerCh := make(chan struct{}) + + var targetAtomic atomic.Pointer[time.Time] + targetAtomic.Store(reflectutil.Box(clock.Now().Add(initialTtl))) + + go func() { + target := targetAtomic.Load() + + for { + remaining := (*target).Sub(clock.Now()) + if remaining <= 0 { + break + } + + clock.Sleep(remaining) + newTarget := targetAtomic.Load() + if newTarget == target { + break // break without checking clock.Now() to ensure we don't sleep on the same target multiple times + } + target = newTarget + } + + timerCh <- struct{}{} + }() + + return timerCh, func(newTtl time.Duration) { + targetAtomic.Store(reflectutil.Box(clock.Now().Add(newTtl))) + } +} diff --git a/pkg/crds/apis/register.go b/pkg/crds/apis/register.go new file mode 100644 index 00000000..5cf6a170 --- /dev/null +++ b/pkg/crds/apis/register.go @@ -0,0 +1,15 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kelemetry + +const GroupName = "kelemetry.kubewharf.io" diff --git a/pkg/crds/apis/v1alpha1/doc.go b/pkg/crds/apis/v1alpha1/doc.go new file mode 100644 index 00000000..d6764c1e --- /dev/null +++ b/pkg/crds/apis/v1alpha1/doc.go @@ -0,0 +1,16 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +k8s:deepcopy-gen=package +// +groupName=kelemetry.kubewharf.io + +package v1alpha1 diff --git a/pkg/crds/apis/v1alpha1/register.go b/pkg/crds/apis/v1alpha1/register.go new file mode 100644 index 00000000..4f858cac --- /dev/null +++ b/pkg/crds/apis/v1alpha1/register.go @@ -0,0 +1,46 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + kelemetry "github.com/kubewharf/kelemetry/pkg/crds/apis" +) + +var SchemeGroupVersion = schema.GroupVersion{ + Group: kelemetry.GroupName, + Version: "v1alpha1", +} + +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + SchemeBuilder runtime.SchemeBuilder + localSchemeBuilder = &SchemeBuilder + AddToScheme = localSchemeBuilder.AddToScheme +) + +func init() { + localSchemeBuilder.Register(addKnownTypes) +} + +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, &LinkRule{}, &LinkRuleList{}) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/pkg/crds/apis/v1alpha1/types_linkrule.go b/pkg/crds/apis/v1alpha1/types_linkrule.go new file mode 100644 index 00000000..61705cc8 --- /dev/null +++ b/pkg/crds/apis/v1alpha1/types_linkrule.go @@ -0,0 +1,114 @@ +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:validation:Required +// +kubebuilder:resource:path=linkrules,scope=Cluster +// +kubebuilder:object:root=true + +// LinkRule instructs Kelemetry to display multiple objects (the "source" and the "target") in the same trace +// by looking up the "target" when the span of the "source" object gets created. +// +// LinkRule is bidirectional. +// Once the link is recorded, searching "source" or "target" would both display the other trace in the link +// as long as the link is not filtered out by tfconfig. +type LinkRule struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // SourceFilter determines whether a source object matches this rule. + SourceFilter LinkRuleSourceFilter `json:"sourceFilter"` + + // TargetTemplate indicates how to find the target object from a matched source object. + TargetTemplate LinkRuleTargetTemplate `json:"targetTemplate"` + + // Link specifies how the two objects are linked together. + Link LinkRuleLink `json:"link"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type LinkRuleList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []LinkRule `json:"items"` +} + +// LinkRuleSourceFilter determines whether an object is a child object that matches a rule. +type LinkRuleSourceFilter struct { + // Resources are the possible resource types that a child object may belong to. + // + // +optional + Resources *[]metav1.GroupResource `json:"resources"` + + // Selector selects matching child objects by label. + // + // +optional + Selector metav1.LabelSelector `json:"selector"` +} + +// LinkRuleTargetTemplate indicates how to find the target object from a matched source object. +type LinkRuleTargetTemplate struct { + // ClusterTemplate is a Go text template string to compute the cluster name of the target object. + // + // The context of the template is the child object that matched the rule. + // + // If empty or unspecified, uses the cluster of the child object. + // +optional + ClusterTemplate string `json:"clusterTemplate,omitempty"` + + // The type of the target object. + metav1.GroupVersionResource `json:",inline"` + + // NamespaceTemplate is a Go text template string to compute the target object name. + // + // If the namespace is empty, the target object is expected to be cluster-scoped. + NamespaceTemplate string `json:"namespaceTemplate"` + + // NameTemplate is a Go text template string to compute the target object name. + // + // The context of the template is the child object that matched the rule. + NameTemplate string `json:"nameTemplate"` +} + +// LinkRuleLink specifies how the two objects are linked together. +type LinkRuleLink struct { + // TargetRole selects the display position of the target object. + // + // One of the target objects will be arbitrarily selected if there are multiple links with preferTargetParent=true. + // +kubebuilder:default=Child + TargetRole TargetRole `json:"targetRole"` + + // If Class is non empty, a pseudospan named the given value is inserted in the hierarchy between the target and source objects. + // + // Multiple links with the same nonempty Class share the same pseudospan. + // +optional + Class string `json:"class,omitempty"` +} + +// TargetRole selects whether the target object is rendered as the parent or as the child of the source object. +type TargetRole string + +const ( + TargetRoleChild TargetRole = "Child" + TargetRoleParent TargetRole = "Parent" +) diff --git a/pkg/crds/apis/v1alpha1/util/util.go b/pkg/crds/apis/v1alpha1/util/util.go new file mode 100644 index 00000000..0dfd6cba --- /dev/null +++ b/pkg/crds/apis/v1alpha1/util/util.go @@ -0,0 +1,104 @@ +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kelemetryv1a1util + +import ( + "bytes" + "fmt" + "text/template" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + + kelemetryv1a1 "github.com/kubewharf/kelemetry/pkg/crds/apis/v1alpha1" + utilobject "github.com/kubewharf/kelemetry/pkg/util/object" +) + +func TestObject(objectRef utilobject.Key, object metav1.Object, rule *kelemetryv1a1.LinkRuleSourceFilter) (bool, error) { + objectGr := metav1.GroupResource{Group: objectRef.Group, Resource: objectRef.Resource} + + if rule.Resources != nil { + matchedGr := false + for _, ruleGr := range *rule.Resources { + if ruleGr == objectGr { + matchedGr = true + } + } + + if !matchedGr { + return false, nil + } + } + + selector, err := metav1.LabelSelectorAsSelector(&rule.Selector) + if err != nil { + return false, fmt.Errorf("rule has invalid label selector: %w", err) + } + + objectLabels := labels.Set(object.GetLabels()) + + return selector.Matches(objectLabels), nil +} + +func GenerateTarget( + rule *kelemetryv1a1.LinkRuleTargetTemplate, + sourceObjectRef utilobject.Rich, + sourceObject *unstructured.Unstructured, +) (_target utilobject.VersionedKey, _ error) { + cluster := sourceObjectRef.Cluster + if rule.ClusterTemplate != "" { + var err error + cluster, err = executeTemplate("clusterTemplate", rule.ClusterTemplate, sourceObject) + if err != nil { + return _target, err + } + } + + namespace, err := executeTemplate("namespaceTemplate", rule.NamespaceTemplate, sourceObject) + if err != nil { + return _target, err + } + + name, err := executeTemplate("nameTemplate", rule.NameTemplate, sourceObject) + if err != nil { + return _target, err + } + + return utilobject.VersionedKey{ + Key: utilobject.Key{ + Cluster: cluster, + Group: rule.Group, + Resource: rule.Resource, + Namespace: namespace, + Name: name, + }, + Version: rule.Version, + }, nil +} + +func executeTemplate(templateName string, templateString string, sourceObject *unstructured.Unstructured) (string, error) { + tmpl, err := template.New(templateName).Parse(templateString) + if err != nil { + return "", fmt.Errorf("cannot parse %s as Go text/template: %w", templateName, err) + } + + buf := new(bytes.Buffer) + if err := tmpl.Execute(buf, sourceObject.Object); err != nil { + return "", fmt.Errorf("cannot execute %s: %w", templateName, err) + } + + return buf.String(), nil +} diff --git a/pkg/crds/apis/v1alpha1/zz_generated.deepcopy.go b/pkg/crds/apis/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000..0ee4eb77 --- /dev/null +++ b/pkg/crds/apis/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,146 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LinkRule) DeepCopyInto(out *LinkRule) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.SourceFilter.DeepCopyInto(&out.SourceFilter) + out.TargetTemplate = in.TargetTemplate + out.Link = in.Link + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinkRule. +func (in *LinkRule) DeepCopy() *LinkRule { + if in == nil { + return nil + } + out := new(LinkRule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LinkRule) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LinkRuleLink) DeepCopyInto(out *LinkRuleLink) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinkRuleLink. +func (in *LinkRuleLink) DeepCopy() *LinkRuleLink { + if in == nil { + return nil + } + out := new(LinkRuleLink) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LinkRuleList) DeepCopyInto(out *LinkRuleList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]LinkRule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinkRuleList. +func (in *LinkRuleList) DeepCopy() *LinkRuleList { + if in == nil { + return nil + } + out := new(LinkRuleList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LinkRuleList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LinkRuleSourceFilter) DeepCopyInto(out *LinkRuleSourceFilter) { + *out = *in + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = new([]v1.GroupResource) + if **in != nil { + in, out := *in, *out + *out = make([]v1.GroupResource, len(*in)) + copy(*out, *in) + } + } + in.Selector.DeepCopyInto(&out.Selector) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinkRuleSourceFilter. +func (in *LinkRuleSourceFilter) DeepCopy() *LinkRuleSourceFilter { + if in == nil { + return nil + } + out := new(LinkRuleSourceFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LinkRuleTargetTemplate) DeepCopyInto(out *LinkRuleTargetTemplate) { + *out = *in + out.GroupVersionResource = in.GroupVersionResource + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinkRuleTargetTemplate. +func (in *LinkRuleTargetTemplate) DeepCopy() *LinkRuleTargetTemplate { + if in == nil { + return nil + } + out := new(LinkRuleTargetTemplate) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/crds/client/clientset/versioned/clientset.go b/pkg/crds/client/clientset/versioned/clientset.go new file mode 100644 index 00000000..48ccc62d --- /dev/null +++ b/pkg/crds/client/clientset/versioned/clientset.go @@ -0,0 +1,119 @@ +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by client-gen. DO NOT EDIT. + +package versioned + +import ( + "fmt" + "net/http" + + discovery "k8s.io/client-go/discovery" + rest "k8s.io/client-go/rest" + flowcontrol "k8s.io/client-go/util/flowcontrol" + + kelemetryv1alpha1 "github.com/kubewharf/kelemetry/pkg/crds/client/clientset/versioned/typed/apis/v1alpha1" +) + +type Interface interface { + Discovery() discovery.DiscoveryInterface + KelemetryV1alpha1() kelemetryv1alpha1.KelemetryV1alpha1Interface +} + +// Clientset contains the clients for groups. +type Clientset struct { + *discovery.DiscoveryClient + kelemetryV1alpha1 *kelemetryv1alpha1.KelemetryV1alpha1Client +} + +// KelemetryV1alpha1 retrieves the KelemetryV1alpha1Client +func (c *Clientset) KelemetryV1alpha1() kelemetryv1alpha1.KelemetryV1alpha1Interface { + return c.kelemetryV1alpha1 +} + +// Discovery retrieves the DiscoveryClient +func (c *Clientset) Discovery() discovery.DiscoveryInterface { + if c == nil { + return nil + } + return c.DiscoveryClient +} + +// NewForConfig creates a new Clientset for the given config. +// If config's RateLimiter is not set and QPS and Burst are acceptable, +// NewForConfig will generate a rate-limiter in configShallowCopy. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *rest.Config) (*Clientset, error) { + configShallowCopy := *c + + if configShallowCopy.UserAgent == "" { + configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent() + } + + // share the transport between all clients + httpClient, err := rest.HTTPClientFor(&configShallowCopy) + if err != nil { + return nil, err + } + + return NewForConfigAndClient(&configShallowCopy, httpClient) +} + +// NewForConfigAndClient creates a new Clientset for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +// If config's RateLimiter is not set and QPS and Burst are acceptable, +// NewForConfigAndClient will generate a rate-limiter in configShallowCopy. +func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) { + configShallowCopy := *c + if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { + if configShallowCopy.Burst <= 0 { + return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0") + } + configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) + } + + var cs Clientset + var err error + cs.kelemetryV1alpha1, err = kelemetryv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) + if err != nil { + return nil, err + } + + cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient) + if err != nil { + return nil, err + } + return &cs, nil +} + +// NewForConfigOrDie creates a new Clientset for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *Clientset { + cs, err := NewForConfig(c) + if err != nil { + panic(err) + } + return cs +} + +// New creates a new Clientset for the given RESTClient. +func New(c rest.Interface) *Clientset { + var cs Clientset + cs.kelemetryV1alpha1 = kelemetryv1alpha1.New(c) + + cs.DiscoveryClient = discovery.NewDiscoveryClient(c) + return &cs +} diff --git a/pkg/crds/client/clientset/versioned/fake/clientset_generated.go b/pkg/crds/client/clientset/versioned/fake/clientset_generated.go new file mode 100644 index 00000000..4818e9b6 --- /dev/null +++ b/pkg/crds/client/clientset/versioned/fake/clientset_generated.go @@ -0,0 +1,84 @@ +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/discovery" + fakediscovery "k8s.io/client-go/discovery/fake" + "k8s.io/client-go/testing" + + clientset "github.com/kubewharf/kelemetry/pkg/crds/client/clientset/versioned" + kelemetryv1alpha1 "github.com/kubewharf/kelemetry/pkg/crds/client/clientset/versioned/typed/apis/v1alpha1" + fakekelemetryv1alpha1 "github.com/kubewharf/kelemetry/pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/fake" +) + +// NewSimpleClientset returns a clientset that will respond with the provided objects. +// It's backed by a very simple object tracker that processes creates, updates and deletions as-is, +// without applying any validations and/or defaults. It shouldn't be considered a replacement +// for a real clientset and is mostly useful in simple unit tests. +func NewSimpleClientset(objects ...runtime.Object) *Clientset { + o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) + for _, obj := range objects { + if err := o.Add(obj); err != nil { + panic(err) + } + } + + cs := &Clientset{tracker: o} + cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} + cs.AddReactor("*", "*", testing.ObjectReaction(o)) + cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { + gvr := action.GetResource() + ns := action.GetNamespace() + watch, err := o.Watch(gvr, ns) + if err != nil { + return false, nil, err + } + return true, watch, nil + }) + + return cs +} + +// Clientset implements clientset.Interface. Meant to be embedded into a +// struct to get a default implementation. This makes faking out just the method +// you want to test easier. +type Clientset struct { + testing.Fake + discovery *fakediscovery.FakeDiscovery + tracker testing.ObjectTracker +} + +func (c *Clientset) Discovery() discovery.DiscoveryInterface { + return c.discovery +} + +func (c *Clientset) Tracker() testing.ObjectTracker { + return c.tracker +} + +var ( + _ clientset.Interface = &Clientset{} + _ testing.FakeClient = &Clientset{} +) + +// KelemetryV1alpha1 retrieves the KelemetryV1alpha1Client +func (c *Clientset) KelemetryV1alpha1() kelemetryv1alpha1.KelemetryV1alpha1Interface { + return &fakekelemetryv1alpha1.FakeKelemetryV1alpha1{Fake: &c.Fake} +} diff --git a/pkg/crds/client/clientset/versioned/fake/doc.go b/pkg/crds/client/clientset/versioned/fake/doc.go new file mode 100644 index 00000000..87cb60a9 --- /dev/null +++ b/pkg/crds/client/clientset/versioned/fake/doc.go @@ -0,0 +1,18 @@ +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated fake clientset. +package fake diff --git a/pkg/crds/client/clientset/versioned/fake/register.go b/pkg/crds/client/clientset/versioned/fake/register.go new file mode 100644 index 00000000..9a5acd79 --- /dev/null +++ b/pkg/crds/client/clientset/versioned/fake/register.go @@ -0,0 +1,57 @@ +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + serializer "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + + kelemetryv1alpha1 "github.com/kubewharf/kelemetry/pkg/crds/apis/v1alpha1" +) + +var ( + scheme = runtime.NewScheme() + codecs = serializer.NewCodecFactory(scheme) +) + +var localSchemeBuilder = runtime.SchemeBuilder{ + kelemetryv1alpha1.AddToScheme, +} + +// AddToScheme adds all types of this clientset into the given scheme. This allows composition +// of clientsets, like in: +// +// import ( +// "k8s.io/client-go/kubernetes" +// clientsetscheme "k8s.io/client-go/kubernetes/scheme" +// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" +// ) +// +// kclientset, _ := kubernetes.NewForConfig(c) +// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) +// +// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types +// correctly. +var AddToScheme = localSchemeBuilder.AddToScheme + +func init() { + v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) + utilruntime.Must(AddToScheme(scheme)) +} diff --git a/pkg/crds/client/clientset/versioned/scheme/doc.go b/pkg/crds/client/clientset/versioned/scheme/doc.go new file mode 100644 index 00000000..915166a5 --- /dev/null +++ b/pkg/crds/client/clientset/versioned/scheme/doc.go @@ -0,0 +1,18 @@ +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by client-gen. DO NOT EDIT. + +// This package contains the scheme of the automatically generated clientset. +package scheme diff --git a/pkg/crds/client/clientset/versioned/scheme/register.go b/pkg/crds/client/clientset/versioned/scheme/register.go new file mode 100644 index 00000000..4c90e8cd --- /dev/null +++ b/pkg/crds/client/clientset/versioned/scheme/register.go @@ -0,0 +1,57 @@ +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by client-gen. DO NOT EDIT. + +package scheme + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + serializer "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + + kelemetryv1alpha1 "github.com/kubewharf/kelemetry/pkg/crds/apis/v1alpha1" +) + +var ( + Scheme = runtime.NewScheme() + Codecs = serializer.NewCodecFactory(Scheme) + ParameterCodec = runtime.NewParameterCodec(Scheme) + localSchemeBuilder = runtime.SchemeBuilder{ + kelemetryv1alpha1.AddToScheme, + } +) + +// AddToScheme adds all types of this clientset into the given scheme. This allows composition +// of clientsets, like in: +// +// import ( +// "k8s.io/client-go/kubernetes" +// clientsetscheme "k8s.io/client-go/kubernetes/scheme" +// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" +// ) +// +// kclientset, _ := kubernetes.NewForConfig(c) +// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) +// +// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types +// correctly. +var AddToScheme = localSchemeBuilder.AddToScheme + +func init() { + v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) + utilruntime.Must(AddToScheme(Scheme)) +} diff --git a/pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/apis_client.go b/pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/apis_client.go new file mode 100644 index 00000000..177e7292 --- /dev/null +++ b/pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/apis_client.go @@ -0,0 +1,106 @@ +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "net/http" + + rest "k8s.io/client-go/rest" + + v1alpha1 "github.com/kubewharf/kelemetry/pkg/crds/apis/v1alpha1" + "github.com/kubewharf/kelemetry/pkg/crds/client/clientset/versioned/scheme" +) + +type KelemetryV1alpha1Interface interface { + RESTClient() rest.Interface + LinkRulesGetter +} + +// KelemetryV1alpha1Client is used to interact with features provided by the kelemetry.kubewharf.io group. +type KelemetryV1alpha1Client struct { + restClient rest.Interface +} + +func (c *KelemetryV1alpha1Client) LinkRules() LinkRuleInterface { + return newLinkRules(c) +} + +// NewForConfig creates a new KelemetryV1alpha1Client for the given config. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *rest.Config) (*KelemetryV1alpha1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + httpClient, err := rest.HTTPClientFor(&config) + if err != nil { + return nil, err + } + return NewForConfigAndClient(&config, httpClient) +} + +// NewForConfigAndClient creates a new KelemetryV1alpha1Client for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +func NewForConfigAndClient(c *rest.Config, h *http.Client) (*KelemetryV1alpha1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientForConfigAndClient(&config, h) + if err != nil { + return nil, err + } + return &KelemetryV1alpha1Client{client}, nil +} + +// NewForConfigOrDie creates a new KelemetryV1alpha1Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *KelemetryV1alpha1Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new KelemetryV1alpha1Client for the given RESTClient. +func New(c rest.Interface) *KelemetryV1alpha1Client { + return &KelemetryV1alpha1Client{c} +} + +func setConfigDefaults(config *rest.Config) error { + gv := v1alpha1.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + + return nil +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *KelemetryV1alpha1Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/doc.go b/pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/doc.go new file mode 100644 index 00000000..e0863049 --- /dev/null +++ b/pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/doc.go @@ -0,0 +1,18 @@ +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated typed clients. +package v1alpha1 diff --git a/pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/fake/doc.go b/pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/fake/doc.go new file mode 100644 index 00000000..c677baf6 --- /dev/null +++ b/pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/fake/doc.go @@ -0,0 +1,18 @@ +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by client-gen. DO NOT EDIT. + +// Package fake has the automatically generated clients. +package fake diff --git a/pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_apis_client.go b/pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_apis_client.go new file mode 100644 index 00000000..8bb25361 --- /dev/null +++ b/pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_apis_client.go @@ -0,0 +1,39 @@ +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" + + v1alpha1 "github.com/kubewharf/kelemetry/pkg/crds/client/clientset/versioned/typed/apis/v1alpha1" +) + +type FakeKelemetryV1alpha1 struct { + *testing.Fake +} + +func (c *FakeKelemetryV1alpha1) LinkRules() v1alpha1.LinkRuleInterface { + return &FakeLinkRules{c} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeKelemetryV1alpha1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_linkrule.go b/pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_linkrule.go new file mode 100644 index 00000000..e1d73037 --- /dev/null +++ b/pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/fake/fake_linkrule.go @@ -0,0 +1,135 @@ +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" + + v1alpha1 "github.com/kubewharf/kelemetry/pkg/crds/apis/v1alpha1" +) + +// FakeLinkRules implements LinkRuleInterface +type FakeLinkRules struct { + Fake *FakeKelemetryV1alpha1 +} + +var linkrulesResource = v1alpha1.SchemeGroupVersion.WithResource("linkrules") + +var linkrulesKind = v1alpha1.SchemeGroupVersion.WithKind("LinkRule") + +// Get takes name of the linkRule, and returns the corresponding linkRule object, and an error if there is any. +func (c *FakeLinkRules) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.LinkRule, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootGetAction(linkrulesResource, name), &v1alpha1.LinkRule{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.LinkRule), err +} + +// List takes label and field selectors, and returns the list of LinkRules that match those selectors. +func (c *FakeLinkRules) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.LinkRuleList, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootListAction(linkrulesResource, linkrulesKind, opts), &v1alpha1.LinkRuleList{}) + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.LinkRuleList{ListMeta: obj.(*v1alpha1.LinkRuleList).ListMeta} + for _, item := range obj.(*v1alpha1.LinkRuleList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested linkRules. +func (c *FakeLinkRules) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewRootWatchAction(linkrulesResource, opts)) +} + +// Create takes the representation of a linkRule and creates it. Returns the server's representation of the linkRule, and an error, if there is any. +func (c *FakeLinkRules) Create( + ctx context.Context, + linkRule *v1alpha1.LinkRule, + opts v1.CreateOptions, +) (result *v1alpha1.LinkRule, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootCreateAction(linkrulesResource, linkRule), &v1alpha1.LinkRule{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.LinkRule), err +} + +// Update takes the representation of a linkRule and updates it. Returns the server's representation of the linkRule, and an error, if there is any. +func (c *FakeLinkRules) Update( + ctx context.Context, + linkRule *v1alpha1.LinkRule, + opts v1.UpdateOptions, +) (result *v1alpha1.LinkRule, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateAction(linkrulesResource, linkRule), &v1alpha1.LinkRule{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.LinkRule), err +} + +// Delete takes name of the linkRule and deletes it. Returns an error if one occurs. +func (c *FakeLinkRules) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewRootDeleteActionWithOptions(linkrulesResource, name, opts), &v1alpha1.LinkRule{}) + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeLinkRules) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewRootDeleteCollectionAction(linkrulesResource, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.LinkRuleList{}) + return err +} + +// Patch applies the patch and returns the patched linkRule. +func (c *FakeLinkRules) Patch( + ctx context.Context, + name string, + pt types.PatchType, + data []byte, + opts v1.PatchOptions, + subresources ...string, +) (result *v1alpha1.LinkRule, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootPatchSubresourceAction(linkrulesResource, name, pt, data, subresources...), &v1alpha1.LinkRule{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.LinkRule), err +} diff --git a/pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/generated_expansion.go b/pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/generated_expansion.go new file mode 100644 index 00000000..662577fd --- /dev/null +++ b/pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/generated_expansion.go @@ -0,0 +1,19 @@ +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +type LinkRuleExpansion interface{} diff --git a/pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/linkrule.go b/pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/linkrule.go new file mode 100644 index 00000000..ebb1d240 --- /dev/null +++ b/pkg/crds/client/clientset/versioned/typed/apis/v1alpha1/linkrule.go @@ -0,0 +1,181 @@ +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" + + v1alpha1 "github.com/kubewharf/kelemetry/pkg/crds/apis/v1alpha1" + scheme "github.com/kubewharf/kelemetry/pkg/crds/client/clientset/versioned/scheme" +) + +// LinkRulesGetter has a method to return a LinkRuleInterface. +// A group's client should implement this interface. +type LinkRulesGetter interface { + LinkRules() LinkRuleInterface +} + +// LinkRuleInterface has methods to work with LinkRule resources. +type LinkRuleInterface interface { + Create(ctx context.Context, linkRule *v1alpha1.LinkRule, opts v1.CreateOptions) (*v1alpha1.LinkRule, error) + Update(ctx context.Context, linkRule *v1alpha1.LinkRule, opts v1.UpdateOptions) (*v1alpha1.LinkRule, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.LinkRule, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.LinkRuleList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch( + ctx context.Context, + name string, + pt types.PatchType, + data []byte, + opts v1.PatchOptions, + subresources ...string, + ) (result *v1alpha1.LinkRule, err error) + LinkRuleExpansion +} + +// linkRules implements LinkRuleInterface +type linkRules struct { + client rest.Interface +} + +// newLinkRules returns a LinkRules +func newLinkRules(c *KelemetryV1alpha1Client) *linkRules { + return &linkRules{ + client: c.RESTClient(), + } +} + +// Get takes name of the linkRule, and returns the corresponding linkRule object, and an error if there is any. +func (c *linkRules) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.LinkRule, err error) { + result = &v1alpha1.LinkRule{} + err = c.client.Get(). + Resource("linkrules"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of LinkRules that match those selectors. +func (c *linkRules) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.LinkRuleList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.LinkRuleList{} + err = c.client.Get(). + Resource("linkrules"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested linkRules. +func (c *linkRules) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Resource("linkrules"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a linkRule and creates it. Returns the server's representation of the linkRule, and an error, if there is any. +func (c *linkRules) Create(ctx context.Context, linkRule *v1alpha1.LinkRule, opts v1.CreateOptions) (result *v1alpha1.LinkRule, err error) { + result = &v1alpha1.LinkRule{} + err = c.client.Post(). + Resource("linkrules"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(linkRule). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a linkRule and updates it. Returns the server's representation of the linkRule, and an error, if there is any. +func (c *linkRules) Update(ctx context.Context, linkRule *v1alpha1.LinkRule, opts v1.UpdateOptions) (result *v1alpha1.LinkRule, err error) { + result = &v1alpha1.LinkRule{} + err = c.client.Put(). + Resource("linkrules"). + Name(linkRule.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(linkRule). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the linkRule and deletes it. Returns an error if one occurs. +func (c *linkRules) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Resource("linkrules"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *linkRules) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Resource("linkrules"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched linkRule. +func (c *linkRules) Patch( + ctx context.Context, + name string, + pt types.PatchType, + data []byte, + opts v1.PatchOptions, + subresources ...string, +) (result *v1alpha1.LinkRule, err error) { + result = &v1alpha1.LinkRule{} + err = c.client.Patch(pt). + Resource("linkrules"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/crds/client/informers/externalversions/apis/interface.go b/pkg/crds/client/informers/externalversions/apis/interface.go new file mode 100644 index 00000000..f169df20 --- /dev/null +++ b/pkg/crds/client/informers/externalversions/apis/interface.go @@ -0,0 +1,44 @@ +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by informer-gen. DO NOT EDIT. + +package apis + +import ( + v1alpha1 "github.com/kubewharf/kelemetry/pkg/crds/client/informers/externalversions/apis/v1alpha1" + internalinterfaces "github.com/kubewharf/kelemetry/pkg/crds/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to each of this group's versions. +type Interface interface { + // V1alpha1 provides access to shared informers for resources in V1alpha1. + V1alpha1() v1alpha1.Interface +} + +type group struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// V1alpha1 returns a new v1alpha1.Interface. +func (g *group) V1alpha1() v1alpha1.Interface { + return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions) +} diff --git a/pkg/crds/client/informers/externalversions/apis/v1alpha1/interface.go b/pkg/crds/client/informers/externalversions/apis/v1alpha1/interface.go new file mode 100644 index 00000000..8b1043b7 --- /dev/null +++ b/pkg/crds/client/informers/externalversions/apis/v1alpha1/interface.go @@ -0,0 +1,43 @@ +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + internalinterfaces "github.com/kubewharf/kelemetry/pkg/crds/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // LinkRules returns a LinkRuleInformer. + LinkRules() LinkRuleInformer +} + +type version struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// LinkRules returns a LinkRuleInformer. +func (v *version) LinkRules() LinkRuleInformer { + return &linkRuleInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/crds/client/informers/externalversions/apis/v1alpha1/linkrule.go b/pkg/crds/client/informers/externalversions/apis/v1alpha1/linkrule.go new file mode 100644 index 00000000..17e43f6f --- /dev/null +++ b/pkg/crds/client/informers/externalversions/apis/v1alpha1/linkrule.go @@ -0,0 +1,98 @@ +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" + + apisv1alpha1 "github.com/kubewharf/kelemetry/pkg/crds/apis/v1alpha1" + versioned "github.com/kubewharf/kelemetry/pkg/crds/client/clientset/versioned" + internalinterfaces "github.com/kubewharf/kelemetry/pkg/crds/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/kubewharf/kelemetry/pkg/crds/client/listers/apis/v1alpha1" +) + +// LinkRuleInformer provides access to a shared informer and lister for +// LinkRules. +type LinkRuleInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.LinkRuleLister +} + +type linkRuleInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// NewLinkRuleInformer constructs a new informer for LinkRule type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewLinkRuleInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredLinkRuleInformer(client, resyncPeriod, indexers, nil) +} + +// NewFilteredLinkRuleInformer constructs a new informer for LinkRule type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredLinkRuleInformer( + client versioned.Interface, + resyncPeriod time.Duration, + indexers cache.Indexers, + tweakListOptions internalinterfaces.TweakListOptionsFunc, +) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.KelemetryV1alpha1().LinkRules().List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.KelemetryV1alpha1().LinkRules().Watch(context.TODO(), options) + }, + }, + &apisv1alpha1.LinkRule{}, + resyncPeriod, + indexers, + ) +} + +func (f *linkRuleInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredLinkRuleInformer( + client, + resyncPeriod, + cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, + f.tweakListOptions, + ) +} + +func (f *linkRuleInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apisv1alpha1.LinkRule{}, f.defaultInformer) +} + +func (f *linkRuleInformer) Lister() v1alpha1.LinkRuleLister { + return v1alpha1.NewLinkRuleLister(f.Informer().GetIndexer()) +} diff --git a/pkg/crds/client/informers/externalversions/factory.go b/pkg/crds/client/informers/externalversions/factory.go new file mode 100644 index 00000000..fed3298e --- /dev/null +++ b/pkg/crds/client/informers/externalversions/factory.go @@ -0,0 +1,259 @@ +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by informer-gen. DO NOT EDIT. + +package externalversions + +import ( + reflect "reflect" + sync "sync" + time "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + cache "k8s.io/client-go/tools/cache" + + versioned "github.com/kubewharf/kelemetry/pkg/crds/client/clientset/versioned" + apis "github.com/kubewharf/kelemetry/pkg/crds/client/informers/externalversions/apis" + internalinterfaces "github.com/kubewharf/kelemetry/pkg/crds/client/informers/externalversions/internalinterfaces" +) + +// SharedInformerOption defines the functional option type for SharedInformerFactory. +type SharedInformerOption func(*sharedInformerFactory) *sharedInformerFactory + +type sharedInformerFactory struct { + client versioned.Interface + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc + lock sync.Mutex + defaultResync time.Duration + customResync map[reflect.Type]time.Duration + + informers map[reflect.Type]cache.SharedIndexInformer + // startedInformers is used for tracking which informers have been started. + // This allows Start() to be called multiple times safely. + startedInformers map[reflect.Type]bool + // wg tracks how many goroutines were started. + wg sync.WaitGroup + // shuttingDown is true when Shutdown has been called. It may still be running + // because it needs to wait for goroutines. + shuttingDown bool +} + +// WithCustomResyncConfig sets a custom resync period for the specified informer types. +func WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + for k, v := range resyncConfig { + factory.customResync[reflect.TypeOf(k)] = v + } + return factory + } +} + +// WithTweakListOptions sets a custom filter on all listers of the configured SharedInformerFactory. +func WithTweakListOptions(tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.tweakListOptions = tweakListOptions + return factory + } +} + +// WithNamespace limits the SharedInformerFactory to the specified namespace. +func WithNamespace(namespace string) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.namespace = namespace + return factory + } +} + +// NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces. +func NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory { + return NewSharedInformerFactoryWithOptions(client, defaultResync) +} + +// NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory. +// Listers obtained via this SharedInformerFactory will be subject to the same filters +// as specified here. +// Deprecated: Please use NewSharedInformerFactoryWithOptions instead +func NewFilteredSharedInformerFactory( + client versioned.Interface, + defaultResync time.Duration, + namespace string, + tweakListOptions internalinterfaces.TweakListOptionsFunc, +) SharedInformerFactory { + return NewSharedInformerFactoryWithOptions(client, defaultResync, WithNamespace(namespace), WithTweakListOptions(tweakListOptions)) +} + +// NewSharedInformerFactoryWithOptions constructs a new instance of a SharedInformerFactory with additional options. +func NewSharedInformerFactoryWithOptions( + client versioned.Interface, + defaultResync time.Duration, + options ...SharedInformerOption, +) SharedInformerFactory { + factory := &sharedInformerFactory{ + client: client, + namespace: v1.NamespaceAll, + defaultResync: defaultResync, + informers: make(map[reflect.Type]cache.SharedIndexInformer), + startedInformers: make(map[reflect.Type]bool), + customResync: make(map[reflect.Type]time.Duration), + } + + // Apply all options + for _, opt := range options { + factory = opt(factory) + } + + return factory +} + +func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { + f.lock.Lock() + defer f.lock.Unlock() + + if f.shuttingDown { + return + } + + for informerType, informer := range f.informers { + if !f.startedInformers[informerType] { + f.wg.Add(1) + // We need a new variable in each loop iteration, + // otherwise the goroutine would use the loop variable + // and that keeps changing. + informer := informer + go func() { + defer f.wg.Done() + informer.Run(stopCh) + }() + f.startedInformers[informerType] = true + } + } +} + +func (f *sharedInformerFactory) Shutdown() { + f.lock.Lock() + f.shuttingDown = true + f.lock.Unlock() + + // Will return immediately if there is nothing to wait for. + f.wg.Wait() +} + +func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { + informers := func() map[reflect.Type]cache.SharedIndexInformer { + f.lock.Lock() + defer f.lock.Unlock() + + informers := map[reflect.Type]cache.SharedIndexInformer{} + for informerType, informer := range f.informers { + if f.startedInformers[informerType] { + informers[informerType] = informer + } + } + return informers + }() + + res := map[reflect.Type]bool{} + for informType, informer := range informers { + res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced) + } + return res +} + +// InternalInformerFor returns the SharedIndexInformer for obj using an internal +// client. +func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer { + f.lock.Lock() + defer f.lock.Unlock() + + informerType := reflect.TypeOf(obj) + informer, exists := f.informers[informerType] + if exists { + return informer + } + + resyncPeriod, exists := f.customResync[informerType] + if !exists { + resyncPeriod = f.defaultResync + } + + informer = newFunc(f.client, resyncPeriod) + f.informers[informerType] = informer + + return informer +} + +// SharedInformerFactory provides shared informers for resources in all known +// API group versions. +// +// It is typically used like this: +// +// ctx, cancel := context.Background() +// defer cancel() +// factory := NewSharedInformerFactory(client, resyncPeriod) +// defer factory.WaitForStop() // Returns immediately if nothing was started. +// genericInformer := factory.ForResource(resource) +// typedInformer := factory.SomeAPIGroup().V1().SomeType() +// factory.Start(ctx.Done()) // Start processing these informers. +// synced := factory.WaitForCacheSync(ctx.Done()) +// for v, ok := range synced { +// if !ok { +// fmt.Fprintf(os.Stderr, "caches failed to sync: %v", v) +// return +// } +// } +// +// // Creating informers can also be created after Start, but then +// // Start must be called again: +// anotherGenericInformer := factory.ForResource(resource) +// factory.Start(ctx.Done()) +type SharedInformerFactory interface { + internalinterfaces.SharedInformerFactory + + // Start initializes all requested informers. They are handled in goroutines + // which run until the stop channel gets closed. + Start(stopCh <-chan struct{}) + + // Shutdown marks a factory as shutting down. At that point no new + // informers can be started anymore and Start will return without + // doing anything. + // + // In addition, Shutdown blocks until all goroutines have terminated. For that + // to happen, the close channel(s) that they were started with must be closed, + // either before Shutdown gets called or while it is waiting. + // + // Shutdown may be called multiple times, even concurrently. All such calls will + // block until all goroutines have terminated. + Shutdown() + + // WaitForCacheSync blocks until all started informers' caches were synced + // or the stop channel gets closed. + WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool + + // ForResource gives generic access to a shared informer of the matching type. + ForResource(resource schema.GroupVersionResource) (GenericInformer, error) + + // InternalInformerFor returns the SharedIndexInformer for obj using an internal + // client. + InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer + + Kelemetry() apis.Interface +} + +func (f *sharedInformerFactory) Kelemetry() apis.Interface { + return apis.New(f, f.namespace, f.tweakListOptions) +} diff --git a/pkg/crds/client/informers/externalversions/generic.go b/pkg/crds/client/informers/externalversions/generic.go new file mode 100644 index 00000000..e9581f3f --- /dev/null +++ b/pkg/crds/client/informers/externalversions/generic.go @@ -0,0 +1,60 @@ +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by informer-gen. DO NOT EDIT. + +package externalversions + +import ( + "fmt" + + schema "k8s.io/apimachinery/pkg/runtime/schema" + cache "k8s.io/client-go/tools/cache" + + v1alpha1 "github.com/kubewharf/kelemetry/pkg/crds/apis/v1alpha1" +) + +// GenericInformer is type of SharedIndexInformer which will locate and delegate to other +// sharedInformers based on type +type GenericInformer interface { + Informer() cache.SharedIndexInformer + Lister() cache.GenericLister +} + +type genericInformer struct { + informer cache.SharedIndexInformer + resource schema.GroupResource +} + +// Informer returns the SharedIndexInformer. +func (f *genericInformer) Informer() cache.SharedIndexInformer { + return f.informer +} + +// Lister returns the GenericLister. +func (f *genericInformer) Lister() cache.GenericLister { + return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource) +} + +// ForResource gives generic access to a shared informer of the matching type +// TODO extend this to unknown resources with a client pool +func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { + switch resource { + // Group=kelemetry.kubewharf.io, Version=v1alpha1 + case v1alpha1.SchemeGroupVersion.WithResource("linkrules"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Kelemetry().V1alpha1().LinkRules().Informer()}, nil + } + + return nil, fmt.Errorf("no informer found for %v", resource) +} diff --git a/pkg/crds/client/informers/externalversions/internalinterfaces/factory_interfaces.go b/pkg/crds/client/informers/externalversions/internalinterfaces/factory_interfaces.go new file mode 100644 index 00000000..dcde767b --- /dev/null +++ b/pkg/crds/client/informers/externalversions/internalinterfaces/factory_interfaces.go @@ -0,0 +1,39 @@ +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by informer-gen. DO NOT EDIT. + +package internalinterfaces + +import ( + time "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + cache "k8s.io/client-go/tools/cache" + + versioned "github.com/kubewharf/kelemetry/pkg/crds/client/clientset/versioned" +) + +// NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer. +type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer + +// SharedInformerFactory a small interface to allow for adding an informer without an import cycle +type SharedInformerFactory interface { + Start(stopCh <-chan struct{}) + InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer +} + +// TweakListOptionsFunc is a function that transforms a v1.ListOptions. +type TweakListOptionsFunc func(*v1.ListOptions) diff --git a/pkg/crds/client/listers/apis/v1alpha1/expansion_generated.go b/pkg/crds/client/listers/apis/v1alpha1/expansion_generated.go new file mode 100644 index 00000000..015ba055 --- /dev/null +++ b/pkg/crds/client/listers/apis/v1alpha1/expansion_generated.go @@ -0,0 +1,21 @@ +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +// LinkRuleListerExpansion allows custom methods to be added to +// LinkRuleLister. +type LinkRuleListerExpansion interface{} diff --git a/pkg/crds/client/listers/apis/v1alpha1/linkrule.go b/pkg/crds/client/listers/apis/v1alpha1/linkrule.go new file mode 100644 index 00000000..e40b46ed --- /dev/null +++ b/pkg/crds/client/listers/apis/v1alpha1/linkrule.go @@ -0,0 +1,67 @@ +// Copyright 2023 The Kelemetry Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + + v1alpha1 "github.com/kubewharf/kelemetry/pkg/crds/apis/v1alpha1" +) + +// LinkRuleLister helps list LinkRules. +// All objects returned here must be treated as read-only. +type LinkRuleLister interface { + // List lists all LinkRules in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.LinkRule, err error) + // Get retrieves the LinkRule from the index for a given name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.LinkRule, error) + LinkRuleListerExpansion +} + +// linkRuleLister implements the LinkRuleLister interface. +type linkRuleLister struct { + indexer cache.Indexer +} + +// NewLinkRuleLister returns a new LinkRuleLister. +func NewLinkRuleLister(indexer cache.Indexer) LinkRuleLister { + return &linkRuleLister{indexer: indexer} +} + +// List lists all LinkRules in the indexer. +func (s *linkRuleLister) List(selector labels.Selector) (ret []*v1alpha1.LinkRule, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.LinkRule)) + }) + return ret, err +} + +// Get retrieves the LinkRule from the index for a given name. +func (s *linkRuleLister) Get(name string) (*v1alpha1.LinkRule, error) { + obj, exists, err := s.indexer.GetByKey(name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("linkrule"), name) + } + return obj.(*v1alpha1.LinkRule), nil +} diff --git a/pkg/imports.go b/pkg/imports.go index f7a779ff..86c029f7 100644 --- a/pkg/imports.go +++ b/pkg/imports.go @@ -18,13 +18,15 @@ package kelemetry_pkg import ( _ "github.com/kubewharf/kelemetry/pkg/aggregator/aggregatorevent" _ "github.com/kubewharf/kelemetry/pkg/aggregator/eventdecorator/eventtagger" + _ "github.com/kubewharf/kelemetry/pkg/aggregator/linker/annotation" _ "github.com/kubewharf/kelemetry/pkg/aggregator/linker/job/local" _ "github.com/kubewharf/kelemetry/pkg/aggregator/linker/job/worker" + _ "github.com/kubewharf/kelemetry/pkg/aggregator/linker/owner" + _ "github.com/kubewharf/kelemetry/pkg/aggregator/linker/rule" _ "github.com/kubewharf/kelemetry/pkg/aggregator/objectspandecorator/resourcetagger" _ "github.com/kubewharf/kelemetry/pkg/aggregator/spancache/etcd" _ "github.com/kubewharf/kelemetry/pkg/aggregator/spancache/local" _ "github.com/kubewharf/kelemetry/pkg/aggregator/tracer/otel" - _ "github.com/kubewharf/kelemetry/pkg/annotationlinker" _ "github.com/kubewharf/kelemetry/pkg/audit" _ "github.com/kubewharf/kelemetry/pkg/audit/consumer" _ "github.com/kubewharf/kelemetry/pkg/audit/dump" @@ -58,5 +60,4 @@ import ( _ "github.com/kubewharf/kelemetry/pkg/kelemetrix/defaults/tags" _ "github.com/kubewharf/kelemetry/pkg/metrics/noop" _ "github.com/kubewharf/kelemetry/pkg/metrics/prometheus" - _ "github.com/kubewharf/kelemetry/pkg/ownerlinker" ) diff --git a/pkg/k8s/k8s.go b/pkg/k8s/k8s.go index b8758370..cc29b2a7 100644 --- a/pkg/k8s/k8s.go +++ b/pkg/k8s/k8s.go @@ -35,6 +35,7 @@ import ( "k8s.io/client-go/tools/record" "k8s.io/klog/v2" + kelemetryversioned "github.com/kubewharf/kelemetry/pkg/crds/client/clientset/versioned" k8sconfig "github.com/kubewharf/kelemetry/pkg/k8s/config" "github.com/kubewharf/kelemetry/pkg/manager" ) @@ -77,6 +78,7 @@ type Client interface { ClusterName() string DynamicClient() dynamic.Interface KubernetesClient() kubernetes.Interface + KelemetryClient() kelemetryversioned.Interface InformerFactory() informers.SharedInformerFactory NewInformerFactory(options ...informers.SharedInformerOption) informers.SharedInformerFactory EventRecorder(name string) record.EventRecorder @@ -99,6 +101,7 @@ type client struct { restConfig *rest.Config dynamicClient dynamic.Interface kubernetesClient *kubernetes.Clientset + kelemetryClient kelemetryversioned.Interface informerFactory informers.SharedInformerFactory eventBroadcaster record.EventBroadcaster } @@ -191,6 +194,11 @@ func (clients *clusterClients) newClient(name string) (*client, error) { return nil, fmt.Errorf("kubernetes client creation failed: %w", err) } + kelemetryClient, err := kelemetryversioned.NewForConfig(config) + if err != nil { + return nil, fmt.Errorf("kelemetry client creation failed: %w", err) + } + var informerFactory informers.SharedInformerFactory if atomic.LoadInt32(&clients.started) == 0 { // we do not support using informers on clusters created after start @@ -205,6 +213,7 @@ func (clients *clusterClients) newClient(name string) (*client, error) { restConfig: config, dynamicClient: dynamicClient, kubernetesClient: kubernetesClient, + kelemetryClient: kelemetryClient, informerFactory: informerFactory, eventBroadcaster: broadcaster, }, nil @@ -226,6 +235,10 @@ func (client *client) KubernetesClient() kubernetes.Interface { return client.kubernetesClient } +func (client *client) KelemetryClient() kelemetryversioned.Interface { + return client.kelemetryClient +} + func (client *client) InformerFactory() informers.SharedInformerFactory { if client.informerFactory == nil { panic("InformerFactory is only available for clusters requested at Init phase") diff --git a/pkg/k8s/k8smock.go b/pkg/k8s/k8smock.go index c9fcef3d..da6e471d 100644 --- a/pkg/k8s/k8smock.go +++ b/pkg/k8s/k8smock.go @@ -28,6 +28,9 @@ import ( "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/tools/record" "k8s.io/klog/v2" + + kelemetryversioned "github.com/kubewharf/kelemetry/pkg/crds/client/clientset/versioned" + kelemetryfake "github.com/kubewharf/kelemetry/pkg/crds/client/clientset/versioned/fake" ) type MockClients struct { @@ -55,6 +58,7 @@ type MockClient struct { singleFakeGuard bool dynamicClient *dynamicfake.FakeDynamicClient k8sClient *k8sfake.Clientset + kelemetryClient *kelemetryfake.Clientset } func (client *MockClient) BindKlog(verbosity int32) { @@ -98,6 +102,17 @@ func (client *MockClient) KubernetesClient() kubernetes.Interface { return client.k8sClient } +func (client *MockClient) KelemetryClient() kelemetryversioned.Interface { + client.mutex.Lock() + defer client.mutex.Unlock() + + if client.kelemetryClient == nil { + client.acquireSingleFake() + client.kelemetryClient = kelemetryfake.NewSimpleClientset(client.Objects...) + } + return client.kelemetryClient +} + func (client *MockClient) InformerFactory() informers.SharedInformerFactory { panic("not yet implemented") } diff --git a/pkg/util/cache/ttl.go b/pkg/util/cache/ttl.go index 79fed06f..caf16e3c 100644 --- a/pkg/util/cache/ttl.go +++ b/pkg/util/cache/ttl.go @@ -46,7 +46,7 @@ func NewTtlOnce(ttl time.Duration, clock clock.Clock) *TtlOnce { return &TtlOnce{ ttl: ttl, clock: clock, - wakeupCh: make(chan struct{}), + wakeupCh: make(chan struct{}, 1), cleanupQueue: channel.NewDeque[cleanupEntry](16), data: map[string]any{}, } @@ -60,6 +60,11 @@ func (cache *TtlOnce) Add(key string, value any) { cache.data[key] = value expiry := cache.clock.Now().Add(cache.ttl) cache.cleanupQueue.LockedPushBack(cleanupEntry{key: key, expiry: expiry}) + + select { + case cache.wakeupCh <- struct{}{}: + default: + } } } @@ -85,8 +90,11 @@ func (cache *TtlOnce) RunCleanupLoop(ctx context.Context, logger logrus.FieldLog wakeup := cache.wakeupCh var nextExpiryCh <-chan time.Time if expiry, hasNext := cache.peekExpiry(); hasNext { - nextExpiryCh = cache.clock.After(expiry.Sub(cache.clock.Now()) + time.Second) // +1s to mitigate race conditions - wakeup = nil + timeout := expiry.Sub(cache.clock.Now()) + time.Second + if timeout > 0 { + nextExpiryCh = cache.clock.After(timeout) // +1s to mitigate race conditions + wakeup = nil + } } select { diff --git a/pkg/util/reflect/reflect.go b/pkg/util/reflect/reflect.go index 3ce6179d..17d21116 100644 --- a/pkg/util/reflect/reflect.go +++ b/pkg/util/reflect/reflect.go @@ -25,3 +25,5 @@ func TypeOf[T any]() reflect.Type { func ZeroOf[T any]() (_ T) { return } func Identity[T any](t T) T { return t } + +func Box[T any](t T) *T { return &t } diff --git a/pkg/util/zconstants/link.go b/pkg/util/zconstants/link.go index dd17ffa5..a237a02a 100644 --- a/pkg/util/zconstants/link.go +++ b/pkg/util/zconstants/link.go @@ -15,8 +15,12 @@ package zconstants import ( + "fmt" + "github.com/jaegertracing/jaeger/model" + kelemetryv1a1 "github.com/kubewharf/kelemetry/pkg/crds/apis/v1alpha1" + "github.com/kubewharf/kelemetry/pkg/metrics" utilobject "github.com/kubewharf/kelemetry/pkg/util/object" ) @@ -117,6 +121,19 @@ const ( LinkRoleChild LinkRoleValue = "child" ) +var ErrUnknownTargetRole = metrics.LabelError(fmt.Errorf("unknown target role"), "UnknownTargetRole") + +func LinkRoleValueFromTargetRole(targetRole kelemetryv1a1.TargetRole) (LinkRoleValue, error) { + switch targetRole { + case kelemetryv1a1.TargetRoleChild: + return LinkRoleChild, nil + case kelemetryv1a1.TargetRoleParent: + return LinkRoleParent, nil + } + + return "", fmt.Errorf("%w %q", ErrUnknownTargetRole, targetRole) +} + // Determines the role of the reverse link. func ReverseLinkRole(role LinkRoleValue) LinkRoleValue { switch role { From 84b725b9f20657e935ee82cc2f5a21327476658d Mon Sep 17 00:00:00 2001 From: chankyin Date: Mon, 4 Dec 2023 14:42:10 +0800 Subject: [PATCH 2/6] fix(make): add LinkRule CRD to test cluster --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index f41d937c..8a24eee9 100644 --- a/Makefile +++ b/Makefile @@ -141,6 +141,7 @@ kind: docker network inspect kind -f '{{(index .IPAM.Config 0).Gateway}}' \ )/g" hack/tracing-config.yaml >hack/tracing-config.local.yaml cd hack && kind create cluster --config kind-cluster.yaml + kubectl --context kind-tracetest create -f crds/config COMPOSE_COMMAND ?= up --build -d --remove-orphans From 5859aac402d1c63c811e057a1264ba257e51836c Mon Sep 17 00:00:00 2001 From: chankyin Date: Tue, 5 Dec 2023 14:29:59 +0800 Subject: [PATCH 3/6] fix(linkrule): add per-rule dedup ID --- .editorconfig | 2 +- .golangci.yaml | 1 + charts/kelemetry/templates/_helpers.yaml | 1 + charts/kelemetry/values.yaml | 2 + crds/samples/helm-release-link-rule.yaml | 4 +- crds/samples/pod-secret.yaml | 22 ++++ e2e/run-all.sh | 2 + e2e/secret-link/client.sh | 39 ++++++ e2e/secret-link/config.sh | 9 ++ e2e/secret-link/validate.jq | 17 +++ hack/tfconfig.yaml | 7 ++ pkg/aggregator/linker/linker.go | 14 ++- pkg/aggregator/linker/rule/linker.go | 145 +++++++++++++++-------- pkg/crds/apis/register.go | 2 + pkg/crds/apis/v1alpha1/doc.go | 2 + pkg/crds/apis/v1alpha1/register.go | 2 + pkg/crds/apis/v1alpha1/util/util.go | 40 ++++--- pkg/util/marshal/marshal.go | 14 ++- quickstart.docker-compose.yaml | 1 + 19 files changed, 252 insertions(+), 74 deletions(-) create mode 100644 crds/samples/pod-secret.yaml create mode 100755 e2e/secret-link/client.sh create mode 100644 e2e/secret-link/config.sh create mode 100644 e2e/secret-link/validate.jq diff --git a/.editorconfig b/.editorconfig index 4e0d5f95..e9db2d4a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -22,7 +22,7 @@ indent_style = space indent_size = 4 [*.sh] -indent_style = tab +indent_style = space indent_size = 2 [*.dot] diff --git a/.golangci.yaml b/.golangci.yaml index 8557b16e..a479033d 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -2,6 +2,7 @@ run: timeout: 5m skip-dirs: - tools + - pkg/crds/client linters: enable: diff --git a/charts/kelemetry/templates/_helpers.yaml b/charts/kelemetry/templates/_helpers.yaml index 3ebd958a..404bca8f 100644 --- a/charts/kelemetry/templates/_helpers.yaml +++ b/charts/kelemetry/templates/_helpers.yaml @@ -114,6 +114,7 @@ span-cache-etcd-prefix: {{ .Values.aggregator.spanCache.etcd.prefix | toJson }} linker-worker-count: {{ .Values.linkers.workerCount }} annotation-linker-enable: {{ .Values.linkers.annotation }} owner-linker-enable: {{ .Values.linkers.ownerReference }} +rule-linker-enable: {{ .Values.linkers.rule }} {{/* TRACER */}} tracer-otel-endpoint: {{.Release.Name}}-collector.{{.Release.Namespace}}.svc:4317 diff --git a/charts/kelemetry/values.yaml b/charts/kelemetry/values.yaml index f9d57797..f50b721c 100644 --- a/charts/kelemetry/values.yaml +++ b/charts/kelemetry/values.yaml @@ -339,6 +339,8 @@ linkers: ownerReference: true # Enable the annotation linker, which links objects based on the `kelemetry.kubewharf.io/parent-link` annotation. annotation: true + # Enable the rule linker, which links objects using rules in the `LinkRule` CRD in the corresponding cluster. + rule: true # Object cache is an LRU cache for reusing lazily-fetched objects. objectCache: diff --git a/crds/samples/helm-release-link-rule.yaml b/crds/samples/helm-release-link-rule.yaml index f7b66e3c..45435002 100644 --- a/crds/samples/helm-release-link-rule.yaml +++ b/crds/samples/helm-release-link-rule.yaml @@ -10,8 +10,8 @@ targetTemplate: group: "" version: v1 resource: secrets - namespaceTemplate: '{{.metadata.annotations["meta.helm.sh/release-namespace"]}}' - nameTemplate: '{{.metadata.annotations["meta.helm.sh/release-name"]}}' + namespaceTemplate: '{{.metadata.annotations | index "meta.helm.sh/release-namespace"}}' + nameTemplate: '{{.metadata.annotations | index "meta.helm.sh/release-name"}}' link: targetRole: Parent class: templates diff --git a/crds/samples/pod-secret.yaml b/crds/samples/pod-secret.yaml new file mode 100644 index 00000000..acf4a913 --- /dev/null +++ b/crds/samples/pod-secret.yaml @@ -0,0 +1,22 @@ +apiVersion: kelemetry.kubewharf.io/v1alpha1 +kind: LinkRule +metadata: + name: pod-secret +sourceFilter: + resources: + - group: "" + resource: pods +targetTemplate: + group: "" + version: v1 + resource: secrets + namespaceTemplate: "{{.metadata.namespace}}" + nameTemplate: | + {{- range .spec.volumes -}} + {{- if .secret -}} + {{- .secret.secretName -}}, + {{- end -}} + {{- end -}} +link: + targetRole: Child + class: volumes diff --git a/e2e/run-all.sh b/e2e/run-all.sh index 6487c3ac..4dcab563 100755 --- a/e2e/run-all.sh +++ b/e2e/run-all.sh @@ -4,6 +4,8 @@ set -euo pipefail cd $(dirname $0) +export REPO_PATH=$(realpath ..) + export TEST_DISPLAY_MODE="21000000" export DISPLAY_MODES="${TEST_DISPLAY_MODE} ${DISPLAY_MODES:-}" diff --git a/e2e/secret-link/client.sh b/e2e/secret-link/client.sh new file mode 100755 index 00000000..6c025a3a --- /dev/null +++ b/e2e/secret-link/client.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +kubectl create -f ${REPO_PATH}/crds/samples/pod-secret.yaml + +kubectl create secret generic content + +kubectl create -f - << EOF +apiVersion: apps/v1 +kind: Deployment +metadata: + name: reader + labels: + app: reader +spec: + selector: + matchLabels: + app: reader + replicas: 2 + template: + metadata: + labels: + app: reader + spec: + containers: + - name: reader + command: ["sleep", "infinity"] + image: alpine:3.16 + volumeMounts: + - name: content + mountPath: /mnt/content + volumes: + - name: content + secret: + secretName: content +EOF +sleep 10 + +kubectl delete deployment/reader secret/content +sleep 20 diff --git a/e2e/secret-link/config.sh b/e2e/secret-link/config.sh new file mode 100644 index 00000000..6446501e --- /dev/null +++ b/e2e/secret-link/config.sh @@ -0,0 +1,9 @@ +local TRACE_DISPLAY_NAME="Volume links" +local TEST_DISPLAY_MODE="24100000" + +declare -A TRACE_SEARCH_TAGS +TRACE_SEARCH_TAGS[cluster]=tracetest +TRACE_SEARCH_TAGS[group]=apps +TRACE_SEARCH_TAGS[resource]=deployments +TRACE_SEARCH_TAGS[namespace]=default +TRACE_SEARCH_TAGS[name]=reader diff --git a/e2e/secret-link/validate.jq b/e2e/secret-link/validate.jq new file mode 100644 index 00000000..4fed9f6c --- /dev/null +++ b/e2e/secret-link/validate.jq @@ -0,0 +1,17 @@ +include "assert"; +include "graph"; + +.data[0] +| ._ = ( + # check tags + .spans + | root + | .tags | from_entries + | assertEq("root span is deployment"; .resource; "deployments") + | assertEq("root span has the correct name"; .name; "reader") +) | ._ = ( + # check that secret is linked + .spans + | map(select(.tags | from_entries | .resource == "secrets" and .name == "content")) + | assertEq("has content"; length; 1) +) | null diff --git a/hack/tfconfig.yaml b/hack/tfconfig.yaml index b8ddc198..2d20d5af 100644 --- a/hack/tfconfig.yaml +++ b/hack/tfconfig.yaml @@ -75,6 +75,13 @@ modifiers: - linkClass: children fromChild: false downwardDistance: 3 + "00100000": + # include secrets under pods + displayName: volume secrets + modifierName: link-selector + args: + ifAll: + - linkClass: volumes # Uncomment to enable extension trace from apiserver # "00000001": diff --git a/pkg/aggregator/linker/linker.go b/pkg/aggregator/linker/linker.go index 2bf517ee..92429b2d 100644 --- a/pkg/aggregator/linker/linker.go +++ b/pkg/aggregator/linker/linker.go @@ -27,8 +27,16 @@ type Linker interface { } type LinkerResult struct { - Object utilobject.Rich - Role zconstants.LinkRoleValue - Class string + // The linked object. + Object utilobject.Rich + // Determines whether the linked object should be displayed as a parent or child of the object span. + Role zconstants.LinkRoleValue + // Classifies links to help with link selection in tfconfig. + Class string + // DedupId is a variable in a span cache key that distinguishes the link pseudospan + // from other pseudospans in the same object in the same time window. + // + // This ID shall be consistent such that the link pseudospan does not get recreated many times, + // and shall be unique such that each link creator maintains its own links. DedupId string } diff --git a/pkg/aggregator/linker/rule/linker.go b/pkg/aggregator/linker/rule/linker.go index f9d4cb87..49ca9243 100644 --- a/pkg/aggregator/linker/rule/linker.go +++ b/pkg/aggregator/linker/rule/linker.go @@ -23,13 +23,20 @@ import ( "github.com/sirupsen/logrus" "github.com/spf13/pflag" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" + kubernetesscheme "k8s.io/client-go/kubernetes/scheme" + corev1client "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/record" "k8s.io/utils/clock" "github.com/kubewharf/kelemetry/pkg/aggregator/linker" + kelemetryv1a1 "github.com/kubewharf/kelemetry/pkg/crds/apis/v1alpha1" kelemetryv1a1util "github.com/kubewharf/kelemetry/pkg/crds/apis/v1alpha1/util" - "github.com/kubewharf/kelemetry/pkg/crds/client/informers/externalversions/apis/v1alpha1" + kelemetryv1a1informers "github.com/kubewharf/kelemetry/pkg/crds/client/informers/externalversions/apis/v1alpha1" kelemetryv1a1listers "github.com/kubewharf/kelemetry/pkg/crds/client/listers/apis/v1alpha1" "github.com/kubewharf/kelemetry/pkg/k8s" "github.com/kubewharf/kelemetry/pkg/k8s/discovery" @@ -62,7 +69,8 @@ func (options *Options) Setup(fs *pflag.FlagSet) { &options.CacheSyncTimeout, "rule-linker-sync-timeout", time.Second*5, - "duration to wait for the initial LinkRule list to become ready; timeout has no effect other than potentially missing link rules in some objects", + "duration to wait for the initial LinkRule list to become ready; "+ + "timeout has no effect other than potentially missing link rules in some objects", ) } @@ -76,22 +84,23 @@ type Controller struct { Discovery discovery.DiscoveryCache ObjectCache *objectcache.ObjectCache - listersMu sync.RWMutex - listers map[string]*listerEntry + clustersMu sync.RWMutex + clusters map[string]*clusterEntry } -type listerEntry struct { - readyCh <-chan struct{} - lister kelemetryv1a1listers.LinkRuleLister - err error - resetTimer func(time.Duration) +type clusterEntry struct { + readyCh <-chan struct{} + eventRecorder record.EventRecorder + lister kelemetryv1a1listers.LinkRuleLister + err error + resetTimer func(time.Duration) } var _ manager.Component = &Controller{} func (ctrl *Controller) Options() manager.Options { return &ctrl.options } func (ctrl *Controller) Init() error { - ctrl.listers = make(map[string]*listerEntry) + ctrl.clusters = make(map[string]*clusterEntry) return nil } func (ctrl *Controller) Start(ctx context.Context) error { return nil } @@ -119,7 +128,7 @@ func (ctrl *Controller) Lookup(ctx context.Context, object utilobject.Rich) ([]l } } - lister, err := ctrl.getLister(ctx, object.Cluster) + lister, eventRecorder, err := ctrl.getCluster(ctx, object.Cluster) if err != nil { return nil, fmt.Errorf("lister is unavailable for cluster %q", object.Cluster) } @@ -132,25 +141,60 @@ func (ctrl *Controller) Lookup(ctx context.Context, object utilobject.Rich) ([]l } for _, rule := range rules { - matched, err := kelemetryv1a1util.TestObject(object.Key, raw, &rule.SourceFilter) - if err != nil { - logger.WithField("rule", rule.Name).WithError(err).Error("error testing object for rule match") - continue + if ruleResults, err := ctrl.processRule(ctx, logger.WithField("rule", rule.Name), object, raw, rule); err != nil { + logger.WithField("rule", rule.Name).WithError(err).Error() + eventRecorder.Eventf( + rule, + corev1.EventTypeWarning, + "ProcessRule", + "Error processing %s %s/%s: %s", + object.Resource, + object.Namespace, + object.Name, + err.Error(), + ) + } else { + results = append(results, ruleResults...) } + } - if !matched { - continue - } + return results, nil +} + +func (ctrl *Controller) processRule( + ctx context.Context, + logger logrus.FieldLogger, + object utilobject.Rich, + objectRaw *unstructured.Unstructured, + rule *kelemetryv1a1.LinkRule, +) ([]linker.LinkerResult, error) { + matched, err := kelemetryv1a1util.TestObject(object.Key, objectRaw, &rule.SourceFilter) + if err != nil { + return nil, fmt.Errorf("cannot test object for rule match: %w", err) + } - parentRef, err := kelemetryv1a1util.GenerateTarget(&rule.TargetTemplate, object, raw) + if !matched { + return nil, nil + } + + targetRefs, err := kelemetryv1a1util.GenerateTargets(&rule.TargetTemplate, object, objectRaw) + if err != nil { + return nil, fmt.Errorf("cannot infer target object: %w", err) + } + + results := make([]linker.LinkerResult, 0) + for _, targetRef := range targetRefs { + targetLogger := logger.WithFields(targetRef.AsFields("target")) + + targetLogger.Debug("Testing if target exists") + targetRaw, err := ctrl.ObjectCache.Get(ctx, targetRef) if err != nil { - return nil, fmt.Errorf("cannot infer parent object: %w", err) + return nil, fmt.Errorf("cannot fetch target object from cache: %w", err) } - logger.WithField("rule", rule.Name).WithFields(parentRef.AsFields("parent")).Debug("Testing if parent exists") - parentRaw, err := ctrl.ObjectCache.Get(ctx, parentRef) - if err != nil { - return nil, fmt.Errorf("cannot get parent specified by rule: %w", err) + if targetRaw == nil { + targetLogger.Warn("target object does not exist") + continue } role, err := zconstants.LinkRoleValueFromTargetRole(rule.Link.TargetRole) @@ -158,20 +202,23 @@ func (ctrl *Controller) Lookup(ctx context.Context, object utilobject.Rich) ([]l return nil, fmt.Errorf("cannot parse target role: %w", err) } - results = append(results, linker.LinkerResult{ + result := linker.LinkerResult{ Object: utilobject.Rich{ - VersionedKey: parentRef, - Uid: parentRaw.GetUID(), - Raw: parentRaw, + VersionedKey: targetRef, + Uid: targetRaw.GetUID(), + Raw: targetRaw, }, - Role: role, - }) + Role: role, + Class: rule.Link.Class, + DedupId: fmt.Sprintf("linkRule/%s", rule.Name), + } + results = append(results, result) } return results, nil } -func (ctrl *Controller) getLister(ctx context.Context, cluster string) (kelemetryv1a1listers.LinkRuleLister, error) { +func (ctrl *Controller) getCluster(ctx context.Context, cluster string) (kelemetryv1a1listers.LinkRuleLister, record.EventRecorder, error) { if lister, exists := ctrl.tryGetLister(cluster); exists { return lister.touch(ctrl.options.InformerTtl) } @@ -187,10 +234,14 @@ func (ctrl *Controller) getLister(ctx context.Context, cluster string) (kelemetr if err != nil { err := fmt.Errorf("cannot initialize clients for cluster %q: %w", cluster, err) entry.err = err - return nil, err + return nil, nil, err } - informer := v1alpha1.NewLinkRuleInformer(client.KelemetryClient(), 0, cache.Indexers{}) + informer := kelemetryv1a1informers.NewLinkRuleInformer(client.KelemetryClient(), 0, cache.Indexers{}) + + broadcaster := record.NewBroadcaster() + broadcaster.StartRecordingToSink(&corev1client.EventSinkImpl{Interface: client.KubernetesClient().CoreV1().Events(metav1.NamespaceAll)}) + entry.eventRecorder = broadcaster.NewRecorder(kubernetesscheme.Scheme, corev1.EventSource{Component: "kelemetry"}) entry.lister = kelemetryv1a1listers.NewLinkRuleLister(informer.GetIndexer()) @@ -206,43 +257,43 @@ func (ctrl *Controller) getLister(ctx context.Context, cluster string) (kelemetr entry.resetTimer = resetFunc - return entry.lister, nil + return entry.lister, entry.eventRecorder, nil } -func (ctrl *Controller) tryGetLister(cluster string) (*listerEntry, bool) { - ctrl.listersMu.RLock() - defer ctrl.listersMu.RUnlock() - lister, exists := ctrl.listers[cluster] +func (ctrl *Controller) tryGetLister(cluster string) (*clusterEntry, bool) { + ctrl.clustersMu.RLock() + defer ctrl.clustersMu.RUnlock() + lister, exists := ctrl.clusters[cluster] return lister, exists } -func (ctrl *Controller) setPendingLister(cluster string) (_ *listerEntry, _readyCh chan<- struct{}) { - ctrl.listersMu.Lock() - defer ctrl.listersMu.Unlock() +func (ctrl *Controller) setPendingLister(cluster string) (_ *clusterEntry, _readyCh chan<- struct{}) { + ctrl.clustersMu.Lock() + defer ctrl.clustersMu.Unlock() - lister, exists := ctrl.listers[cluster] + lister, exists := ctrl.clusters[cluster] if exists { return lister, nil } readyCh := make(chan struct{}) - entry := &listerEntry{ + entry := &clusterEntry{ readyCh: readyCh, } - ctrl.listers[cluster] = entry + ctrl.clusters[cluster] = entry return entry, readyCh } -func (entry *listerEntry) touch(ttl time.Duration) (kelemetryv1a1listers.LinkRuleLister, error) { +func (entry *clusterEntry) touch(ttl time.Duration) (kelemetryv1a1listers.LinkRuleLister, record.EventRecorder, error) { <-entry.readyCh if entry.err != nil { - return nil, entry.err + return nil, nil, entry.err } entry.resetTimer(ttl) - return entry.lister, nil + return entry.lister, entry.eventRecorder, nil } // a specialized timer optimized for frequent resets and only supports strictly increasing resets. diff --git a/pkg/crds/apis/register.go b/pkg/crds/apis/register.go index 5cf6a170..9fa0c074 100644 --- a/pkg/crds/apis/register.go +++ b/pkg/crds/apis/register.go @@ -1,3 +1,5 @@ +// Copyright 2023 The Kelemetry Authors. +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/pkg/crds/apis/v1alpha1/doc.go b/pkg/crds/apis/v1alpha1/doc.go index d6764c1e..69e6473d 100644 --- a/pkg/crds/apis/v1alpha1/doc.go +++ b/pkg/crds/apis/v1alpha1/doc.go @@ -1,3 +1,5 @@ +// Copyright 2023 The Kelemetry Authors. +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/pkg/crds/apis/v1alpha1/register.go b/pkg/crds/apis/v1alpha1/register.go index 4f858cac..2a9a07a0 100644 --- a/pkg/crds/apis/v1alpha1/register.go +++ b/pkg/crds/apis/v1alpha1/register.go @@ -1,3 +1,5 @@ +// Copyright 2023 The Kelemetry Authors. +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/pkg/crds/apis/v1alpha1/util/util.go b/pkg/crds/apis/v1alpha1/util/util.go index 0dfd6cba..da8ebaeb 100644 --- a/pkg/crds/apis/v1alpha1/util/util.go +++ b/pkg/crds/apis/v1alpha1/util/util.go @@ -17,6 +17,7 @@ package kelemetryv1a1util import ( "bytes" "fmt" + "strings" "text/template" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -53,40 +54,47 @@ func TestObject(objectRef utilobject.Key, object metav1.Object, rule *kelemetryv return selector.Matches(objectLabels), nil } -func GenerateTarget( +func GenerateTargets( rule *kelemetryv1a1.LinkRuleTargetTemplate, sourceObjectRef utilobject.Rich, sourceObject *unstructured.Unstructured, -) (_target utilobject.VersionedKey, _ error) { +) (_targets []utilobject.VersionedKey, _ error) { cluster := sourceObjectRef.Cluster if rule.ClusterTemplate != "" { var err error cluster, err = executeTemplate("clusterTemplate", rule.ClusterTemplate, sourceObject) if err != nil { - return _target, err + return _targets, err } } namespace, err := executeTemplate("namespaceTemplate", rule.NamespaceTemplate, sourceObject) if err != nil { - return _target, err + return _targets, err } - name, err := executeTemplate("nameTemplate", rule.NameTemplate, sourceObject) + names, err := executeTemplate("nameTemplate", rule.NameTemplate, sourceObject) if err != nil { - return _target, err + return _targets, err } - return utilobject.VersionedKey{ - Key: utilobject.Key{ - Cluster: cluster, - Group: rule.Group, - Resource: rule.Resource, - Namespace: namespace, - Name: name, - }, - Version: rule.Version, - }, nil + targets := make([]utilobject.VersionedKey, 0) + for _, name := range strings.Split(names, ",") { + if name != "" { + targets = append(targets, utilobject.VersionedKey{ + Key: utilobject.Key{ + Cluster: cluster, + Group: rule.Group, + Resource: rule.Resource, + Namespace: namespace, + Name: name, + }, + Version: rule.Version, + }) + } + } + + return targets, nil } func executeTemplate(templateName string, templateString string, sourceObject *unstructured.Unstructured) (string, error) { diff --git a/pkg/util/marshal/marshal.go b/pkg/util/marshal/marshal.go index 3066a426..7ef1ec1c 100644 --- a/pkg/util/marshal/marshal.go +++ b/pkg/util/marshal/marshal.go @@ -97,7 +97,9 @@ func (filter *ObjectFilter) Matches(key utilobject.Key) bool { type stringPredicate = func(string) bool type StringFilter struct { - fn stringPredicate + pattern string + raw fields + fn stringPredicate } type fields struct { @@ -142,21 +144,23 @@ func (value fields) getBasePredicate() (stringPredicate, error) { func (f *StringFilter) UnmarshalJSON(buf []byte) error { var pattern string if err := json.Unmarshal(buf, &pattern); err == nil { + f.pattern = pattern f.fn = func(s string) bool { return s == pattern } return nil } - var value fields - if err := json.Unmarshal(buf, &value); err != nil { + var raw fields + if err := json.Unmarshal(buf, &raw); err != nil { return err } + f.raw = raw - predicate, err := value.getBasePredicate() + predicate, err := raw.getBasePredicate() if err != nil { return err } - then := value.Then.GetOr(true) + then := raw.Then.GetOr(true) f.fn = func(s string) bool { base := predicate(s) if base { diff --git a/quickstart.docker-compose.yaml b/quickstart.docker-compose.yaml index 7068f8f7..cdf7d027 100644 --- a/quickstart.docker-compose.yaml +++ b/quickstart.docker-compose.yaml @@ -60,6 +60,7 @@ services: "--event-informer-enable", "--annotation-linker-enable", "--owner-linker-enable", + "--rule-linker-enable", "--diff-decorator-enable", "--diff-controller-enable", "--diff-api-enable", From 0d8d4a037186c34608e1c012903a6121f9953b57 Mon Sep 17 00:00:00 2001 From: chankyin Date: Tue, 5 Dec 2023 17:23:47 +0800 Subject: [PATCH 4/6] fix(frontend/config): non-depth link selectors should not be fused after rejection --- pkg/frontend/reader/merge/merge.go | 4 +- pkg/frontend/reader/merge/merge_test.go | 12 ++-- pkg/frontend/tf/config/link_selector.go | 69 +++++++++++-------- .../tf/defaults/modifier/link_selector.go | 31 +++++---- 4 files changed, 65 insertions(+), 51 deletions(-) diff --git a/pkg/frontend/reader/merge/merge.go b/pkg/frontend/reader/merge/merge.go index 68740a30..e0e636ff 100644 --- a/pkg/frontend/reader/merge/merge.go +++ b/pkg/frontend/reader/merge/merge.go @@ -110,8 +110,8 @@ func (fl *followLinkPool[M]) scheduleFrom(obj *object[M], followLimit *atomic.In parentKey, childKey, parentIsSource = link.Key, obj.key, false } - subSelector := linkSelector.Admit(parentKey, childKey, parentIsSource, link.Class) - if subSelector != nil { + isAdmitted, subSelector := linkSelector.Admit(parentKey, childKey, parentIsSource, link.Class) + if isAdmitted { admittedLinks = append(admittedLinks, link) fl.knownKeys.Insert(link.Key) fl.schedule(link.Key, subSelector, followLimit, int32(fl.endTime.Sub(fl.startTime)/(time.Minute*30))) diff --git a/pkg/frontend/reader/merge/merge_test.go b/pkg/frontend/reader/merge/merge_test.go index f66574b0..50ae5e48 100644 --- a/pkg/frontend/reader/merge/merge_test.go +++ b/pkg/frontend/reader/merge/merge_test.go @@ -309,10 +309,10 @@ func TestFilteredTree(t *testing.T) { type rsPodLinksOnly struct{} -func (rsPodLinksOnly) Admit(parent, child utilobject.Key, parentIsSource bool, class string) tfconfig.LinkSelector { - if parent.Resource == "replicasets" && child.Resource == "pods" { - return rsPodLinksOnly{} - } else { - return nil - } +func (rsPodLinksOnly) Admit( + parent, child utilobject.Key, + parentIsSource bool, + class string, +) (_admit bool, _nextState tfconfig.LinkSelector) { + return parent.Resource == "replicasets" && child.Resource == "pods", rsPodLinksOnly{} } diff --git a/pkg/frontend/tf/config/link_selector.go b/pkg/frontend/tf/config/link_selector.go index d61b0277..270111bf 100644 --- a/pkg/frontend/tf/config/link_selector.go +++ b/pkg/frontend/tf/config/link_selector.go @@ -19,9 +19,25 @@ import utilobject "github.com/kubewharf/kelemetry/pkg/util/object" type LinkSelector interface { // Whether to follow the given link. // - // If link should be followed, return a non-nil LinkSelector. - // The returned object will be used to recursively follow links in the linked object. - Admit(parent utilobject.Key, child utilobject.Key, parentIsSource bool, linkClass string) LinkSelector + // The first output indicates whether the selector admits this link. + // The second output is the selector state that transitive links should use for admission. + // The second output may still be useful even if the first output is false when it is part of a UnionLinkSelector. + // A nil state "fuses" the selector, i.e. always return false for further transitive links. + Admit(parent utilobject.Key, child utilobject.Key, parentIsSource bool, linkClass string) (_admit bool, _nextState LinkSelector) +} + +func nilableLinkSelectorAdmit( + selector LinkSelector, + parent utilobject.Key, + child utilobject.Key, + parentIsSource bool, + linkClass string, +) (bool, LinkSelector) { + if selector == nil { + return false, nil + } + + return selector.Admit(parent, child, parentIsSource, linkClass) } type ConstantLinkSelector bool @@ -31,12 +47,12 @@ func (selector ConstantLinkSelector) Admit( child utilobject.Key, parentIsSource bool, linkClass string, -) LinkSelector { +) (_admit bool, _nextState LinkSelector) { if selector { - return selector + return true, selector } - return nil + return false, selector } type IntersectLinkSelector []LinkSelector @@ -46,17 +62,17 @@ func (selector IntersectLinkSelector) Admit( childKey utilobject.Key, parentIsSource bool, linkClass string, -) LinkSelector { - newChildren := make([]LinkSelector, len(selector)) - - for i, child := range selector { - newChildren[i] = child.Admit(parentKey, childKey, parentIsSource, linkClass) - if newChildren[i] == nil { - return nil - } +) (_admit bool, _nextState LinkSelector) { + admit := true + nextMemberStates := make([]LinkSelector, len(selector)) + + for i, member := range selector { + memberAdmit, nextMemberState := nilableLinkSelectorAdmit(member, parentKey, childKey, parentIsSource, linkClass) + admit = admit && memberAdmit + nextMemberStates[i] = nextMemberState } - return IntersectLinkSelector(newChildren) + return admit, IntersectLinkSelector(nextMemberStates) } type UnionLinkSelector []LinkSelector @@ -66,22 +82,15 @@ func (selector UnionLinkSelector) Admit( childKey utilobject.Key, parentIsSource bool, linkClass string, -) LinkSelector { - newChildren := make([]LinkSelector, len(selector)) - - ok := false - for i, child := range selector { - if child != nil { - newChildren[i] = child.Admit(parentKey, childKey, parentIsSource, linkClass) - if newChildren[i] != nil { - ok = true - } - } - } +) (_admit bool, _nextState LinkSelector) { + admit := false + nextMemberStates := make([]LinkSelector, len(selector)) - if ok { - return UnionLinkSelector(newChildren) + for i, member := range selector { + memberAdmit, nextMemberState := nilableLinkSelectorAdmit(member, parentKey, childKey, parentIsSource, linkClass) + admit = admit || memberAdmit + nextMemberStates[i] = nextMemberState } - return nil + return admit, UnionLinkSelector(nextMemberStates) } diff --git a/pkg/frontend/tf/defaults/modifier/link_selector.go b/pkg/frontend/tf/defaults/modifier/link_selector.go index 185ae5aa..eb01a163 100644 --- a/pkg/frontend/tf/defaults/modifier/link_selector.go +++ b/pkg/frontend/tf/defaults/modifier/link_selector.go @@ -146,14 +146,12 @@ func (s denySiblingsLinkSelector) Admit( child utilobject.Key, isFromParent bool, linkClass string, -) tfconfig.LinkSelector { +) (_admit bool, _nextState tfconfig.LinkSelector) { if !s.hasFirst { - return denySiblingsLinkSelector{hasFirst: true, firstIsFromParent: isFromParent} + return true, denySiblingsLinkSelector{hasFirst: true, firstIsFromParent: isFromParent} } - if s.firstIsFromParent != isFromParent { - return nil - } - return s + + return s.firstIsFromParent == isFromParent, s } // The path from queried object to any other object in the tree must only contain links matching this pattern. @@ -161,14 +159,19 @@ type patternLinkSelector struct { patterns []LinkPattern } -func (s patternLinkSelector) Admit(parent utilobject.Key, child utilobject.Key, isFromParent bool, linkClass string) tfconfig.LinkSelector { +func (s patternLinkSelector) Admit( + parent utilobject.Key, + child utilobject.Key, + isFromParent bool, + linkClass string, +) (_admit bool, _nextState tfconfig.LinkSelector) { for _, pattern := range s.patterns { if !pattern.Matches(parent, child, isFromParent, linkClass) { - return nil + return false, s } } - return s + return true, s } type direction bool @@ -189,14 +192,16 @@ func (d directedDistanceLinkSelector) Admit( child utilobject.Key, isFromParent bool, linkClass string, -) tfconfig.LinkSelector { +) (_admit bool, _nextState tfconfig.LinkSelector) { if isFromParent != (d.direction == directionDownwards) { - return d + return true, d } + if d.distance == 0 { - return nil + return false, nil } - return directedDistanceLinkSelector{ + + return true, directedDistanceLinkSelector{ direction: d.direction, distance: d.distance - 1, } From 6ca6263701ee415400f393d5777ddfb2a5e9d721 Mon Sep 17 00:00:00 2001 From: chankyin Date: Tue, 5 Dec 2023 17:47:14 +0800 Subject: [PATCH 5/6] chore: suppress contextcheck for chains through generated code --- pkg/aggregator/linker/rule/linker.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/aggregator/linker/rule/linker.go b/pkg/aggregator/linker/rule/linker.go index 49ca9243..84c3bca9 100644 --- a/pkg/aggregator/linker/rule/linker.go +++ b/pkg/aggregator/linker/rule/linker.go @@ -237,6 +237,7 @@ func (ctrl *Controller) getCluster(ctx context.Context, cluster string) (kelemet return nil, nil, err } + //nolint:contextcheck // cannot pass context through generated code informer := kelemetryv1a1informers.NewLinkRuleInformer(client.KelemetryClient(), 0, cache.Indexers{}) broadcaster := record.NewBroadcaster() From 4bbb418f4f9b3df4925bd33b4fb0ff2f945b5355 Mon Sep 17 00:00:00 2001 From: chankyin Date: Wed, 6 Dec 2023 18:52:42 +0800 Subject: [PATCH 6/6] feat(link): support more flexible templates in LinkRule --- Makefile | 2 +- .../kelemetry.kubewharf.io_linkrules.yaml | 109 ++++++++---- crds/samples/pod-secret.yaml | 22 --- crds/samples/pod-volume.yaml | 24 +++ e2e/secret-link/config.sh | 2 +- go.mod | 2 + go.sum | 4 + pkg/aggregator/linker/rule/linker.go | 7 +- pkg/crds/apis/v1alpha1/types_linkrule.go | 44 +++-- pkg/crds/apis/v1alpha1/util/util.go | 168 ++++++++++++++---- .../apis/v1alpha1/zz_generated.deepcopy.go | 34 +++- .../informers/externalversions/factory.go | 4 +- 12 files changed, 316 insertions(+), 106 deletions(-) delete mode 100644 crds/samples/pod-secret.yaml create mode 100644 crds/samples/pod-volume.yaml diff --git a/Makefile b/Makefile index 8a24eee9..156b9a5d 100644 --- a/Makefile +++ b/Makefile @@ -200,7 +200,7 @@ generate: paths=./pkg/crds/apis/... \ output:crd:dir=./crds/config go run k8s.io/code-generator/cmd/deepcopy-gen \ - -o /tmp/kelemetry-gen/github.com/kubewharf/kelemetry \ + -o /tmp/kelemetry-gen \ --input-dirs=./pkg/crds/apis/v1alpha1 \ --output-file-base=zz_generated.deepcopy \ -h ./hack/boilerplate.txt diff --git a/crds/config/kelemetry.kubewharf.io_linkrules.yaml b/crds/config/kelemetry.kubewharf.io_linkrules.yaml index 4aecc644..60fd5f3a 100644 --- a/crds/config/kelemetry.kubewharf.io_linkrules.yaml +++ b/crds/config/kelemetry.kubewharf.io_linkrules.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.0 + controller-gen.kubebuilder.io/version: v0.13.0 name: linkrules.kelemetry.kubewharf.io spec: group: kelemetry.kubewharf.io @@ -121,43 +121,86 @@ spec: type: object x-kubernetes-map-type: atomic type: object - targetTemplate: + targetTemplates: description: TargetTemplate indicates how to find the target object from a matched source object. - properties: - clusterTemplate: - description: "ClusterTemplate is a Go text template string to compute - the cluster name of the target object. \n The context of the template - is the child object that matched the rule. \n If empty or unspecified, - uses the cluster of the child object." - type: string - group: - type: string - nameTemplate: - description: "NameTemplate is a Go text template string to compute - the target object name. \n The context of the template is the child - object that matched the rule." - type: string - namespaceTemplate: - description: "NamespaceTemplate is a Go text template string to compute - the target object name. \n If the namespace is empty, the target - object is expected to be cluster-scoped." - type: string - resource: - type: string - version: - type: string - required: - - group - - nameTemplate - - namespaceTemplate - - resource - - version - type: object + items: + description: LinkRuleTargetTemplate indicates how to find the target + object from a matched source object. + properties: + cluster: + description: "Cluster computes the cluster name of the target object. + \n If empty or unspecified, uses the cluster of the child object." + properties: + goTemplate: + description: Parsed as a Go `text/template`. May output multiple + comma-delimited values (trailing comma allowed). + type: string + jq: + description: Parsed as a gojq query string. May output a string + or an array of strings. + type: string + literal: + description: A string literal, resolved as-is. + type: string + type: object + name: + description: "The name of the target object. \n Inherits the same + name as the source object if unspecified." + properties: + goTemplate: + description: Parsed as a Go `text/template`. May output multiple + comma-delimited values (trailing comma allowed). + type: string + jq: + description: Parsed as a gojq query string. May output a string + or an array of strings. + type: string + literal: + description: A string literal, resolved as-is. + type: string + type: object + namespace: + description: "The namespace of the target object. \n Cluster-scoped + target objects should emit an empty namespace. If the namespace + is empty, the target object is expected to be cluster-scoped. + \n Inherits the same namespace as the source object if unspecified." + properties: + goTemplate: + description: Parsed as a Go `text/template`. May output multiple + comma-delimited values (trailing comma allowed). + type: string + jq: + description: Parsed as a gojq query string. May output a string + or an array of strings. + type: string + literal: + description: A string literal, resolved as-is. + type: string + type: object + type: + description: The type of the target object, in the form `{groupName}/{version}/{resource}`. + properties: + goTemplate: + description: Parsed as a Go `text/template`. May output multiple + comma-delimited values (trailing comma allowed). + type: string + jq: + description: Parsed as a gojq query string. May output a string + or an array of strings. + type: string + literal: + description: A string literal, resolved as-is. + type: string + type: object + required: + - type + type: object + type: array required: - link - sourceFilter - - targetTemplate + - targetTemplates type: object served: true storage: true diff --git a/crds/samples/pod-secret.yaml b/crds/samples/pod-secret.yaml deleted file mode 100644 index acf4a913..00000000 --- a/crds/samples/pod-secret.yaml +++ /dev/null @@ -1,22 +0,0 @@ -apiVersion: kelemetry.kubewharf.io/v1alpha1 -kind: LinkRule -metadata: - name: pod-secret -sourceFilter: - resources: - - group: "" - resource: pods -targetTemplate: - group: "" - version: v1 - resource: secrets - namespaceTemplate: "{{.metadata.namespace}}" - nameTemplate: | - {{- range .spec.volumes -}} - {{- if .secret -}} - {{- .secret.secretName -}}, - {{- end -}} - {{- end -}} -link: - targetRole: Child - class: volumes diff --git a/crds/samples/pod-volume.yaml b/crds/samples/pod-volume.yaml new file mode 100644 index 00000000..3a6cb13d --- /dev/null +++ b/crds/samples/pod-volume.yaml @@ -0,0 +1,24 @@ +apiVersion: kelemetry.kubewharf.io/v1alpha1 +kind: LinkRule +metadata: + name: pod-volume +sourceFilter: + resources: + - group: "" + resource: pods +targetTemplates: + - type: + literal: v1/secrets + name: + jq: | + (.spec.volumes[] | select(has("secret")).secret.secretName) + + (.spec.volumes[] | select(has("projected")).projected.sources[] | select("secret").secret.name) + - type: + literal: v1/configmaps + name: + jq: | + (.spec.volumes[] | select(has("configMap")).configMap.name) + + (.spec.volumes[] | select(has("projected")).projected.sources[] | select("configMap").configMap.name) +link: + targetRole: Child + class: volumes diff --git a/e2e/secret-link/config.sh b/e2e/secret-link/config.sh index 95cb0e74..d43165dd 100644 --- a/e2e/secret-link/config.sh +++ b/e2e/secret-link/config.sh @@ -1,4 +1,4 @@ -kubectl create -f ${REPO_PATH}/crds/samples/pod-secret.yaml +kubectl create -f ${REPO_PATH}/crds/samples/pod-volume.yaml kubectl create secret generic content diff --git a/go.mod b/go.mod index bedcec7e..9079e490 100644 --- a/go.mod +++ b/go.mod @@ -64,6 +64,7 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/gobuffalo/flect v1.0.2 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/gocql/gocql v1.3.2 // indirect github.com/gogo/googleapis v1.4.1 // indirect @@ -158,6 +159,7 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.28.0 // indirect k8s.io/gengo v0.0.0-20220902162205-c0856e24416d // indirect k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect diff --git a/go.sum b/go.sum index ae162dbb..3dd837ab 100644 --- a/go.sum +++ b/go.sum @@ -161,6 +161,8 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91 github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA= +github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gocql/gocql v1.3.2 h1:ox3T+R7VFibHSIGxRkuUi1uIvAv8jBHCWxc+9aFQ/LA= @@ -861,6 +863,8 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY= k8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0= +k8s.io/apiextensions-apiserver v0.28.0 h1:CszgmBL8CizEnj4sj7/PtLGey6Na3YgWyGCPONv7E9E= +k8s.io/apiextensions-apiserver v0.28.0/go.mod h1:uRdYiwIuu0SyqJKriKmqEN2jThIJPhVmOWETm8ud1VE= k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8= k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg= k8s.io/apiserver v0.28.4 h1:BJXlaQbAU/RXYX2lRz+E1oPe3G3TKlozMMCZWu5GMgg= diff --git a/pkg/aggregator/linker/rule/linker.go b/pkg/aggregator/linker/rule/linker.go index 84c3bca9..4604b2de 100644 --- a/pkg/aggregator/linker/rule/linker.go +++ b/pkg/aggregator/linker/rule/linker.go @@ -140,8 +140,10 @@ func (ctrl *Controller) Lookup(ctx context.Context, object utilobject.Rich) ([]l return nil, fmt.Errorf("cannot list rules") } + templateContext := kelemetryv1a1util.PrepareContext(raw, object.Cluster, object.Resource) + for _, rule := range rules { - if ruleResults, err := ctrl.processRule(ctx, logger.WithField("rule", rule.Name), object, raw, rule); err != nil { + if ruleResults, err := ctrl.processRule(ctx, logger.WithField("rule", rule.Name), object, raw, rule, templateContext); err != nil { logger.WithField("rule", rule.Name).WithError(err).Error() eventRecorder.Eventf( rule, @@ -167,6 +169,7 @@ func (ctrl *Controller) processRule( object utilobject.Rich, objectRaw *unstructured.Unstructured, rule *kelemetryv1a1.LinkRule, + templateContext map[string]any, ) ([]linker.LinkerResult, error) { matched, err := kelemetryv1a1util.TestObject(object.Key, objectRaw, &rule.SourceFilter) if err != nil { @@ -177,7 +180,7 @@ func (ctrl *Controller) processRule( return nil, nil } - targetRefs, err := kelemetryv1a1util.GenerateTargets(&rule.TargetTemplate, object, objectRaw) + targetRefs, err := kelemetryv1a1util.GenerateTargets(ctx, rule.TargetTemplates, object, templateContext) if err != nil { return nil, fmt.Errorf("cannot infer target object: %w", err) } diff --git a/pkg/crds/apis/v1alpha1/types_linkrule.go b/pkg/crds/apis/v1alpha1/types_linkrule.go index 61705cc8..64f529f5 100644 --- a/pkg/crds/apis/v1alpha1/types_linkrule.go +++ b/pkg/crds/apis/v1alpha1/types_linkrule.go @@ -39,7 +39,7 @@ type LinkRule struct { SourceFilter LinkRuleSourceFilter `json:"sourceFilter"` // TargetTemplate indicates how to find the target object from a matched source object. - TargetTemplate LinkRuleTargetTemplate `json:"targetTemplate"` + TargetTemplates []LinkRuleTargetTemplate `json:"targetTemplates"` // Link specifies how the two objects are linked together. Link LinkRuleLink `json:"link"` @@ -68,26 +68,46 @@ type LinkRuleSourceFilter struct { // LinkRuleTargetTemplate indicates how to find the target object from a matched source object. type LinkRuleTargetTemplate struct { - // ClusterTemplate is a Go text template string to compute the cluster name of the target object. - // - // The context of the template is the child object that matched the rule. + // Cluster computes the cluster name of the target object. // // If empty or unspecified, uses the cluster of the child object. // +optional - ClusterTemplate string `json:"clusterTemplate,omitempty"` + Cluster TextTemplate `json:"cluster,omitempty"` - // The type of the target object. - metav1.GroupVersionResource `json:",inline"` + // The type of the target object, in the form `{groupName}/{version}/{resource}`. + Type TextTemplate `json:"type"` - // NamespaceTemplate is a Go text template string to compute the target object name. + // The namespace of the target object. // + // Cluster-scoped target objects should emit an empty namespace. // If the namespace is empty, the target object is expected to be cluster-scoped. - NamespaceTemplate string `json:"namespaceTemplate"` + // + // Inherits the same namespace as the source object if unspecified. + // +optional + Namespace TextTemplate `json:"namespace"` - // NameTemplate is a Go text template string to compute the target object name. + // The name of the target object. // - // The context of the template is the child object that matched the rule. - NameTemplate string `json:"nameTemplate"` + // Inherits the same name as the source object if unspecified. + // +optional + Name TextTemplate `json:"name"` +} + +// TextTemplate is a union struct that supports templating one or multiple values with one of the supported formats. +// +// Template types that accept a context receive the source object as the template with the following additional fields: +// - .metadata.resource, the plural name of the object +// - .metadata.clusterName, the cluster name used by Kelemetry for the cluster that owns the source object +type TextTemplate struct { + // A string literal, resolved as-is. + // +optional + Literal *string `json:"literal"` + // Parsed as a Go `text/template`. May output multiple comma-delimited values (trailing comma allowed). + // +optional + GoTemplate string `json:"goTemplate"` + // Parsed as a gojq query string. May output a string or an array of strings. + // +optional + Jq string `json:"jq"` } // LinkRuleLink specifies how the two objects are linked together. diff --git a/pkg/crds/apis/v1alpha1/util/util.go b/pkg/crds/apis/v1alpha1/util/util.go index da8ebaeb..d9ebfa37 100644 --- a/pkg/crds/apis/v1alpha1/util/util.go +++ b/pkg/crds/apis/v1alpha1/util/util.go @@ -16,13 +16,16 @@ package kelemetryv1a1util import ( "bytes" + "context" "fmt" "strings" "text/template" + "github.com/itchyny/gojq" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" kelemetryv1a1 "github.com/kubewharf/kelemetry/pkg/crds/apis/v1alpha1" utilobject "github.com/kubewharf/kelemetry/pkg/util/object" @@ -55,58 +58,161 @@ func TestObject(objectRef utilobject.Key, object metav1.Object, rule *kelemetryv } func GenerateTargets( - rule *kelemetryv1a1.LinkRuleTargetTemplate, + ctx context.Context, + templates []kelemetryv1a1.LinkRuleTargetTemplate, sourceObjectRef utilobject.Rich, - sourceObject *unstructured.Unstructured, + context map[string]any, ) (_targets []utilobject.VersionedKey, _ error) { - cluster := sourceObjectRef.Cluster - if rule.ClusterTemplate != "" { - var err error - cluster, err = executeTemplate("clusterTemplate", rule.ClusterTemplate, sourceObject) + targets := make([]utilobject.VersionedKey, 0) + + for _, template := range templates { + clusters, err := executeTemplate(ctx, "cluster", template.Cluster, context, []string{sourceObjectRef.Cluster}) if err != nil { return _targets, err } + + if len(clusters) == 0 { + clusters = []string{sourceObjectRef.Cluster} + } + + types, err := executeTemplate(ctx, "type", template.Type, context, nil) + if err != nil { + return _targets, err + } + + namespaces, err := executeTemplate(ctx, "namespace", template.Namespace, context, []string{sourceObjectRef.Namespace}) + if err != nil { + return _targets, err + } + + names, err := executeTemplate(ctx, "name", template.Name, context, []string{sourceObjectRef.Name}) + if err != nil { + return _targets, err + } + + for _, cluster := range clusters { + for _, gvrString := range types { + gvrSplit := strings.Split(gvrString, "/") + var gvr schema.GroupVersionResource + switch len(gvrSplit) { + case 2: + gvr.Version = gvrSplit[0] + gvr.Resource = gvrSplit[1] + case 3: + gvr.Group = gvrSplit[0] + gvr.Version = gvrSplit[1] + gvr.Resource = gvrSplit[2] + default: + return nil, fmt.Errorf("invalid g/v/r notation %q", gvrString) + } + + for _, namespace := range namespaces { + for _, name := range names { + targets = append(targets, utilobject.VersionedKey{ + Key: utilobject.Key{ + Cluster: cluster, + Group: gvr.Group, + Resource: gvr.Resource, + Namespace: namespace, + Name: name, + }, + Version: gvr.Version, + }) + } + } + } + } + } + + return targets, nil +} + +func PrepareContext(sourceObject *unstructured.Unstructured, clusterName string, resource string) map[string]any { + data := sourceObject.DeepCopy().Object + metadata := data["metadata"].(map[string]any) + metadata["clusterName"] = clusterName + metadata["resource"] = resource + return data +} + +func executeTemplate( + ctx context.Context, + templateName string, + templateObject kelemetryv1a1.TextTemplate, + context map[string]any, + defaultOutput []string, +) (output []string, err error) { + if templateObject.GoTemplate != "" { + output, err = executeGoTemplate(templateName, templateObject.Jq, context) + } else if templateObject.Jq != "" { + output, err = executeJqTemplate(ctx, templateName, templateObject.Jq, context) + } else if templateObject.Literal != nil { + output, err = []string{*templateObject.Literal}, nil + } else { + output, err = defaultOutput, nil } - namespace, err := executeTemplate("namespaceTemplate", rule.NamespaceTemplate, sourceObject) if err != nil { - return _targets, err + return nil, err } - names, err := executeTemplate("nameTemplate", rule.NameTemplate, sourceObject) + return output, nil +} + +func executeGoTemplate(templateName string, templateString string, context map[string]any) ([]string, error) { + tmpl, err := template.New(templateName).Parse(templateString) if err != nil { - return _targets, err + return nil, fmt.Errorf("cannot parse %s as Go text/template: %w", templateName, err) } - targets := make([]utilobject.VersionedKey, 0) - for _, name := range strings.Split(names, ",") { - if name != "" { - targets = append(targets, utilobject.VersionedKey{ - Key: utilobject.Key{ - Cluster: cluster, - Group: rule.Group, - Resource: rule.Resource, - Namespace: namespace, - Name: name, - }, - Version: rule.Version, - }) + buf := new(bytes.Buffer) + if err := tmpl.Execute(buf, context); err != nil { + return nil, fmt.Errorf("cannot execute %s: %w", templateName, err) + } + + output := make([]string, 0) + for _, value := range strings.Split(buf.String(), ",") { + if value != "" { + output = append(output, value) } } - return targets, nil + return output, nil } -func executeTemplate(templateName string, templateString string, sourceObject *unstructured.Unstructured) (string, error) { - tmpl, err := template.New(templateName).Parse(templateString) +func executeJqTemplate(ctx context.Context, templateName string, templateString string, context map[string]any) ([]string, error) { + query, err := gojq.Parse(templateString) if err != nil { - return "", fmt.Errorf("cannot parse %s as Go text/template: %w", templateName, err) + return nil, fmt.Errorf("cannot parse %s as JQ query: %w", templateName, err) } - buf := new(bytes.Buffer) - if err := tmpl.Execute(buf, sourceObject.Object); err != nil { - return "", fmt.Errorf("cannot execute %s: %w", templateName, err) + iter := query.RunWithContext(ctx, context) + + output := make([]string, 0) + + for { + value, ok := iter.Next() + if !ok { + break + } + + switch value := value.(type) { + case error: + return nil, fmt.Errorf("error executing JQ query for %s: %w", templateName, value) + case string: + output = append(output, value) + case []any: + for _, item := range value { + if stringItem, isString := item.(string); isString { + output = append(output, stringItem) + } else { + return nil, fmt.Errorf("%s query must return string or []string, got []%T", templateName, item) + } + } + default: + return nil, fmt.Errorf("%s query must return string or []string, got %T", templateName, value) + } } - return buf.String(), nil + return output, nil } diff --git a/pkg/crds/apis/v1alpha1/zz_generated.deepcopy.go b/pkg/crds/apis/v1alpha1/zz_generated.deepcopy.go index 0ee4eb77..6fddd7ad 100644 --- a/pkg/crds/apis/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/crds/apis/v1alpha1/zz_generated.deepcopy.go @@ -30,7 +30,13 @@ func (in *LinkRule) DeepCopyInto(out *LinkRule) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.SourceFilter.DeepCopyInto(&out.SourceFilter) - out.TargetTemplate = in.TargetTemplate + if in.TargetTemplates != nil { + in, out := &in.TargetTemplates, &out.TargetTemplates + *out = make([]LinkRuleTargetTemplate, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } out.Link = in.Link return } @@ -131,7 +137,10 @@ func (in *LinkRuleSourceFilter) DeepCopy() *LinkRuleSourceFilter { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LinkRuleTargetTemplate) DeepCopyInto(out *LinkRuleTargetTemplate) { *out = *in - out.GroupVersionResource = in.GroupVersionResource + in.Cluster.DeepCopyInto(&out.Cluster) + in.Type.DeepCopyInto(&out.Type) + in.Namespace.DeepCopyInto(&out.Namespace) + in.Name.DeepCopyInto(&out.Name) return } @@ -144,3 +153,24 @@ func (in *LinkRuleTargetTemplate) DeepCopy() *LinkRuleTargetTemplate { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TextTemplate) DeepCopyInto(out *TextTemplate) { + *out = *in + if in.Literal != nil { + in, out := &in.Literal, &out.Literal + *out = new(string) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TextTemplate. +func (in *TextTemplate) DeepCopy() *TextTemplate { + if in == nil { + return nil + } + out := new(TextTemplate) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/crds/client/informers/externalversions/factory.go b/pkg/crds/client/informers/externalversions/factory.go index fed3298e..cff28131 100644 --- a/pkg/crds/client/informers/externalversions/factory.go +++ b/pkg/crds/client/informers/externalversions/factory.go @@ -174,7 +174,7 @@ func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[ref return res } -// InternalInformerFor returns the SharedIndexInformer for obj using an internal +// InformerFor returns the SharedIndexInformer for obj using an internal // client. func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer { f.lock.Lock() @@ -247,7 +247,7 @@ type SharedInformerFactory interface { // ForResource gives generic access to a shared informer of the matching type. ForResource(resource schema.GroupVersionResource) (GenericInformer, error) - // InternalInformerFor returns the SharedIndexInformer for obj using an internal + // InformerFor returns the SharedIndexInformer for obj using an internal // client. InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer