From 2afe44ed661cea8ab130458a0d4fd46047e9e869 Mon Sep 17 00:00:00 2001 From: Tim <32556895+Avarei@users.noreply.github.com> Date: Tue, 16 Jul 2024 23:25:00 +0200 Subject: [PATCH 1/3] add https example using git references without pkl package releases. Signed-off-by: Tim <32556895+Avarei@users.noreply.github.com> --- README.md | 5 +- example/README.md | 2 +- example/full/observed.yaml | 9 - example/git/README.md | 14 ++ example/git/composition.yaml | 19 ++ example/git/extraresources.yaml | 5 + example/git/functions.yaml | 11 ++ example/git/pkl/crds/Object.pkl | 335 ++++++++++++++++++++++++++++++++ example/git/pkl/crds/XR.pkl | 27 +++ example/git/pkl/step.pkl | 84 ++++++++ example/git/xr.yaml | 7 + 11 files changed, 507 insertions(+), 11 deletions(-) delete mode 100644 example/full/observed.yaml create mode 100644 example/git/README.md create mode 100644 example/git/composition.yaml create mode 100644 example/git/extraresources.yaml create mode 100644 example/git/functions.yaml create mode 100644 example/git/pkl/crds/Object.pkl create mode 100644 example/git/pkl/crds/XR.pkl create mode 100644 example/git/pkl/step.pkl create mode 100644 example/git/xr.yaml diff --git a/README.md b/README.md index 4d67ac6..a5358c5 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,10 @@ spec: ### Example see [examples](./example/) -and see [pkl/crossplane.contrib.example/DEPLOY.md](pkl/crossplane.contrib.example/DEPLOY.md) on instructions how to deploy it. +and see [pkl/crossplane.contrib.example/DEPLOY.md](pkl/crossplane.contrib.example/DEPLOY.md) on instructions how to deploy using Pkl Packages. + +Alternatively [example/git/README.md](example/git/README.md) shows an appraoch without releasing Packages. + ## Creating a new Composition Function see [pkl/crossplane.contrib.example/DEVELOP.md](pkl/crossplane.contrib.example/DEVELOP.md) diff --git a/example/README.md b/example/README.md index b712340..9eed569 100644 --- a/example/README.md +++ b/example/README.md @@ -10,5 +10,5 @@ $ go run . --insecure --debug ```shell # Then, in another terminal, call it with these example manifests -$ crossplane beta render xr.yaml composition.yaml functions.yaml --observed-resources=observed.yaml +$ crossplane beta render xr.yaml composition.yaml functions.yaml ``` diff --git a/example/full/observed.yaml b/example/full/observed.yaml deleted file mode 100644 index bb5e36b..0000000 --- a/example/full/observed.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: cm-one - namespace: crossplane-system - annotations: - crossplane.io/composition-resource-name: cm-one -data: - foo: bar diff --git a/example/git/README.md b/example/git/README.md new file mode 100644 index 0000000..070e1d1 --- /dev/null +++ b/example/git/README.md @@ -0,0 +1,14 @@ +# Using Raw Git References +Instead of releasing a Pkl Package this approach reads directly from a git repository. + +Currently this method does ot support PklProject dependencies, meaning all dependencies must be declared explicitely. + +```pkl +// e.g. +amends "package://pkg.pkl-lang.org/github.com/crossplane-contrib/function-pkl/crossplane.contrib@0.0.1#/CompositionResponse.pkl" + +// instead of +amends "@crossplane.contrib/CompositionResponse.pkl" +``` + +For more information about Generating the Pkl Modules, XRDs, and Compositions see [DEVELOP.md](../../pkl/crossplane.contrib.example/DEVELOP.md) diff --git a/example/git/composition.yaml b/example/git/composition.yaml new file mode 100644 index 0000000..aacc10a --- /dev/null +++ b/example/git/composition.yaml @@ -0,0 +1,19 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: pkl-uri-example +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: XR + mode: Pipeline + pipeline: + - functionRef: + name: function-pkl + input: + apiVersion: pkl.fn.crossplane.io/v1beta1 + kind: Pkl + spec: + type: uri + uri: https://raw.githubusercontent.com/crossplane-contrib/function-pkl/main/example/git/pkl/step.pkl + step: pkl-template diff --git a/example/git/extraresources.yaml b/example/git/extraresources.yaml new file mode 100644 index 0000000..9b1af57 --- /dev/null +++ b/example/git/extraresources.yaml @@ -0,0 +1,5 @@ +--- +apiVersion: kubernetes.crossplane.io/v1alpha2 +kind: Object +metadata: + name: required diff --git a/example/git/functions.yaml b/example/git/functions.yaml new file mode 100644 index 0000000..95fac53 --- /dev/null +++ b/example/git/functions.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: pkg.crossplane.io/v1beta1 +kind: Function +metadata: + name: function-pkl + annotations: + # This tells crossplane beta render to connect to the function locally. + render.crossplane.io/runtime: Development +spec: + # This is ignored when using the Development runtime. + package: function-pkl diff --git a/example/git/pkl/crds/Object.pkl b/example/git/pkl/crds/Object.pkl new file mode 100644 index 0000000..a7b9742 --- /dev/null +++ b/example/git/pkl/crds/Object.pkl @@ -0,0 +1,335 @@ +/// A Object is an provider Kubernetes API type +/// +/// This module was generated from the CustomResourceDefinition at +/// . +module io.crossplane.kubernetes.v1alpha2.Object + +extends "package://pkg.pkl-lang.org/pkl-k8s/k8s@1.1.0#/K8sResource.pkl" + +import "package://pkg.pkl-lang.org/pkl-k8s/k8s@1.1.0#/apimachinery/pkg/apis/meta/v1/ObjectMeta.pkl" +import "package://pkg.pkl-lang.org/pkl-k8s/k8s@1.1.0#/K8sResource.pkl" + +fixed apiVersion: "kubernetes.crossplane.io/v1alpha2" + +fixed kind: "Object" + +/// Standard object's metadata. +/// +/// More info: . +metadata: ObjectMeta? + +/// A ObjectSpec defines the desired state of a Object. +spec: Spec + +/// A ObjectStatus represents the observed state of a Object. +status: Status? + +/// A ObjectSpec defines the desired state of a Object. +class Spec { + connectionDetails: Listing? + + /// DeletionPolicy specifies what will happen to the underlying external when this managed resource is + /// deleted - either "Delete" or "Orphan" the external resource. This field is planned to be deprecated + /// in favor of the ManagementPolicies field in a future release. Currently, both could be set + /// independently and non-default values would be honored if the feature flag is enabled. See the + /// design doc for more information: + /// https://github.com/crossplane/crossplane/blob/499895a25d1a1a0ba1604944ef98ac7a1a71f197/design/design-doc-observe-only-resources.md?plain=1#L223 + /// + /// Default if undefined: `"Delete"` + deletionPolicy: ("Orphan"|"Delete")? + + /// ObjectParameters are the configurable fields of a Object. + forProvider: ForProvider + + /// THIS IS A BETA FIELD. It is on by default but can be opted out through a Crossplane feature flag. + /// ManagementPolicies specify the array of actions Crossplane is allowed to take on the managed and + /// external resources. This field is planned to replace the DeletionPolicy field in a future release. + /// Currently, both could be set independently and non-default values would be honored if the feature + /// flag is enabled. If both are custom, the DeletionPolicy field will be ignored. See the design doc + /// for more information: + /// https://github.com/crossplane/crossplane/blob/499895a25d1a1a0ba1604944ef98ac7a1a71f197/design/design-doc-observe-only-resources.md?plain=1#L223 + /// and this one: + /// https://github.com/crossplane/crossplane/blob/444267e84783136daa93568b364a5f01228cacbe/design/one-pager-ignore-changes.md + /// + /// Default if undefined: `{ "*" }` + managementPolicies: Listing<"Observe"|"Create"|"Update"|"Delete"|"LateInitialize"|"*">? + + /// ProviderConfigReference specifies how the provider that will be used to create, observe, update, + /// and delete this managed resource should be configured. + /// + /// Default if undefined: `{ ["name"] = "default" }` + providerConfigRef: ProviderConfigRef? + + /// PublishConnectionDetailsTo specifies the connection secret config which contains a name, metadata + /// and a reference to secret store config to which any connection details for this managed resource + /// should be written. Connection details frequently include the endpoint, username, and password + /// required to connect to the managed resource. + publishConnectionDetailsTo: PublishConnectionDetailsTo? + + /// Readiness defines how the object's readiness condition should be computed, if not specified it will + /// be considered ready as soon as the underlying external resource is considered up-to-date. + readiness: Readiness? + + references: Listing? + + /// Watch enables watching the referenced or managed kubernetes resources. + /// + /// THIS IS AN ALPHA FIELD. Do not use it in production. It is not honored unless "watches" feature + /// gate is enabled, and may be changed or removed without notice. + /// + /// Default if undefined: `false` + watch: Boolean? + + /// WriteConnectionSecretToReference specifies the namespace and name of a Secret to which any + /// connection details for this managed resource should be written. Connection details frequently + /// include the endpoint, username, and password required to connect to the managed resource. This + /// field is planned to be replaced in a future release in favor of PublishConnectionDetailsTo. + /// Currently, both could be set independently and connection details would be published to both + /// without affecting each other. + writeConnectionSecretToRef: WriteConnectionSecretToRef? +} + +/// ConnectionDetail represents an entry in the connection secret for an Object +class ConnectionDetail { + /// API version of the referent. + apiVersion: String? + + /// If referring to a piece of an object instead of an entire object, this string should contain a + /// valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if + /// the object reference is to a container within a pod, this would take on a value like: + /// "spec.containers{name}" (where "name" refers to the name of the container that triggered the event) + /// or if no container name is specified "spec.containers[2]" (container with index 2 in this pod). + /// This syntax is chosen only to have some well-defined way of referencing a part of an object. TODO: + /// this design is not final and this field is subject to change in the future. + fieldPath: String? + + /// Kind of the referent. More info: + /// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + kind: String? + + /// Name of the referent. More info: + /// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + name: String? + + /// Namespace of the referent. More info: + /// https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + namespace: String? + + /// Specific resourceVersion to which this reference is made, if any. More info: + /// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + resourceVersion: String? + + toConnectionSecretKey: String? + + /// UID of the referent. More info: + /// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + uid: String? +} + +/// ObjectParameters are the configurable fields of a Object. +class ForProvider { + /// Raw JSON representation of the kubernetes object to be created. + manifest: K8sResource +} + +/// ProviderConfigReference specifies how the provider that will be used to create, observe, update, and +/// delete this managed resource should be configured. +/// +/// Default if undefined: `{ ["name"] = "default" }` +class ProviderConfigRef { + /// Name of the referenced object. + name: String + + /// Policies for referencing. + policy: Policy? +} + +/// Policies for referencing. +class Policy { + /// Resolution specifies whether resolution of this reference is required. The default is 'Required', + /// which means the reconcile will fail if the reference cannot be resolved. 'Optional' means this + /// reference will be a no-op if it cannot be resolved. + /// + /// Default if undefined: `"Required"` + resolution: ("Required"|"Optional")? + + /// Resolve specifies when this reference should be resolved. The default is 'IfNotPresent', which will + /// attempt to resolve the reference only when the corresponding field is not present. Use 'Always' to + /// resolve the reference on every reconcile. + resolve: ("Always"|"IfNotPresent")? +} + +/// PublishConnectionDetailsTo specifies the connection secret config which contains a name, metadata and +/// a reference to secret store config to which any connection details for this managed resource should +/// be written. Connection details frequently include the endpoint, username, and password required to +/// connect to the managed resource. +class PublishConnectionDetailsTo { + /// SecretStoreConfigRef specifies which secret store config should be used for this ConnectionSecret. + /// + /// Default if undefined: `{ ["name"] = "default" }` + configRef: ConfigRef? + + /// Metadata is the metadata for connection secret. + metadata: Metadata? + + /// Name is the name of the connection secret. + name: String +} + +/// SecretStoreConfigRef specifies which secret store config should be used for this ConnectionSecret. +/// +/// Default if undefined: `{ ["name"] = "default" }` +class ConfigRef { + /// Name of the referenced object. + name: String + + /// Policies for referencing. + policy: Policy? +} + +/// Metadata is the metadata for connection secret. +class Metadata { + /// Annotations are the annotations to be added to connection secret. - For Kubernetes secrets, this + /// will be used as "metadata.annotations". - It is up to Secret Store implementation for others store + /// types. + annotations: Mapping? + + /// Labels are the labels/tags to be added to connection secret. - For Kubernetes secrets, this will be + /// used as "metadata.labels". - It is up to Secret Store implementation for others store types. + labels: Mapping? + + /// Type is the SecretType for the connection secret. - Only valid for Kubernetes Secret Stores. + type: String? +} + +/// Readiness defines how the object's readiness condition should be computed, if not specified it will +/// be considered ready as soon as the underlying external resource is considered up-to-date. +class Readiness { + /// CelQuery defines a cel query to evaluate the readiness. The observed object is passed to the cel + /// query with the word `object`. Cel macros are available to be used, see + /// https://github.com/google/cel-spec/blob/master/doc/langdef.md#macros for more information. + /// Examples: `object.status.isReady == true`: checks for a boolean field called isReady on status. + /// `object.status.conditions.all(x, x.status == "True")` mimics the behavior of the AllTrue readiness + /// policy `object.status.conditions.exists(c, c.type == "condition1" && c.status == "True" )` checks + /// just one condition + celQuery: String? + + /// Policy defines how the Object's readiness condition should be computed. + /// + /// Default if undefined: `"SuccessfulCreate"` + policy: ("SuccessfulCreate"|"DeriveFromObject"|"AllTrue"|"DeriveFromCelQuery")? +} + +/// Reference refers to an Object or arbitrary Kubernetes resource and optionally patch values from that +/// resource to the current Object. +class Reference { + /// DependsOn is used to declare dependency on other Object or arbitrary Kubernetes resource. + dependsOn: DependsOn? + + /// PatchesFrom is used to declare dependency on other Object or arbitrary Kubernetes resource, and + /// also patch fields from this object. + patchesFrom: PatchesFrom? + + /// ToFieldPath is the path of the field on the resource whose value will be changed with the result of + /// transforms. Leave empty if you'd like to propagate to the same path as patchesFrom.fieldPath. + toFieldPath: String? +} + +/// DependsOn is used to declare dependency on other Object or arbitrary Kubernetes resource. +class DependsOn { + /// APIVersion of the referenced object. + /// + /// Default if undefined: `"kubernetes.crossplane.io/v1alpha1"` + apiVersion: String? + + /// Kind of the referenced object. + /// + /// Default if undefined: `"Object"` + kind: String? + + /// Name of the referenced object. + name: String + + /// Namespace of the referenced object. + namespace: String? +} + +/// PatchesFrom is used to declare dependency on other Object or arbitrary Kubernetes resource, and also +/// patch fields from this object. +class PatchesFrom { + /// APIVersion of the referenced object. + /// + /// Default if undefined: `"kubernetes.crossplane.io/v1alpha1"` + apiVersion: String? + + /// FieldPath is the path of the field on the resource whose value is to be used as input. + fieldPath: String + + /// Kind of the referenced object. + /// + /// Default if undefined: `"Object"` + kind: String? + + /// Name of the referenced object. + name: String + + /// Namespace of the referenced object. + namespace: String? +} + +/// WriteConnectionSecretToReference specifies the namespace and name of a Secret to which any connection +/// details for this managed resource should be written. Connection details frequently include the +/// endpoint, username, and password required to connect to the managed resource. This field is planned +/// to be replaced in a future release in favor of PublishConnectionDetailsTo. Currently, both could be +/// set independently and connection details would be published to both without affecting each other. +class WriteConnectionSecretToRef { + /// Name of the secret. + name: String + + /// Namespace of the secret. + namespace: String +} + +/// A ObjectStatus represents the observed state of a Object. +class Status { + /// ObjectObservation are the observable fields of a Object. + atProvider: AtProvider? + + /// Conditions of the resource. + conditions: Listing? + + /// ObservedGeneration is the latest metadata.generation which resulted in either a ready state, or + /// stalled due to error it can not recover from without human intervention. + observedGeneration: Int? +} + +/// ObjectObservation are the observable fields of a Object. +class AtProvider { + /// Raw JSON representation of the remote object. + manifest: K8sResource? +} + +/// A Condition that may apply to a resource. +class Condition { + /// LastTransitionTime is the last time this condition transitioned from one status to another. + lastTransitionTime: String + + /// A Message containing details about this condition's last transition from one status to another, if + /// any. + message: String? + + /// ObservedGeneration represents the .metadata.generation that the condition was set based upon. For + /// instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration + /// is 9, the condition is out of date with respect to the current state of the instance. + observedGeneration: Int? + + /// A Reason for this condition's last transition from one status to another. + reason: String + + /// Status of this condition; is it currently True, False, or Unknown? + status: String + + /// Type of this condition. At most one of each condition type may apply to a resource at any point in + /// time. + type: String +} diff --git a/example/git/pkl/crds/XR.pkl b/example/git/pkl/crds/XR.pkl new file mode 100644 index 0000000..a3e118c --- /dev/null +++ b/example/git/pkl/crds/XR.pkl @@ -0,0 +1,27 @@ +module io.crossplane.example.v1.XR + +extends "package://pkg.pkl-lang.org/github.com/crossplane-contrib/function-pkl/crossplane.contrib@0.0.1#/XRDBase.pkl" + +import "package://pkg.pkl-lang.org/pkl-k8s/k8s@1.1.0#/apimachinery/pkg/apis/meta/v1/ObjectMeta.pkl" +import "package://pkg.pkl-lang.org/github.com/crossplane-contrib/function-pkl/crossplane.contrib@0.0.1#/XRDBase.pkl" + +fixed apiVersion: "example.crossplane.io/v1" + +fixed kind: "XR" + +/// Standard object's metadata. +/// +/// More info: . +metadata: ObjectMeta? + +spec: Spec? + +status: Status? + +class Spec extends XRDBase.Spec { + +} + +class Status extends XRDBase.Status { + someStatus: String? +} diff --git a/example/git/pkl/step.pkl b/example/git/pkl/step.pkl new file mode 100644 index 0000000..8afe91b --- /dev/null +++ b/example/git/pkl/step.pkl @@ -0,0 +1,84 @@ +amends "package://pkg.pkl-lang.org/github.com/crossplane-contrib/function-pkl/crossplane.contrib@0.0.1#/CompositionResponse.pkl" +import "package://pkg.pkl-lang.org/github.com/crossplane-contrib/function-pkl/crossplane.contrib@0.0.1#/crossplane.pkl" +import "package://pkg.pkl-lang.org/pkl-k8s/k8s@1.1.0#/api/core/v1/ConfigMap.pkl" + +import "crds/XR.pkl" +import "crds/Object.pkl" + +local request = new crossplane { + customResourceTemplates = new { + ["XR"] { + ["example.crossplane.io/v1"] = XR + } + ["Object"] { + ["kubernetes.crossplane.io/v1alpha2"] = Object + } + } +}.Request + +local observedCompositeResource: XR? = request.observed.composite.resource as XR? +local extraResource: Object? = request.getExtraResource("ineed", 0)?.resource as Object? + +requirements { + extraResources { + ["ineed"] { + apiVersion = Object.apiVersion + kind = Object.kind + match { + matchName = "required" + } + } + } +} + +desired { + composite { + resource = new XR { + status { + someStatus = "pretty status" + } + } + } + resources { + ["cm-one"] = new { + resource = new Object { + spec { + forProvider { + manifest = new ConfigMap { + metadata { + namespace = "crossplane-system" + } + data { + ["foo"] = observedCompositeResource?.metadata?.name ?? throw("Composite could not find observed composite name") + ["required"] = extraResource?.metadata?.name ?? "i could not find what I needed..." + } + } + } + } + } + ready = true + } + } +} +results { + new { + severity = "Normal" + message = "welcome" + } + when (extraResource?.metadata?.name == null) { + new { + severity = "Warning" + message = "the extra resource is missing!" + } + } +} +context { + ["greetings"] = "with <3 from function-pkl" + when (request.context.containsKey("apiextensions.crossplane.io/environment")) { + ["apiextensions.crossplane.io/environment"] = request.context.getOrNull("apiextensions.crossplane.io/environment") + } +} + +meta = if (request.meta != null) new { + ttl = 60.s +} else null diff --git a/example/git/xr.yaml b/example/git/xr.yaml new file mode 100644 index 0000000..5d409ae --- /dev/null +++ b/example/git/xr.yaml @@ -0,0 +1,7 @@ +apiVersion: example.crossplane.io/v1 +kind: XR +spec: + compositionRef: + name: pkl-uri-example +metadata: + name: uri-example From 6fac2f3cf7fe33b94183f47f0383027f0527e888 Mon Sep 17 00:00:00 2001 From: Tim <32556895+Avarei@users.noreply.github.com> Date: Tue, 16 Jul 2024 23:30:20 +0200 Subject: [PATCH 2/3] fix composition name Signed-off-by: Tim <32556895+Avarei@users.noreply.github.com> --- example/git/composition.yaml | 2 +- example/git/xr.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example/git/composition.yaml b/example/git/composition.yaml index aacc10a..1a9d9d5 100644 --- a/example/git/composition.yaml +++ b/example/git/composition.yaml @@ -1,7 +1,7 @@ apiVersion: apiextensions.crossplane.io/v1 kind: Composition metadata: - name: pkl-uri-example + name: pkl-git-example spec: compositeTypeRef: apiVersion: example.crossplane.io/v1 diff --git a/example/git/xr.yaml b/example/git/xr.yaml index 5d409ae..981f3af 100644 --- a/example/git/xr.yaml +++ b/example/git/xr.yaml @@ -2,6 +2,6 @@ apiVersion: example.crossplane.io/v1 kind: XR spec: compositionRef: - name: pkl-uri-example + name: pkl-git-example metadata: name: uri-example From 4d5e328fedf7806cb0104c7f8665423748d960d1 Mon Sep 17 00:00:00 2001 From: Tim <32556895+Avarei@users.noreply.github.com> Date: Tue, 16 Jul 2024 23:48:25 +0200 Subject: [PATCH 3/3] fix xr name Signed-off-by: Tim <32556895+Avarei@users.noreply.github.com> --- example/git/xr.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/git/xr.yaml b/example/git/xr.yaml index 981f3af..e3bc3ea 100644 --- a/example/git/xr.yaml +++ b/example/git/xr.yaml @@ -4,4 +4,4 @@ spec: compositionRef: name: pkl-git-example metadata: - name: uri-example + name: git-example