Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,6 @@ Inspect the VPC again to see `enableDnsHostnames` appear in `forProvider`:
```shell
kubectl get vpc.ec2.aws.m.upbound.io -l crossplane.io/composite=cool-network -o json | jq '.items[0].spec.forProvider'
```

### Clean-up

```shell
Expand All @@ -342,6 +341,36 @@ kubectl delete -f omit/composition.yaml
kubectl delete -f omit/xrd.yaml
```

## Core example

This example demonstrates support for core Kubernetes resources. A Secret is always
created both in Crossplane <2.2.0 without required_schemas capability and in Crossplane >2.2.0
where that capability is supported.

Create the `CoolSecret` XRD and composition:
```shell
kubectl apply -f core/xrd.yaml
kubectl apply -f core/composition.yaml
```

Create a `CoolSecret` instance:
```shell
kubectl apply -f core/xr.yaml
```

Watch the XR become ready when its composed resources satisfy their `readyWhen` conditions:
```shell
crossplane beta trace -w coolsecret.core.example.crossplane.io/cool-secret
```

### Clean-up

```shell
kubectl delete -f core/xr.yaml
kubectl delete -f core/composition.yaml
kubectl delete -f core/xrd.yaml
```

## Collection Limits Example

This example demonstrates the collection size limit guardrail that prevents `forEach`
Expand Down
32 changes: 32 additions & 0 deletions example/core/composition.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: coolsecret-core
spec:
compositeTypeRef:
apiVersion: core.example.crossplane.io/v1
kind: CoolSecret
mode: Pipeline
pipeline:
- step: kro-run
functionRef:
name: function-kro
input:
apiVersion: kro.fn.crossplane.io/v1beta1
kind: ResourceGraph
resources:
# Secret is always ready.
- id: secret
readyWhen:
- ${true}
template:
apiVersion: v1
kind: Secret
metadata: {}
stringData:
secretField: ${schema.spec.secretField}

# note there is no step with function-auto-ready in this pipeline because we are
# exclusively using readyWhen statements for each resource. Crossplane will
# detect that the parent XR is ready when all composed resources have their
# ready state set to true in the function pipeline via the readyWhen statements.
6 changes: 6 additions & 0 deletions example/core/xr.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: core.example.crossplane.io/v1
kind: CoolSecret
metadata:
name: cool-secret
spec:
secretField: cool-value
25 changes: 25 additions & 0 deletions example/core/xrd.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
apiVersion: apiextensions.crossplane.io/v2
kind: CompositeResourceDefinition
metadata:
name: coolsecrets.core.example.crossplane.io
spec:
group: core.example.crossplane.io
names:
kind: CoolSecret
plural: coolsecrets
scope: Namespaced
versions:
- name: v1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
secretField:
type: string
status:
type: object
7 changes: 7 additions & 0 deletions generate/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//go:build generate
// +build generate

package generate

//go:generate rm -f ../schemas/zz_generated.openapi.go
//go:generate go run -tags generate k8s.io/kube-openapi/cmd/openapi-gen --output-dir ../schemas --output-file zz_generated.openapi.go --output-pkg "github.com/crossplane-contrib/function-kro/schemas" k8s.io/api/core/v1 k8s.io/api/apps/v1 k8s.io/api/batch/v1 k8s.io/api/rbac/v1 k8s.io/api/networking/v1 k8s.io/api/policy/v1 k8s.io/api/storage/v1 k8s.io/api/autoscaling/v2 k8s.io/api/coordination/v1 k8s.io/apimachinery/pkg/apis/meta/v1 k8s.io/apimachinery/pkg/runtime k8s.io/apimachinery/pkg/api/resource k8s.io/apimachinery/pkg/util/intstr
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
golang.org/x/exp v0.0.0-20260112195511-716be5621a96
golang.org/x/sync v0.19.0
google.golang.org/protobuf v1.36.11
k8s.io/api v0.35.1
k8s.io/apiextensions-apiserver v0.35.0
k8s.io/apimachinery v0.35.1
k8s.io/apiserver v0.35.0
Expand Down Expand Up @@ -98,7 +99,6 @@ require (
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.35.1 // indirect
k8s.io/code-generator v0.35.0 // indirect
k8s.io/gengo/v2 v2.0.0-20251215205346-5ee0d033ba5b // indirect
k8s.io/klog/v2 v2.130.1 // indirect
Expand Down
29 changes: 27 additions & 2 deletions kro/graph/schema/resolver/combined_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@
package resolver

import (
"maps"

"github.com/crossplane-contrib/function-kro/schemas"
"k8s.io/apiextensions-apiserver/pkg/generated/openapi"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/cel/openapi/resolver"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/validation/spec"
)

Expand All @@ -34,14 +38,35 @@ func NewCombinedResolverFromCRDs(crdResolver *CRDSchemaResolver) resolver.Schema
}
}

