From 600ed59c39d69ea9d42a528e4c3dc1601a301d85 Mon Sep 17 00:00:00 2001 From: Tim <32556895+Avarei@users.noreply.github.com> Date: Thu, 11 Jul 2024 01:37:08 +0200 Subject: [PATCH 01/13] create more strict request types lossely aligned with function-sdk-go Signed-off-by: Tim <32556895+Avarei@users.noreply.github.com> --- pkl/crossplane.contrib/CompositionRequest.pkl | 36 +++++-- .../CompositionResponse.pkl | 87 ++++++++++++++++- pkl/crossplane.contrib/Resource.pkl | 47 +++++++++ pkl/crossplane.contrib/RunFunction.pkl | 40 -------- pkl/crossplane.contrib/crossplane.pkl | 97 ++++++++++++++----- 5 files changed, 230 insertions(+), 77 deletions(-) create mode 100644 pkl/crossplane.contrib/Resource.pkl diff --git a/pkl/crossplane.contrib/CompositionRequest.pkl b/pkl/crossplane.contrib/CompositionRequest.pkl index 0fd9bc2..74ddffa 100644 --- a/pkl/crossplane.contrib/CompositionRequest.pkl +++ b/pkl/crossplane.contrib/CompositionRequest.pkl @@ -1,15 +1,31 @@ @ModuleInfo { minPklVersion = "0.25.1" } module CompositionRequest -import "RunFunction.pkl" +import "Resource.pkl" +import "@k8s/K8sResource.pkl" +import "Pkl.pkl" /// Metadata pertaining to this request. -meta: RunFunction.RequestMeta? +meta: RequestMeta? + +/// RequestMeta contains metadata pertaining to a RunFunctionRequest. +class RequestMeta { + tag: String +} /// The observed state prior to invocation of a Function pipeline. State passed /// to each Function is fresh as of the time the pipeline was invoked, not as /// of the time each Function was invoked. -observed: RunFunction.State? +observed: ObservedState? + +/// State of the composite resource (XR) and any composed resources. +class ObservedState { + /// The state of the composite resource (XR). + composite: Resource.Composite + + /// The state of any composed resources. + resources: Mapping +} /// Desired state according to a Function pipeline. The state passed to a /// particular Function may have been accumulated by previous Functions in the @@ -21,11 +37,12 @@ observed: RunFunction.State? /// likely not what you want to do. Leaving out fields that had been returned /// as desired before will result in them being deleted from the objects in the /// cluster. -desired: RunFunction.State? +desired: Resource.DesiredState? + /// Optional input specific to this Function invocation. A JSON representation /// of the 'input' block of the relevant entry in a Composition's pipeline. -input: Mapping? +input: Pkl? /// Optional context. Crossplane may pass arbitary contextual information to a /// Function. A Function may also return context in its RunFunctionResponse, @@ -39,13 +56,12 @@ context: Mapping? /// extra_resources field. If a Function requested extra resources that /// did not exist, Crossplane sets the map key to an empty Resources message to /// indicate that it attempted to satisfy the request. -extraResources: Mapping +extraResources: Mapping>? /// Helper function to Retrieve extraResources that have been requested under a given `name`. -function getExtraResources(name: String): Listing = - let (listingWithNil = extraResources.getOrNull(name).ifNonNull((it: RunFunction.Resources) -> it.items) ?? new Listing {}) - listingWithNil.toList().filter((n) -> n != null).toListing() as Listing +function getExtraResources(name: String): Listing = + extraResources.getOrNull(name) ?? new Listing {} /// Helper function to Retrieve a specific resource from extraResources. -function getExtraResource(name: String, index: Int): RunFunction.Resource? = +function getExtraResource(name: String, index: Int): Resource.Extra? = getExtraResources(name).toList().getOrNull(index) diff --git a/pkl/crossplane.contrib/CompositionResponse.pkl b/pkl/crossplane.contrib/CompositionResponse.pkl index 7ecca65..d0ec7c7 100644 --- a/pkl/crossplane.contrib/CompositionResponse.pkl +++ b/pkl/crossplane.contrib/CompositionResponse.pkl @@ -2,9 +2,18 @@ module CompositionResponse import "RunFunction.pkl" +import "Resource.pkl" /// Metadata pertaining to this response. -meta: RunFunction.ResponseMeta? +meta: ResponseMeta? + +/// ResponseMeta contains metadata pertaining to a RunFunctionResponse. +class ResponseMeta { + /// Time-to-live of this response. Deterministic Functions with no side-effects + /// (e.g. simple templating Functions) may specify a TTL. Crossplane may choose + /// to cache responses until the TTL expires. + ttl: Duration +} /// Desired state according to a Function pipeline. Functions may add desired /// state, and may mutate or delete any part of the desired state they are @@ -17,17 +26,50 @@ meta: RunFunction.ResponseMeta? /// likely not what you want to do. Leaving out fields that had been returned /// as desired before will result in them being deleted from the objects in the /// cluster. -desired: RunFunction.State? +desired: Resource.DesiredState? /// Results of the Function run. Results are used for observability purposes. -results: Listing +results: List? + +/// A Result of running a Function. +class Result { + /// Severity of this result. + severity: Severity = "Unspecified" + /// Human-readable details about the result. + message: String +} +typealias Severity = "Normal"|"Warning"|"Fatal"|"Unspecified" /// Optional context to be passed to the next Function in the pipeline as part /// of the RunFunctionRequest. Dropped on the last function in the pipeline. context: Mapping? /// Requirements that must be satisfied for this Function to run successfully. -requirements: RunFunction.Requirements? +requirements: Requirements? + +class Requirements { + extraResources: Mapping +} + +/// ResourceSelector selects a group of resources, either by name or by label. +class ResourceSelector { + apiVersion: String + kind: String + match: Match +} + +class Match { + /// Match the resource with this name. + /// Note: matchLabels takes precedence if both are defined. + matchName: String? + /// Match all resources with these labels. + matchLabels: MatchLabels? +} + +/// MatchLabels defines a set of labels to match resources against. +class MatchLabels { + labels: Mapping +} output { renderer = new YamlRenderer { @@ -46,6 +88,43 @@ output { seconds = secs nanos = ns.toUnit("ns").value.toInt() } + [Resource.DesiredComposed] = (dc: Resource.DesiredComposed) -> + if (dc.ready == "True") + new RunFunction.Resource { + resource = dc.resource + ready = READY_TRUE + } + else if (dc.ready == "False") + new RunFunction.Resource { + resource = dc.resource + ready = READY_FALSE + } + else + new RunFunction.Resource { + resource = dc.resource + ready = READY_UNSPECIFIED + } + [Result] = (res: Result) -> + if (res.severity == "Normal") + new RunFunction.Result { + message = res.message + severity = SEVERITY_NORMAL + } + else if (res.severity == "Warning") + new RunFunction.Result { + message = res.message + severity = SEVERITY_WARNING + } + else if (res.severity == "Fatal") + new RunFunction.Result { + message = res.message + severity = SEVERITY_FATAL + } + else + new RunFunction.Result { + message = res.message + severity = SEVERITY_UNSPECIFIED + } } } } diff --git a/pkl/crossplane.contrib/Resource.pkl b/pkl/crossplane.contrib/Resource.pkl new file mode 100644 index 0000000..fd841a8 --- /dev/null +++ b/pkl/crossplane.contrib/Resource.pkl @@ -0,0 +1,47 @@ +@ModuleInfo { minPklVersion = "0.25.1" } +module Resource + +import "@k8s/K8sResource.pkl" + +/// ConnectionDetails created or updated during an operation on an external +/// resource, for example usernames, passwords, endpoints, ports, etc. +typealias ConnectionDetails = Mapping + +/// A Composite resource - aka an XR. +class Composite { + resource: K8sResource? + connectionDetails: ConnectionDetails +} + +/// A Name uniquely identifies a composed resource within a Composition Function +/// pipeline. It's not the resource's metadata.name. +typealias Name = String + +/// State of the composite resource (XR) and any composed resources. +class DesiredState { + /// The state of the composite resource (XR). + composite: Composite? + + /// The state of any composed resources. + resources: Mapping? +} + +/// DesiredComposed reflects the desired state of a composed resource. +class DesiredComposed { + resource: K8sResource? + ready: Ready = "Unspecified" +} + +/// ObservedComposed reflects the observed state of a composed resource. +class ObservedComposed { + resource: K8sResource? + connectionDetails: ConnectionDetails +} + +/// Extra is a resource requested by a Function. +class Extra { + resource: K8sResource? +} + +/// Ready indicates whether a composed resource should be considered ready. +typealias Ready = "Unspecified"|"True"|"False" diff --git a/pkl/crossplane.contrib/RunFunction.pkl b/pkl/crossplane.contrib/RunFunction.pkl index 87b6dfe..f100ebb 100644 --- a/pkl/crossplane.contrib/RunFunction.pkl +++ b/pkl/crossplane.contrib/RunFunction.pkl @@ -12,11 +12,6 @@ class State { resources: Mapping } -/// RequestMeta contains metadata pertaining to a RunFunctionRequest. -class RequestMeta { - tag: String -} - /// Resources represents the state of several Crossplane resources. class Resources { items: Listing @@ -71,13 +66,6 @@ class Resource { } typealias Ready = Int(isBetween(0,2)) -/// ResponseMeta contains metadata pertaining to a RunFunctionResponse. -class ResponseMeta { - /// Time-to-live of this response. Deterministic Functions with no side-effects - /// (e.g. simple templating Functions) may specify a TTL. Crossplane may choose - /// to cache responses until the TTL expires. - ttl: Duration -} /// A Result of running a Function. class Result { @@ -93,31 +81,3 @@ class Result { hidden const SEVERITY_NORMAL: Severity = 3 } typealias Severity = Int(isBetween(0,3)) - -/// Requirements that must be satisfied for a Function to run successfully. -class Requirements { - - /// Extra resources that this Function requires. - /// The map key uniquely identifies the group of resources. - extraResources: Mapping -} - -/// ResourceSelector selects a group of resources, either by name or by label. -class ResourceSelector { - apiVersion: String - kind: String - match: Match -} - -class Match { - /// Match the resource with this name. - /// Note: matchLabels takes precedence if both are defined. - matchName: String? - /// Match all resources with these labels. - matchLabels: MatchLabels? -} - -/// MatchLabels defines a set of labels to match resources against. -class MatchLabels { - labels: Mapping -} diff --git a/pkl/crossplane.contrib/crossplane.pkl b/pkl/crossplane.contrib/crossplane.pkl index dec6751..596b38f 100644 --- a/pkl/crossplane.contrib/crossplane.pkl +++ b/pkl/crossplane.contrib/crossplane.pkl @@ -1,15 +1,31 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. +// +// 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 +// +// https://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. + import "pkl:reflect" import "pkl:yaml" import "@k8s/K8sObject.pkl" import "@k8s/K8sResource.pkl" import "@k8s/k8sSchema.pkl" import "@k8s/api/core/v1/ResourceRequirements.pkl" +import "Pkl.pkl" import "CompositionRequest.pkl" local yamlParser = new yaml.Parser { useMapping = true } -local request= read("crossplane:request").text +local request = read("crossplane:request").text /// Resource modules keyed by `kind` and `apiVersion`. /// @@ -34,36 +50,25 @@ Request: CompositionRequest = YamlToClass(request, CompositionRequest.getClass() /// - Throws an Error if kind and apiVersion cannot be found in k8sSchema.pkl and customResourceTemplates function YamlToK8sResource(resource: String): K8sResource = let (parsedResource = yamlParser.parse(resource) as Mapping) - let (template = getResourceTemplate(parsedResource, customResourceTemplates)) - let (templateType = reflect.DeclaredType(reflect.Module(template).moduleClass)) - convert(parsedResource, templateType) as K8sResource + MappingToK8sResource(parsedResource) + +function MappingToK8sResource(resource: Mapping): K8sResource = + let (template = getResourceTemplate(resource, customResourceTemplates)) + let (templateType = reflect.DeclaredType(reflect.Module(template).moduleClass)) + convert(resource, templateType) as K8sResource /// Converts any to an object of the Specified class. function YamlToClass(resource: String, clazz: Class): Any = let (parsedResource = yamlParser.parse(resource) as Mapping) - let (templateType = reflect.DeclaredType(reflect.Class(clazz))) - convert(parsedResource, templateType) - + MappingToClass(parsedResource, clazz) +function MappingToClass(resource: Mapping, clazz: Class): Any = + let (templateType = reflect.DeclaredType(reflect.Class(clazz))) + convert(resource, templateType) // The Following Section is from https://github.com/apple/pkl-pantry/blob/main/packages/k8s.contrib/convert.pkl // It has only been slightly modified to return Actual Types instead of Dynamic. -//===----------------------------------------------------------------------===// -// Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. -// -// 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 -// -// https://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. - // https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#meaning-of-memory local function convertDataSize(input: K8sObject.Quantity): RenderDirective? = let (inputString: String = if (input is Int|Float|DataSize) input.toString() else input as String) @@ -130,9 +135,55 @@ local function convert(value: Any, type: reflect.Type?): Any = } } } + else if (value is Mapping && referent.reflectee == CompositionRequest.DesiredResource.getClass()) + new CompositionRequest.DesiredResource { + resource = MappingToK8sResource(value["resource"]) + when (value["ready"] == 0) { + ready = "Unspecified" + } + when (value["ready"] == 1) { + ready = "True" + } + when (value["ready"] == 2) { + ready = "False" + } + } + else if (value is Mapping && referent.reflectee == CompositionRequest.getClass()) + new CompositionRequest { + when (value.containsKey("meta")) { + meta = MappingToClass(value["meta"], CompositionRequest.RequestMeta) as CompositionRequest.RequestMeta + } + when (value.containsKey("observed")) { + observed = MappingToClass(value["observed"], CompositionRequest.ObservedState) as CompositionRequest.ObservedState + } + when (value.containsKey("desired")) { + desired = MappingToClass(value["desired"], CompositionRequest.DesiredState) as CompositionRequest.DesiredState + } + when (value.containsKey("input")) { + input = MappingToClass(value["input"], Pkl.getClass()) as Pkl + } + when (value.containsKey("context")) { + context = MappingToClass(value["context"], Mapping) as Mapping + } + when (value.containsKey("extraResources")) { + extraResources = new { + for (k, v in value["extraResources"]) { + [k] = new { + for (i in v["items"]) { + new { + when (i.containsKey("resource")) { + resource = MappingToK8sResource(i["resource"]) + } + } + } + } + } + } + } + } else if (value is Mapping && referent is reflect.Class && referent.reflectee == K8sResource.getClass()) // K8sResource is abstract. Get correct type from kind and apiVersion. - let (template = getResourceTemplate(value, customResourceTemplates)) // TODO: replace customResourceTemplate with actual val + let (template = getResourceTemplate(value, customResourceTemplates)) value .toMap() .map((k, v) -> Pair(k, convert(v, reflect.Class(template.getClass()).properties.getOrNull(k)?.type))) From 6a101e58d1fa410a1ecc04b82884c7be6a1d50a6 Mon Sep 17 00:00:00 2001 From: Tim <32556895+Avarei@users.noreply.github.com> Date: Sat, 13 Jul 2024 10:27:12 +0200 Subject: [PATCH 02/13] remove RunFunction.pkl. Signed-off-by: Tim <32556895+Avarei@users.noreply.github.com> --- .../CompositionResponse.pkl | 83 +++++++++++++++++-- pkl/crossplane.contrib/RunFunction.pkl | 83 ------------------- 2 files changed, 75 insertions(+), 91 deletions(-) delete mode 100644 pkl/crossplane.contrib/RunFunction.pkl diff --git a/pkl/crossplane.contrib/CompositionResponse.pkl b/pkl/crossplane.contrib/CompositionResponse.pkl index d0ec7c7..5a0696a 100644 --- a/pkl/crossplane.contrib/CompositionResponse.pkl +++ b/pkl/crossplane.contrib/CompositionResponse.pkl @@ -1,8 +1,8 @@ @ModuleInfo { minPklVersion = "0.25.1" } module CompositionResponse -import "RunFunction.pkl" import "Resource.pkl" +import "@k8s/K8sResource.pkl" /// Metadata pertaining to this response. meta: ResponseMeta? @@ -90,38 +90,38 @@ output { } [Resource.DesiredComposed] = (dc: Resource.DesiredComposed) -> if (dc.ready == "True") - new RunFunction.Resource { + new OutputResource { resource = dc.resource ready = READY_TRUE } else if (dc.ready == "False") - new RunFunction.Resource { + new OutputResource { resource = dc.resource ready = READY_FALSE } else - new RunFunction.Resource { + new OutputResource { resource = dc.resource ready = READY_UNSPECIFIED } [Result] = (res: Result) -> if (res.severity == "Normal") - new RunFunction.Result { + new OutputResult { message = res.message severity = SEVERITY_NORMAL } else if (res.severity == "Warning") - new RunFunction.Result { + new OutputResult { message = res.message severity = SEVERITY_WARNING } else if (res.severity == "Fatal") - new RunFunction.Result { + new OutputResult { message = res.message severity = SEVERITY_FATAL } else - new RunFunction.Result { + new OutputResult { message = res.message severity = SEVERITY_UNSPECIFIED } @@ -129,6 +129,73 @@ output { } } +/// A Resource represents the state of a composite or composed resource. +local class OutputResource { + /// The JSON representation of the resource. + /// + /// - Crossplane will set this field in a RunFunctionRequest to the entire + /// observed state of a resource - including its metadata, spec, and status. + /// + /// - A Function should set this field in a RunFunctionRequest to communicate + /// the desired state of a composite or composed resource. + /// + /// - A Function may only specify the desired status of a composite resource - + /// not its metadata or spec. A Function should not return desired metadata + /// or spec for a composite resource. This will be ignored. + /// + /// - A Function may not specify the desired status of a composed resource - + /// only its metadata and spec. A Function should not return desired status + /// for a composed resource. This will be ignored. + resource: K8sResource? + + /// Ready indicates whether the resource should be considered ready. + /// + /// * Crossplane will never set this field in a RunFunctionRequest. + /// + /// - A Function should set this field to READY_TRUE in a RunFunctionResponse + /// to indicate that a desired composed resource is ready. + /// + /// - A Function should not set this field in a RunFunctionResponse to indicate + /// that the desired composite resource is ready. This will be ignored. + ready: OutputReady? = READY_UNSPECIFIED + + hidden const READY_UNSPECIFIED: OutputReady = 0 + hidden const READY_TRUE: OutputReady = 1 + hidden const READY_FALSE: OutputReady = 2 + + /// The resource's connection details. + /// + /// - Crossplane will set this field in a RunFunctionRequest to communicate the + /// the observed connection details of a composite or composed resource. + /// + /// - A Function should set this field in a RunFunctionResponse to indicate the + /// desired connection details of the composite resource. + /// + /// - A Function should not set this field in a RunFunctionResponse to indicate + /// the desired connection details of a composed resource. This will be + /// ignored. + connectionDetails: Mapping? +} +local typealias OutputReady = Int(isBetween(0,2)) + + +/// A Result of running a Function. +local class OutputResult { + /// Severity of this result. + severity: OutputSeverity = SEVERITY_UNSPECIFIED + + /// Human-readable details about the result. + message: String + + hidden const SEVERITY_UNSPECIFIED: OutputSeverity = 0 + hidden const SEVERITY_FATAL: OutputSeverity = 1 + hidden const SEVERITY_WARNING: OutputSeverity = 2 + hidden const SEVERITY_NORMAL: OutputSeverity = 3 +} +local typealias OutputSeverity = Int(isBetween(0,3)) + + + local class protobufDuration { seconds: Int // int64 not available in pkl nanos: Int diff --git a/pkl/crossplane.contrib/RunFunction.pkl b/pkl/crossplane.contrib/RunFunction.pkl deleted file mode 100644 index f100ebb..0000000 --- a/pkl/crossplane.contrib/RunFunction.pkl +++ /dev/null @@ -1,83 +0,0 @@ -@ModuleInfo { minPklVersion = "0.25.1" } -module RunFunction - -import "@k8s/K8sResource.pkl" - -/// State of the composite resource (XR) and any composed resources. -class State { - /// The state of the composite resource (XR). - composite: Resource - - /// The state of any composed resources. - resources: Mapping -} - -/// Resources represents the state of several Crossplane resources. -class Resources { - items: Listing -} - -/// A Resource represents the state of a composite or composed resource. -class Resource { - /// The JSON representation of the resource. - /// - /// - Crossplane will set this field in a RunFunctionRequest to the entire - /// observed state of a resource - including its metadata, spec, and status. - /// - /// - A Function should set this field in a RunFunctionRequest to communicate - /// the desired state of a composite or composed resource. - /// - /// - A Function may only specify the desired status of a composite resource - - /// not its metadata or spec. A Function should not return desired metadata - /// or spec for a composite resource. This will be ignored. - /// - /// - A Function may not specify the desired status of a composed resource - - /// only its metadata and spec. A Function should not return desired status - /// for a composed resource. This will be ignored. - resource: K8sResource? - - /// Ready indicates whether the resource should be considered ready. - /// - /// * Crossplane will never set this field in a RunFunctionRequest. - /// - /// - A Function should set this field to READY_TRUE in a RunFunctionResponse - /// to indicate that a desired composed resource is ready. - /// - /// - A Function should not set this field in a RunFunctionResponse to indicate - /// that the desired composite resource is ready. This will be ignored. - ready: Ready? = READY_UNSPECIFIED - - hidden const READY_UNSPECIFIED: Ready = 0 - hidden const READY_TRUE: Ready = 1 - hidden const READY_FALSE: Ready = 2 - - /// The resource's connection details. - /// - /// - Crossplane will set this field in a RunFunctionRequest to communicate the - /// the observed connection details of a composite or composed resource. - /// - /// - A Function should set this field in a RunFunctionResponse to indicate the - /// desired connection details of the composite resource. - /// - /// - A Function should not set this field in a RunFunctionResponse to indicate - /// the desired connection details of a composed resource. This will be - /// ignored. - connectionDetails: Mapping? -} -typealias Ready = Int(isBetween(0,2)) - - -/// A Result of running a Function. -class Result { - /// Severity of this result. - severity: Severity = SEVERITY_UNSPECIFIED - - /// Human-readable details about the result. - message: String - - hidden const SEVERITY_UNSPECIFIED: Severity = 0 - hidden const SEVERITY_FATAL: Severity = 1 - hidden const SEVERITY_WARNING: Severity = 2 - hidden const SEVERITY_NORMAL: Severity = 3 -} -typealias Severity = Int(isBetween(0,3)) From 1ba05b2a16fec5027b35a2c0243b3f633d8c8332 Mon Sep 17 00:00:00 2001 From: Tim <32556895+Avarei@users.noreply.github.com> Date: Sat, 13 Jul 2024 10:50:02 +0200 Subject: [PATCH 03/13] make fields nullable instead of having "Unspecified" string. Signed-off-by: Tim <32556895+Avarei@users.noreply.github.com> --- .../CompositionResponse.pkl | 80 ++++++++++--------- pkl/crossplane.contrib/Resource.pkl | 4 +- 2 files changed, 43 insertions(+), 41 deletions(-) diff --git a/pkl/crossplane.contrib/CompositionResponse.pkl b/pkl/crossplane.contrib/CompositionResponse.pkl index 5a0696a..48b9a36 100644 --- a/pkl/crossplane.contrib/CompositionResponse.pkl +++ b/pkl/crossplane.contrib/CompositionResponse.pkl @@ -29,16 +29,16 @@ class ResponseMeta { desired: Resource.DesiredState? /// Results of the Function run. Results are used for observability purposes. -results: List? +results: Listing? /// A Result of running a Function. class Result { /// Severity of this result. - severity: Severity = "Unspecified" + severity: Severity? /// Human-readable details about the result. message: String } -typealias Severity = "Normal"|"Warning"|"Fatal"|"Unspecified" +typealias Severity = "Normal"|"Warning"|"Fatal" /// Optional context to be passed to the next Function in the pipeline as part /// of the RunFunctionRequest. Dropped on the last function in the pipeline. @@ -89,42 +89,46 @@ output { nanos = ns.toUnit("ns").value.toInt() } [Resource.DesiredComposed] = (dc: Resource.DesiredComposed) -> - if (dc.ready == "True") - new OutputResource { - resource = dc.resource - ready = READY_TRUE - } - else if (dc.ready == "False") - new OutputResource { - resource = dc.resource - ready = READY_FALSE - } - else - new OutputResource { - resource = dc.resource - ready = READY_UNSPECIFIED - } + let (ready = dc.ready) + if (ready == null) + new OutputResource { + resource = dc.resource + ready = READY_UNSPECIFIED + } + else if (ready == true) + new OutputResource { + resource = dc.resource + ready = READY_TRUE + } + else + new OutputResource { + resource = dc.resource + ready = READY_FALSE + } [Result] = (res: Result) -> - if (res.severity == "Normal") - new OutputResult { - message = res.message - severity = SEVERITY_NORMAL - } - else if (res.severity == "Warning") - new OutputResult { - message = res.message - severity = SEVERITY_WARNING - } - else if (res.severity == "Fatal") - new OutputResult { - message = res.message - severity = SEVERITY_FATAL - } - else - new OutputResult { - message = res.message - severity = SEVERITY_UNSPECIFIED - } + let (severity = res.severity) + if (severity == null) + new OutputResult { + message = res.message + severity = SEVERITY_UNSPECIFIED + } + else if (severity == "Normal") + new OutputResult { + message = res.message + severity = SEVERITY_NORMAL + } + else if (severity == "Warning") + new OutputResult { + message = res.message + severity = SEVERITY_WARNING + } + else if (severity == "Fatal") + new OutputResult { + message = res.message + severity = SEVERITY_FATAL + } + else + throw("unknown result severity \"\(res.severity)\"") } } } diff --git a/pkl/crossplane.contrib/Resource.pkl b/pkl/crossplane.contrib/Resource.pkl index fd841a8..de0b1ee 100644 --- a/pkl/crossplane.contrib/Resource.pkl +++ b/pkl/crossplane.contrib/Resource.pkl @@ -29,7 +29,7 @@ class DesiredState { /// DesiredComposed reflects the desired state of a composed resource. class DesiredComposed { resource: K8sResource? - ready: Ready = "Unspecified" + ready: Boolean? } /// ObservedComposed reflects the observed state of a composed resource. @@ -43,5 +43,3 @@ class Extra { resource: K8sResource? } -/// Ready indicates whether a composed resource should be considered ready. -typealias Ready = "Unspecified"|"True"|"False" From f30ee806622a8ea4f942f6136a0ff2b9863168c2 Mon Sep 17 00:00:00 2001 From: Tim <32556895+Avarei@users.noreply.github.com> Date: Sat, 13 Jul 2024 10:50:52 +0200 Subject: [PATCH 04/13] update examples Signed-off-by: Tim <32556895+Avarei@users.noreply.github.com> --- pkl/crossplane.contrib.example/compositions/steps/event.pkl | 2 +- pkl/crossplane.contrib.example/compositions/steps/full.pkl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkl/crossplane.contrib.example/compositions/steps/event.pkl b/pkl/crossplane.contrib.example/compositions/steps/event.pkl index 78005e7..dabb777 100644 --- a/pkl/crossplane.contrib.example/compositions/steps/event.pkl +++ b/pkl/crossplane.contrib.example/compositions/steps/event.pkl @@ -20,7 +20,7 @@ desired = request.desired results { new { - severity = SEVERITY_WARNING + severity = "Normal" message = "I am an Event from Pkl!" } } diff --git a/pkl/crossplane.contrib.example/compositions/steps/full.pkl b/pkl/crossplane.contrib.example/compositions/steps/full.pkl index 8334cd3..5e69add 100644 --- a/pkl/crossplane.contrib.example/compositions/steps/full.pkl +++ b/pkl/crossplane.contrib.example/compositions/steps/full.pkl @@ -57,13 +57,13 @@ desired { } } } - ready = READY_TRUE + ready = true } } } results { new { - severity = SEVERITY_NORMAL + severity = "Normal" message = "welcome" } } From fcd3a322fd02959a8d968b72573e5134887e3872 Mon Sep 17 00:00:00 2001 From: Tim <32556895+Avarei@users.noreply.github.com> Date: Sat, 13 Jul 2024 11:00:24 +0200 Subject: [PATCH 05/13] fix tests Signed-off-by: Tim <32556895+Avarei@users.noreply.github.com> --- internal/function/fn_test.go | 1 - .../compositions/steps/event.pkl | 2 +- pkl/crossplane.contrib/crossplane.pkl | 17 ++++++++--------- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/internal/function/fn_test.go b/internal/function/fn_test.go index 9d2186c..a445bfe 100644 --- a/internal/function/fn_test.go +++ b/internal/function/fn_test.go @@ -89,7 +89,6 @@ func TestRunFunction(t *testing.T) { Ttl: durationpb.New(response.DefaultTTL), }, Desired: &fnv1beta1.State{ - Composite: &fnv1beta1.Resource{}, Resources: map[string]*fnv1beta1.Resource{ "cm-minimal": { Resource: resource.MustStructJSON(`{ diff --git a/pkl/crossplane.contrib.example/compositions/steps/event.pkl b/pkl/crossplane.contrib.example/compositions/steps/event.pkl index dabb777..b2aa191 100644 --- a/pkl/crossplane.contrib.example/compositions/steps/event.pkl +++ b/pkl/crossplane.contrib.example/compositions/steps/event.pkl @@ -20,7 +20,7 @@ desired = request.desired results { new { - severity = "Normal" + severity = "Warning" message = "I am an Event from Pkl!" } } diff --git a/pkl/crossplane.contrib/crossplane.pkl b/pkl/crossplane.contrib/crossplane.pkl index 596b38f..c48366b 100644 --- a/pkl/crossplane.contrib/crossplane.pkl +++ b/pkl/crossplane.contrib/crossplane.pkl @@ -22,6 +22,7 @@ import "@k8s/api/core/v1/ResourceRequirements.pkl" import "Pkl.pkl" import "CompositionRequest.pkl" +import "Resource.pkl" local yamlParser = new yaml.Parser { useMapping = true } @@ -135,17 +136,15 @@ local function convert(value: Any, type: reflect.Type?): Any = } } } - else if (value is Mapping && referent.reflectee == CompositionRequest.DesiredResource.getClass()) - new CompositionRequest.DesiredResource { - resource = MappingToK8sResource(value["resource"]) - when (value["ready"] == 0) { - ready = "Unspecified" - } + else if (value is Mapping && referent.reflectee == Resource.DesiredComposed.getClass()) + new Resource.DesiredComposed { + resource = MappingToK8sResource(value["resource"]) + // ready == 0 is unspecified -> ready = null when (value["ready"] == 1) { - ready = "True" + ready = true } when (value["ready"] == 2) { - ready = "False" + ready = false } } else if (value is Mapping && referent.reflectee == CompositionRequest.getClass()) @@ -157,7 +156,7 @@ local function convert(value: Any, type: reflect.Type?): Any = observed = MappingToClass(value["observed"], CompositionRequest.ObservedState) as CompositionRequest.ObservedState } when (value.containsKey("desired")) { - desired = MappingToClass(value["desired"], CompositionRequest.DesiredState) as CompositionRequest.DesiredState + desired = MappingToClass(value["desired"], Resource.DesiredState) as Resource.DesiredState } when (value.containsKey("input")) { input = MappingToClass(value["input"], Pkl.getClass()) as Pkl From 2ac742c6e409073907c5f6b6d5e08d204450a0a2 Mon Sep 17 00:00:00 2001 From: Tim <32556895+Avarei@users.noreply.github.com> Date: Sat, 13 Jul 2024 12:23:52 +0200 Subject: [PATCH 06/13] check extraResource for nullness Signed-off-by: Tim <32556895+Avarei@users.noreply.github.com> --- pkl/crossplane.contrib/CompositionRequest.pkl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkl/crossplane.contrib/CompositionRequest.pkl b/pkl/crossplane.contrib/CompositionRequest.pkl index 74ddffa..2066775 100644 --- a/pkl/crossplane.contrib/CompositionRequest.pkl +++ b/pkl/crossplane.contrib/CompositionRequest.pkl @@ -60,7 +60,10 @@ extraResources: Mapping>? /// Helper function to Retrieve extraResources that have been requested under a given `name`. function getExtraResources(name: String): Listing = - extraResources.getOrNull(name) ?? new Listing {} + extraResources.ifNonNull( + (extraRes: Mapping>) -> + extraRes.getOrNull(name)) + ?? new Listing {} /// Helper function to Retrieve a specific resource from extraResources. function getExtraResource(name: String, index: Int): Resource.Extra? = From 332a5e6e5548f06b0804e7db8719df7fa32edb2c Mon Sep 17 00:00:00 2001 From: Tim <32556895+Avarei@users.noreply.github.com> Date: Sat, 13 Jul 2024 12:24:35 +0200 Subject: [PATCH 07/13] move json objects to top of tests and add full without extraResources test Signed-off-by: Tim <32556895+Avarei@users.noreply.github.com> --- internal/function/fn_test.go | 152 ++++++++++++++++++----------------- 1 file changed, 79 insertions(+), 73 deletions(-) diff --git a/internal/function/fn_test.go b/internal/function/fn_test.go index a445bfe..5540028 100644 --- a/internal/function/fn_test.go +++ b/internal/function/fn_test.go @@ -37,6 +37,14 @@ import ( var ( workdir, _ = os.Getwd() pklPackage = fmt.Sprintf("%s/../../pkl/crossplane.contrib.example", workdir) + + xr = `{"apiVersion": "example.crossplane.io/v1","kind": "XR","metadata": {"name": "example-xr"},"spec": {}}` + xrStatus = `{"apiVersion": "example.crossplane.io/v1","kind": "XR","status": {"someStatus": "pretty status"}}` + environmentConfig = `{"apiextensions.crossplane.io/environment": {"foo": "bar"}, "greetings": "with <3 from function-pkl"}` + + objectWithRequired = `{"apiVersion": "kubernetes.crossplane.io/v1alpha2","kind": "Object","spec": {"forProvider": {"manifest": {"apiVersion": "v1","kind": "ConfigMap","metadata": {"namespace": "crossplane-system"},"data": {"foo": "example-xr","required": "required"}}}}}` + objectWithoutRequired = `{"apiVersion": "kubernetes.crossplane.io/v1alpha2","kind": "Object","spec": {"forProvider": {"manifest": {"apiVersion": "v1","kind": "ConfigMap","metadata": {"namespace": "crossplane-system"},"data": {"foo": "example-xr","required": "i could not find what I needed..."}}}}}` + objectMinimal = `{"apiVersion": "kubernetes.crossplane.io/v1alpha2","kind": "Object","spec": {"forProvider": {"manifest": {"apiVersion": "v1","kind": "ConfigMap","metadata": {"namespace": "crossplane-system"},"data": {"foo": "bar"}}}}}` ) func TestRunFunction(t *testing.T) { @@ -71,14 +79,7 @@ func TestRunFunction(t *testing.T) { }), Observed: &fnv1beta1.State{ Composite: &fnv1beta1.Resource{ - Resource: resource.MustStructJSON(`{ - "apiVersion": "example.crossplane.io/v1", - "kind": "XR", - "metadata": { - "name": "example-xr" - }, - "spec": {} - }`), + Resource: resource.MustStructJSON(xr), }, }, }, @@ -91,30 +92,76 @@ func TestRunFunction(t *testing.T) { Desired: &fnv1beta1.State{ Resources: map[string]*fnv1beta1.Resource{ "cm-minimal": { - Resource: resource.MustStructJSON(`{ - "apiVersion": "kubernetes.crossplane.io/v1alpha2", - "kind": "Object", - "spec": { - "forProvider": { - "manifest": { - "apiVersion": "v1", - "kind": "ConfigMap", - "metadata": { - "namespace": "crossplane-system" - }, - "data": { - "foo": "bar" - } - } - } - } - }`), + Resource: resource.MustStructJSON(objectMinimal), }, }, }, }, }, }, + "FullFirstRun": { + reason: "The Function should create a full functionResult", + args: args{ + ctx: context.TODO(), + req: &fnv1beta1.RunFunctionRequest{ + Meta: &fnv1beta1.RequestMeta{Tag: "extra"}, + Input: resource.MustStructObject(&v1beta1.Pkl{ + Spec: v1beta1.PklSpec{ + Type: "local", + Local: &v1beta1.Local{ + ProjectDir: pklPackage, + File: pklPackage + "/compositions/steps/full.pkl", + }, + }, + }), + Context: resource.MustStructJSON(environmentConfig), + Observed: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + }, + }, + }, + want: want{ + rsp: &fnv1beta1.RunFunctionResponse{ + Desired: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(xrStatus), + }, + Resources: map[string]*fnv1beta1.Resource{ + "cm-one": { + Resource: resource.MustStructJSON(objectWithoutRequired), + Ready: fnv1beta1.Ready_READY_TRUE, + }, + }, + }, + Requirements: &fnv1beta1.Requirements{ + ExtraResources: map[string]*fnv1beta1.ResourceSelector{ + "ineed": { + ApiVersion: "kubernetes.crossplane.io/v1alpha2", + Kind: "Object", + Match: &fnv1beta1.ResourceSelector_MatchName{ + MatchName: "required", + }, + }, + }, + }, + Meta: &fnv1beta1.ResponseMeta{ + Tag: "extra", + Ttl: &durationpb.Duration{ + Seconds: 60, + }, + }, + Context: resource.MustStructJSON(environmentConfig), + Results: []*fnv1beta1.Result{ + { + Severity: fnv1beta1.Severity_SEVERITY_NORMAL, + Message: "welcome", + }, + }, + }, + }, + }, "Full": { reason: "The Function should create a full functionResult", args: args{ @@ -168,36 +215,12 @@ func TestRunFunction(t *testing.T) { rsp: &fnv1beta1.RunFunctionResponse{ Desired: &fnv1beta1.State{ Composite: &fnv1beta1.Resource{ - Resource: resource.MustStructJSON(`{ - "apiVersion": "example.crossplane.io/v1", - "kind": "XR", - "status": { - "someStatus": "pretty status" - } - }`), + Resource: resource.MustStructJSON(xrStatus), }, Resources: map[string]*fnv1beta1.Resource{ "cm-one": { - Resource: resource.MustStructJSON(`{ - "apiVersion": "kubernetes.crossplane.io/v1alpha2", - "kind": "Object", - "spec": { - "forProvider": { - "manifest": { - "apiVersion": "v1", - "kind": "ConfigMap", - "metadata": { - "namespace": "crossplane-system" - }, - "data": { - "foo": "example-xr", - "required": "required" - } - } - } - } - }`), - Ready: fnv1beta1.Ready_READY_TRUE, + Resource: resource.MustStructJSON(objectWithRequired), + Ready: fnv1beta1.Ready_READY_TRUE, }, }, }, @@ -218,12 +241,7 @@ func TestRunFunction(t *testing.T) { Seconds: 60, }, }, - Context: resource.MustStructJSON(`{ - "apiextensions.crossplane.io/environment": { - "foo": "bar" - }, - "greetings": "with <3 from function-pkl" - }`), + Context: resource.MustStructJSON(environmentConfig), Results: []*fnv1beta1.Result{ { Severity: fnv1beta1.Severity_SEVERITY_NORMAL, @@ -249,13 +267,7 @@ func TestRunFunction(t *testing.T) { }), Desired: &fnv1beta1.State{ Composite: &fnv1beta1.Resource{ - Resource: resource.MustStructJSON(`{ - "apiVersion": "example.crossplane.io/v1", - "kind": "XR", - "status": { - "someStatus": "pretty status" - } - }`), + Resource: resource.MustStructJSON(xrStatus), }, }, }, @@ -264,13 +276,7 @@ func TestRunFunction(t *testing.T) { rsp: &fnv1beta1.RunFunctionResponse{ Desired: &fnv1beta1.State{ Composite: &fnv1beta1.Resource{ - Resource: resource.MustStructJSON(`{ - "apiVersion": "example.crossplane.io/v1", - "kind": "XR", - "status": { - "someStatus": "pretty status" - } - }`), + Resource: resource.MustStructJSON(xrStatus), }, }, Results: []*fnv1beta1.Result{ From 848b8244d6737fcdd104f0900e8c7d02a07018ad Mon Sep 17 00:00:00 2001 From: Tim <32556895+Avarei@users.noreply.github.com> Date: Sat, 13 Jul 2024 13:23:37 +0200 Subject: [PATCH 08/13] add a few debug messages Signed-off-by: Tim <32556895+Avarei@users.noreply.github.com> --- example/full/extraresources.yaml | 5 +++++ internal/function/fn.go | 3 ++- internal/pkl/reader/crossplane_reader.go | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 example/full/extraresources.yaml diff --git a/example/full/extraresources.yaml b/example/full/extraresources.yaml new file mode 100644 index 0000000..9b1af57 --- /dev/null +++ b/example/full/extraresources.yaml @@ -0,0 +1,5 @@ +--- +apiVersion: kubernetes.crossplane.io/v1alpha2 +kind: Object +metadata: + name: required diff --git a/internal/function/fn.go b/internal/function/fn.go index cddc944..d43f7b3 100644 --- a/internal/function/fn.go +++ b/internal/function/fn.go @@ -43,7 +43,7 @@ type Function struct { // RunFunction runs the Function. func (f *Function) RunFunction(ctx context.Context, req *fnv1beta1.RunFunctionRequest) (*fnv1beta1.RunFunctionResponse, error) { - f.Log.Info("Running function", "tag", req.GetMeta().GetTag()) + f.Log.Debug("Running function", "tag", req.GetMeta().GetTag()) rsp := response.To(req, response.DefaultTTL) @@ -101,6 +101,7 @@ func (f *Function) RunFunction(ctx context.Context, req *fnv1beta1.RunFunctionRe renderedManifest, err := evaluator.EvaluateOutputText(ctx, moduleSource) if err != nil { + f.Log.Debug("Error parsing Pkl file", "error", err) response.Fatal(rsp, errors.Wrap(err, "error while parsing the Pkl file")) return rsp, nil } diff --git a/internal/pkl/reader/crossplane_reader.go b/internal/pkl/reader/crossplane_reader.go index 1122dd2..f88ff45 100644 --- a/internal/pkl/reader/crossplane_reader.go +++ b/internal/pkl/reader/crossplane_reader.go @@ -92,6 +92,7 @@ func (f *CrossplaneReader) BaseRead(url url.URL) ([]byte, error) { return nil, err } + f.Log.Debug("compositionRequest", "request", requestYaml) return requestYaml, nil default: return nil, fmt.Errorf("unsupported path") From 8af4df0d192ae4d86a63e5ea6915f4f3f9f3ed48 Mon Sep 17 00:00:00 2001 From: Tim <32556895+Avarei@users.noreply.github.com> Date: Sat, 13 Jul 2024 13:40:13 +0200 Subject: [PATCH 09/13] if if extraResources have items. Signed-off-by: Tim <32556895+Avarei@users.noreply.github.com> --- internal/function/fn_test.go | 66 +++++++++++++++++++++++++++ pkl/crossplane.contrib/crossplane.pkl | 10 ++-- 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/internal/function/fn_test.go b/internal/function/fn_test.go index 5540028..60e1f11 100644 --- a/internal/function/fn_test.go +++ b/internal/function/fn_test.go @@ -162,6 +162,72 @@ func TestRunFunction(t *testing.T) { }, }, }, + "FullCantFindRequirement": { + reason: "The Function should create a full functionResult", + args: args{ + ctx: context.TODO(), + req: &fnv1beta1.RunFunctionRequest{ + Meta: &fnv1beta1.RequestMeta{Tag: "extra"}, + Input: resource.MustStructObject(&v1beta1.Pkl{ + Spec: v1beta1.PklSpec{ + Type: "local", + Local: &v1beta1.Local{ + ProjectDir: pklPackage, + File: pklPackage + "/compositions/steps/full.pkl", + }, + }, + }), + Context: resource.MustStructJSON(environmentConfig), + Observed: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + }, + ExtraResources: map[string]*fnv1beta1.Resources{ + "ineed": {}, + }, + }, + }, + want: want{ + rsp: &fnv1beta1.RunFunctionResponse{ + Desired: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(xrStatus), + }, + Resources: map[string]*fnv1beta1.Resource{ + "cm-one": { + Resource: resource.MustStructJSON(objectWithoutRequired), + Ready: fnv1beta1.Ready_READY_TRUE, + }, + }, + }, + Requirements: &fnv1beta1.Requirements{ + ExtraResources: map[string]*fnv1beta1.ResourceSelector{ + "ineed": { + ApiVersion: "kubernetes.crossplane.io/v1alpha2", + Kind: "Object", + Match: &fnv1beta1.ResourceSelector_MatchName{ + MatchName: "required", + }, + }, + }, + }, + Meta: &fnv1beta1.ResponseMeta{ + Tag: "extra", + Ttl: &durationpb.Duration{ + Seconds: 60, + }, + }, + Context: resource.MustStructJSON(environmentConfig), + Results: []*fnv1beta1.Result{ + { + Severity: fnv1beta1.Severity_SEVERITY_NORMAL, + Message: "welcome", + }, + }, + }, + }, + }, "Full": { reason: "The Function should create a full functionResult", args: args{ diff --git a/pkl/crossplane.contrib/crossplane.pkl b/pkl/crossplane.contrib/crossplane.pkl index c48366b..98a5d02 100644 --- a/pkl/crossplane.contrib/crossplane.pkl +++ b/pkl/crossplane.contrib/crossplane.pkl @@ -168,10 +168,12 @@ local function convert(value: Any, type: reflect.Type?): Any = extraResources = new { for (k, v in value["extraResources"]) { [k] = new { - for (i in v["items"]) { - new { - when (i.containsKey("resource")) { - resource = MappingToK8sResource(i["resource"]) + when (v.containsKey("items")) { + for (i in v["items"]) { + new { + when (i.containsKey("resource")) { + resource = MappingToK8sResource(i["resource"]) + } } } } From c7d1135f70ebf253b4944a4b13676d5fe8cdc42f Mon Sep 17 00:00:00 2001 From: Tim <32556895+Avarei@users.noreply.github.com> Date: Sat, 13 Jul 2024 14:51:31 +0200 Subject: [PATCH 10/13] re-add import statement for PklPoject for local make generate runs. Signed-off-by: Tim <32556895+Avarei@users.noreply.github.com> --- Makefile | 2 +- example/full/composition.yaml | 2 +- example/inline/composition.yaml | 4 ---- pkl/crossplane.contrib.example/compositions/uri.pkl | 8 +++++++- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 6accbee..fb18a08 100644 --- a/Makefile +++ b/Makefile @@ -63,7 +63,7 @@ generate: pkl-resolve-hack pkl-resolve pkl eval --working-dir $(PROJECT_DIR)hack/pklcrd -m ../../pkl/crossplane.contrib crd2module-composition-fix.pkl pkl eval --working-dir $(PROJECT_DIR)pkl/crossplane.contrib.example -m crds xrds/ExampleXR.pkl pkl eval --working-dir $(PROJECT_DIR)pkl/crossplane.contrib.example compositions/inline.pkl > $(PROJECT_DIR)example/inline/composition.yaml - pkl eval --working-dir $(PROJECT_DIR)pkl/crossplane.contrib.example compositions/uri.pkl > $(PROJECT_DIR)example/full/composition.yaml + pkl eval --working-dir $(PROJECT_DIR)pkl/crossplane.contrib.example $(EXAMPLE_PARAM) compositions/uri.pkl > $(PROJECT_DIR)example/full/composition.yaml pkl eval --working-dir $(PROJECT_DIR)pkl/crossplane.contrib.example xrs/inline.pkl > $(PROJECT_DIR)example/inline/xr.yaml pkl eval --working-dir $(PROJECT_DIR)pkl/crossplane.contrib.example xrs/uri.pkl > $(PROJECT_DIR)example/full/xr.yaml diff --git a/example/full/composition.yaml b/example/full/composition.yaml index ac32717..32a35eb 100644 --- a/example/full/composition.yaml +++ b/example/full/composition.yaml @@ -15,5 +15,5 @@ spec: kind: Pkl spec: type: uri - uri: package://pkg.pkl-lang.org/github.com/crossplane-contrib/function-pkl/crossplane.contrib.example@0.0.0#/compositions/steps/full.pkl + uri: package://pkg.pkl-lang.org/github.com/crossplane-contrib/function-pkl/crossplane.contrib.example@0.0.1#/compositions/steps/full.pkl step: pkl-template diff --git a/example/inline/composition.yaml b/example/inline/composition.yaml index 7945c24..9386b2f 100644 --- a/example/inline/composition.yaml +++ b/example/inline/composition.yaml @@ -23,14 +23,10 @@ spec: resources { ["cm-minimal"] = new { resource = new Object { - metadata { - name = "cm-one" - } spec { forProvider { manifest = new ConfigMap { metadata { - name = "cm-minimal" namespace = "crossplane-system" } data { diff --git a/pkl/crossplane.contrib.example/compositions/uri.pkl b/pkl/crossplane.contrib.example/compositions/uri.pkl index 9c57dd6..7575939 100644 --- a/pkl/crossplane.contrib.example/compositions/uri.pkl +++ b/pkl/crossplane.contrib.example/compositions/uri.pkl @@ -24,7 +24,13 @@ spec { input = new Pkl { spec { type = "uri" - uri = reflect.Module(full).uri + uri = + let (uri = reflect.Module(full).uri) + if (uri.startsWith("file:")) + let (pkg = import("../PklProject").package) + pkg.baseUri + "@" + pkg.version + "#/compositions/steps/full.pkl" + else + uri } } } From c04fcfe09da7069b14ff5484816f1eceed608292 Mon Sep 17 00:00:00 2001 From: Tim <32556895+Avarei@users.noreply.github.com> Date: Sat, 13 Jul 2024 15:04:17 +0200 Subject: [PATCH 11/13] add explaining comment Signed-off-by: Tim <32556895+Avarei@users.noreply.github.com> --- pkl/crossplane.contrib.example/compositions/uri.pkl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkl/crossplane.contrib.example/compositions/uri.pkl b/pkl/crossplane.contrib.example/compositions/uri.pkl index 7575939..8cac16a 100644 --- a/pkl/crossplane.contrib.example/compositions/uri.pkl +++ b/pkl/crossplane.contrib.example/compositions/uri.pkl @@ -26,7 +26,7 @@ spec { type = "uri" uri = let (uri = reflect.Module(full).uri) - if (uri.startsWith("file:")) + if (uri.startsWith("file:")) // This assumes that PklProject is present when run locally. It is not available in the package let (pkg = import("../PklProject").package) pkg.baseUri + "@" + pkg.version + "#/compositions/steps/full.pkl" else From b5aa33c1020a7e9ed0bc2dadee8e356a3e8c2ecb Mon Sep 17 00:00:00 2001 From: Tim <32556895+Avarei@users.noreply.github.com> Date: Sat, 13 Jul 2024 18:03:55 +0200 Subject: [PATCH 12/13] add codegeneration for xrds Signed-off-by: Tim <32556895+Avarei@users.noreply.github.com> --- Makefile | 3 ++- hack/pklcrd/crd2module.pkl | 12 +++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index fb18a08..6f9159a 100644 --- a/Makefile +++ b/Makefile @@ -59,7 +59,8 @@ PROJECT_DIR := $(dir $(firstword $(MAKEFILE_LIST))) .PHONY: generate generate: pkl-resolve-hack pkl-resolve go generate ./... - pkl eval --working-dir $(PROJECT_DIR)hack/pklcrd -m ../../pkl/crossplane.contrib crd2module.pkl + pkl eval --working-dir $(PROJECT_DIR)hack/pklcrd -m ../../pkl/crossplane.contrib crd2module.pkl -p source="package/input/pkl.fn.crossplane.io_pkls.yaml" + pkl eval --working-dir $(PROJECT_DIR)hack/pklcrd -m ../../pkl/crossplane.contrib.xrd crd2module.pkl -p source="https://raw.githubusercontent.com/crossplane/crossplane/master/cluster/crds/apiextensions.crossplane.io_compositeresourcedefinitions.yaml" pkl eval --working-dir $(PROJECT_DIR)hack/pklcrd -m ../../pkl/crossplane.contrib crd2module-composition-fix.pkl pkl eval --working-dir $(PROJECT_DIR)pkl/crossplane.contrib.example -m crds xrds/ExampleXR.pkl pkl eval --working-dir $(PROJECT_DIR)pkl/crossplane.contrib.example compositions/inline.pkl > $(PROJECT_DIR)example/inline/composition.yaml diff --git a/hack/pklcrd/crd2module.pkl b/hack/pklcrd/crd2module.pkl index e6cfcd5..608fe80 100644 --- a/hack/pklcrd/crd2module.pkl +++ b/hack/pklcrd/crd2module.pkl @@ -1,6 +1,16 @@ amends "@k8s.contrib.crd/generate.pkl" -source = "package/input/pkl.fn.crossplane.io_pkls.yaml" +import "@k8s/apiextensions-apiserver/pkg/apis/apiextensions/v1/CustomResourceDefinition.pkl" + +// source = "package/input/pkl.fn.crossplane.io_pkls.yaml" + +// These Converters Provide Type Safety on the Object resource. Without them manifest would be of type "Dynamic" +converters { + ["compositeresourcedefinitions.apiextensions.crossplane.io"] { + [List("spec", "versions", "version", "schema", "openAPIV3Schema")] = CustomResourceDefinition.JSONSchemaProps + } +} // The Package references to be used within the new Module k8sImportPath = "@k8s" // package://pkg.pkl-lang.org/pkl-k8s/k8s@# + From ec7f9765bc657ceb84a8e481994144331d25a05c Mon Sep 17 00:00:00 2001 From: Tim <32556895+Avarei@users.noreply.github.com> Date: Sat, 13 Jul 2024 18:12:50 +0200 Subject: [PATCH 13/13] pin version of xrd module Signed-off-by: Tim <32556895+Avarei@users.noreply.github.com> --- Makefile | 2 +- pkl/crossplane.contrib.xrd/CompositeResourceDefinition.pkl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 6f9159a..f416783 100644 --- a/Makefile +++ b/Makefile @@ -60,7 +60,7 @@ PROJECT_DIR := $(dir $(firstword $(MAKEFILE_LIST))) generate: pkl-resolve-hack pkl-resolve go generate ./... pkl eval --working-dir $(PROJECT_DIR)hack/pklcrd -m ../../pkl/crossplane.contrib crd2module.pkl -p source="package/input/pkl.fn.crossplane.io_pkls.yaml" - pkl eval --working-dir $(PROJECT_DIR)hack/pklcrd -m ../../pkl/crossplane.contrib.xrd crd2module.pkl -p source="https://raw.githubusercontent.com/crossplane/crossplane/master/cluster/crds/apiextensions.crossplane.io_compositeresourcedefinitions.yaml" + pkl eval --working-dir $(PROJECT_DIR)hack/pklcrd -m ../../pkl/crossplane.contrib.xrd crd2module.pkl -p source="https://raw.githubusercontent.com/crossplane/crossplane/v1.16.0/cluster/crds/apiextensions.crossplane.io_compositeresourcedefinitions.yaml" pkl eval --working-dir $(PROJECT_DIR)hack/pklcrd -m ../../pkl/crossplane.contrib crd2module-composition-fix.pkl pkl eval --working-dir $(PROJECT_DIR)pkl/crossplane.contrib.example -m crds xrds/ExampleXR.pkl pkl eval --working-dir $(PROJECT_DIR)pkl/crossplane.contrib.example compositions/inline.pkl > $(PROJECT_DIR)example/inline/composition.yaml diff --git a/pkl/crossplane.contrib.xrd/CompositeResourceDefinition.pkl b/pkl/crossplane.contrib.xrd/CompositeResourceDefinition.pkl index 31cc5c3..2f9c72a 100644 --- a/pkl/crossplane.contrib.xrd/CompositeResourceDefinition.pkl +++ b/pkl/crossplane.contrib.xrd/CompositeResourceDefinition.pkl @@ -4,7 +4,7 @@ /// CustomResourceDefinitions](https://docs.crossplane.io/latest/concepts/composite-resource-definitions). /// /// This module was generated from the CustomResourceDefinition at -/// . +/// . module io.crossplane.apiextensions.v1.CompositeResourceDefinition extends "@k8s/K8sResource.pkl"