Patterns
A Pattern is a custom resource that describes one relational smell. It is the core building block of KubePattern: you codify what a defect looks like, and the analysis engine reports every resource that matches it.
Describe the defect, not the rule
A pattern states the condition that a defective resource satisfies, so every match is a finding. Write what is wrong, not what would be right.
This condition is not always a negative one. An orphan or a dangling reference is an absence (matchNone). A pooler named like another database cluster is the presence of a relation (matchAll).
Not in a tagged release yet
These parts of the language are on the main branch and arrive with the next release: element scope ([@]), indexes and quoted keys in paths, kind wildcards, categories and derived kinds, label selectors, defaults, transforms, literal values, typed EQUALS and valuesFrom. The latest tagged release at the time of writing is v0.0.6. A pattern that does not use them behaves exactly as before. To use them now, see Try unreleased features. Do not apply such a pattern to an older engine: its linter does not reject them all, and a [@] path or a default it does not know can make the pattern report every target.
Anatomy of a pattern
A pattern has three parts. The target is the resource the smell is reported on. The dependencies are the other resources that must be inspected to judge it. The relationships are the relations that the target is expected to have with them.
apiVersion: kubepattern.dev/v1
kind: Pattern
metadata:
name: argocd-appproject-empty
spec:
# Descriptive fields, copied into every finding
displayName: AppProject Without Applications
category: Security
severity: LOW
message: "AppProject {{target.metadata.name}} grants permissions but no Application uses it."
reference: "https://argo-cd.readthedocs.io/en/stable/user-guide/projects/"
# What the smell is about
target:
kind: AppProject
apiVersion: argoproj.io/v1alpha1
plural: appprojects
# Narrow the population before any relation is evaluated
filters:
matchNone:
- path: "metadata.name"
operator: EQUALS
values: ["default"]
# What gives it context
dependencies:
- id: application
kind: Application
apiVersion: argoproj.io/v1alpha1
plural: applications
# The relation that makes it a smell
relationships:
matchNone:
- with: application
type: custom
criteria:
- targetPath: "metadata.name"
dependencyPath: "spec.project"
operator: EQUALSRead as a whole: an AppProject other than default is defective when no Application names it in spec.project.
A pattern contains no expressions. Its checks are paths, operators and values placed inside this fixed structure.
API reference v1
Root
| Property | Type | Required | Description |
|---|---|---|---|
apiVersion | string | Yes | Always kubepattern.dev/v1. |
kind | string | Yes | Always Pattern. |
metadata.name | string | Yes | Unique name of the pattern, a valid Kubernetes name (for example my-pattern). Patterns are cluster-scoped. |
spec | object | Yes | The specification of the smell. |
spec
| Property | Type | Required | Description |
|---|---|---|---|
displayName | string | Yes | Human-readable name of the pattern. |
category | string | Yes | Free-form category, copied into every finding (for example Security, Reliability, Architecture, Cost). |
severity | enum | Yes | LOW, MEDIUM, HIGH or CRITICAL. |
message | string | Yes | Message of every finding. Supports placeholders. |
reference | string | No | URL of documentation on how to fix the smell. |
for next release | duration | No | Minimum time the condition must hold before a finding becomes Active, such as 10m or 24h. See Lifecycle. Empty means 0s. |
target | object | Yes | The resource the smell is reported on. |
dependencies | array | No | Other resources that give the target context. |
relationships | object | No | The relations the target has with its dependencies. |
displayName, category, severity and message are descriptive. They play no part in detection.
target
| Property | Type | Required | Description |
|---|---|---|---|
kind | string | Yes | Kind of the resource, for example Deployment or ClusterIssuer. "*" stands for every kind of the API version. |
apiVersion | string | Yes | API version of the kind, for example apps/v1. |
plural | string | Yes | Plural resource name, which the dynamic client needs to list the kind. Omitted when kind is "*". |
category | string | No | Every kind listed under this discovery category. Replaces kind, apiVersion and plural. |
kinds | array | No | A list of resource types, each in one of the forms above. Replaces kind, apiVersion and plural. |
filters | object | No | Predicates that narrow the population before any relation is evaluated. |
emitOnEmpty next release | bool | No | When no resource matches the target, emit a single synthetic Smell. See absence patterns. |
A target uses exactly one of kind, category or kinds. Resource types describes the forms that select more than one kind.
dependencies[]
A dependency is described like the target, plus an id.
| Property | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique identifier, used by relationships[].with. |
kind, apiVersion, plural, category, kinds | Yes | As in target. | |
fromTarget | object | No | Instead of a fixed kind: the kinds named by fields of the targets. See derived kinds. |
filters | object | No | Predicates on the dependency's own fields. |
Dependencies are cluster-wide. When a smell only makes sense within a namespace, make the namespace an explicit criterion, as in the CloudNativePG example below.
Paths
Filters and criteria address the fields of a resource with paths. Segments are separated by dots.
| Syntax | Meaning |
|---|---|
spec.secretStoreRef.name | a nested field |
spec.containers[*].image | every element of an array. The values of different elements are not correlated. |
spec.data[@].name | the element bound by the evaluation. See element scope. |
spec.data[0].name | one element |
metadata.labels['app.kubernetes.io/name'] | a key that contains dots or slashes. ["..."] works too. |
A path that reaches nothing yields no value. A criterion with no value on one side does not hold, a filter EXISTS is false, and IS_EMPTY is true.
Filters
A filter is a predicate on a single resource: a path, an operator and the values to compare against. Filters restrict the population before any relation is evaluated, so they express the scope of a pattern rather than its detection logic.
| Property | Type | Description |
|---|---|---|
matchAll | array | Every condition must hold. |
matchAny | array | At least one condition must hold. |
matchNone | array | No condition may hold. |
Each condition has:
| Property | Type | Required | Description |
|---|---|---|---|
path | string | Yes | A path into the object, such as metadata.namespace or spec.containers[*].image. [@] is allowed in target filters only. |
operator | enum | Yes | One of the filter operators. |
values | array | Depends | Values to compare with. Required for every operator except EXISTS and IS_EMPTY, which must have none. |
valuesFrom | string | No | Instead of values, with EQUALS only: compare with the values at another path of the same resource, under the same element binding. |
Relationships
A filter constrains one resource, while a relationship constrains a pair.
| Property | Type | Description |
|---|---|---|
matchAll | array | Every relationship must hold. |
matchAny | array | At least one relationship must hold. |
matchNone | array | No relationship may hold. |
Each entry has:
| Property | Type | Required | Description |
|---|---|---|---|
with | string | Yes | The id of the dependency. |
type | enum | Yes | custom, owns, ownedBy, selects or selectedBy. See relationship types. |
criteria | array | For custom | The fields that define the relation. Must be omitted for owns and ownedBy. Optional for selects and selectedBy, where they add conditions to the selection. |
selectorPath | string | No | For selects and selectedBy: where the label selector is. Defaults to spec.selector. |
emptySelector | enum | No | For selects and selectedBy: what a selector without requirements selects, all or none. See label selectors. |
A relationship holds for the target when at least one instance of the dependency satisfies it, so a single entry is an existential statement. Inside matchNone, it is negated: no instance of the dependency relates to the target in the declared way. That is exactly what an orphan or an unused definition is.
When a dependency has no instances at all
matchNone is satisfied, so every target yields a finding. That is the intended reading when nobody has created any instance of the kind. A dependency kind that is not installed or not readable is different: the pattern is skipped instead of reporting everything.
Criteria
Each criterion of a relationship compares a value of the target with a value of the dependency.
| Property | Type | Required | Description |
|---|---|---|---|
targetPath | string | Yes | Path into the target, unless targetValue is set. |
dependencyPath | string | Yes | Path into the dependency, unless dependencyValue is set. [*] ranges over an array, and may be nested. |
operator | enum | Yes | EQUALS. |
targetValue, dependencyValue | string | No | A literal instead of the path on that side, for example to require that the bound element has resource: pages. At most one side can be a literal. |
targetDefault, dependencyDefault | object | No | The value to use when the path yields nothing. See defaults. |
targetTransform, dependencyTransform | object | No | Rewrites each value before the comparison. See transforms. |
EQUALS holds when the values extracted from the two sides have a non-empty intersection. All the criteria of one entry must hold for the same dependency instance, and with [@], for the same bound element.
Defaults
Many APIs fill in a field that is omitted. A reference without a namespace points to the referrer's own namespace, and a store reference without a kind means a SecretStore. A default supplies that value, so the omitted field is compared like an explicit one.
criteria:
- targetPath: "metadata.namespace"
dependencyPath: "spec.sourceRef.namespace"
dependencyDefault: {path: "metadata.namespace"} # another field of the same resource
operator: EQUALS
- targetPath: "kind"
dependencyPath: "spec.secretStoreRef.kind"
dependencyDefault: {value: SecretStore} # or a literal
operator: EQUALSA default sets exactly one of path or value. It cannot be used with a [*] path, where it would apply only when no element has a value. Use [@] to default each element. The default's own path cannot contain [*] either.
Transforms
Some references are encoded in a string: a group inside an API version, a name before an @ revision, a value inside a URL. A transform extracts the part to compare. It is applied to each value, after the default.
| Transform | Keeps | Example |
|---|---|---|
{apiGroup: true} | the group of an API version | cert-manager.io/v1 → cert-manager.io, and v1 → empty |
{split: {separator: "@", index: 0}} | one part of the value. A negative index counts from the end. | webservice@v2 → webservice |
{regex: "name=([^&]+)"} | the first capture group. A value that does not match is dropped. | ?name=pg&ns=a → pg |
{lowercase: true} | the value in lower case | ClusterIssuer → clusterissuer |
A transform sets exactly one of these, and a regex must have exactly one capture group.
Resource types
A target or a dependency can stand for several kinds at once, resolved through API discovery at run time.
| Form | Example | Selects |
|---|---|---|
| one kind | {kind: Secret, apiVersion: v1, plural: secrets} | that kind |
| every kind of a group version | {kind: "*", apiVersion: widgets.templates.krateo.io/v1beta1} | the listable kinds that discovery finds in it |
| a category | {category: fluxcd-appliers} | every kind whose discovery categories include it |
| a list | kinds: [{kind: Markdown, ...}, {kind: "*", ...}, {category: ...}] | the union of its entries |
| derived from the targets (dependencies only) | fromTarget: {apiVersionPath: status.apiVersion, kindPath: status.kind} | the kinds named by fields of the targets. See derived kinds. |
In this Krateo pattern, one target covers three widget kinds, and one dependency covers two:
message: "{{target.kind}} {{target.metadata.namespace}}/{{target.metadata.name}} is labelled managed-by a RESTAction or Table that does not exist in its namespace."
target:
kinds:
- {kind: Markdown, apiVersion: widgets.templates.krateo.io/v1beta1, plural: markdowns}
- {kind: YamlViewer, apiVersion: widgets.templates.krateo.io/v1beta1, plural: yamlviewers}
- {kind: RESTAction, apiVersion: templates.krateo.io/v1, plural: restactions}
filters:
matchAll:
- {path: "metadata.labels['managed-by']", operator: EXISTS}
dependencies:
- id: manager
kinds:
- {kind: RESTAction, apiVersion: templates.krateo.io/v1, plural: restactions}
- {kind: Table, apiVersion: widgets.templates.krateo.io/v1beta1, plural: tables}{{target.kind}}in the message names the kind of each finding's target.- A category resolves to the server's preferred version of each kind.
- An unknown group version in a
kind: "*"makes the pattern be skipped, as a missing CRD does. - A category that matches nothing is not an error: it simply has no instances. As a dependency under
matchNone, it then reports every target, so check the category name withkubectl api-resources --categories=<name>.
Derived kinds
Some resources name the kind they relate to in their own fields, as a definition does for the kind it generates. fromTarget reads that kind from each target instead of listing it in the pattern.
| Property | Description |
|---|---|
apiVersionPath or apiVersion | Path to the API version in the target, or a fixed one. Exactly one is set. |
kindPath | Path to the kind in the target. Required. |
pluralPath | Path to the plural resource name. When it is omitted, discovery finds it. |
The target filters apply, so only the kinds named by actual candidates are fetched. With [@] in the paths, as in spec.generates[@].kind, each element names one kind. [*] is rejected, because it would pair the API version of one element with the kind of another. A kind that the API server does not serve has no instances, and a warning is logged. The CompositionDefinition example uses it.
Element scope
With [*], every criterion ranges over the whole array on its own, so a name found in one entry and a namespace found in another count as a match. With [@], all the paths of one resource that share the prefix up to the [@] refer to the same element. The engine enumerates the elements and evaluates one binding at a time.
- On the dependency side, a dependency matches when one of its elements satisfies all the criteria.
spec.data[@].sourceRef.storeRef.nameandspec.data[@].sourceRef.storeRef.kindare read from the same entry. - On the target side, the target filters and all the relationships share the target's bindings. The target is defective when the relationships hold for at least one element that passes the target filters. Under
matchNone, this reads "some reference resolves to nothing".
This RoleBinding pattern examines each ServiceAccount subject on its own. The binding is defective when one of them does not exist, even if the others do. A subject without a namespace defaults to the binding's own.
target:
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
plural: rolebindings
filters:
matchAll: # examine ServiceAccount subjects only
- {path: "subjects[@].kind", operator: EQUALS, values: [ServiceAccount]}
dependencies:
- {id: serviceaccount, kind: ServiceAccount, apiVersion: v1, plural: serviceaccounts}
relationships:
matchNone: # defective if SOME subject does not exist
- with: serviceaccount
type: custom
criteria:
- {targetPath: "subjects[@].name", dependencyPath: metadata.name, operator: EQUALS}
- {targetPath: "subjects[@].namespace", targetDefault: {path: metadata.namespace},
dependencyPath: metadata.namespace, operator: EQUALS}The linter enforces these rules:
[@]is not allowed in dependency filters, because dependencies are filtered before any binding. It cannot follow a[*]in the same path.- The anchors of one side form a single nested chain.
spec.rules[@]andspec.rules[@].refs[@]together are fine.spec.data[@]andspec.dataFrom[@]together are rejected, because two independent arrays would bind their cartesian product, which is empty as soon as one of them is. The target side covers the target filters and every relationship. The dependency side is checked per relationship, so two relationships may anchor different arrays of the same dependency. - A target whose anchored array is missing or empty has no binding and is not a candidate. A RoleBinding without subjects has no dangling subject.
- When the target side has anchors, every relationship must read the innermost anchored element, through a target path or a target default. A relationship that ignores it, such as
owns, would never be evaluated for a target without the element. Put it in its own pattern.
Relationship types
KubePattern captures relations through primitives that map onto the mechanisms Kubernetes resources use to refer to one another.
| Type | The relationship holds when… | Mechanism |
|---|---|---|
owns | the target transitively owns the dependency | ownerReferences |
ownedBy | the target is transitively owned by the dependency | ownerReferences |
custom | user-defined criteria hold between target and dependency fields | references by name |
selects | the target's selector matches the dependency's labels | label selectors |
selectedBy | the dependency's selector matches the target's labels | label selectors |
Ownership is transitive, and needs no further specification: a Deployment owns a Pod through the intermediate ReplicaSet without the pattern ever mentioning it. Custom resources usually refer to one another through plain specification fields that KubePattern cannot know in advance, which is why custom lets you name those fields.
Label selectors
selects reads the selector of the target at selectorPath and matches it against the labels of the dependency. selectedBy does the reverse. Optional criteria add conditions that must hold for the same pair.
- A selector can be a
LabelSelector(matchLabels,matchExpressions) or a plain label map, as inService.spec.selector. - A missing or invalid selector selects nothing. An invalid one is logged.
- Namespaces are not implied. Add a namespace criterion when the selection is namespaced.
A selector without requirements selects what emptySelector says, all or none. When it is unset, the API conventions apply. A LabelSelector with empty matchLabels and matchExpressions, or an empty {} in a field whose name ends in Selector (such as podSelector: {} in a NetworkPolicy), selects everything. An empty {} in any other field selects nothing, as for an empty Service selector. Set emptySelector: all where the API differs. A PodDisruptionBudget, for example, selects every Pod of its namespace with spec.selector: {}.
The selector path can be bound to an element. In External Secrets, a PushSecret can choose its stores by label, one entry at a time:
- with: push
type: selectedBy
selectorPath: "spec.secretStoreRefs[@].labelSelector"
criteria:
- {targetPath: kind, dependencyPath: "spec.secretStoreRefs[@].kind",
dependencyDefault: {value: SecretStore}, operator: EQUALS}
- {targetPath: metadata.namespace, dependencyPath: metadata.namespace, operator: EQUALS}Operators
Filter operators
| Operator | Holds when… |
|---|---|
EQUALS | the field equals any of the values, compared as strings. Booleans and numbers compare by their string form, so values: ["true"] matches spec.suspend: true. |
EXISTS | the field exists |
IS_EMPTY | the field is missing or empty |
GREATER_THAN, GREATER_OR_EQUAL, LESS_THAN, LESS_OR_EQUAL | the field, read as a number, compares to the value |
ARRAY_SIZE_EQUALS, ARRAY_SIZE_GREATER_THAN, ARRAY_SIZE_GREATER_OR_EQUAL, ARRAY_SIZE_LESS_THAN, ARRAY_SIZE_LESS_OR_EQUAL | the size of the array field compares to the value |
Criteria operators
| Operator | Holds when… |
|---|---|
EQUALS | the values on the two sides have a non-empty intersection |
CONTAINS and LABEL_SELECTOR are declared in the API but not implemented, and the linter rejects them. Use transforms and the label selector relationships instead.
Message placeholders
The message of a pattern can contain placeholders, which are filled in for every finding.
| Placeholder | Replaced with |
|---|---|
{{target.metadata.name}} | name of the target |
{{target.metadata.namespace}} | namespace of the target |
{{target.metadata.uid}} | UID of the target |
{{target.kind}} | kind of the target, useful when the target stands for several kinds |
{{target.apiVersion}} | API version of the target |
{{smell.since}} next release | when the condition was first observed |
{{smell.phase}} next release | Pending or Active |
Absence patterns
next release Sometimes the problem is that nothing of a kind exists at all. Set target.emitOnEmpty: true and, when no resource matches the target and its filters, KubePattern emits a single synthetic Smell named <pattern-name>-empty, with no target resource.
spec:
displayName: No Network Policies
category: Security
severity: HIGH
message: "The cluster has no NetworkPolicy at all."
target:
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
plural: networkpolicies
emitOnEmpty: trueLinting
Patterns are read from the cluster and checked by a linter before use. A malformed pattern is logged and skipped, and the others still run. The linter checks the structure: required fields, known operators, that every with names a dependency, and that custom relationships have criteria. It also checks the syntax of every path, and the rules of element scope, defaults and transforms.
The linter cannot know your field names
A mistyped dependencyPath yields no values, and inside matchNone that means a finding for every target. Paths are checked for syntax, not against the CRD schemas. Check the paths of a new pattern against the schema (kubectl explain <kind>.spec) before you rely on it, and try it on a cluster you can inspect.
Things to know when writing patterns
The language is small on purpose. These are the limits you will meet most often.
| Limit | What it means in practice |
|---|---|
| A snapshot, not a history | A pattern sees the cluster at one instant. Use for to ignore transient states. To ignore history objects, such as the CertificateRequests that a Certificate owns and that record its past issuances, add a dependency filter like metadata.ownerReferences IS_EMPTY. |
| Content inside templates and remote references | A reference that only appears in a CUE or Go template, or inside an OCI bundle, is not seen. KubePattern analyses the state of the cluster, not what a template would render. |
| Paths are not checked against the schemas | A mistyped path yields no value. See Linting. |
| One API version per kind | Read each kind at the same API version in all your patterns. The same object read at two versions has one UID, and the graph keeps it only once. |
| String-encoded references, partly | Transforms extract most of them. A short host name that would need a conditional default to become a fully qualified one cannot be matched yet. |
The roadmap lists what is planned next.
Examples
These patterns are written for real platforms, and run without any change to the engine. More are in the Registry.
An unused definition, with several referrers
A cert-manager ClusterIssuer is used by a Certificate or a CertificateRequest. The dependencies are filtered because an omitted issuerRef.kind means a namespaced Issuer, so only references that name a ClusterIssuer count.
apiVersion: kubepattern.dev/v1
kind: Pattern
metadata:
name: certmanager-clusterissuer-not-used
spec:
displayName: ClusterIssuer Not Used
category: Security
severity: LOW
message: "ClusterIssuer {{target.metadata.name}} is not referenced by any Certificate or CertificateRequest; it still holds signing material."
reference: "https://cert-manager.io/docs/usage/certificate/"
target:
kind: ClusterIssuer
apiVersion: cert-manager.io/v1
plural: clusterissuers
dependencies:
- id: certificate
kind: Certificate
apiVersion: cert-manager.io/v1
plural: certificates
filters:
matchAll:
- path: "spec.issuerRef.kind"
operator: EQUALS
values: ["ClusterIssuer"]
- id: request
kind: CertificateRequest
apiVersion: cert-manager.io/v1
plural: certificaterequests
filters:
matchAll:
- path: "spec.issuerRef.kind"
operator: EQUALS
values: ["ClusterIssuer"]
relationships:
matchNone:
- with: certificate
type: custom
criteria:
- targetPath: "metadata.name"
dependencyPath: "spec.issuerRef.name"
operator: EQUALS
- with: request
type: custom
criteria:
- targetPath: "metadata.name"
dependencyPath: "spec.issuerRef.name"
operator: EQUALSAn ownership relation
An Argo CD ApplicationSet whose generators match nothing owns no Application. The owns type needs no criteria, because it follows ownerReferences.
apiVersion: kubepattern.dev/v1
kind: Pattern
metadata:
name: argocd-applicationset-generates-nothing
spec:
displayName: ApplicationSet Generates No Applications
category: Architecture
severity: MEDIUM
message: "ApplicationSet {{target.metadata.namespace}}/{{target.metadata.name}} currently owns no Application: its generators match nothing."
target:
kind: ApplicationSet
apiVersion: argoproj.io/v1alpha1
plural: applicationsets
dependencies:
- id: application
kind: Application
apiVersion: argoproj.io/v1alpha1
plural: applications
relationships:
matchNone:
- with: application
type: ownsA misconfiguration
A CloudNativePG Pooler that shares its name with a Cluster in the same namespace. This is the presence of a relation, so it uses matchAll.
apiVersion: kubepattern.dev/v1
kind: Pattern
metadata:
name: cnpg-pooler-name-clashes-with-cluster
spec:
displayName: Pooler Named Like a Cluster
category: Misconfiguration
severity: HIGH
message: "Pooler {{target.metadata.namespace}}/{{target.metadata.name}} has the same name as a CNPG Cluster in the same namespace."
target:
kind: Pooler
apiVersion: postgresql.cnpg.io/v1
plural: poolers
dependencies:
- id: cluster
kind: Cluster
apiVersion: postgresql.cnpg.io/v1
plural: clusters
relationships:
matchAll:
- with: cluster
type: custom
criteria:
- targetPath: "metadata.name"
dependencyPath: "metadata.name"
operator: EQUALS
- targetPath: "metadata.namespace"
dependencyPath: "metadata.namespace"
operator: EQUALSWith kind sets, defaults and transforms
These patterns come from KubePattern's evaluation, rewritten with kind sets, defaults and transforms. They were validated on minikube. Each one gives the right verdict on the cases that the original version missed, and removing the construct brings the miss back.
A reference whose namespace may be omitted
A Flux OCIRepository is consumed by a HelmRelease or a Kustomization. The referrers are the kinds of the fluxcd-appliers category, so an applier kind added by a later Flux release is covered too. A reference without a namespace points to the referrer's own.
apiVersion: kubepattern.dev/v1
kind: Pattern
metadata:
name: flux-ocirepository-not-used
spec:
displayName: OCIRepository Not Consumed
category: Cost
severity: LOW
message: "OCIRepository {{target.metadata.namespace}}/{{target.metadata.name}} is polled by source-controller but no HelmRelease or Kustomization consumes it."
reference: "https://fluxcd.io/flux/components/source/ocirepositories/"
target:
kind: OCIRepository
apiVersion: source.toolkit.fluxcd.io/v1
plural: ocirepositories
dependencies:
- id: applier
category: fluxcd-appliers
relationships:
matchNone:
- with: applier
type: custom
criteria:
- {targetPath: "metadata.name", dependencyPath: "spec.chartRef.name", operator: EQUALS}
- {targetPath: "kind", dependencyPath: "spec.chartRef.kind", operator: EQUALS}
- targetPath: "metadata.namespace"
dependencyPath: "spec.chartRef.namespace"
dependencyDefault: {path: "metadata.namespace"}
operator: EQUALS
- with: applier
type: custom
criteria:
- {targetPath: "metadata.name", dependencyPath: "spec.sourceRef.name", operator: EQUALS}
- {targetPath: "kind", dependencyPath: "spec.sourceRef.kind", operator: EQUALS}
- targetPath: "metadata.namespace"
dependencyPath: "spec.sourceRef.namespace"
dependencyDefault: {path: "metadata.namespace"}
operator: EQUALSA reference encoded in an API version
A Crossplane Composition names the composite kind it implements with an API version and a kind. The group inside the API version is compared with the group of each CompositeResourceDefinition, so a kind defined only in another group is detected.
apiVersion: kubepattern.dev/v1
kind: Pattern
metadata:
name: crossplane-composition-without-xrd
spec:
displayName: Composition Targets an Undefined Composite Kind
category: Architecture
severity: HIGH
message: "Composition {{target.metadata.name}} composes a kind that no CompositeResourceDefinition defines: it can never be selected."
reference: "https://docs.crossplane.io/latest/composition/compositions/"
target:
kind: Composition
apiVersion: apiextensions.crossplane.io/v1
plural: compositions
dependencies:
- id: xrd
kind: CompositeResourceDefinition
apiVersion: apiextensions.crossplane.io/v2
plural: compositeresourcedefinitions
relationships:
matchNone:
- with: xrd
type: custom
criteria:
- {targetPath: "spec.compositeTypeRef.kind", dependencyPath: "spec.names.kind", operator: EQUALS}
- targetPath: "spec.compositeTypeRef.apiVersion"
targetTransform: {apiGroup: true}
dependencyPath: "spec.group"
operator: EQUALSA kind named by another resource
A Krateo CompositionDefinition generates a kind, which it records in its status. The compositions are listed from that kind, so no generated kind has to be named in the pattern.
apiVersion: kubepattern.dev/v1
kind: Pattern
metadata:
name: krateo-compositiondefinition-unused
spec:
displayName: CompositionDefinition Unused
category: Architecture
severity: LOW
message: "CompositionDefinition {{target.metadata.namespace}}/{{target.metadata.name}} has no composition of the kind it generates, but keeps a CRD and a composition-dynamic-controller running."
reference: "https://docs.krateo.io/key-concepts/kco/core-provider"
target:
kind: CompositionDefinition
apiVersion: core.krateo.io/v1alpha1
plural: compositiondefinitions
filters:
matchAll:
- {path: status.kind, operator: EXISTS}
dependencies:
- id: composition
fromTarget: {apiVersionPath: status.apiVersion, kindPath: status.kind, pluralPath: status.resource}
relationships:
matchNone:
- with: composition
type: custom
criteria:
- {targetPath: status.kind, dependencyPath: kind, operator: EQUALS}
- {targetPath: status.apiVersion, dependencyPath: apiVersion, operator: EQUALS}