// newCoreResolver creates a resolver for built-in Kubernetes types.
// newCoreResolver creates a resolver for built-in Kubernetes types using
// compiled-in OpenAPI definitions. This handles types like Deployment, Service,
// ConfigMap, etc. that are part of the core Kubernetes API.
//
// It merges two definition sources into a single resolver so that $ref
// resolution works across both:
// - generated definitions: common API groups (core/v1, apps/v1, batch/v1,
// rbac/v1, networking/v1, policy/v1, storage/v1, autoscaling/v2, coordination/v1)
// - apiextensions-apiserver definitions: CRD types, meta/v1 (ObjectMeta,
// LabelSelector, etc.), and other apimachinery types
func newCoreResolver() resolver.SchemaResolver {
return resolver.NewDefinitionsSchemaResolver(
openapi.GetOpenAPIDefinitions,
mergedOpenAPIDefinitions,
scheme.Scheme,
)
}

// mergedOpenAPIDefinitions returns OpenAPI definitions from both our generated
// definitions and the apiextensions-apiserver definitions. Our generated
// definitions take priority for overlapping types.
func mergedOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
// Start with apiextensions-apiserver definitions (meta/v1, CRD types, etc.)
merged := openapi.GetOpenAPIDefinitions(ref)
// Layer our generated definitions on top (core/v1, apps/v1, etc.)
maps.Copy(merged, schemas.GetOpenAPIDefinitions(ref))

return merged
}

// combinedResolver tries resolvers in order until one returns a schema.
type combinedResolver struct {
primary resolver.SchemaResolver
Expand Down
63 changes: 58 additions & 5 deletions patches/v0.9.0_PATCHES.md
Original file line number Diff line number Diff line change
Expand Up @@ -818,39 +818,92 @@ func (r *CRDSchemaResolver) ResolveSchema(gvk schema.GroupVersionKind) (*spec.Sc
Combined resolver factories that pair Crossplane-provided schemas with core Kubernetes type resolution. In v0.8.x these were appended to `resolver.go`; in v0.9.0 they live in a separate file since `resolver.go` is now identical to upstream.

```go
// Package resolver provides schema resolution strategies for function-kro.
//
// This file contains function-kro additions that are not present in upstream KRO.
// These provide combined resolvers that pair Crossplane-provided schemas (or CRD-extracted
// schemas) with a core resolver for built-in Kubernetes types.
package resolver

import (
"maps"

"github.com/crossplane-contrib/function-kro/schemas"
"k8s.io/apiextensions-apiserver/pkg/generated/openapi"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/cel/openapi/resolver"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/validation/spec"
)

// NewCombinedResolverFromSchemas creates a schema resolver that combines
// a schema map resolver (for Crossplane-provided schemas) with a core resolver
// (for built-in Kubernetes types).
func NewCombinedResolverFromSchemas(schemaMapResolver *SchemaMapResolver) resolver.SchemaResolver {
return &combinedResolver{primary: schemaMapResolver, fallback: newCoreResolver()}
coreResolver := newCoreResolver()
return &combinedResolver{
primary: schemaMapResolver,
fallback: coreResolver,
}
}

// NewCombinedResolverFromCRDs creates a schema resolver that combines
// a CRD schema resolver with a core resolver.
func NewCombinedResolverFromCRDs(crdResolver *CRDSchemaResolver) resolver.SchemaResolver {
return &combinedResolver{primary: crdResolver, fallback: newCoreResolver()}
coreResolver := newCoreResolver()
return &combinedResolver{
primary: crdResolver,
fallback: coreResolver,
}
}

// newCoreResolver creates a resolver for built-in Kubernetes types using
// compiled-in OpenAPI definitions. This handles types like Deployment, Service,
// ConfigMap, etc. that are part of the core Kubernetes API.
//
// It merges two definition sources into a single resolver so that $ref
// resolution works across both:
// - generated definitions: common API groups (core/v1, apps/v1, batch/v1,
// rbac/v1, networking/v1, policy/v1, storage/v1, autoscaling/v2, coordination/v1)
// - apiextensions-apiserver definitions: CRD types, meta/v1 (ObjectMeta,
// LabelSelector, etc.), and other apimachinery types
func newCoreResolver() resolver.SchemaResolver {
return resolver.NewDefinitionsSchemaResolver(openapi.GetOpenAPIDefinitions, scheme.Scheme)
return resolver.NewDefinitionsSchemaResolver(
mergedOpenAPIDefinitions,
scheme.Scheme,
)
}

// mergedOpenAPIDefinitions returns OpenAPI definitions from both our generated
// definitions and the apiextensions-apiserver definitions. Our generated
// definitions take priority for overlapping types.
func mergedOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
// Start with apiextensions-apiserver definitions (meta/v1, CRD types, etc.)
merged := openapi.GetOpenAPIDefinitions(ref)
// Layer our generated definitions on top (core/v1, apps/v1, etc.)
maps.Copy(merged, schemas.GetOpenAPIDefinitions(ref))

return merged
}

// combinedResolver tries resolvers in order until one returns a schema.
type combinedResolver struct {
primary resolver.SchemaResolver
fallback resolver.SchemaResolver
}

func (c *combinedResolver) ResolveSchema(gvk schema.GroupVersionKind) (*spec.Schema, error) {
s, err := c.primary.ResolveSchema(gvk)
if err != nil { return nil, err }
if s != nil { return s, nil }
if err != nil {
return nil, err
}
if s != nil {
return s, nil
}
return c.fallback.ResolveSchema(gvk)
}

```

### Addition to `kro/graph/schema/schema.go`
Expand Down
Loading
Loading