Feature 11074: Enhanced OSM declarative modelling for applications. OSM's SDK for intent manipulation
Change-Id: I6d03faa143eafcf30380b3b854c54f177dcf8f25
Signed-off-by: garciadeblas <gerardo.garciadeblas@telefonica.com>
diff --git a/docker/osm-nushell-krm-functions/krm/concatenate.nu b/docker/osm-nushell-krm-functions/krm/concatenate.nu
new file mode 100644
index 0000000..dde5095
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/concatenate.nu
@@ -0,0 +1,105 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+# Module with utility functions to concatenate descriptions of Kubernetes resources, either embedded in ResourceLists or from plain multi-resource manifests (i.e. lists of records).
+
+
+# Join two ResourceLists, one from stdin and another from the first argument.
+# Empty records at stdin or at the parameter are valid and should be treated as empty ResourceLists.
+export def resourcelists [
+ resourcelist2: record # 2nd `ResourceList` to concatenate
+]: [
+ record -> record
+ nothing -> record
+] {
+ # Gather the input and convert to record if empty
+ let list1: record = if $in == null { {} } else { $in }
+ let list2: record = if $resourcelist2 == null { {} } else { $resourcelist2 }
+
+ # If both are empty, returns an empty ResourceList
+ if $list1 == {} and $list2 == {} {
+ {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: []
+ }
+ } else if $list2 == {} {
+ # If the second ResourceList is empty, returns just the one from stdin
+ $list1
+ } else {
+ # Merge both resource lists strategically
+ {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: ($list1.items? | append $list2.items?)
+ }
+ # ALTERNATIVELY: $in_list | merge deep --strategy "append" $source_list
+ ## Strategy is "append", so that item lists are appended
+ }
+}
+
+export alias resourcelist = resourcelists
+export alias rl = resourcelists
+
+
+# Join two ResourceList files
+# NOT EXPORTED
+def "resourcelists from files" [file1: path, file2: path] {
+ let list1 = (open $file1)
+ let list2 = (open $file2)
+ $list1 | merge $list2
+}
+
+alias join_lists = resourcelists from files
+
+
+# Join two manifests, one from stdin and another from the first argument
+# Empty manifests at stdin or at the parameter are valid and should be treated as empty manifests.
+export def manifests [
+ mnfst2: any # 2nd manifest to concatenate
+]: [
+ any -> list<any>
+] {
+
+ # Gather the input and convert to list
+ # let manifest1: list<any> = if $in == null { [] } else { $in }
+ # let manifest2: list<any> = if $mnfst2 == null { [] } else { $mnfst2 }
+ let manifest1: list<any> = (if $in == null { [] }
+ else if ($in | describe | str starts-with "record") { [ $in ] }
+ else if ($in | describe | str starts-with "list") or ($in | describe | str starts-with "table") { $in }
+ else { error make {msg: $"Error: Expected a record or a list of records, but received ($in | describe)."}})
+
+ let manifest2: list<any> = (if $mnfst2 == null { [] }
+ else if ($mnfst2 | describe | str starts-with "record") { [ $mnfst2 ] }
+ else if ($mnfst2 | describe | str starts-with "list") or ($mnfst2 | describe | str starts-with "table") { $mnfst2 }
+ else { error make {msg: $"Error: Expected a record or a list of records, but received ($mnfst2 | describe)."}})
+
+ # Return the concatenation
+ [
+ $manifest1
+ $manifest2
+ ] | flatten
+
+ # How to convert to YAML manifests again:
+ #
+ # let merged_manifests = ($manifest1 | manifests $manifest2)
+ # $merged_manifests
+ # | each { |obj| $obj | to yaml }
+ # | str join "---\n"
+}
+
+export alias manifest = manifests
diff --git a/docker/osm-nushell-krm-functions/krm/convert.nu b/docker/osm-nushell-krm-functions/krm/convert.nu
new file mode 100644
index 0000000..07578a0
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/convert.nu
@@ -0,0 +1,147 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+# Module with functions to convert between the different alternative representations of a set of Kubernetes resources: folders with manifests, lists of records (concatenated manifests), or ResourceLists.
+
+
+use ./concatenate.nu
+
+
+
+# Substitute environment variables in text from stdin.
+#
+# Environment variables can be listed either as a string in the format accepted by the `envsubst` command (e.g. `$VAR1,$VAR2`) or as a list of strings with the environment variable names that will be formatted by the function. In case the parameter is empty, it should invoke `envsubst` with no parameters, so that it replaces all the environment variables that are found.
+export def "replace environment variables" [vars_to_replace: any = ""]: [
+ string -> string
+] {
+ # Gather reference to stdin before it is lost
+ let text: string = $in
+
+ # Adapt the input as needed
+ let filter: string = (if ($vars_to_replace | describe) == "string" {
+ # If it is a string, it can be used directly as filter for envsubst
+ $vars_to_replace
+ } else if ($vars_to_replace | describe) == "list<string>" {
+ # If it is a list of strings, we can concatenate them in a single string with the right format for envsubst
+ ($vars_to_replace | each {|var| $"${($var)}" } | str join ',')
+ } else {
+ # Handle unexpected type for $vars_to_replace
+ error make {msg: $"Error: Expected a string or list of strings, but received ($vars_to_replace | describe)"}
+ })
+
+ # Proceed with the substitution
+ if ($filter | is-empty) {
+ $text | ^envsubst
+ } else {
+ $text | ^envsubst $filter
+ }
+}
+
+alias replace_env_vars = replace environment variables
+
+
+# Convert manifests in a source folder to a ResourceList
+export def "folder to resourcelist" [
+ --subst-env, # Set if environment variables should be replaced
+ folder: path,
+ env_filter?: any = ""
+]: [
+ record -> record
+ nothing -> record
+] {
+ # Gather the input and convert to record if empty
+ let in_list: record = if $in == null { {} } else { $in }
+
+ # Create a ResourceList from the source folder and substitute environment variables if needed
+ let source_list: record = (
+ kpt fn source $folder
+ | if $subst_env {
+ $in | replace environment variables $env_filter
+ } else {
+ $in
+ }
+ | from yaml
+ )
+
+ # Merge both resource lists carefully
+ $in_list | concatenate resourcelists $source_list
+}
+
+export alias "folder to rl" = folder to resourcelist
+export alias folder2list_generator = folder to resourcelist
+
+
+# Convert a manifest from stdin to a ResourceList
+## NOTE: It is an equivalent with type-checks to:
+## kustomize cfg cat --wrap-kind ResourceList
+export def "manifest to resourcelist" []: [
+ any -> record
+] {
+ # Gather the input and convert to list
+ let manifest_in: list<any> = (if $in == null { [] }
+ else if ($in | describe | str starts-with "record") { [ $in ] }
+ else if ($in | describe | str starts-with "list") or ($in | describe | str starts-with "table") { $in }
+ else { error make {msg: $"Error: Expected a record or a list of records, but received ($in | describe)."}})
+
+ {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: $manifest_in
+ }
+}
+
+export alias manifest2list = manifest to resourcelist
+
+
+# Convert a ResourceList to file manifests in a target folder
+export def "resourcelist to folder" [
+ --sync, # If sync is true, replaces all contents in the folder, otherwise just copies over.
+ folder: path,
+ dry_run?: bool = false # If true, just prints the ResourceList but does not render any file
+]: [
+ record -> any
+] {
+
+ # Preserves the input value
+ let list_in: record = $in
+
+ # As optional parameter, defaults to $env.DRY_RUN when not set
+ let is_dry_run: bool = if $dry_run == null { $env.DRY_RUN | into bool } else { $dry_run }
+
+ # If it is a dry-run, just prints the input ResourceList and exits
+ if $is_dry_run {
+ return ($list_in | to yaml)
+ }
+
+ # First, render the manifests to a temporary folder
+ let tmp_folder: string = (mktemp -t -d)
+ let tmp_target: string = ($tmp_folder | path join manifests)
+ $list_in | to yaml | ^kpt fn sink $tmp_target
+
+ # Writes the contents to the target folder
+ if $sync {
+ # Sync actually removes any previous contents in the folder
+ rm -rf $folder
+ }
+ mkdir $folder
+ ls $tmp_target | get name | cp -r ...$in $folder
+
+ # Removes the temporary folder
+ rm -rf $tmp_folder
+}
+
+export alias list2folder = resourcelist to folder
diff --git a/docker/osm-nushell-krm-functions/krm/generator.nu b/docker/osm-nushell-krm-functions/krm/generator.nu
new file mode 100644
index 0000000..98d52b5
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/generator.nu
@@ -0,0 +1,252 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+# Module with KRM generator functions for various kinds of Kubernetes resources.
+
+
+use ./convert.nu
+use ./concatenate.nu
+use ./patch.nu
+use ./keypair.nu
+
+
+# KRM generator function with input from a ResourceList.
+# Works as generic generator function to insert any resource from a ResourceList, passed as parameter, into another ResourceList, passed from stdin.
+export def "from resourcelist" [
+ rl: record
+]: [
+ record -> record
+] {
+ # Regularizes ResourceList from stdin
+ let list1: record = if $in == null { {} } else { $in }
+
+ # Regularizes the second ResourceList
+ let list2: record = if $rl == null { {} } else { $rl }
+
+ # Checks that both ResourceLists are actual ResourceLists
+ {stdin: $list1, "input parameter": $list2} | items { |name, rl|
+ if (
+ $rl != {}
+ and (
+ ($rl | get -i kind) != "ResourceList"
+ or ($rl | get -i apiVersion) != "config.kubernetes.io/v1"
+ )
+ ) {
+ error make {msg: $"Error: Expected a ResourceList, but received ($rl) from ($name)."}
+ }
+ }
+
+ # Merges both ResourceLists
+ $list1
+ | concatenate resourcelists $list2
+}
+
+
+# KRM generator function with input from a manifest
+#
+# Example of use: Generator from an encrypted secret:
+#
+# use ./keypair.nu
+#
+# let secret_value: string = "my_secret_value"
+# let secret_name: string = "mysecret"
+# let secret_key: string = "mykey"
+# let public_key: string = "age1s236gmpr7myjjyqfrl6hwz0npqjgxa9t6tjj46yq28j2c4nk653saqreav"
+#
+# {}
+# | generator from manifest (
+# $secret_value
+# | (^kubectl create secret generic ($secret_name)
+# --from-file=($secret_key)=/dev/stdin
+# --dry-run=client
+# -o yaml)
+# | keypair encrypt_secret_from_stdin $public_key
+# | from yaml
+# )
+# | to yaml
+#
+# RESULT:
+#
+# apiVersion: config.kubernetes.io/v1
+# kind: ResourceList
+# items:
+# - apiVersion: v1
+# data:
+# mykey: ENC[AES256_GCM,data:XKTW8X5ZI6c3yWYtyOPUP/UskKc=,iv:ZOkqLmSgXNCNCQrsMUq7iDL05rklDBuTaVS6E5Bgyl8=,tag:/2rLYqnh+RJWWH4OmEHJBA==,type:str]
+# kind: Secret
+# metadata:
+# creationTimestamp: null
+# name: mysecret
+# sops:
+# kms: []
+# gcp_kms: []
+# azure_kv: []
+# hc_vault: []
+# age:
+# - recipient: age1s236gmpr7myjjyqfrl6hwz0npqjgxa9t6tjj46yq28j2c4nk653saqreav
+# enc: |
+# -----BEGIN AGE ENCRYPTED FILE-----
+# YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSByNk9KWnhBa2xiMFpyT1Fj
+# QWw3aGxRZnhHbUNOcXUvc05zZDZIckdPWWtNCm91VzUwU2l5NnVSajJyQkhBMldK
+# ZkJYWXFTd1J5Q2Z1cTJ6MExkeVBWVXcKLS0tIHpKQ0EvdmpzNS9nZFFHK0JoV0Rx
+# NXRyMXROK2p3bkpnOXowQ1RYdFk2blkKzsJiw31EA7hZbcRaHe0RkjsrSs7GQjXc
+# YNAtoPquu0xaocX3pEUV/aojG/WejNY7peDXVDI43yfv8eJlO072Sw==
+# -----END AGE ENCRYPTED FILE-----
+# lastmodified: 2025-03-10T17:47:08Z
+# mac: ENC[AES256_GCM,data:JZttY7AvtRmVaJpCIdJc4Tve7EykKpR7SETQoR7fSiFOVfm4EX+ZcwYoxQYiMsNWXnx/K/IAo8VKoT1+x/lsyFTFucP3YsZ35cfXtAPt43d+gi+IEYS9hfjDQL4BmLAlIiwmij0QGOzcWFFSDhatD717zIBzEDbs2qNGHTqc68E=,iv:Dtiwbvb7LPTyShw2DrnpM/EAWdLyxSDimh7Kk15Jox4=,tag:1VBGnQbotN5KDSmznvNPdg==,type:str]
+# pgp: []
+# encrypted_regex: ^(data|stringData)$
+# version: 3.8.1
+export def "from manifest" [
+ manifest: any
+]: [
+ record -> record
+ nothing -> record
+] {
+ # Keeps prior ResourceList, with regularization if needed
+ let in_rl: record = if $in == null { {} } else { $in }
+
+ # Regularizes the manifest in the parameter so that is is a list
+ let manifest1: list<any> = (if $manifest == null { [] }
+ else if ($manifest | describe | str starts-with "record") { [ $manifest ] }
+ else if ($manifest | describe | str starts-with "list") or ($manifest | describe | str starts-with "table") { $manifest }
+ else { error make {msg: $"Error: Expected a record or a list of records, but received ($manifest | describe)."}})
+
+ # Creates a ResourceList from the manifest and merges with ResourceList from stdin
+ $in_rl
+ | concatenate resourcelists ($manifest1 | convert manifest to resourcelist)
+}
+
+
+# KRM generator function for a ConfigMap
+export def "configmap" [
+ --filename: string, # File name to keep the manifest
+ --index: int, # Number of the index in the file, for multi-resource manifests
+ key_pairs: record, # Key-value pairs to add to the ConfigMap
+ name: string,
+ namespace?: string = "default"
+]: [
+ record -> record
+ nothing -> record
+] {
+ # Regularizes ResourceList from stdin
+ let in_rl: record = if $in == null { {} } else { $in }
+
+ $in_rl
+ | ( from manifest
+ # ConfigMap manifest structure
+ {
+ apiVersion: v1,
+ kind: ConfigMap,
+ metadata: {
+ name: $name,
+ namespace: $namespace,
+ },
+ data: $key_pairs
+ }
+ )
+ # Add file name if required
+ | if ($filename | is-empty) {
+ $in
+ } else {
+ $in
+ | (patch resource filename set
+ --index $index
+ $filename
+ "v1"
+ "ConfigMap"
+ $name
+ $namespace
+ )
+ }
+}
+
+
+# KRM generator function for a Secret
+export def "secret" [
+ --filename: string, # File name to keep the manifest
+ --index: int, # Number of the index in the file, for multi-resource manifests
+ --public-age-key: string # Age key to encrypt the contents of the Secret manifest
+ --type: string # Type of Kubernetes secret. Built-in types: `Opaque` (default), `kubernetes.io/service-account-token`, `kubernetes.io/dockercfg`, `kubernetes.io/dockerconfigjson`, `kubernetes.io/basic-auth`, `kubernetes.io/ssh-auth`, `kubernetes.io/tls`, `bootstrap.kubernetes.io/token`
+ key_pairs: record, # Key-value pairs to add to the Secret
+ name: string,
+ namespace?: string = "default"
+]: [
+ record -> record
+ nothing -> record
+] {
+ # Regularizes ResourceList from stdin
+ let in_rl: record = if $in == null { {} } else { $in }
+
+ # Encode the values with base64
+ let encoded_key_pairs: record = (
+ ($key_pairs | columns)
+ | zip (
+ $key_pairs
+ | values
+ | each {$in | encode base64}
+ )
+ | reduce -f {} {|it, acc| $acc | upsert $it.0 $it.1 }
+ )
+
+ # Generate the secret
+ $in_rl
+ | ( from manifest
+ # ConfigMap manifest structure
+ (
+ {
+ apiVersion: v1,
+ kind: Secret,
+ metadata: {
+ name: $name,
+ namespace: $namespace,
+ },
+ data: $encoded_key_pairs
+ }
+ # Add Secret type if specified
+ | if ($type | is-empty) {
+ $in
+ } else {
+ $in
+ | insert type $type
+ }
+ # Encode if an age key was supplied
+ | if ($public_age_key | is-empty) {
+ $in
+ } else {
+ $in
+ | to yaml
+ | keypair encrypt_secret_from_stdin $public_age_key
+ | from yaml
+ }
+ )
+ )
+ # Add file name if required
+ | if ($filename | is-empty) {
+ $in
+ } else {
+ $in
+ | (patch resource filename set
+ --index $index
+ $filename
+ "v1"
+ "Secret"
+ $name
+ $namespace
+ )
+ }
+}
diff --git a/docker/osm-nushell-krm-functions/krm/jsonpatch.nu b/docker/osm-nushell-krm-functions/krm/jsonpatch.nu
new file mode 100644
index 0000000..1718b9b
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/jsonpatch.nu
@@ -0,0 +1,107 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+# Module with helper functions to create JSON patches for Kubernetes resources as per RFC6902 (patchJson6902).
+
+
+# Helper function to create an operation for a JSON patch (patchJson6902) as per RFC6902
+#
+# Example:
+# $ jsonpatch create operation add /spec/template/spec/securityContext {runAsUser: 10000, fsGroup: 1337} | to yaml
+#
+# op: add
+# path: /spec/template/spec/securityContext
+# value:
+# runAsUser: 10000
+# fsGroup: 1337
+export def "create operation" [
+ op: string, # Operation type: "add", "remove", "replace", "move", "copy", or "test"
+ path: string, # JSON pointer path at the target key location in format "/a/b/c"
+ value?: any # Value to be added, replaced, or removed
+ from?: string, # JSON pointer path (format "/a/b/c") at the TARGET RESOURCE to take as source in "copy" or "move" operations.
+]: [
+ nothing -> record
+] {
+ if $op in ["add", "replace"] {
+ if not ($value | is-empty) {
+ {
+ op: $op,
+ path: $path,
+ value: $value
+ }
+ } else {
+ error make { msg: "Value is required for 'add' and 'replace' operations." }
+ }
+ } else if $op in ["remove"] {
+ {
+ op: $op,
+ path: $path
+ }
+ } else if $op in ["move", "copy"] {
+ if not ($from | is-empty) {
+ {
+ op: $op,
+ from: $from,
+ path: $path
+ }
+ } else {
+ error make { msg: "Source path is required for 'move' and 'copy' operations." }
+ }
+ } else {
+ error make { msg: "Invalid operation type. Supported values are 'add', 'remove', 'replace', 'move', 'copy'. See RFC6902 for details." }
+ }
+}
+
+
+# Helper to create a full JSON patch (patchJson6902), including the target object specification and a list of operations
+#
+# Example 1: Using records directly
+# $ jsonpatch create {kind: Deployment, name: podinfo} {op: add, target: /spec/template/spec/securityContext, value: {runAsUser: 10000, fsGroup: 1337}} | to yaml
+#
+# target:
+# kind: Deployment
+# name: podinfo
+# patch: |
+# - op: add
+# path: /spec/template/spec/securityContext
+# value:
+# runAsUser: 10000
+# fsGroup: 1337
+#
+# Example 2: Leveraging the operation helper function
+# $ jsonpatch create {kind: Deployment, name: podinfo} (jsonpatch create operation add /spec/template/spec/securityContext {runAsUser: 10000, fsGroup: 1337}) | to yaml
+#
+# target:
+# kind: Deployment
+# name: podinfo
+# patch: |
+# - op: add
+# path: /spec/template/spec/securityContext
+# value:
+# runAsUser: 10000
+# fsGroup: 1337
+export def "create" [
+ target: record, # Target resource specification as per <https://github.com/kubernetes-sigs/kustomize/blob/master/examples/patchMultipleObjects.md>
+ ...operations: record # List of patch operations as per RFC6902
+]: [
+ nothing -> record
+] {
+ {
+ target: $target,
+ patch: ($operations | to yaml)
+ }
+}
diff --git a/docker/osm-nushell-krm-functions/krm/keypair.nu b/docker/osm-nushell-krm-functions/krm/keypair.nu
new file mode 100644
index 0000000..83689e3
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/keypair.nu
@@ -0,0 +1,105 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+# Module with custom commands to create and manage age key pairs for SOPS encryption/decryption of Kubernetes secrets.
+
+
+# Create a new age key pair
+export def "create age" [
+ age_key_name: string,
+ credentials_dir?: path # Optional, defaults to $env.CREDENTIALS_DIR
+] {
+ let dir: path = if $credentials_dir == null { $env.CREDENTIALS_DIR } else { $credentials_dir }
+ let key_path: path = ({ parent: $dir, stem: $age_key_name, extension: "key"} | path join)
+ let pub_path: path = ({ parent: $dir, stem: $age_key_name, extension: "pub"} | path join)
+
+ # Delete existing keys
+ rm -f $key_path $pub_path
+
+ # Generate private key
+ ^age-keygen -o $key_path
+
+ # Extract public key
+ ^age-keygen -y $key_path | save $pub_path
+}
+
+export alias create_age_keypair = create age
+
+
+# In-place encrypt secrets in manifest
+# -- NOT EXPORTED --
+def "encrypt secret inplace" [
+ file: path,
+ public_key: string
+]: [
+ nothing -> nothing
+] {
+ ^sops --age $public_key --encrypt --encrypted-regex '^(data|stringData)$' --in-place $file
+}
+
+export alias encrypt_secret_inplace = encrypt secret inplace
+
+
+# Encrypt with SOPS a manifest of Kubernetes secret received from stdin
+export def "encrypt secret manifest" [public_key: string]: [
+ string -> string
+] {
+ # Saves the input to preserve it from multiple invokes
+ let manifest: string = $in
+
+ # If the input empty, just returns an empty string
+ if $manifest == "" {
+ return ""
+ }
+
+ let tmp_file = (mktemp -t --suffix .yaml)
+ $manifest | save -f $tmp_file
+
+ ^sops --age $public_key --encrypt --encrypted-regex '^(data|stringData)$' --in-place $tmp_file
+
+ let content: string = (open $tmp_file | to yaml)
+ rm -f $tmp_file
+ $content
+}
+
+export alias encrypt_secret_from_stdin = encrypt secret manifest
+
+
+# Decrypt with SOPS a manifest of a Kubernetes secret received from stdin
+export def "decrypt secret manifest" [private_key: string]: [
+ string -> string
+] {
+ # Saves the input to preserve it from multiple invokes
+ let encrypted_manifest: string = $in
+
+ # If the input empty, just returns an empty string
+ if $encrypted_manifest == "" {
+ return ""
+ }
+
+ # Decrypt using temporary file
+ let tmp_encrypted_file = (mktemp -t --suffix .yaml)
+ $encrypted_manifest | save -f $tmp_encrypted_file
+ let decrypted_manifest: string = (
+ $private_key
+ | SOPS_AGE_KEY_FILE="/dev/stdin" sops --decrypt $tmp_encrypted_file
+ )
+ rm $tmp_encrypted_file # Clean up temporary key file
+
+ # Returns the decrypted secret
+ $decrypted_manifest
+}
diff --git a/docker/osm-nushell-krm-functions/krm/mod.nu b/docker/osm-nushell-krm-functions/krm/mod.nu
new file mode 100644
index 0000000..57b4507
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/mod.nu
@@ -0,0 +1,47 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+# Meta-module of helper functions to manage, transform and generate specifications of Kubernetes resources.
+# This meta-module comprises all the modules of the OSM's SDK for App Modelling.
+
+
+# Import submodules
+export module ./keypair.nu
+export module ./concatenate.nu
+export module ./convert.nu
+export module ./generator.nu
+export module ./patch.nu
+export module ./jsonpatch.nu
+export module ./strategicmergepatch.nu
+export module ./overlaypatch.nu
+
+
+# Convert input string to a safe name for Kubernetes resources
+export def "safe resource name" [input: string]: [
+ nothing -> string
+] {
+ $input
+ | str downcase
+ | str replace -a './' ''
+ | str replace -a '.' '-'
+ | str replace -a '/' '-'
+ | str replace -a '_' '-'
+ | str replace -a ' ' '-'
+ | str replace -a ':' '-'
+}
+
+export alias safe_name = safe resource name
diff --git a/docker/osm-nushell-krm-functions/krm/overlaypatch.nu b/docker/osm-nushell-krm-functions/krm/overlaypatch.nu
new file mode 100644
index 0000000..1fcaf0d
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/overlaypatch.nu
@@ -0,0 +1,436 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+# Module with custom commands to generate `overlay patches`, i.e., patches to a Kustomization that references the resources that we intend to patch at runtime.
+
+
+use ./patch.nu
+use ./jsonpatch.nu
+use ./strategicmergepatch.nu
+use ./generator.nu
+
+
+# Add overlay patch to Kustomization item (in a ResourceList) to modify a key in a referenced resource, using the JSON patch (patchJson6902) format
+export def "add patch" [
+ --ks-namespace: string, # Namespace of the Kustomization
+ kustomization_name: string, # Kustomization to add the patch to
+ target: record, # Target resource for the patch, as per <https://github.com/kubernetes-sigs/kustomize/blob/master/examples/patchMultipleObjects.md>
+ patch_value: record # Patch content as record type. It can be a JSON patch (patchJson6902) or a Strategic Merge Patch
+]: [
+ record -> record
+] {
+ let in_resourcelist: record = $in
+
+ let patch_content: record = (
+ {
+ target: $target,
+ patch: ($patch_value | to yaml)
+ }
+ )
+
+ $in_resourcelist
+ | (patch list append item
+ $.spec.patches
+ $patch_content
+ "kustomize.toolkit.fluxcd.io/v1"
+ "Kustomization"
+ $kustomization_name
+ $ks_namespace
+ )
+}
+
+
+# Add an overlay patch to a Kustomization item (in a ResourceList) to modify a key in a referenced resource, using the JSON patch (patchJson6902) format
+# This command provides a user-friendly interface to create a JSON patch with exactly ONE operation
+export def "add jsonpatch" [
+ --ks-namespace: string, # Namespace of the Kustomization
+ --operation: string = "add", # Operation types: "add", "remove", "replace", "move", "copy", or "test", as per RFC6902
+ kustomization_name: string, # Kustomization to add the patch to
+ target: record, # Target resource for the patch, as per <https://github.com/kubernetes-sigs/kustomize/blob/master/examples/patchMultipleObjects.md>
+ path: string, # JSON pointer path (format "/a/b/c") at the TARGET RESOURCE to be patched.
+ value?: any # Value to set in the target path (required for "add" and "replace" operations)
+ from?: string, # JSON pointer path (format "/a/b/c") at the TARGET RESOURCE to take as source in "copy" or "move" operations.
+]: [
+ record -> record
+] {
+ let in_resourcelist: record = $in
+
+ let operation_spec: record = (
+ if $operation in ["add", "replace"] {
+ {
+ op: $operation,
+ path: $path,
+ value: $value
+ }
+ } else if $operation in ["remove"] {
+ {
+ op: $operation,
+ path: $path
+ }
+ } else if $operation in ["move", "copy"] {
+ {
+ op: $operation,
+ from: $from,
+ path: $path
+ }
+ } else {
+ error make { msg: "Invalid operation type. Supported values are 'add', 'remove', 'replace', 'move', 'copy'. See RFC6902 for details" }
+ }
+ )
+
+ let patch_content: record = (
+ jsonpatch create
+ $target
+ $operation_spec
+ )
+
+ $in_resourcelist
+ | (patch list append item
+ $.spec.patches
+ $patch_content
+ "kustomize.toolkit.fluxcd.io/v1"
+ "Kustomization"
+ $kustomization_name
+ $ks_namespace
+ )
+}
+
+
+# Add a StrategicMergePatch to a Kustomization item (in a ResourceList) to modify a key in a referenced resource
+# This command provides a user-friendly interface to create a patch
+export def "add strategicmergepatch" [
+ --ks-namespace: string, # Namespace of the Kustomization
+ kustomization_name: string, # Kustomization to add the patch to
+ target: record, # Target resource for the patch, as per <https://github.com/kubernetes-sigs/kustomize/blob/master/examples/patchMultipleObjects.md>
+ patch: record, # Contents of the strategic patch in the format of a record
+]: [
+ record -> record
+] {
+ let in_resourcelist: record = $in
+
+ let patch_content: record = (
+ strategicmergepatch create
+ $target
+ $patch
+ )
+
+ $in_resourcelist
+ | (patch list append item
+ $.spec.patches
+ $patch_content
+ "kustomize.toolkit.fluxcd.io/v1"
+ "Kustomization"
+ $kustomization_name
+ $ks_namespace
+ )
+}
+
+
+# Modify a referenced HelmRelease to add inline values via an overlay patch in a Kustomization (in a ResourceList)
+export def "helmrelease add inline values" [
+ --ks-namespace: string, # Namespace of the Kustomization
+ --hr-namespace: string, # Namespace of the HelmRelease
+ --operation: string = "add", # Allowed operation types: "add", "replace". Default is "add"
+ kustomization_name: string, # Kustomization to add the patch to
+ helmrelease_name: string, # HelmRelease to add the values to
+ values: record # Helm values to include inline in the HelmRelease spec
+]: [
+ record -> record
+] {
+ let in_resourcelist: record = $in
+
+ # Exit if the operation is not supported
+ if $operation not-in ["add", "replace"] {
+ error make { msg: "Invalid operation type. Supported values are 'add', 'replace'. See RFC6902 for details" }
+ }
+
+ $in_resourcelist
+ | (add jsonpatch
+ --ks-namespace $ks_namespace
+ --operation $operation
+ $kustomization_name
+ (
+ if ($hr_namespace | is-empty) {
+ { kind: "HelmRelease", name: $helmrelease_name }
+ } else {
+ { kind: "HelmRelease", name: $helmrelease_name, namespace: $hr_namespace }
+ }
+ )
+ "/spec/values"
+ $values
+ )
+
+}
+
+
+# Modify a referenced HelmRelease to add values from a ConfigMap via an overlay patch in a Kustomization (in a ResourceList)
+export def "helmrelease add values from configmap" [
+ --ks-namespace: string, # Namespace of the Kustomization
+ --hr-namespace: string, # Namespace of the HelmRelease
+ --target-path: string, # Optional `targetPath` to merge the values to (optional)
+ --optional, # Optional flag to indicate if the values reference is optional
+ kustomization_name: string, # Kustomization to add the patch to
+ helmrelease_name: string, # HelmRelease to add the values to
+ cm_name: string # ConfigMap to read the values from
+ cm_key?: string = "values.yaml" # ConfigMap key to read the values from
+]: [
+ record -> record
+] {
+ let in_resourcelist: record = $in
+
+ # Record to reference the values in the ConfigMap and, optionally, specify on how to merge them
+ let full_reference: record = {
+ kind: "ConfigMap",
+ name: $cm_name,
+ valuesKey: $cm_key
+ }
+ | (
+ if ($target_path | is-empty) {
+ $in
+ } else {
+ $in | insert targetPath $target_path
+ }
+ ) | (
+ if $optional {
+ $in | insert optional true
+ } else {
+ $in
+ }
+ )
+
+ $in_resourcelist
+ | (
+ add strategicmergepatch
+ --ks-namespace $ks_namespace
+ $kustomization_name
+ (
+ if ($hr_namespace | is-empty) {
+ { kind: "HelmRelease", name: $helmrelease_name }
+ } else {
+ { kind: "HelmRelease", name: $helmrelease_name, namespace: $hr_namespace }
+ }
+ )
+ {
+ apiVersion: "helm.toolkit.fluxcd.io/v2",
+ kind: "HelmRelease",
+ metadata: (
+ if ($hr_namespace | is-empty) {
+ { name: $helmrelease_name }
+ } else {
+ { name: $helmrelease_name, namespace: $hr_namespace }
+ }
+ ),
+ spec: {
+ valuesFrom: [
+ $full_reference
+ ]
+ }
+ }
+ )
+}
+
+alias "helmrelease add values from cm" = helmrelease add values from configmap
+
+
+# Modify a referenced HelmRelease to add values from a Secret via an overlay patch in a Kustomization (in a ResourceList)
+export def "helmrelease add values from secret" [
+ --ks-namespace: string, # Namespace of the Kustomization
+ --hr-namespace: string, # Namespace of the HelmRelease
+ --target-path: string, # Optional `targetPath` to merge the values to (optional)
+ --optional, # Optional flag to indicate if the values reference is optional
+ kustomization_name: string, # Kustomization to add the patch to
+ helmrelease_name: string, # HelmRelease to add the values to
+ secret_name: string # Secret to read the values from
+ secret_key?: string = "values.yaml" # Secret key to read the values from
+]: [
+ record -> record
+] {
+ let in_resourcelist: record = $in
+
+ # Record to reference the values in the Secret and, optionally, specify on how to merge them
+ let full_reference: record = {
+ kind: "Secret",
+ name: $secret_name,
+ valuesKey: $secret_key
+ }
+ | (
+ if ($target_path | is-empty) {
+ $in
+ } else {
+ $in | insert targetPath $target_path
+ }
+ ) | (
+ if $optional {
+ $in | insert optional true
+ } else {
+ $in
+ }
+ )
+
+ $in_resourcelist
+ | (
+ add strategicmergepatch
+ --ks-namespace $ks_namespace
+ $kustomization_name
+ (
+ if ($hr_namespace | is-empty) {
+ { kind: "HelmRelease", name: $helmrelease_name }
+ } else {
+ { kind: "HelmRelease", name: $helmrelease_name, namespace: $hr_namespace }
+ }
+ )
+ {
+ apiVersion: "helm.toolkit.fluxcd.io/v2",
+ kind: "HelmRelease",
+ metadata: (
+ if ($hr_namespace | is-empty) {
+ { name: $helmrelease_name }
+ } else {
+ { name: $helmrelease_name, namespace: $hr_namespace }
+ }
+ ),
+ spec: {
+ valuesFrom: [
+ $full_reference
+ ]
+ }
+ }
+ )
+}
+
+
+# Umbrella command to add values to a HelmRelease via an overlay patch to a Kustomization, using either inline values, a reference to a ConfigMap and/or a reference to a Secret.
+# Parameters representing values (`inline_values`, `cm_name` or `secret_name`) that are empty will be skipped; only non-empty parameters will be used and add an overlay patch.
+export def "helmrelease set values" [
+ --ks-namespace: string, # Namespace of the Kustomization
+ --hr-namespace: string, # Namespace of the HelmRelease (optional)
+ --operation: string = "add", # Allowed operation types: "add", "replace". Default is "add"
+ --cm-key: string = "values.yaml", # ConfigMap key to reference values from (default: "values.yaml")
+ --cm-target-path: string, # Optional targetPath for ConfigMap values
+ --cm-optional, # Flag to mark ConfigMap values as optional (optional)
+ --create-cm-with-values: record, # Record with values to include in a new generated ConfigMap (default: empty, i.e., does not create a new ConfigMap).
+ --secret-key: string = "values.yaml", # Secret key to reference values from (default: "values.yaml")
+ --secret-target-path: string, # Optional targetPath for Secret values
+ --secret-optional, # Flag to mark Secret values as optional (optional)
+ --create-secret-with-values: record, # Record with values to include in a new generated Secret (default: empty, i.e., does not create a new Secret).
+ --public-age-key: string # Age key to encrypt the contents of the new Secret (if applicable)
+ kustomization_name: string, # Kustomization to add the patch to
+ helmrelease_name: string, # HelmRelease to modify
+ inline_values?: record, # Inline values to add to the HelmRelease spec (optional)
+ cm_name?: string, # ConfigMap name to reference values from (optional)
+ secret_name?: string # Secret name to reference values from (optional)
+]: [
+ record -> record
+] {
+ let in_resourcelist: record = $in
+
+ # Validate operation type
+ if $operation not-in ["add", "replace"] {
+ error make { msg: "Invalid operation type. Supported values are 'add', 'replace'. See RFC6902 for details" }
+ }
+
+ # === Transformations ===
+ $in_resourcelist
+ # Add inline values if provided and not empty
+ | if ($inline_values | is-empty) {
+ $in
+ } else {
+ $in
+ | (
+ helmrelease add inline values
+ --ks-namespace $ks_namespace
+ --hr-namespace $hr_namespace
+ --operation $operation
+ $kustomization_name
+ $helmrelease_name
+ $inline_values
+ )
+ }
+ # Add reference to ConfigMap-based values if cm_name is provided and not empty
+ | if ($cm_name | is-empty) {
+ $in
+ } else {
+ $in
+ | (
+ helmrelease add values from configmap
+ --ks-namespace $ks_namespace
+ --hr-namespace $hr_namespace
+ --target-path $cm_target_path
+ --optional=$cm_optional
+ $kustomization_name
+ $helmrelease_name
+ $cm_name
+ $cm_key
+ )
+ }
+ # Add reference to Secret-based values if secret_name is provided and not empty
+ | if ($secret_name | is-empty) {
+ $in
+ } else {
+ $in
+ | (
+ helmrelease add values from secret
+ --ks-namespace $ks_namespace
+ --hr-namespace $hr_namespace
+ --target-path $secret_target_path
+ --optional=$secret_optional
+ $kustomization_name
+ $helmrelease_name
+ $secret_name
+ $secret_key
+ )
+ }
+ # Generate a ConfigMap if required
+ | if ($create_cm_with_values | is-empty) or ($cm_name | is-empty) {
+ $in
+ } else {
+ $in
+ | (
+ generator configmap
+ --filename $"($cm_name).yaml"
+ { $cm_key: ($create_cm_with_values | to yaml | str trim)}
+ $cm_name
+ ($hr_namespace | default "default")
+ )
+ }
+ # Generate a Secret if required
+ | if ($create_secret_with_values | is-empty) or ($secret_name | is-empty) {
+ $in
+ } else {
+ # If there is an age key, it is used to encrypt the secret manifest; otherwise, it is kept clear
+ if ($public_age_key | is-empty) {
+ $in
+ | (
+ generator secret
+ --filename $"($secret_name).yaml"
+ { $secret_key: ($create_secret_with_values | to yaml | str trim)}
+ $secret_name
+ ($hr_namespace | default "default")
+ )
+ } else {
+ $in
+ | (
+ generator secret
+ --filename $"($secret_name).yaml"
+ --public-age-key $public_age_key
+ { $secret_key: ($create_secret_with_values | to yaml | str trim)}
+ $secret_name
+ ($hr_namespace | default "default")
+ )
+ }
+ }
+}
diff --git a/docker/osm-nushell-krm-functions/krm/patch.nu b/docker/osm-nushell-krm-functions/krm/patch.nu
new file mode 100644
index 0000000..91403d1
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/patch.nu
@@ -0,0 +1,453 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+# Module with utility functions to patch or amend Kubernetes resources (items) enumerated in a ResourceList.
+
+
+# Checks that the ResourceList is an actual ResourceList
+# -- NOT EXPORTED --
+def "check if resourcelist" [
+ name?: string
+]: [
+ record -> nothing
+] {
+
+ $in
+ | if (
+ $in != {}
+ and (
+ ($in | get -i kind) != "ResourceList"
+ or ($in | get -i apiVersion) != "config.kubernetes.io/v1"
+ )
+ ) {
+ if ($name | is-empty) {
+ error make {msg: $"Error: Expected a ResourceList, but received ($in)."}
+ } else {
+ error make {msg: $"Error: Expected a ResourceList, but received ($in) from ($name)."}
+ }
+ }
+}
+
+
+# Keep item in ResourceList
+export def "resource keep" [
+ apiVersion?: string
+ kind?: string,
+ name?: string,
+ namespace?: string,
+]: [
+ record -> record
+] {
+ let in_rl: record = $in
+
+ # If not a valid ResourceList, throws an error; otherwise, continues
+ $in_rl | check if resourcelist
+
+ $in_rl
+ | update items {|items|
+ $items.items | filter {|it| (
+ (($apiVersion | is-empty) or $it.apiVersion == $apiVersion)
+ and (($kind | is-empty) or $it.kind == $kind)
+ and (($name | is-empty) or $it.metadata.name == $name)
+ and (($namespace | is-empty) or $it.metadata.namespace == $namespace)
+ )
+ }
+ }
+}
+
+
+# Delete item in ResourceList
+export def "resource delete" [
+ apiVersion?: string
+ kind?: string,
+ name?: string,
+ namespace?: string,
+]: [
+ record -> record
+] {
+ let in_rl: record = $in
+
+ # If not a valid ResourceList, throws an error; otherwise, continues
+ $in_rl | check if resourcelist
+
+ $in_rl
+ | update items {|items|
+ $items.items | filter {|it| (
+ (not ($name | is-empty) and $it.metadata.name != $name)
+ or (not ($namespace | is-empty) and $it.metadata.namespace != $namespace)
+ or (not ($kind | is-empty) and $it.kind != $kind)
+ or (not ($apiVersion | is-empty) and $it.apiVersion != $apiVersion)
+ )
+ }
+ }
+}
+
+
+# Patch item in ResourceList with a custom closure
+export def "resource custom function" [
+ custom_function: closure, # Custom function to apply to the keypath
+ key_path: cell-path,
+ value: any,
+ apiVersion?: string
+ kind?: string,
+ name?: string,
+ namespace?: string,
+]: [
+ record -> record
+] {
+ let in_rl: record = $in
+
+ # If not a valid ResourceList, throws an error; otherwise, continues
+ $in_rl | check if resourcelist
+
+ $in_rl
+ | update items {|items|
+ $items.items | each {|item|
+ if ((($name | is-empty) or $item.metadata.name == $name) and
+ (($namespace | is-empty) or ($item | get -i metadata.namespace) == $namespace) and
+ (($kind | is-empty) or ($item | get -i kind) == $kind) and
+ (($apiVersion | is-empty) or ($item | get -i apiVersion) == $apiVersion)) {
+ $item | do $custom_function $key_path $value
+ } else {
+ $item
+ }
+ }
+ }
+}
+
+
+# Patch item in ResourceList with an insert. Fails if key already exists.
+export def "resource insert key" [
+ key_path: cell-path,
+ value: any,
+ apiVersion?: string
+ kind?: string,
+ name?: string,
+ namespace?: string,
+]: [
+ record -> record
+] {
+ let in_rl: record = $in
+
+ # If not a valid ResourceList, throws an error; otherwise, continues
+ $in_rl | check if resourcelist
+
+ $in_rl
+ | (resource custom function
+ { |k, v| ($in | insert $k $v) }
+ $key_path
+ $value
+ $apiVersion
+ $kind
+ $name
+ $namespace
+ )
+}
+
+
+# Patch item in ResourceList with an upsert (update if exists, otherwise insert)
+export def "resource upsert key" [
+ key_path: cell-path,
+ value: any,
+ apiVersion?: string
+ kind?: string,
+ name?: string,
+ namespace?: string,
+]: [
+ record -> record
+] {
+ let in_rl: record = $in
+
+ # If not a valid ResourceList, throws an error; otherwise, continues
+ $in_rl | check if resourcelist
+
+ $in_rl
+ | (resource custom function
+ { |k, v| ($in | upsert $k $v) }
+ $key_path
+ $value
+ $apiVersion
+ $kind
+ $name
+ $namespace
+ )
+}
+
+export alias patch_replace = resource upsert key
+
+
+# Patch item in ResourceList with an update. Fails if key does not exist.
+export def "resource update key" [
+ key_path: cell-path,
+ value: any,
+ apiVersion?: string
+ kind?: string,
+ name?: string,
+ namespace?: string,
+]: [
+ record -> record
+] {
+ let in_rl: record = $in
+
+ # If not a valid ResourceList, throws an error; otherwise, continues
+ $in_rl | check if resourcelist
+
+ $in_rl
+ | (resource custom function
+ { |k, v| ($in | update $k $v) }
+ $key_path
+ $value
+ $apiVersion
+ $kind
+ $name
+ $namespace
+ )
+}
+
+
+# Patch item in ResourceList by deleting a key.
+export def "resource reject key" [
+ key_path: cell-path,
+ apiVersion?: string
+ kind?: string,
+ name?: string,
+ namespace?: string,
+]: [
+ record -> record
+] {
+ let in_rl: record = $in
+
+ # If not a valid ResourceList, throws an error; otherwise, continues
+ $in_rl | check if resourcelist
+
+ $in_rl
+ | (resource custom function
+ { |k, v| ($in | reject $k) }
+ $key_path
+ ""
+ $apiVersion
+ $kind
+ $name
+ $namespace
+ )
+}
+
+export alias "resource delete key" = resource reject key
+
+
+# Patch item in ResourceList to add a file name (and, optionally, order in the file) for an eventual conversion to a folder of manifests
+export def "resource filename set" [
+ --index: int, # Number of the index in the file, for multi-resource manifests
+ filename: string,
+ apiVersion?: string
+ kind?: string,
+ name?: string,
+ namespace?: string,
+]: [
+ record -> record
+] {
+ let in_rl: record = $in
+
+ # If not a valid ResourceList, throws an error; otherwise, continues
+ $in_rl | check if resourcelist
+
+ # Adds index in file if specified; otherwise, keeps the input
+ let input_rl: record = if ($index | is-empty) {
+ $in_rl
+ } else {
+ $in_rl
+ | (resource upsert key
+ $.metadata.annotations."config.kubernetes.io/index"
+ ($index | into string)
+ $apiVersion
+ $kind
+ $name
+ $namespace
+ )
+ | (resource upsert key
+ $.metadata.annotations."internal.config.kubernetes.io/index"
+ ($index | into string)
+ $apiVersion
+ $kind
+ $name
+ $namespace
+ )
+ }
+
+ # Finally, adds file name to the items in the ResourceList
+ $input_rl
+ | (resource upsert key
+ $.metadata.annotations."config.kubernetes.io/path"
+ $filename
+ $apiVersion
+ $kind
+ $name
+ $namespace
+ )
+ | (resource upsert key
+ $.metadata.annotations."internal.config.kubernetes.io/path"
+ $filename
+ $apiVersion
+ $kind
+ $name
+ $namespace
+ )
+}
+
+export alias set_filename_to_items = resource filename set
+
+
+# Patch item in ResourceList to append/upsert element to a list at a given key.
+#
+# The expected behaviour should be as follows:
+#
+# 1. If the key already exists, the value should be a list, and the item should be appended to the list.
+# 2. If the key does not exist, the value should be created as a list with the new item.
+# 3. If the key already exists but the value is not a list, it should throw an error.
+#
+export def "list append item" [
+ key_path: cell-path,
+ value: any,
+ apiVersion?: string
+ kind?: string,
+ name?: string,
+ namespace?: string,
+]: [
+ record -> record
+] {
+ let in_rl: record = $in
+
+ # If not a valid ResourceList, throws an error; otherwise, continues
+ $in_rl | check if resourcelist
+
+ # Checks if all the preexisting values at the matching keys are lists; otherwise, throws an error
+ let non_conformant: list<any> = (
+ $in_rl
+ # Only the resources that match the input criteria
+ | (resource keep
+ $apiVersion
+ $kind
+ $name
+ $namespace
+ )
+ # Only keeps the resources in a regular list
+ | get -i items
+ # Removes the resources where the key does not exist
+ | filter { |it| ($it | get -i $key_path) != null }
+ # Keeps only the resources where the key is not a list
+ | filter { |it| not (
+ $it
+ | get -i $key_path
+ | describe
+ | ($in | str starts-with "list") or ($in | str starts-with "table")
+ )
+ }
+ )
+
+ if not ($non_conformant | is-empty) {
+ error make { msg: $"Error: Some matching keys are not lists. Non conformant:\n($non_conformant | to yaml)"}
+ }
+
+ # Actual processing
+ $in_rl
+ | (resource custom function
+ { |k, v| ($in | upsert $k {
+ |row|
+ let existing = ($row | get $k -i)
+ if $existing == null {
+ [$v]
+ } else {
+ $existing | append $value
+ }
+ }
+ )
+ }
+ $key_path
+ $value
+ $apiVersion
+ $kind
+ $name
+ $namespace
+ )
+}
+
+export alias patch_add_to_list = list append item
+export alias "list upsert item" = list append item
+
+
+# Patch item in ResourceList to drop/delete element from a list at a given key with a given value.
+export def "list drop item" [
+ --keep-empty-list,
+ key_path: cell-path,
+ value: any,
+ apiVersion?: string
+ kind?: string,
+ name?: string,
+ namespace?: string,
+]: [
+ record -> record
+] {
+ let in_rl: record = $in
+
+ # If not a valid ResourceList, throws an error; otherwise, continues
+ $in_rl | check if resourcelist
+
+ # Checks if all the preexisting values at the matching keys are lists; otherwise, throws an error
+ let non_conformant: list<any> = (
+ $in_rl
+ # Only the resources that match the input criteria
+ | (resource keep
+ $apiVersion
+ $kind
+ $name
+ $namespace
+ )
+ # Only keeps the resources in a regular list
+ | get -i items
+ # Removes the resources where the key does not exist
+ | filter { |it| ($it | get -i $key_path) != null }
+ # Keeps only the resources where the key is not a list
+ | filter { |it| not ($it | get -i $key_path | describe | str starts-with "list") }
+ )
+
+ if not ($non_conformant | is-empty) {
+ error make { msg: $"Error: Some matching keys are not lists. Non conformant:\n($non_conformant | to yaml)"}
+ }
+
+ # Actual processing
+ $in_rl
+ | (resource custom function
+ { |k, v| ($in
+ | update $k {|row|
+ $row | get $k -i | filter {|value| ($value != $v)}
+ }
+ # Delete the key in case the list at the key is now empty and the flag is disabled
+ | if (not $keep_empty_list) and ($in | get $k -i | is-empty) {
+ $in | reject $k
+ } else { $in }
+ )
+ }
+ $key_path
+ $value
+ $apiVersion
+ $kind
+ $name
+ $namespace
+ )
+}
+
+export alias patch_delete_from_list = list drop item
diff --git a/docker/osm-nushell-krm-functions/krm/strategicmergepatch.nu b/docker/osm-nushell-krm-functions/krm/strategicmergepatch.nu
new file mode 100644
index 0000000..895acfc
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/strategicmergepatch.nu
@@ -0,0 +1,61 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+# Module with helper functions to create strategic merge patches for Kubernetes resources (patchStrategicMerge).
+
+
+# Helper to create a full strategic merge patch, including the target object specification and partial resource manifest.
+#
+# Example:
+# $ strategicmergepatch create {kind: Deployment, name: podinfo} (
+# 'apiVersion: apps/v1
+# kind: Deployment
+# metadata:
+# name: not-used
+# spec:
+# template:
+# metadata:
+# annotations:
+# cluster-autoscaler.kubernetes.io/safe-to-evict: "true"' | from yaml
+# ) | to yaml
+#
+# target:
+# kind: Deployment
+# name: podinfo
+# patch: |
+# apiVersion: apps/v1
+# kind: Deployment
+# metadata:
+# name: not-used
+# spec:
+# template:
+# metadata:
+# annotations:
+# cluster-autoscaler.kubernetes.io/safe-to-evict: 'true'
+#
+# Further information about advanced syntax for strategic merge patch (e.g. '$patch' directives) can be found at <https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/strategic-merge-patch.md>
+export def "create" [
+ target: record, # Target resource specification as per <https://github.com/kubernetes-sigs/kustomize/blob/master/examples/patchMultipleObjects.md>
+ patch: record # Partial resource manifest
+]: [
+ nothing -> record
+] {
+ {
+ target: $target,
+ patch: ($patch | to yaml)
+ }
+}
diff --git a/docker/osm-nushell-krm-functions/krm/tests/artifacts/configmap/templates/configmap.yaml b/docker/osm-nushell-krm-functions/krm/tests/artifacts/configmap/templates/configmap.yaml
new file mode 100644
index 0000000..a758a7b
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/tests/artifacts/configmap/templates/configmap.yaml
@@ -0,0 +1,24 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+apiVersion: v1
+data:
+ mykey: myvalue
+kind: ConfigMap
+metadata:
+ creationTimestamp: null
+ name: my-cm
diff --git a/docker/osm-nushell-krm-functions/krm/tests/artifacts/empty/.gitkeep b/docker/osm-nushell-krm-functions/krm/tests/artifacts/empty/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/tests/artifacts/empty/.gitkeep
diff --git a/docker/osm-nushell-krm-functions/krm/tests/artifacts/jenkins/manifests/bitnamicharts-repo.yaml b/docker/osm-nushell-krm-functions/krm/tests/artifacts/jenkins/manifests/bitnamicharts-repo.yaml
new file mode 100644
index 0000000..354b837
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/tests/artifacts/jenkins/manifests/bitnamicharts-repo.yaml
@@ -0,0 +1,27 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+---
+apiVersion: source.toolkit.fluxcd.io/v1beta2
+kind: HelmRepository
+metadata:
+ name: bitnamicharts
+ namespace: jenkins
+spec:
+ interval: 10m0s
+ type: oci
+ url: oci://registry-1.docker.io/bitnamicharts
diff --git a/docker/osm-nushell-krm-functions/krm/tests/artifacts/jenkins/manifests/jenkins-hr.yaml b/docker/osm-nushell-krm-functions/krm/tests/artifacts/jenkins/manifests/jenkins-hr.yaml
new file mode 100644
index 0000000..c87a95e
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/tests/artifacts/jenkins/manifests/jenkins-hr.yaml
@@ -0,0 +1,37 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+---
+apiVersion: helm.toolkit.fluxcd.io/v2beta1
+kind: HelmRelease
+metadata:
+ name: jenkins
+ namespace: jenkins
+spec:
+ chart:
+ spec:
+ chart: jenkins
+ reconcileStrategy: ChartVersion
+ sourceRef:
+ kind: HelmRepository
+ name: bitnamicharts
+ namespace: jenkins
+ install:
+ createNamespace: true
+ interval: 3m0s
+ targetNamespace: jenkins
+ values: {}
diff --git a/docker/osm-nushell-krm-functions/krm/tests/artifacts/jenkins/templates/jenkins-ks.yaml b/docker/osm-nushell-krm-functions/krm/tests/artifacts/jenkins/templates/jenkins-ks.yaml
new file mode 100644
index 0000000..bbf4d7b
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/tests/artifacts/jenkins/templates/jenkins-ks.yaml
@@ -0,0 +1,31 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+---
+apiVersion: kustomize.toolkit.fluxcd.io/v1
+kind: Kustomization
+metadata:
+ name: jenkins
+ namespace: jenkins
+spec:
+ interval: 1h0m0s
+ path: ./apps/jenkins/manifests
+ prune: true
+ sourceRef:
+ kind: GitRepository
+ name: sw-catalogs
+ namespace: flux-system
diff --git a/docker/osm-nushell-krm-functions/krm/tests/artifacts/jenkins/templates/jenkins-ns.yaml b/docker/osm-nushell-krm-functions/krm/tests/artifacts/jenkins/templates/jenkins-ns.yaml
new file mode 100644
index 0000000..ca2fff8
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/tests/artifacts/jenkins/templates/jenkins-ns.yaml
@@ -0,0 +1,24 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+apiVersion: v1
+kind: Namespace
+metadata:
+ creationTimestamp: null
+ name: jenkins
+spec: {}
+status: {}
diff --git a/docker/osm-nushell-krm-functions/krm/tests/artifacts/namespace/templates/namespace.yaml b/docker/osm-nushell-krm-functions/krm/tests/artifacts/namespace/templates/namespace.yaml
new file mode 100644
index 0000000..d4c0386
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/tests/artifacts/namespace/templates/namespace.yaml
@@ -0,0 +1,22 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+apiVersion: v1
+kind: Namespace
+metadata:
+ name: ${TARGET_NS}
+
diff --git a/docker/osm-nushell-krm-functions/krm/tests/artifacts/secret/templates/secret.yaml b/docker/osm-nushell-krm-functions/krm/tests/artifacts/secret/templates/secret.yaml
new file mode 100644
index 0000000..54346c5
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/tests/artifacts/secret/templates/secret.yaml
@@ -0,0 +1,24 @@
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+apiVersion: v1
+data:
+ mykey: bXl2YWx1ZQ==
+kind: Secret
+metadata:
+ creationTimestamp: null
+ name: my-secret
diff --git a/docker/osm-nushell-krm-functions/krm/tests/concatenate.nu b/docker/osm-nushell-krm-functions/krm/tests/concatenate.nu
new file mode 100644
index 0000000..ae674b8
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/tests/concatenate.nu
@@ -0,0 +1,251 @@
+#!/usr/bin/env -S nu --stdin
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+
+use std assert
+use ../../krm/concatenate.nu *
+
+
+# --- resourcelists tests ---
+
+export def "test concatenate resourcelists empty inputs" []: [
+ nothing -> nothing
+] {
+ let input: record = {}
+ let resourcelist2: record = {}
+
+ let actual: record = resourcelists $resourcelist2
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: []
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test concatenate resourcelists empty stdin" []: [
+ nothing -> nothing
+] {
+ let input: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: ["item1", "item2"]
+ }
+ let resourcelist2: record = {}
+
+ # Simulate empty stdin by passing input as an argument
+ let actual: record = resourcelists $input
+ let expected: record = $input
+
+ assert equal $actual $expected
+}
+
+
+export def "test concatenate resourcelists empty second list" []: [
+ nothing -> nothing
+] {
+ let input: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: ["item1", "item2"]
+ }
+ let resourcelist2: record = {}
+
+ # Simulate empty stdin by passing input as an argument
+ let actual: record = resourcelists $resourcelist2
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: []
+ }
+
+ # Since we're testing with stdin, we need to simulate it
+ let actual_with_stdin: record = (
+ echo $input | resourcelists $resourcelist2
+ )
+ let expected_with_stdin: record = $input
+
+ assert equal $actual_with_stdin $expected_with_stdin
+}
+
+
+export def "test concatenate resourcelists merge lists" []: [
+ nothing -> nothing
+] {
+ let input: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: ["item1", "item2"]
+ }
+ let resourcelist2: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: ["item3", "item4"]
+ }
+
+ let actual: record = (
+ echo $input | resourcelists $resourcelist2
+ )
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: ["item1", "item2", "item3", "item4"]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test concatenate resourcelists non-empty inputs with no items" []: [
+ nothing -> nothing
+] {
+ let input: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ }
+ let resourcelist2: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ }
+
+ let actual: record = (
+ echo $input | resourcelists $resourcelist2
+ )
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: []
+ }
+
+ assert equal $actual $expected
+}
+
+
+
+# --- manifests tests ---
+
+export def "test concatenate manifests empty inputs" []: [
+ nothing -> nothing
+] {
+ let mnfst2: any = null
+
+ let actual: list<any> = manifests $mnfst2
+ let expected: list<any> = []
+
+ assert equal $actual $expected
+}
+
+
+export def "test concatenate manifests empty stdin" []: [
+ nothing -> nothing
+] {
+ let mnfst2: record = {
+ name: "example"
+ kind: "Deployment"
+ }
+
+ let actual: list<any> = manifests $mnfst2
+ let expected: list<any> = [ $mnfst2 ]
+
+ assert equal $actual $expected
+}
+
+
+export def "test concatenate manifests empty second manifest" []: [
+ nothing -> nothing
+] {
+ let mnfst1: record = {
+ name: "example1"
+ kind: "Deployment"
+ }
+
+ let mnfst2: any = null
+
+ let actual: list<any> = (
+ echo $mnfst1 | manifests $mnfst2
+ )
+ let expected: list<any> = [ $mnfst1 ]
+
+ assert equal $actual $expected
+}
+
+
+export def "test concatenate manifests single records" []: [
+ nothing -> nothing
+] {
+ let mnfst1: record = {
+ name: "example1"
+ kind: "Deployment"
+ }
+ let mnfst2: record = {
+ name: "example2"
+ kind: "Service"
+ }
+
+ let actual: list<any> = (
+ echo $mnfst1 | manifests $mnfst2
+ )
+ let expected: list<any> = [ $mnfst1, $mnfst2 ]
+
+ assert equal $actual $expected
+}
+
+
+export def "test concatenate manifests lists of records" []: [
+ nothing -> nothing
+] {
+ let mnfst1: list<any> = [
+ { name: "example1", kind: "Deployment" }
+ { name: "example2", kind: "Service" }
+ ]
+ let mnfst2: list<any> = [
+ { name: "example3", kind: "Pod" }
+ { name: "example4", kind: "ConfigMap" }
+ ]
+
+ let actual: list<any> = (
+ echo $mnfst1 | manifests $mnfst2
+ )
+ let expected: list<any> = [
+ { name: "example1", kind: "Deployment" }
+ { name: "example2", kind: "Service" }
+ { name: "example3", kind: "Pod" }
+ { name: "example4", kind: "ConfigMap" }
+ ]
+
+ assert equal $actual $expected
+}
+
+
+export def "test concatenate manifests invalid input" []: [
+ nothing -> nothing
+] {
+ let mnfst2: string = "Invalid manifest"
+
+ let actual_error: error = (
+ try {
+ manifests $mnfst2
+ } catch {
+ |err| $err.msg
+ }
+ )
+
+ assert equal $actual_error "Error: Expected a record or a list of records, but received string."
+}
diff --git a/docker/osm-nushell-krm-functions/krm/tests/convert.nu b/docker/osm-nushell-krm-functions/krm/tests/convert.nu
new file mode 100644
index 0000000..09710eb
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/tests/convert.nu
@@ -0,0 +1,448 @@
+#!/usr/bin/env -S nu --stdin
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+
+use std assert
+use std null-device
+use ../../krm/convert.nu *
+
+
+# --- replace environment variables tests ---
+
+export def "test convert replace environment variables no vars" []: [
+ nothing -> nothing
+] {
+ let text: string = "Hello, $USER!"
+
+ $env.USER = "test_user"
+ let actual: string = (
+ echo $text | replace environment variables
+ )
+ let expected: string = "Hello, test_user!"
+
+ assert equal $actual $expected
+}
+
+
+export def "test convert replace environment variables string vars" []: [
+ nothing -> nothing
+] {
+ let text: string = "Hello, $USER! Your HOME is $HOME."
+ let vars_to_replace: string = "$USER,$HOME"
+
+ load-env {
+ USER: "test_user"
+ HOME: "/home/test_user"
+ }
+
+ let actual: string = (
+ echo $text | replace environment variables $vars_to_replace
+ )
+ let expected: string = "Hello, test_user! Your HOME is /home/test_user."
+
+ assert equal $actual $expected
+}
+
+
+export def "test convert replace environment variables list vars" []: [
+ nothing -> nothing
+] {
+ let text: string = "Hello, $USER! Your HOME is $HOME."
+ let vars_to_replace: list<string> = ["USER", "HOME"]
+
+ load-env {
+ USER: "test_user"
+ HOME: "/home/test_user"
+ }
+
+ let actual: string = (
+ echo $text | replace environment variables $vars_to_replace
+ )
+ let expected: string = "Hello, test_user! Your HOME is /home/test_user."
+
+ assert equal $actual $expected
+}
+
+
+export def "test convert replace environment variables invalid input" []: [
+ nothing -> nothing
+] {
+ let text: string = "Hello, $USER!"
+ let vars_to_replace: int = 123
+
+ let error_occurred: error = try {
+ echo $text | replace environment variables $vars_to_replace
+
+ } catch {
+ |err| $err.msg
+ }
+
+ assert equal $error_occurred "Error: Expected a string or list of strings, but received int"
+}
+
+
+export def "test convert replace environment variables no replacement" []: [
+ nothing -> nothing
+] {
+ let text: string = "Hello, $NON_EXISTENT_VAR!"
+ let actual: string = (
+ echo $text | replace environment variables
+ )
+
+ let expected: string = "Hello, !"
+
+ assert equal $actual $expected
+}
+
+
+# --- folder to resourcelist tests ---
+
+export def "test convert folder to resourcelist empty input" []: [
+ nothing -> nothing
+] {
+ let folder: path = "./artifacts/empty"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: []
+ }
+
+ let actual: record = (folder to resourcelist $folder)
+ assert equal $actual $expected
+}
+
+
+export def "test convert folder to resourcelist no substitution" []: [
+ nothing -> nothing
+] {
+ let folder: path = "./artifacts/jenkins/templates"
+ let input_list: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: ["item1", "item2"]
+ }
+
+ let actual: record = (
+ echo $input_list | folder to resourcelist $folder
+ )
+ let expected_items: list<string> = ["item1", "item2"] | append (
+ kpt fn source $folder
+ | from yaml
+ | get items
+ )
+
+ assert equal $actual.apiVersion "config.kubernetes.io/v1"
+ assert equal $actual.kind "ResourceList"
+ assert equal $actual.items $expected_items
+}
+
+
+export def "test convert folder to resourcelist with substitution" []: [
+ nothing -> nothing
+] {
+ let folder: path = "./artifacts/namespace/templates"
+ let input_list: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: ["item1", "item2"]
+ }
+ $env.TARGET_NS = "target-namespace"
+
+ let actual: record = (
+ echo $input_list | folder to resourcelist --subst-env $folder
+ )
+ let expected_items: list<string> = ["item1", "item2"] | append (
+ kpt fn source $folder
+ | replace environment variables
+ | from yaml
+ | get items
+ )
+
+ assert equal $actual.apiVersion "config.kubernetes.io/v1"
+ assert equal $actual.kind "ResourceList"
+ assert equal $actual.items $expected_items
+ assert equal $actual.items.2.metadata.name $env.TARGET_NS
+}
+
+
+export def "test convert folder to resourcelist with filter" []: [
+ nothing -> nothing
+] {
+ let folder: path = "./artifacts/namespace/templates"
+ let input_list: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: ["item1", "item2"]
+ }
+ $env.TARGET_NS = "target-namespace"
+ let env_filter = "$TARGET_NS"
+
+ let actual: record = (
+ echo $input_list | folder to resourcelist --subst-env $folder $env_filter
+ )
+ let expected_items: list<string> = ["item1", "item2"] | append (
+ kpt fn source $folder
+ | replace environment variables $env_filter
+ | from yaml
+ | get items
+ )
+
+ assert equal $actual.apiVersion "config.kubernetes.io/v1"
+ assert equal $actual.kind "ResourceList"
+ assert equal $actual.items $expected_items
+ assert equal $actual.items.2.metadata.name $env.TARGET_NS
+}
+
+
+export def "test convert folder to resourcelist invalid input" []: [
+ nothing -> nothing
+] {
+ let folder: path = "./non-existent-folder"
+ let input_list: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: ["item1", "item2"]
+ }
+
+ let error_occurred: bool = try {
+ echo $input_list | folder to resourcelist $folder err> (null-device)
+ } catch {
+ |err| $err.msg
+ }
+
+ assert equal $error_occurred "Can't convert to record."
+}
+
+
+
+# --- manifest to resourcelist tests ---
+
+export def "test convert manifest to resourcelist empty input" []: [
+ nothing -> nothing
+] {
+ let actual: record = manifest to resourcelist
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: []
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test convert manifest to resourcelist single record" []: [
+ nothing -> nothing
+] {
+ let manifest: record = {
+ name: "example"
+ kind: "Deployment"
+ }
+
+ let actual: record = (
+ $manifest | manifest to resourcelist
+ )
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [ $manifest ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test convert manifest to resourcelist list of records" []: [
+ nothing -> nothing
+] {
+ let manifests: list<any> = [
+ { name: "example1", kind: "Deployment" }
+ { name: "example2", kind: "Service" }
+ ]
+
+ let actual: record = (
+ $manifests | manifest to resourcelist
+ )
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: $manifests
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test convert manifest to resourcelist invalid input" []: [
+ nothing -> nothing
+] {
+ let invalid_manifest: string = "Invalid manifest"
+
+ let error_occurred: bool = try {
+ $invalid_manifest | manifest to resourcelist
+ } catch {
+ |err| $err.msg
+ }
+
+ assert equal $error_occurred "Error: Expected a record or a list of records, but received string."
+}
+
+
+
+# --- resourcelist to folder tests ---
+
+export def "test convert resourcelist to folder dry run" []: [
+ nothing -> nothing
+] {
+ let rl: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: ["item1", "item2"]
+ }
+
+ let output: record = (
+ $rl | resourcelist to folder "no-folder" true | from yaml
+ )
+
+ assert equal $output $rl
+}
+
+
+export def "test convert resourcelist to folder no sync" []: [
+ nothing -> nothing
+] {
+ let source_folder: path = "./artifacts/jenkins/templates/"
+ let rl: record = (
+ convert folder to resourcelist $source_folder
+ )
+ let target_folder: string = (mktemp -t -d)
+
+ # Run the command
+ $rl | resourcelist to folder $target_folder
+
+ # Check if the contents were copied correctly
+ let actual_contents: list<string> = (
+ ls --short-names $target_folder
+ | get name
+ | sort
+ )
+
+ # Cleanup
+ rm -rf $target_folder
+
+ # Expected
+ let expected_contents: list<string> = (
+ ls --short-names $source_folder
+ | get name
+ | sort
+ )
+
+ assert equal $actual_contents $expected_contents
+}
+
+
+export def "test convert resourcelist to folder sync" []: [
+ nothing -> nothing
+] {
+ let source_folder: path = "./artifacts/jenkins/templates/"
+ let rl: record = (
+ convert folder to resourcelist $source_folder
+ )
+ let target_folder: string = (mktemp -t -d)
+
+ # Add an extra file to the target folder (it should be removed by the synchronization)
+ ^touch ($target_folder | path join "extra_file.txt")
+
+ # Run the command
+ $rl | resourcelist to folder --sync $target_folder
+
+ # Check if the contents were copied correctly
+ let actual_contents: list<string> = (
+ ls --short-names $target_folder
+ | get name
+ | sort
+ )
+
+ # Cleanup
+ rm -rf $target_folder
+
+ # Expected
+ let expected_contents: list<string> = (
+ ls --short-names $source_folder
+ | get name
+ | sort
+ )
+
+ assert equal $actual_contents $expected_contents
+}
+
+
+# export def "test convert resourcelist to folder invalid input" []: [
+# nothing -> nothing
+# ] {
+# let invalid_input: record = { "Invalid input": "invalid value" }
+# let target_folder: string = (mktemp -t -d)
+
+# let error_occurred: any = try {
+# $invalid_input | resourcelist to folder $target_folder
+# } catch {
+# |err| $err.msg
+# }
+
+# # Cleanup
+# print $target_folder
+# # rm -rf $target_folder
+
+# assert equal $error_occurred "Can't convert to boolean."
+# }
+
+
+export def "test convert resourcelist to folder non-existent folder" []: [
+ nothing -> nothing
+] {
+ let source_folder: path = "./artifacts/jenkins/templates/"
+ let rl: record = (
+ convert folder to resourcelist $source_folder
+ )
+
+ let temp_folder: string = (mktemp -t -d)
+ let target_folder: string = ($temp_folder | path join "new-folder")
+ mkdir $target_folder
+
+ # Run the command
+ $rl | resourcelist to folder $target_folder
+
+ # Check if the contents were copied correctly
+ let actual_contents: list<string> = (
+ ls --short-names $target_folder
+ | get name
+ | sort
+ )
+
+ # Cleanup
+ rm -rf $temp_folder
+
+ # Expected
+ let expected_contents: list<string> = (
+ ls --short-names $source_folder
+ | get name
+ | sort
+ )
+
+ assert equal $actual_contents $expected_contents
+}
diff --git a/docker/osm-nushell-krm-functions/krm/tests/generator.nu b/docker/osm-nushell-krm-functions/krm/tests/generator.nu
new file mode 100644
index 0000000..5f133a0
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/tests/generator.nu
@@ -0,0 +1,640 @@
+#!/usr/bin/env -S nu --stdin
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+
+use std assert
+use ../../krm/generator.nu *
+
+
+# --- from resourcelist tests ---
+
+export def "test generator from resourcelist empty inputs" []: [
+ nothing -> nothing
+] {
+ let in_rl: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: ["item1", "item2"]
+ }
+ let rl: record = {}
+
+ let actual: record = ($in_rl | from resourcelist $rl)
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: ["item1", "item2"]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test generator from resourcelist empty stdin" []: [
+ nothing -> nothing
+] {
+ let in_rl: record = {}
+ let rl: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: ["item1", "item2"]
+ }
+
+ let actual: record = ($in_rl | from resourcelist $rl)
+ let expected: record = $rl
+
+ assert equal $actual $expected
+}
+
+
+export def "test generator from resourcelist merge lists" []: [
+ nothing -> nothing
+] {
+ let in_rl: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: ["item1", "item2"]
+ }
+ let rl: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: ["item3", "item4"]
+ }
+
+ let actual: record = (
+ echo $in_rl | from resourcelist $rl
+ )
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: ["item1", "item2", "item3", "item4"]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test generator from resourcelist non-empty inputs with no items" []: [
+ nothing -> nothing
+] {
+ let in_rl: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ }
+ let rl: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ }
+
+ let actual: record = (
+ echo $in_rl | from resourcelist $rl
+ )
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: []
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test generator from resourcelist invalid input parameter" []: [
+ nothing -> nothing
+] {
+ let in_rl: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: ["item1", "item2"]
+ }
+ let rl: record = { "Invalid input": "Invalid value" }
+
+ let error_occurred: any = try {
+ $in_rl | from resourcelist $rl
+ } catch {
+ |err| $err.json | from json | get inner.msg.0
+ }
+
+ assert ($error_occurred | str starts-with "Error: Expected a ResourceList, but received")
+}
+
+
+export def "test generator from resourcelist invalid input from stdin" []: [
+ nothing -> nothing
+] {
+ let in_rl: record = { "Invalid input": "Invalid value" }
+ let rl: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: ["item1", "item2"]
+ }
+
+ let error_occurred: any = try {
+ $in_rl | from resourcelist $rl
+ } catch {
+ |err| $err.json | from json | get inner.msg.0
+ }
+
+ assert ($error_occurred | str starts-with "Error: Expected a ResourceList, but received")
+}
+
+
+# --- from manifest tests ---
+
+export def "test generator from manifest empty inputs" []: [
+ nothing -> nothing
+] {
+ let manifest: any = null
+
+ let actual: record = from manifest $manifest
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: []
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test generator from manifest empty stdin" []: [
+ nothing -> nothing
+] {
+ let manifest: record = {
+ name: "example"
+ kind: "Deployment"
+ }
+
+ let actual: record = from manifest $manifest
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [ $manifest ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test generator from manifest merge lists" []: [
+ nothing -> nothing
+] {
+ let in_rl: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: ["item1", "item2"]
+ }
+ let manifest: record = {
+ name: "example"
+ kind: "Deployment"
+ }
+
+ let actual: record = (
+ echo $in_rl | from manifest $manifest
+ )
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: ["item1", "item2", $manifest]
+ }
+
+ assert equal $actual.apiVersion $expected.apiVersion
+ assert equal $actual.kind $expected.kind
+ assert equal $actual.items $expected.items
+}
+
+
+export def "test generator from manifest list of manifests" []: [
+ nothing -> nothing
+] {
+ let in_rl: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: ["item1", "item2"]
+ }
+ let manifest: list<any> = [
+ { name: "example1", kind: "Deployment" }
+ { name: "example2", kind: "Service" }
+ ]
+
+ let actual: record = (
+ echo $in_rl | from manifest $manifest
+ )
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: ["item1", "item2", { name: "example1", kind: "Deployment" }, { name: "example2", kind: "Service" }]
+ }
+
+ assert equal $actual.apiVersion $expected.apiVersion
+ assert equal $actual.kind $expected.kind
+ assert equal $actual.items $expected.items
+}
+
+
+export def "test generator from manifest invalid input" []: [
+ nothing -> nothing
+] {
+ let manifest: string = "Invalid manifest"
+
+ let error_occurred: error = try {
+ from manifest $manifest
+ } catch {
+ |err| $err.msg
+ }
+
+ assert equal $error_occurred "Error: Expected a record or a list of records, but received string."
+}
+
+
+
+# --- configmap tests ---
+
+export def "test generator configmap basic" []: [
+ nothing -> nothing
+] {
+ let key_pairs: record = {
+ key1: "value1",
+ key2: "value2"
+ }
+ let name: string = "example-configmap"
+
+ let actual: record = configmap $key_pairs $name
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1",
+ kind: ResourceList,
+ items: [
+ {
+ apiVersion: "v1"
+ kind: "ConfigMap"
+ metadata: { name: $name, namespace: "default" }
+ data: $key_pairs
+ }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test generator configmap with namespace" []: [
+ nothing -> nothing
+] {
+ let key_pairs: record = {
+ key1: "value1",
+ key2: "value2"
+ }
+ let name: string = "example-configmap"
+ let namespace: string = "custom-namespace"
+
+ let actual: record = configmap $key_pairs $name $namespace
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1",
+ kind: ResourceList,
+ items: [
+ {
+ apiVersion: "v1"
+ kind: "ConfigMap"
+ metadata: { name: $name, namespace: $namespace }
+ data: $key_pairs
+ }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test generator configmap with filename" []: [
+ nothing -> nothing
+] {
+ let key_pairs: record = {
+ key1: "value1",
+ key2: "value2"
+ }
+ let name: string = "example-configmap"
+ let filename: string = "example-configmap.yaml"
+
+ let actual: record = configmap --filename $filename $key_pairs $name
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1",
+ kind: ResourceList,
+ items: [
+ {
+ apiVersion: "v1"
+ kind: "ConfigMap"
+ metadata: {
+ name: $name,
+ namespace: "default",
+ annotations: {
+ "config.kubernetes.io/path": $filename,
+ "internal.config.kubernetes.io/path": $filename
+ }
+ }
+ data: $key_pairs
+ }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test generator configmap with filename and index" []: [
+ nothing -> nothing
+] {
+ let key_pairs: record = {
+ key1: "value1",
+ key2: "value2"
+ }
+ let name: string = "example-configmap"
+ let filename: string = "example-configmap.yaml"
+ let index: int = 0
+
+ let actual: record = configmap --filename $filename --index $index $key_pairs $name
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1",
+ kind: ResourceList,
+ items: [
+ {
+ apiVersion: "v1"
+ kind: "ConfigMap"
+ metadata: {
+ name: $name,
+ namespace: "default",
+ annotations: {
+ "config.kubernetes.io/path": $filename,
+ "internal.config.kubernetes.io/path": $filename,
+ "config.kubernetes.io/index": "0",
+ "internal.config.kubernetes.io/index": "0"
+ }
+ }
+ data: $key_pairs
+ }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+
+# TODO:
+
+# --- secret tests ---
+
+export def "test generator secret basic" []: [
+ nothing -> nothing
+] {
+ let key_pairs: record = {
+ key1: "value1",
+ key2: "value2"
+ }
+ let name: string = "example-secret"
+
+ let actual: record = secret $key_pairs $name
+ let expected_encoded_values: record = {
+ key1: ("value1" | encode base64),
+ key2: ("value2" | encode base64)
+ }
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1",
+ kind: ResourceList,
+ items: [
+ {
+ apiVersion: "v1"
+ kind: "Secret"
+ metadata: { name: $name, namespace: "default" }
+ data: $expected_encoded_values
+ }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test generator secret with namespace" []: [
+ nothing -> nothing
+] {
+ let key_pairs: record = {
+ key1: "value1",
+ key2: "value2"
+ }
+ let name: string = "example-secret"
+ let namespace: string = "custom-namespace"
+
+ let actual: record = secret $key_pairs $name $namespace
+ let expected_encoded_values: record = {
+ key1: ("value1" | encode base64),
+ key2: ("value2" | encode base64)
+ }
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1",
+ kind: ResourceList,
+ items: [
+ {
+ apiVersion: "v1"
+ kind: "Secret"
+ metadata: { name: $name, namespace: $namespace }
+ data: $expected_encoded_values
+ }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test generator secret with filename" []: [
+ nothing -> nothing
+] {
+ let key_pairs: record = {
+ key1: "value1",
+ key2: "value2"
+ }
+ let name: string = "example-secret"
+ let filename: string = "example-secret.yaml"
+
+ let actual: record = (
+ secret
+ --filename $filename
+ $key_pairs
+ $name
+ )
+ let expected_encoded_values: record = {
+ key1: ("value1" | encode base64),
+ key2: ("value2" | encode base64)
+ }
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1",
+ kind: ResourceList,
+ items: [
+ {
+ apiVersion: "v1"
+ kind: "Secret"
+ metadata: {
+ name: $name,
+ namespace: "default",
+ annotations: {
+ "config.kubernetes.io/path": $filename,
+ "internal.config.kubernetes.io/path": $filename
+ }
+ }
+ data: $expected_encoded_values
+ }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test generator secret with filename and index" []: [
+ nothing -> nothing
+] {
+ let key_pairs: record = {
+ key1: "value1",
+ key2: "value2"
+ }
+ let name: string = "example-secret"
+ let filename: string = "example-secret.yaml"
+ let index: int = 0
+
+ let actual: record = (
+ secret
+ --filename $filename
+ --index $index
+ $key_pairs
+ $name
+ )
+ let expected_encoded_values: record = {
+ key1: ("value1" | encode base64),
+ key2: ("value2" | encode base64)
+ }
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1",
+ kind: ResourceList,
+ items: [
+ {
+ apiVersion: "v1"
+ kind: "Secret"
+ metadata: {
+ name: $name,
+ namespace: "default",
+ annotations: {
+ "config.kubernetes.io/path": $filename,
+ "internal.config.kubernetes.io/path": $filename,
+ "config.kubernetes.io/index": "0",
+ "internal.config.kubernetes.io/index": "0"
+ }
+ }
+ data: $expected_encoded_values
+ }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test generator secret with type" []: [
+ nothing -> nothing
+] {
+ let key_pairs: record = {
+ key1: "value1",
+ key2: "value2"
+ }
+ let name: string = "example-secret"
+ let type: string = "Opaque"
+
+ let actual: record = secret --type $type $key_pairs $name
+ let expected_encoded_values: record = {
+ key1: ("value1" | encode base64),
+ key2: ("value2" | encode base64)
+ }
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1",
+ kind: ResourceList,
+ items: [
+ {
+ apiVersion: "v1"
+ kind: "Secret"
+ metadata: { name: $name, namespace: "default" }
+ type: $type
+ data: $expected_encoded_values
+ }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test generator secret with age encryption" []: [
+ nothing -> nothing
+] {
+ let test_public_key: string = "age1hsrtxphk7exrdc0kt8dgr8a8r3hx88v3xpsw0ezaxvefsy9asegqknppc0"
+ let test_private_key: string = "AGE-SECRET-KEY-12CC3A4LEDYF4S26UV6Z2MEG7ZQL9PTU5NHH6N3FN6FLJ5HACW9LQX0UWP2"
+
+ let key_pairs: record = {
+ key1: "value1",
+ key2: "value2"
+ }
+ let name: string = "example-secret"
+ let filename: string = "example-secret.yaml"
+
+ # Here we extract the encrypted manifest only
+ # File name and index are also removed, since they were not taken into account for age encryptio
+ let result: record = (
+ secret
+ --filename $filename
+ --public-age-key $test_public_key
+ $key_pairs
+ $name
+ )
+ | get items.0
+ | reject $.metadata.annotations
+
+ # Verify decryption
+ let tmp_encrypted_file = (mktemp -t --suffix .yaml)
+ $result | save -f $tmp_encrypted_file
+ let actual: record = (
+ $test_private_key
+ | SOPS_AGE_KEY_FILE="/dev/stdin" sops --decrypt $tmp_encrypted_file
+ | from yaml
+ )
+ rm $tmp_encrypted_file # Clean up temporary key file
+
+
+ let expected_encoded_values: record = {
+ key1: ("value1" | encode base64),
+ key2: ("value2" | encode base64)
+ }
+
+ let expected: record = {
+ apiVersion: "v1"
+ kind: "Secret"
+ metadata: {
+ name: $name,
+ namespace: "default"
+ }
+ data: $expected_encoded_values
+ }
+
+ assert equal $actual $expected
+}
diff --git a/docker/osm-nushell-krm-functions/krm/tests/jsonpatch.nu b/docker/osm-nushell-krm-functions/krm/tests/jsonpatch.nu
new file mode 100644
index 0000000..77cbcd4
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/tests/jsonpatch.nu
@@ -0,0 +1,250 @@
+#!/usr/bin/env -S nu --stdin
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+
+use std assert
+use ../../krm/jsonpatch.nu *
+
+
+# --- create operation tests ---
+
+export def "test jsonpatch create operation add" []: [
+ nothing -> nothing
+] {
+ let op: string = "add"
+ let path: string = "/spec/template/spec/securityContext"
+ let value: record = {
+ runAsUser: 10000
+ fsGroup: 1337
+ }
+
+ let actual: record = create operation $op $path $value
+ let expected: record = {
+ op: $op,
+ path: $path,
+ value: $value
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test jsonpatch create operation remove" []: [
+ nothing -> nothing
+] {
+ let op: string = "remove"
+ let path: string = "/spec/template/spec/securityContext"
+
+ let actual: record = create operation $op $path
+ let expected: record = {
+ op: $op,
+ path: $path
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test jsonpatch create operation replace" []: [
+ nothing -> nothing
+] {
+ let op: string = "replace"
+ let path: string = "/spec/template/spec/securityContext"
+ let value: record = {
+ runAsUser: 10000
+ fsGroup: 1337
+ }
+
+ let actual: record = create operation $op $path $value
+ let expected: record = {
+ op: $op,
+ path: $path,
+ value: $value
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test jsonpatch create operation move" []: [
+ nothing -> nothing
+] {
+ let op: string = "move"
+ let from: string = "/spec/template/spec/securityContext"
+ let path: string = "/spec/template/spec/newSecurityContext"
+
+ let actual: record = create operation $op $path '' $from
+ let expected: record = {
+ op: $op,
+ from: $from,
+ path: $path
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test jsonpatch create operation copy" []: [
+ nothing -> nothing
+] {
+ let op: string = "copy"
+ let from: string = "/spec/template/spec/securityContext"
+ let path: string = "/spec/template/spec/newSecurityContext"
+
+ let actual: record = create operation $op $path '' $from
+ let expected: record = {
+ op: $op,
+ from: $from,
+ path: $path
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test jsonpatch create operation invalid op" []: [
+ nothing -> nothing
+] {
+ let op: string = "invalid"
+ let path: string = "/spec/template/spec/securityContext"
+
+ let error_occurred: error = try {
+ create operation $op $path
+ } catch {
+ |err| $err.msg
+ }
+
+ assert equal $error_occurred "Invalid operation type. Supported values are 'add', 'remove', 'replace', 'move', 'copy'. See RFC6902 for details."
+}
+
+
+export def "test jsonpatch create operation missing value for add/replace" []: [
+ nothing -> nothing
+] {
+ let op: string = "add"
+ let path: string = "/spec/template/spec/securityContext"
+
+ let error_occurred: error = try {
+ create operation $op $path
+ } catch {
+ |err| $err.msg
+ }
+
+ assert equal $error_occurred "Value is required for 'add' and 'replace' operations."
+}
+
+
+export def "test jsonpatch create operation missing from for move/copy" []: [
+ nothing -> nothing
+] {
+ let op: string = "move"
+ let path: string = "/spec/template/spec/newSecurityContext"
+
+ let error_occurred: error = try {
+ create operation $op $path
+ } catch {
+ |err| $err.msg
+ }
+
+ assert equal $error_occurred "Source path is required for 'move' and 'copy' operations."
+}
+
+
+# --- create JSON patch tests ---
+
+export def "test jsonpatch create JSON patch basic" []: [
+ nothing -> nothing
+] {
+ let target: record = {
+ kind: "Deployment"
+ name: "podinfo"
+ }
+ let operation: record = {
+ op: "add"
+ path: "/spec/template/spec/securityContext"
+ value: {
+ runAsUser: 10000
+ fsGroup: 1337
+ }
+ }
+
+ let actual: record = create $target $operation
+ let expected: record = {
+ target: $target,
+ patch: ([$operation] | to yaml)
+ }
+
+ assert equal $actual.target $expected.target
+ assert equal $actual.patch $expected.patch
+}
+
+
+export def "test jsonpatch create JSON patch multiple operations" []: [
+ nothing -> nothing
+] {
+ let target: record = {
+ kind: "Deployment"
+ name: "podinfo"
+ }
+ let operation1: record = {
+ op: "add"
+ path: "/spec/template/spec/securityContext"
+ value: {
+ runAsUser: 10000
+ fsGroup: 1337
+ }
+ }
+ let operation2: record = {
+ op: "replace"
+ path: "/spec/replicas"
+ value: 3
+ }
+
+ let actual: record = create $target $operation1 $operation2
+ let expected: record = {
+ target: $target,
+ patch: (
+ [$operation1, $operation2] | to yaml
+ )
+ }
+
+ assert equal $actual.target $expected.target
+ assert equal $actual.patch $expected.patch
+}
+
+
+export def "test jsonpatch create JSON patch using operation helper" []: [
+ nothing -> nothing
+] {
+ let target: record = {
+ kind: "Deployment"
+ name: "podinfo"
+ }
+ let operation: record = (
+ create operation add "/spec/template/spec/securityContext" {runAsUser: 10000, fsGroup: 1337}
+ )
+
+ let actual: record = create $target $operation
+ let expected: record = {
+ target: $target,
+ patch: ([$operation] | to yaml)
+ }
+
+ assert equal $actual.target $expected.target
+ assert equal $actual.patch $expected.patch
+}
diff --git a/docker/osm-nushell-krm-functions/krm/tests/keypair.nu b/docker/osm-nushell-krm-functions/krm/tests/keypair.nu
new file mode 100644
index 0000000..e3a1ab7
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/tests/keypair.nu
@@ -0,0 +1,214 @@
+#!/usr/bin/env -S nu --stdin
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+
+use std assert
+use std null-device
+use ../../krm/keypair.nu *
+
+
+# --- create age tests ---
+
+export def "test keypair create age basic functionality" [] {
+ # Setup
+ let test_dir = (mktemp -t -d)
+ let key_name = "test_key"
+
+ # Execute
+ create age $key_name $test_dir err> (null-device)
+
+ # Assert
+ assert ([$test_dir $"($key_name).key"] | path join | path exists)
+ assert ([$test_dir $"($key_name).pub"] | path join | path exists)
+
+ # Cleanup
+ rm -rf $test_dir
+}
+
+
+export def "test keypair create age overwrites existing keys" [] {
+ # Setup
+ let test_dir = (mktemp -t -d)
+ let key_name = "test_key"
+ touch ([$test_dir $"($key_name).key"] | path join)
+ touch ([$test_dir $"($key_name).pub"] | path join)
+
+ # Execute
+ create age $key_name $test_dir err> (null-device)
+
+ # Assert
+ let key_path = [$test_dir $"($key_name).key"] | path join
+ let pub_path = [$test_dir $"($key_name).pub"] | path join
+ assert ($key_path | path exists)
+ assert ($pub_path | path exists)
+ assert greater (open $key_path | str length) 0
+ assert greater (open $pub_path | str length) 0
+
+ # Cleanup
+ rm -rf $test_dir
+}
+
+
+export def "test keypair create age uses default directory" [] {
+ # Setup
+ let original_credentials_dir = $env.CREDENTIALS_DIR?
+ let test_dir = (mktemp -t -d)
+ $env.CREDENTIALS_DIR = $test_dir
+ let key_name = "test_key"
+
+ # Execute
+ create age $key_name err> (null-device)
+
+ # Assert
+ assert ([$test_dir $"($key_name).key"] | path join | path exists)
+ assert ([$test_dir $"($key_name).pub"] | path join | path exists)
+
+ # Cleanup
+ rm -rf $test_dir
+ $env.CREDENTIALS_DIR = $original_credentials_dir
+}
+
+
+export def "test keypair create age generates valid keys" [] {
+ # Setup
+ let test_dir = (mktemp -t -d)
+ let key_name = "test_key"
+
+ # Execute
+ create age $key_name $test_dir err> (null-device)
+
+ # Assert
+ let pub_path = [$test_dir $"($key_name).pub"] | path join
+ let pub_key = (open $pub_path)
+ assert ($pub_key | str starts-with "age1")
+ assert equal ($pub_key | str length) 63 # Standard length for age public keys
+
+ # Cleanup
+ rm -rf $test_dir
+}
+
+
+# --- encrypt secret manifest tests ---
+
+export def "test keypair encrypt secret manifest basic functionality" [] {
+ # Setup
+ let test_public_key: string = "age1hsrtxphk7exrdc0kt8dgr8a8r3hx88v3xpsw0ezaxvefsy9asegqknppc0"
+ let test_private_key: string = "AGE-SECRET-KEY-12CC3A4LEDYF4S26UV6Z2MEG7ZQL9PTU5NHH6N3FN6FLJ5HACW9LQX0UWP2"
+ let input_yaml: string = "apiVersion: v1\nkind: Secret\nmetadata:\n name: test-secret\ndata:\n username: dXNlcm5hbWU=\n password: cGFzc3dvcmQ="
+
+ # Execute
+ let result = ($input_yaml | encrypt secret manifest $test_public_key)
+
+ # Assert
+ assert ($result | str contains "sops:")
+ assert ($result | str contains "encrypted_regex: ^(data|stringData)$")
+ assert ($result | str contains "ENC[AES256_GCM,data:")
+
+ # Verify decryption
+ let tmp_encrypted_file = (mktemp -t --suffix .yaml)
+ $result | save -f $tmp_encrypted_file
+
+ let decrypted: string = ($test_private_key
+ | SOPS_AGE_KEY_FILE="/dev/stdin" sops --decrypt $tmp_encrypted_file
+ )
+ rm $tmp_encrypted_file # Clean up temporary key file
+
+ assert str contains $decrypted "username: dXNlcm5hbWU="
+ assert str contains $decrypted "password: cGFzc3dvcmQ="
+}
+
+
+export def "test keypair encrypt secret manifest handles empty input" [] {
+ # Setup
+ let test_public_key = "age1hsrtxphk7exrdc0kt8dgr8a8r3hx88v3xpsw0ezaxvefsy9asegqknppc0"
+
+ # Execute and Assert
+ let result: string = (try { ""
+ | encrypt secret manifest $test_public_key
+ } catch { $in | to yaml })
+
+ # assert str contains $result "Error"
+ assert (not ($result | str contains "Error")) $"ERROR: Got ($result)"
+}
+
+
+export def "test keypair encrypt secret manifest encrypts correct fields" [] {
+ # Setup
+ let test_public_key: string = "age1hsrtxphk7exrdc0kt8dgr8a8r3hx88v3xpsw0ezaxvefsy9asegqknppc0"
+ let test_private_key: string = "AGE-SECRET-KEY-12CC3A4LEDYF4S26UV6Z2MEG7ZQL9PTU5NHH6N3FN6FLJ5HACW9LQX0UWP2"
+ let input_yaml: string = "apiVersion: v1\nkind: Secret\nmetadata:\n name: test-secret\ndata:\n username: dXNlcm5hbWU=\n password: cGFzc3dvcmQ=\nstringData:\n api_key: my-api-key"
+
+ # Execute
+ let result: string = ($input_yaml | encrypt secret manifest $test_public_key)
+
+ # Assert
+ assert str contains $result "ENC[AES256_GCM,data:"
+ assert str contains $result "username:"
+ assert str contains $result "password:"
+ assert str contains $result "api_key:"
+ assert (not ($result | str contains "dXNlcm5hbWU="))
+ assert (not ($result | str contains "cGFzc3dvcmQ="))
+ assert (not ($result | str contains "my-api-key"))
+ assert str contains $result "metadata:\n name: test-secret"
+
+ # Verify decryption
+ let tmp_encrypted_file = (mktemp -t --suffix .yaml)
+ $result | save -f $tmp_encrypted_file
+ let decrypted: string = ($test_private_key
+ | SOPS_AGE_KEY_FILE="/dev/stdin" sops --decrypt $tmp_encrypted_file
+ )
+ rm $tmp_encrypted_file # Clean up temporary key file
+ assert str contains $decrypted "username: dXNlcm5hbWU="
+ assert str contains $decrypted "password: cGFzc3dvcmQ="
+ assert str contains $decrypted "api_key: my-api-key"
+}
+
+
+export def "test keypair decrypt secret manifest" [] {
+ # Setup
+ let test_public_key: string = "age1hsrtxphk7exrdc0kt8dgr8a8r3hx88v3xpsw0ezaxvefsy9asegqknppc0"
+ let test_private_key: string = "AGE-SECRET-KEY-12CC3A4LEDYF4S26UV6Z2MEG7ZQL9PTU5NHH6N3FN6FLJ5HACW9LQX0UWP2"
+ let input_record: record = {
+ apiVersion: v1,
+ kind: Secret,
+ metadata: { name: test-secret }
+ data: {
+ username: ('myusername' | encode base64)
+ password: ('mypassword' | encode base64)
+ }
+ }
+
+ # Encrypt
+ let encrypted_record: record = (
+ $input_record
+ | to yaml
+ | encrypt secret manifest $test_public_key
+ | from yaml
+ )
+
+ # Decrypt
+ let decrypted_record: record = (
+ $encrypted_record
+ | to yaml
+ | keypair decrypt secret manifest $test_private_key
+ | from yaml
+ )
+
+ # Test
+ assert equal $input_record $decrypted_record
+}
diff --git a/docker/osm-nushell-krm-functions/krm/tests/mod.nu b/docker/osm-nushell-krm-functions/krm/tests/mod.nu
new file mode 100644
index 0000000..d206563
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/tests/mod.nu
@@ -0,0 +1,67 @@
+#!/usr/bin/env -S nu --stdin
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+use std assert
+use ../../krm *
+use ./keypair.nu *
+use ./concatenate.nu *
+use ./convert.nu *
+use ./strategicmergepatch.nu *
+use ./jsonpatch.nu *
+use ./generator.nu *
+use ./patch.nu *
+use ./overlaypatch.nu *
+
+
+# Test launcher
+def main [] {
+ print "Running tests..."
+
+ let test_commands: list<string> = (
+ scope commands
+ | where ($it.type == "custom")
+ and ($it.name | str starts-with "test ")
+ and not ($it.description | str starts-with "ignore")
+ | get name
+ )
+
+ let count_test_commands: int = ($test_commands | length)
+ let test_commands_together: string = (
+ $test_commands
+ | enumerate
+ | each { |test|
+ [$"print '--> [($test.index + 1)/($count_test_commands)] ($test.item)'", $test.item]
+ }
+ | flatten
+ | str join ";"
+ )
+
+ nu --commands $"source `($env.CURRENT_FILE)`; ($test_commands_together)"
+ print $"\n✅ ALL TESTS COMPLETED SUCCESSFULLY"
+}
+
+
+# --- safe_name tests ---
+
+export def "test safe resource name" []: [
+ nothing -> nothing
+] {
+ let actual: string = safe resource name "This is a_test w/special:characters."
+ let expected: string = "this-is-a-test-w-special-characters-"
+ assert equal $actual $expected
+}
diff --git a/docker/osm-nushell-krm-functions/krm/tests/overlaypatch.nu b/docker/osm-nushell-krm-functions/krm/tests/overlaypatch.nu
new file mode 100644
index 0000000..5473093
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/tests/overlaypatch.nu
@@ -0,0 +1,1992 @@
+#!/usr/bin/env -S nu --stdin
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+
+use std assert
+use ../../krm/patch.nu *
+use ../../krm/overlaypatch.nu *
+
+
+
+# --- add patch tests ---
+
+export def "test overlaypatch add patch to kustomization" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ # spec: { patches: [] }
+ spec: {}
+ }
+ ]
+ }
+
+ let kustomization_name: string = "example-kustomization"
+ let ks_namespace: string = "default"
+ let target: record = {
+ kind: "Deployment"
+ name: "example-deployment"
+ }
+ let patch_value: record = {
+ op: "replace"
+ path: "/spec/replicas"
+ value: 3
+ }
+
+ let actual: record = $resourcelist | add patch --ks-namespace $ks_namespace $kustomization_name $target $patch_value
+ let expected_patch_content: record = {
+ target: $target
+ patch: ($patch_value | to yaml)
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content] }
+ }
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+export def "test overlaypatch add patch to kustomization with existing patches" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ spec: { patches: [{ target: { kind: "Service", name: "example-service" }, patch: "op:\n replace\npath:\n /spec/type\nvalue:\n NodePort" }] }
+ }
+ ]
+ }
+
+ let kustomization_name: string = "example-kustomization"
+ let ks_namespace: string = "default"
+ let target: record = {
+ kind: "Deployment"
+ name: "example-deployment"
+ }
+ let patch_value: record = {
+ op: "replace"
+ path: "/spec/replicas"
+ value: 3
+ }
+
+ let actual: record = $resourcelist | add patch --ks-namespace $ks_namespace $kustomization_name $target $patch_value
+ let expected_patch_content_1st_patch: record = {
+ target: { kind: "Service", name: "example-service" }
+ patch: "op:\n replace\npath:\n /spec/type\nvalue:\n NodePort"
+ }
+ let expected_patch_content_2nd_patch: record = {
+ target: $target
+ patch: ($patch_value | to yaml)
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content_1st_patch, $expected_patch_content_2nd_patch] }
+ }
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+# --- add jsonpatch tests ---
+
+export def "test overlaypatch add jsonpatch add operation" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ # spec: { patches: [] }
+ spec: {}
+ }
+ ]
+ }
+
+ let kustomization_name: string = "example-kustomization"
+ let ks_namespace: string = "default"
+ let target: record = {
+ kind: "Deployment"
+ name: "example-deployment"
+ }
+ let path: string = "/spec/replicas"
+ let value: any = 3
+
+ let actual: record = $resourcelist | add jsonpatch --ks-namespace $ks_namespace $kustomization_name $target $path $value
+ let expected_patch_content: record = {
+ target: $target
+ patch: (
+ [{ op: "add", path: $path, value: $value }] | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content] }
+ }
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+export def "test overlaypatch add jsonpatch replace operation" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ # spec: { patches: [] }
+ spec: {}
+ }
+ ]
+ }
+
+ let kustomization_name: string = "example-kustomization"
+ let ks_namespace: string = "default"
+ let target: record = {
+ kind: "Deployment"
+ name: "example-deployment"
+ }
+ let path: string = "/spec/replicas"
+ let value: any = 3
+
+ let actual: record = $resourcelist | (
+ add jsonpatch
+ --ks-namespace $ks_namespace
+ --operation "replace"
+ $kustomization_name
+ $target
+ $path
+ $value
+ )
+ let expected_patch_content: record = {
+ target: $target
+ patch: (
+ [{ op: "replace", path: $path, value: $value }]
+ | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content] }
+ }
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+export def "test overlaypatch add jsonpatch remove operation" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ spec: { patches: [] }
+ }
+ ]
+ }
+
+ let kustomization_name: string = "example-kustomization"
+ let ks_namespace: string = "default"
+ let target: record = {
+ kind: "Deployment"
+ name: "example-deployment"
+ }
+ let path: string = "/spec/replicas"
+
+ let actual: record = $resourcelist | (
+ add jsonpatch
+ --ks-namespace $ks_namespace
+ --operation "remove"
+ $kustomization_name
+ $target
+ $path
+ )
+ let expected_patch_content: record = {
+ target: $target
+ patch: (
+ [{ op: "remove", path: $path}]
+ | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content] }
+ }
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+export def "test overlaypatch add jsonpatch move operation" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ # spec: { patches: [] }
+ spec: {}
+ }
+ ]
+ }
+
+ let kustomization_name: string = "example-kustomization"
+ let ks_namespace: string = "default"
+ let target: record = {
+ kind: "Deployment"
+ name: "example-deployment"
+ }
+ let path: string = "/spec/new-replicas"
+ let from: string = "/spec/replicas"
+
+ let actual: record = (
+ $resourcelist
+ | add jsonpatch
+ --ks-namespace $ks_namespace
+ --operation "move"
+ $kustomization_name
+ $target
+ $path
+ ''
+ $from
+ )
+ let expected_patch_content: record = {
+ target: $target
+ patch: (
+ [{ op: "move", from: $from, path: $path }]
+ | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content] }
+ }
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+export def "test overlaypatch add jsonpatch copy operation" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ spec: { patches: [] }
+ }
+ ]
+ }
+
+ let kustomization_name: string = "example-kustomization"
+ let ks_namespace: string = "default"
+ let target: record = {
+ kind: "Deployment"
+ name: "example-deployment"
+ }
+ let path: string = "/spec/new-replicas"
+ let from: string = "/spec/replicas"
+
+ let actual: record = (
+ $resourcelist
+ | add jsonpatch
+ --ks-namespace $ks_namespace
+ --operation "copy"
+ $kustomization_name
+ $target
+ $path
+ ''
+ $from
+ )
+ let expected_patch_content: record = {
+ target: $target
+ patch: (
+ [{ op: "copy", from: $from, path: $path }]
+ | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content] }
+ }
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+export def "test overlaypatch add jsonpatch invalid operation" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ # spec: { patches: [] }
+ spec: {}
+ }
+ ]
+ }
+
+ let kustomization_name: string = "example-kustomization"
+ let ks_namespace: string = "default"
+ let target: record = {
+ kind: "Deployment"
+ name: "example-deployment"
+ }
+ let path: string = "/spec/replicas"
+
+ let error_occurred: any = try {
+ $resourcelist | (
+ add jsonpatch
+ --ks-namespace $ks_namespace
+ --operation "invalid"
+ $kustomization_name
+ $target
+ $path
+ )
+ } catch {
+ |err| $err.msg
+ }
+
+ assert equal $error_occurred "Invalid operation type. Supported values are 'add', 'remove', 'replace', 'move', 'copy'. See RFC6902 for details"
+}
+
+
+# --- helmrelease add inline values tests ---
+
+export def "test overlaypatch helmrelease add inline values with add operation" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ # spec: { patches: [] }
+ spec: {}
+ }
+ ]
+ }
+
+ let ks_namespace: string = "default"
+ let hr_namespace: string = ""
+ let kustomization_name: string = "example-kustomization"
+ let helmrelease_name: string = "example-helmrelease"
+ let values: record = { key1: "value1", key2: "value2" }
+
+ let actual: record = (
+ $resourcelist
+ | (
+ helmrelease add inline values
+ --ks-namespace $ks_namespace
+ $kustomization_name
+ $helmrelease_name
+ $values
+ )
+ )
+ let expected_patch_content: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [{ op: "add", path: "/spec/values", value: $values }]
+ | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content] }
+ }
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+export def "test overlaypatch helmrelease add inline values with replace operation" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ # spec: { patches: [] }
+ spec: {}
+ }
+ ]
+ }
+
+ let ks_namespace: string = "default"
+ let hr_namespace: string = ""
+ let kustomization_name: string = "example-kustomization"
+ let helmrelease_name: string = "example-helmrelease"
+ let values: record = { key1: "value1", key2: "value2" }
+
+ let actual: record = (
+ $resourcelist
+ | (
+ helmrelease add inline values
+ --ks-namespace $ks_namespace
+ --operation replace
+ $kustomization_name
+ $helmrelease_name
+ $values
+ )
+ )
+ let expected_patch_content: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [{ op: "replace", path: "/spec/values", value: $values }]
+ | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content] }
+ }
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+export def "test overlaypatch helmrelease add inline values with existing patches" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ spec: { patches: [
+ {
+ target: { kind: "HelmRelease", name: "existing-helmrelease" }
+ patch: (
+ [{ op: "replace", path: "/spec/values/replicaCount", value: 2 }]
+ | to yaml
+ )
+ }
+ ] }
+ }
+ ]
+ }
+
+ let ks_namespace: string = "default"
+ let hr_namespace: string = ""
+ let kustomization_name: string = "example-kustomization"
+ let helmrelease_name: string = "example-helmrelease"
+ let values: record = { key1: "value1", key2: "value2" }
+
+ let actual: record = $resourcelist | helmrelease add inline values --ks-namespace $ks_namespace $kustomization_name $helmrelease_name $values
+ let expected_patch_content_new: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [{ op: "add", path: "/spec/values", value: $values }]
+ | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [
+ {
+ target: { kind: "HelmRelease", name: "existing-helmrelease" }
+ patch: (
+ [{ op: "replace", path: "/spec/values/replicaCount", value: 2 }]
+ | to yaml
+ )
+ },
+ $expected_patch_content_new
+ ] }
+ }
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+# --- helmrelease add values from configmap tests ---
+
+export def "test overlaypatch helmrelease add values from configmap basic" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ # spec: { patches: [] }
+ spec: {}
+ }
+ ]
+ }
+
+ let ks_namespace: string = "default"
+ let hr_namespace: string = ""
+ let kustomization_name: string = "example-kustomization"
+ let helmrelease_name: string = "example-helmrelease"
+ let cm_name: string = "example-configmap"
+ let cm_key: string = "values.yaml"
+
+ let actual: record = (
+ $resourcelist
+ | (
+ helmrelease add values from configmap
+ --ks-namespace $ks_namespace
+ $kustomization_name
+ $helmrelease_name
+ $cm_name
+ $cm_key
+ )
+ )
+ let expected_patch_content: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [{ op: "add", path: "/spec/valuesFrom/-", value: { kind: "ConfigMap", name: $cm_name, key: $cm_key } }]
+ | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content] }
+ }
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+export def "test overlaypatch helmrelease add values from configmap with target path" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ # spec: { patches: [] }
+ spec: {}
+ }
+ ]
+ }
+
+ let ks_namespace: string = "default"
+ let hr_namespace: string = ""
+ let kustomization_name: string = "example-kustomization"
+ let helmrelease_name: string = "example-helmrelease"
+ let cm_name: string = "example-configmap"
+ let cm_key: string = "values.yaml"
+ let target_path: string = "/custom/path"
+
+ let actual: record = (
+ $resourcelist
+ | (
+ helmrelease add values from configmap
+ --ks-namespace $ks_namespace
+ --target-path $target_path
+ $kustomization_name
+ $helmrelease_name
+ $cm_name
+ $cm_key
+ )
+ )
+ let expected_patch_content: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [{ op: "add", path: "/spec/valuesFrom/-", value: { kind: "ConfigMap", name: $cm_name, key: $cm_key, targetPath: $target_path } }]
+ | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content] }
+ }
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+export def "test overlaypatch helmrelease add values from configmap with existing patches" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ spec: { patches: [
+ {
+ target: { kind: "HelmRelease", name: "existing-helmrelease" }
+ patch: (
+ [{ op: "add", path: "/spec/valuesFrom/-", value: { kind: "ConfigMap", name: "existing-configmap", key: "existing-values.yaml" } }]
+ | to yaml
+ )
+ }
+ ] }
+ }
+ ]
+ }
+
+ let ks_namespace: string = "default"
+ let hr_namespace: string = ""
+ let kustomization_name: string = "example-kustomization"
+ let helmrelease_name: string = "example-helmrelease"
+ let cm_name: string = "example-configmap"
+ let cm_key: string = "values.yaml"
+
+ let actual: record = (
+ $resourcelist
+ | (
+ helmrelease add values from configmap
+ --ks-namespace $ks_namespace
+ $kustomization_name
+ $helmrelease_name
+ $cm_name
+ $cm_key
+ )
+ )
+ let expected_patch_content_existing: record = {
+ target: { kind: "HelmRelease", name: "existing-helmrelease" }
+ patch: (
+ [{ op: "add", path: "/spec/valuesFrom/-", value: { kind: "ConfigMap", name: "existing-configmap", key: "existing-values.yaml" } }]
+ | to yaml
+ )
+ }
+ let expected_patch_content_new: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [{ op: "add", path: "/spec/valuesFrom/-", value: { kind: "ConfigMap", name: $cm_name, key: $cm_key } }]
+ | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content_existing, $expected_patch_content_new] }
+ }
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+
+# --- helmrelease add values from secret tests ---
+
+export def "test overlaypatch helmrelease add values from secret basic" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ # spec: { patches: [] }
+ spec: {}
+ }
+ ]
+ }
+
+ let ks_namespace: string = "default"
+ let hr_namespace: string = ""
+ let kustomization_name: string = "example-kustomization"
+ let helmrelease_name: string = "example-helmrelease"
+ let secret_name: string = "example-secret"
+ let secret_key: string = "values.yaml"
+
+ let actual: record = (
+ $resourcelist
+ | (
+ helmrelease add values from secret
+ --ks-namespace $ks_namespace
+ $kustomization_name
+ $helmrelease_name
+ $secret_name
+ $secret_key
+ )
+ )
+ let expected_patch_content: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [{ op: "add", path: "/spec/valuesFrom/-", value: { kind: "Secret", name: $secret_name, key: $secret_key } }]
+ | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content] }
+ }
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+export def "test overlaypatch helmrelease add values from secret with target path" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ # spec: { patches: [] }
+ spec: {}
+ }
+ ]
+ }
+
+ let ks_namespace: string = "default"
+ let hr_namespace: string = ""
+ let kustomization_name: string = "example-kustomization"
+ let helmrelease_name: string = "example-helmrelease"
+ let secret_name: string = "example-secret"
+ let secret_key: string = "values.yaml"
+ let target_path: string = "/custom/path"
+
+ let actual: record = (
+ $resourcelist
+ | (
+ helmrelease add values from secret
+ --ks-namespace $ks_namespace
+ --target-path $target_path
+ $kustomization_name
+ $helmrelease_name
+ $secret_name
+ $secret_key
+ )
+ )
+ let expected_patch_content: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [{ op: "add", path: "/spec/valuesFrom/-", value: { kind: "Secret", name: $secret_name, key: $secret_key, targetPath: $target_path } }]
+ | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content] }
+ }
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+export def "test overlaypatch helmrelease add values from secret with optional flag" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ # spec: { patches: [] }
+ spec: {}
+ }
+ ]
+ }
+
+ let ks_namespace: string = "default"
+ let hr_namespace: string = ""
+ let kustomization_name: string = "example-kustomization"
+ let helmrelease_name: string = "example-helmrelease"
+ let secret_name: string = "example-secret"
+ let secret_key: string = "values.yaml"
+
+ let actual: record = (
+ $resourcelist
+ | (
+ helmrelease add values from secret
+ --ks-namespace $ks_namespace
+ --optional $kustomization_name
+ $helmrelease_name
+ $secret_name
+ $secret_key
+ )
+ )
+ let expected_patch_content: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [{ op: "add", path: "/spec/valuesFrom/-", value: { kind: "Secret", name: $secret_name, key: $secret_key, optional: true } }]
+ | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content] }
+ }
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+export def "test overlaypatch helmrelease add values from secret with hr namespace" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ # spec: { patches: [] }
+ spec: {}
+ }
+ ]
+ }
+
+ let ks_namespace: string = "default"
+ let hr_namespace: string = "example-namespace"
+ let kustomization_name: string = "example-kustomization"
+ let helmrelease_name: string = "example-helmrelease"
+ let secret_name: string = "example-secret"
+ let secret_key: string = "values.yaml"
+
+ let actual: record = (
+ $resourcelist
+ | (
+ helmrelease add values from secret
+ --ks-namespace $ks_namespace
+ --hr-namespace $hr_namespace
+ $kustomization_name
+ $helmrelease_name
+ $secret_name
+ $secret_key
+ )
+ )
+ let expected_patch_content: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name, namespace: $hr_namespace }
+ patch: (
+ [{ op: "add", path: "/spec/valuesFrom/-", value: { kind: "Secret", name: $secret_name, key: $secret_key } }]
+ | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content] }
+ }
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+export def "test overlaypatch helmrelease add values from secret with existing patches" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ spec: { patches: [
+ {
+ target: { kind: "HelmRelease", name: "existing-helmrelease" }
+ patch: (
+ [{ op: "add", path: "/spec/valuesFrom/-", value: { kind: "Secret", name: "existing-secret", key: "existing-values.yaml" } }]
+ | to yaml
+ )
+ }
+ ] }
+ }
+ ]
+ }
+
+ let ks_namespace: string = "default"
+ let hr_namespace: string = ""
+ let kustomization_name: string = "example-kustomization"
+ let helmrelease_name: string = "example-helmrelease"
+ let secret_name: string = "example-secret"
+ let secret_key: string = "values.yaml"
+
+ let actual: record = (
+ $resourcelist
+ | (
+ helmrelease add values from secret
+ --ks-namespace $ks_namespace
+ $kustomization_name
+ $helmrelease_name
+ $secret_name
+ $secret_key
+ )
+ )
+ let expected_patch_content_existing: record = {
+ target: { kind: "HelmRelease", name: "existing-helmrelease" }
+ patch: (
+ [{ op: "add", path: "/spec/valuesFrom/-", value: { kind: "Secret", name: "existing-secret", key: "existing-values.yaml" } }]
+ | to yaml
+ )
+ }
+ let expected_patch_content_new: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [{ op: "add", path: "/spec/valuesFrom/-", value: { kind: "Secret", name: $secret_name, key: $secret_key } }]
+ | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content_existing, $expected_patch_content_new] }
+ }
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+# TODO:
+
+# --- helmrelease set values ---
+
+## Inline values only
+export def "test overlaypatch helmrelease set values with inline values only" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ # spec: { patches: [] }
+ spec: {}
+ }
+ ]
+ }
+
+ let ks_namespace: string = "default"
+ let kustomization_name: string = "example-kustomization"
+ let helmrelease_name: string = "example-helmrelease"
+ let inline_values: record = { key1: "value1", key2: "value2" }
+
+ let actual: record = (
+ $resourcelist
+ | (
+ helmrelease set values
+ --ks-namespace $ks_namespace
+ $kustomization_name
+ $helmrelease_name
+ $inline_values
+ )
+ )
+ let expected_patch_content: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [{ op: "add", path: "/spec/values", value: $inline_values }]
+ | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content] }
+ }
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+## Values from ConfigMap only
+export def "test overlaypatch helmrelease set values with configmap only" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ # spec: { patches: [] }
+ spec: {}
+ }
+ ]
+ }
+
+ let ks_namespace: string = "default"
+ let kustomization_name: string = "example-kustomization"
+ let helmrelease_name: string = "example-helmrelease"
+ let cm_name: string = "example-configmap"
+
+ let actual: record = (
+ $resourcelist
+ | (
+ helmrelease set values
+ --ks-namespace $ks_namespace
+ $kustomization_name
+ $helmrelease_name
+ {}
+ $cm_name
+ )
+ )
+ let expected_patch_content: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [{ op: "add", path: "/spec/valuesFrom/-", value: { kind: "ConfigMap", name: $cm_name, key: "values.yaml" } }]
+ | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content] }
+ }
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+## Values from Secret only
+export def "test overlaypatch helmrelease set values with secret only" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ # spec: { patches: [] }
+ spec: {}
+ }
+ ]
+ }
+
+ let ks_namespace: string = "default"
+ let kustomization_name: string = "example-kustomization"
+ let helmrelease_name: string = "example-helmrelease"
+ let secret_name: string = "example-secret"
+
+ let actual: record = (
+ $resourcelist
+ | (
+ helmrelease set values
+ --ks-namespace $ks_namespace
+ $kustomization_name
+ $helmrelease_name
+ {}
+ ''
+ $secret_name
+ )
+ )
+ let expected_patch_content: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [{ op: "add", path: "/spec/valuesFrom/-", value: { kind: "Secret", name: $secret_name, key: "values.yaml" } }]
+ | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content] }
+ }
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+## Inline values and values from ConfigMap
+export def "test overlaypatch helmrelease set values with inline and configmap" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ # spec: { patches: [] }
+ spec: {}
+ }
+ ]
+ }
+
+ let ks_namespace: string = "default"
+ let kustomization_name: string = "example-kustomization"
+ let helmrelease_name: string = "example-helmrelease"
+ let inline_values: record = { key1: "value1", key2: "value2" }
+ let cm_name: string = "example-configmap"
+
+ let actual: record = (
+ $resourcelist
+ | (
+ helmrelease set values
+ --ks-namespace $ks_namespace
+ $kustomization_name
+ $helmrelease_name
+ $inline_values
+ $cm_name
+ )
+ )
+ let expected_patch_content_inline_values: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [{ op: "add", path: "/spec/values", value: $inline_values }]
+ | to yaml
+ )
+ }
+ let expected_patch_content_cm_values: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [{ op: "add", path: "/spec/valuesFrom/-", value: { kind: "ConfigMap", name: $cm_name, key: "values.yaml" } }]
+ | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content_inline_values, $expected_patch_content_cm_values] }
+ }
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+## Inline values and values from secret
+export def "test overlaypatch helmrelease set values with inline and secret" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ # spec: { patches: [] }
+ spec: {}
+ }
+ ]
+ }
+
+ let ks_namespace: string = "default"
+ let kustomization_name: string = "example-kustomization"
+ let helmrelease_name: string = "example-helmrelease"
+ let inline_values: record = { key1: "value1", key2: "value2" }
+ let secret_name: string = "example-secret"
+
+ let actual: record = (
+ $resourcelist
+ | (
+ helmrelease set values
+ --ks-namespace $ks_namespace
+ $kustomization_name
+ $helmrelease_name
+ $inline_values
+ ''
+ $secret_name
+ )
+ )
+ let expected_patch_content_inline_values: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [{ op: "add", path: "/spec/values", value: $inline_values }]
+ | to yaml
+ )
+ }
+ let expected_patch_content_secret_values: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [{ op: "add", path: "/spec/valuesFrom/-", value: { kind: "Secret", name: $secret_name, key: "values.yaml" } }]
+ | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content_inline_values, $expected_patch_content_secret_values] }
+ }
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+## Values from cm and values from secret
+export def "test overlaypatch helmrelease set values with configmap and secret" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ # spec: { patches: [] }
+ spec: {}
+ }
+ ]
+ }
+
+ let ks_namespace: string = "default"
+ let kustomization_name: string = "example-kustomization"
+ let helmrelease_name: string = "example-helmrelease"
+ let cm_name: string = "example-configmap"
+ let secret_name: string = "example-secret"
+
+ let actual: record = (
+ $resourcelist
+ | (
+ helmrelease set values
+ --ks-namespace $ks_namespace
+ $kustomization_name
+ $helmrelease_name
+ {}
+ $cm_name
+ $secret_name
+ )
+ )
+ let expected_patch_content_cm_values: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [{ op: "add", path: "/spec/valuesFrom/-", value: { kind: "ConfigMap", name: $cm_name, key: "values.yaml" } }]
+ | to yaml
+ )
+ }
+ let expected_patch_content_secret_values: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [{ op: "add", path: "/spec/valuesFrom/-", value: { kind: "Secret", name: $secret_name, key: "values.yaml" } }]
+ | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content_cm_values, $expected_patch_content_secret_values] }
+ }
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+## Inline values, values from cm and values from secret
+export def "test overlaypatch helmrelease set values with inline, configmap, and secret" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ # spec: { patches: [] }
+ spec: {}
+ }
+ ]
+ }
+
+ let ks_namespace: string = "default"
+ let kustomization_name: string = "example-kustomization"
+ let helmrelease_name: string = "example-helmrelease"
+ let inline_values: record = { key1: "value1", key2: "value2" }
+ let cm_name: string = "example-configmap"
+ let secret_name: string = "example-secret"
+
+ let actual_resourcelist: record = (
+ $resourcelist
+ | (
+ helmrelease set values
+ --ks-namespace $ks_namespace
+ $kustomization_name
+ $helmrelease_name
+ $inline_values
+ $cm_name
+ $secret_name
+ )
+ )
+
+ # Expected patches for inline values, ConfigMap, and Secret
+ let expected_patch_inline_values: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [{ op: "add", path: "/spec/values", value: $inline_values }]
+ | to yaml
+ )
+ }
+ let expected_patch_cm_values: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [{ op: "add", path: "/spec/valuesFrom/-", value: { kind: "ConfigMap", name: $cm_name, key: "values.yaml" } }]
+ | to yaml
+ )
+ }
+ let expected_patch_secret_values: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [{ op: "add", path: "/spec/valuesFrom/-", value: { kind: "Secret", name: $secret_name, key: "values.yaml" } }]
+ | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_inline_values, $expected_patch_cm_values, $expected_patch_secret_values] }
+ }
+ ]
+ }
+
+ assert equal $actual_resourcelist $expected_resourcelist
+}
+
+
+export def "test helmrelease set values with create configmap" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ # spec: { patches: [] }
+ spec: {}
+ }
+ ]
+ }
+
+ let ks_namespace: string = "default"
+ let kustomization_name: string = "example-kustomization"
+ let helmrelease_name: string = "example-helmrelease"
+ let cm_name: string = "example-configmap"
+ let create_cm_with_values: record = { key1: "value1", key2: "value2" }
+
+ let actual: record = (
+ $resourcelist |
+ (
+ helmrelease set values
+ --ks-namespace $ks_namespace
+ --create-cm-with-values $create_cm_with_values
+ $kustomization_name
+ $helmrelease_name
+ {}
+ $cm_name
+ )
+ )
+ let expected_cm_manifest: record = {
+ apiVersion: "v1"
+ kind: "ConfigMap"
+ metadata: {
+ name: $cm_name,
+ namespace: "default"
+ annotations: {
+ # "config.kubernetes.io/path": "example-kustomization-example-helmrelease-example-configmap.yaml",
+ # "internal.config.kubernetes.io/path": "example-kustomization-example-helmrelease-example-configmap.yaml"
+ "config.kubernetes.io/path": "example-configmap.yaml",
+ "internal.config.kubernetes.io/path": "example-configmap.yaml"
+ }
+ }
+ data: {
+ "values.yaml": ($create_cm_with_values | to yaml | str trim)
+ }
+ }
+ let expected_patch_content: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [
+ {
+ op: "add",
+ path: "/spec/valuesFrom/-",
+ value: { kind: "ConfigMap", name: $cm_name, key: "values.yaml" }
+ }
+ ] | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content] }
+ },
+ $expected_cm_manifest
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+export def "test helmrelease set values with create secret" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ # spec: { patches: [] }
+ spec: {}
+ }
+ ]
+ }
+
+ let ks_namespace: string = "default"
+ let kustomization_name: string = "example-kustomization"
+ let helmrelease_name: string = "example-helmrelease"
+ let secret_name: string = "example-secret"
+ let create_secret_with_values: record = {key1: "value1", key2: "value2"}
+
+ let actual: record = $resourcelist | (
+ helmrelease set values
+ --ks-namespace $ks_namespace
+ --create-secret-with-values $create_secret_with_values
+ $kustomization_name
+ $helmrelease_name
+ {}
+ ''
+ $secret_name
+ )
+ let expected_secret_manifest: record = {
+ apiVersion: "v1"
+ kind: "Secret"
+ metadata: {
+ name: $secret_name,
+ namespace: "default"
+ annotations: {
+ # "config.kubernetes.io/path": "example-kustomization-example-helmrelease-example-secret.yaml",
+ # "internal.config.kubernetes.io/path": "example-kustomization-example-helmrelease-example-secret.yaml"
+ "config.kubernetes.io/path": "example-secret.yaml",
+ "internal.config.kubernetes.io/path": "example-secret.yaml"
+ }
+ }
+ data: {
+ "values.yaml": ($create_secret_with_values | to yaml | str trim | encode base64)
+ }
+ }
+ let expected_patch_content: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [
+ {
+ op: "add",
+ path: "/spec/valuesFrom/-",
+ value: { kind: "Secret", name: $secret_name, key: "values.yaml" }
+ }
+ ] | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content] }
+ },
+ $expected_secret_manifest
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+export def "test helmrelease set values with create configmap and secret" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ # spec: { patches: [] }
+ spec: {}
+ }
+ ]
+ }
+
+ let ks_namespace: string = "default"
+ let kustomization_name: string = "example-kustomization"
+ let helmrelease_name: string = "example-helmrelease"
+ let cm_name: string = "example-configmap"
+ let secret_name: string = "example-secret"
+ let create_cm_with_values: record = { key1: "value1", key2: "value2" }
+ let create_secret_with_values: record = { key3: "value3", key4: "value4" }
+
+ let actual: record = $resourcelist | (
+ helmrelease set values
+ --ks-namespace $ks_namespace
+ --create-cm-with-values $create_cm_with_values
+ --create-secret-with-values $create_secret_with_values
+ $kustomization_name
+ $helmrelease_name
+ {}
+ $cm_name
+ $secret_name
+ )
+ let expected_cm_manifest: record = {
+ apiVersion: "v1"
+ kind: "ConfigMap"
+ metadata: {
+ name: $cm_name,
+ namespace: "default"
+ annotations: {
+ # "config.kubernetes.io/path": "example-kustomization-example-helmrelease-example-configmap.yaml",
+ # "internal.config.kubernetes.io/path": "example-kustomization-example-helmrelease-example-configmap.yaml"
+ "config.kubernetes.io/path": "example-configmap.yaml",
+ "internal.config.kubernetes.io/path": "example-configmap.yaml"
+ }
+ }
+ data: {
+ "values.yaml": ($create_cm_with_values | to yaml | str trim)
+ }
+ }
+ let expected_secret_manifest: record = {
+ apiVersion: "v1"
+ kind: "Secret"
+ metadata: {
+ name: $secret_name,
+ namespace: "default",
+ annotations: {
+ # "config.kubernetes.io/path": "example-kustomization-example-helmrelease-example-secret.yaml",
+ # "internal.config.kubernetes.io/path": "example-kustomization-example-helmrelease-example-secret.yaml"
+ "config.kubernetes.io/path": "example-secret.yaml",
+ "internal.config.kubernetes.io/path": "example-secret.yaml"
+ }
+ }
+ data: {
+ "values.yaml": ($create_secret_with_values | to yaml | str trim | encode base64)
+ }
+ }
+ let expected_patch_content_cm: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [
+ {
+ op: "add",
+ path: "/spec/valuesFrom/-",
+ value: { kind: "ConfigMap", name: $cm_name, key: "values.yaml" }
+ }
+ ] | to yaml
+ )
+ }
+ let expected_patch_content_secret: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [
+ {
+ op: "add",
+ path: "/spec/valuesFrom/-",
+ value: { kind: "Secret", name: $secret_name, key: "values.yaml" }
+ }
+ ] | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content_cm, $expected_patch_content_secret] }
+ },
+ $expected_cm_manifest,
+ $expected_secret_manifest
+ ]
+ }
+
+ assert equal $actual $expected_resourcelist
+}
+
+
+export def "test helmrelease set values with create secret and age encryption" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: "example-kustomization", namespace: "default" }
+ # spec: { patches: [] }
+ spec: {}
+ }
+ ]
+ }
+
+ let ks_namespace: string = "default"
+ let kustomization_name: string = "example-kustomization"
+ let helmrelease_name: string = "example-helmrelease"
+ let secret_name: string = "example-secret"
+ let create_secret_with_values: record = {
+ key1: "value1",
+ key2: "value2"
+ }
+ let test_public_key: string = "age1hsrtxphk7exrdc0kt8dgr8a8r3hx88v3xpsw0ezaxvefsy9asegqknppc0"
+ let test_private_key: string = "AGE-SECRET-KEY-12CC3A4LEDYF4S26UV6Z2MEG7ZQL9PTU5NHH6N3FN6FLJ5HACW9LQX0UWP2"
+
+ let actual: record = $resourcelist | (
+ helmrelease set values
+ --ks-namespace $ks_namespace
+ --create-secret-with-values $create_secret_with_values
+ --public-age-key $test_public_key
+ $kustomization_name
+ $helmrelease_name
+ {}
+ ''
+ $secret_name
+ )
+
+ let expected_secret_manifest: record = {
+ apiVersion: "v1"
+ kind: "Secret"
+ metadata: {
+ name: $secret_name,
+ namespace: "default"
+ annotations: {
+ # "config.kubernetes.io/path": "example-kustomization-example-helmrelease-example-secret.yaml",
+ # "internal.config.kubernetes.io/path": "example-kustomization-example-helmrelease-example-secret.yaml"
+ "config.kubernetes.io/path": "example-secret.yaml",
+ "internal.config.kubernetes.io/path": "example-secret.yaml"
+ }
+ }
+ data: {
+ "values.yaml": ($create_secret_with_values | to yaml | str trim | encode base64)
+ }
+ }
+
+ # NOTE: Here the secret is kept decrypted intentionally, since the same secret encrypted twice is never equal and we will need to decrypt them anyway to check they are equal
+ let expected_patch_content: record = {
+ target: { kind: "HelmRelease", name: $helmrelease_name }
+ patch: (
+ [
+ {
+ op: "add",
+ path: "/spec/valuesFrom/-",
+ value: { kind: "Secret", name: $secret_name, key: "values.yaml" }
+ }
+ ] | to yaml
+ )
+ }
+ let expected_resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ $expected_secret_manifest,
+ {
+ apiVersion: "kustomize.toolkit.fluxcd.io/v1"
+ kind: "Kustomization"
+ metadata: { name: $kustomization_name, namespace: $ks_namespace }
+ spec: { patches: [$expected_patch_content] }
+ }
+ ]
+ }
+
+ # Check that everything except the encrypted secret is equal
+ (assert equal
+ ($actual | patch resource delete '' 'Secret')
+ ($expected_resourcelist | patch resource delete '' 'Secret')
+ )
+
+ # Check that both secrets, once decrypted, are equal
+ let actual_secret_manifest: record = (
+ # First, extracts the manifest of the encrypted Secret
+ $actual
+ | patch resource keep '' 'Secret'
+ | get items.0
+ # Removes the filename annotations, since they are excluded from encryption
+ | reject $.metadata.annotations
+ # Then, decrypts the manifest using the private key
+ | to yaml
+ | keypair decrypt secret manifest $test_private_key
+ | from yaml
+ )
+ (assert equal
+ $actual_secret_manifest
+ ($expected_secret_manifest | reject $.metadata.annotations)
+ )
+}
diff --git a/docker/osm-nushell-krm-functions/krm/tests/patch.nu b/docker/osm-nushell-krm-functions/krm/tests/patch.nu
new file mode 100644
index 0000000..ec2ae70
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/tests/patch.nu
@@ -0,0 +1,1438 @@
+#!/usr/bin/env -S nu --stdin
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+
+use std assert
+use ../../krm/patch.nu *
+
+
+# --- resource keep tests ---
+
+export def "test patch resource keep no filters" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let actual: record = $resourcelist | resource keep
+ let expected: record = $resourcelist
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource keep by apiVersion" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let actual: record = $resourcelist | resource keep "apps/v1"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource keep by kind" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let actual: record = $resourcelist | resource keep '' "Deployment"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource keep by name" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let actual: record = $resourcelist | resource keep '' '' "example1"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource keep by namespace" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let actual: record = $resourcelist | resource keep '' '' '' "default"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource keep multiple filters" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let actual: record = $resourcelist | resource keep "apps/v1" "Deployment" '' "default"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource keep invalid input" []: [
+ nothing -> nothing
+] {
+ let invalid_input: record = {kind: "Invalid kind"}
+
+ let error_occurred: any = try {
+ $invalid_input | resource keep
+ } catch {
+ |err| $err.msg
+ }
+
+ assert ($error_occurred | str starts-with "Error: Expected a ResourceList, but received")
+}
+
+
+
+# --- resource delete tests ---
+
+export def "test patch resource delete no filters" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let actual: record = $resourcelist | resource delete
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: []
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource delete by apiVersion" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let actual: record = $resourcelist | resource delete "apps/v1"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource delete by kind" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let actual: record = $resourcelist | resource delete '' "Deployment"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource delete by name" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let actual: record = $resourcelist | resource delete '' '' "example1"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource delete by namespace" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let actual: record = $resourcelist | resource delete '' '' '' "default"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource delete multiple filters" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let actual: record = $resourcelist | resource delete "apps/v1" "Deployment" '' "default"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource delete invalid input" []: [
+ nothing -> nothing
+] {
+ let invalid_input: record = {kind: "Invalid kind"}
+
+ let error_occurred: any = try {
+ echo $invalid_input | resource delete
+ } catch {
+ |err| $err.msg
+ }
+
+ assert ($error_occurred | str starts-with "Error: Expected a ResourceList, but received")
+
+}
+
+
+
+# --- resource custom function tests ---
+
+export def "test patch resource custom function no filters" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let custom_function: closure = { |k: cell-path, v: any| ($in | upsert $k $v) }
+ let key_path: cell-path = $.metadata.labels
+ let value: any = { app: "example" }
+
+ let actual: record = $resourcelist | resource custom function $custom_function $key_path $value
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", labels: { app: "example" } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default", labels: { app: "example" } } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", labels: { app: "example" } } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource custom function by apiVersion" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let custom_function: closure = { |k: cell-path, v: any| ($in | upsert $k $v) }
+ let key_path: cell-path = $.metadata.labels
+ let value: any = { app: "example" }
+
+ let actual: record = $resourcelist | resource custom function $custom_function $key_path $value "apps/v1"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", labels: { app: "example" } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", labels: { app: "example" } } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource custom function by kind" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let custom_function: closure = { |k: cell-path, v: any| ($in | upsert $k $v) }
+ let key_path: cell-path = $.metadata.labels
+ let value: any = { app: "example" }
+
+ let actual: record = $resourcelist | resource custom function $custom_function $key_path $value '' "Deployment"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", labels: { app: "example" } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", labels: { app: "example" } } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource custom function by name" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let custom_function: closure = { |k: cell-path, v: any| ($in | upsert $k $v) }
+ let key_path: cell-path = $.metadata.labels
+ let value: any = { app: "example" }
+
+ let actual: record = $resourcelist | resource custom function $custom_function $key_path $value '' '' "example1"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", labels: { app: "example" } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource custom function by namespace" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let custom_function: closure = { |k: cell-path, v: any| ($in | upsert $k $v) }
+ let key_path: cell-path = $.metadata.labels
+ let value: any = { app: "example" }
+
+ let actual: record = $resourcelist | resource custom function $custom_function $key_path $value '' '' '' "default"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", labels: { app: "example" } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default", labels: { app: "example" } } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource custom function multiple filters" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let custom_function: closure = { |k: cell-path, v: any| ($in | upsert $k $v) }
+ let key_path: cell-path = $.metadata.labels
+ let value: any = { app: "example" }
+
+ let actual: record = $resourcelist | resource custom function $custom_function $key_path $value "apps/v1" "Deployment" '' "default"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", labels: { app: "example" } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource custom function invalid input" []: [
+ nothing -> nothing
+] {
+ let invalid_input: record = {kind: "Invalid kind"}
+
+ let error_occurred: any = try {
+ $invalid_input | resource custom function {|item, key_path, value| $item | update $key_path $value } $.metadata.labels { app: "example" }
+ } catch {
+ |err| $err.msg
+ }
+
+ assert ($error_occurred | str starts-with "Error: Expected a ResourceList, but received")
+}
+
+
+
+# --- resource upsert key tests ---
+
+export def "test patch resource upsert key no filters" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let key_path: cell-path = $.metadata.labels
+ let value: any = { app: "example" }
+
+ let actual: record = $resourcelist | resource upsert key $key_path $value
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", labels: { app: "example" } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default", labels: { app: "example" } } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", labels: { app: "example" } } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource upsert key by apiVersion" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let key_path: cell-path = $.metadata.labels
+ let value: any = { app: "example" }
+
+ let actual: record = $resourcelist | resource upsert key $key_path $value "apps/v1"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", labels: { app: "example" } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", labels: { app: "example" } } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource upsert key by kind" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let key_path: cell-path = $.metadata.labels
+ let value: any = { app: "example" }
+
+ let actual: record = $resourcelist | resource upsert key $key_path $value '' "Deployment"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", labels: { app: "example" } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", labels: { app: "example" } } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource upsert key by name" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let key_path: cell-path = $.metadata.labels
+ let value: any = { app: "example" }
+
+ let actual: record = $resourcelist | resource upsert key $key_path $value '' '' "example1"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", labels: { app: "example" } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource upsert key by namespace" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let key_path: cell-path = $.metadata.labels
+ let value: any = { app: "example" }
+
+ let actual: record = $resourcelist | resource upsert key $key_path $value '' '' '' "default"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", labels: { app: "example" } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default", labels: { app: "example" } } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource upsert key multiple filters" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let key_path: cell-path = $.metadata.labels
+ let value: any = { app: "example" }
+
+ let actual: record = $resourcelist | resource upsert key $key_path $value "apps/v1" "Deployment" '' "default"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", labels: { app: "example" } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource upsert key invalid input" []: [
+ nothing -> nothing
+] {
+ let invalid_input: record = {kind: "Invalid kind"}
+
+ let error_occurred: any = try {
+ $invalid_input | resource upsert key $.metadata.labels { app: "example" }
+ } catch {
+ |err| $err.msg
+ }
+
+ assert ($error_occurred | str starts-with "Error: Expected a ResourceList, but received")
+}
+
+
+
+# --- resource filename set tests ---
+
+export def "test patch resource filename set no index" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let filename: string = "example.yaml"
+ let actual: record = $resourcelist | resource filename set $filename
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { "config.kubernetes.io/path": $filename, "internal.config.kubernetes.io/path": $filename } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default", annotations: { "config.kubernetes.io/path": $filename, "internal.config.kubernetes.io/path": $filename } } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", annotations: { "config.kubernetes.io/path": $filename, "internal.config.kubernetes.io/path": $filename } } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource filename set with index" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let filename: string = "example.yaml"
+ let index: int = 0
+ let actual: record = $resourcelist | resource filename set --index $index $filename
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { "config.kubernetes.io/path": $filename, "internal.config.kubernetes.io/path": $filename, "config.kubernetes.io/index": "0", "internal.config.kubernetes.io/index": "0" } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default", annotations: { "config.kubernetes.io/path": $filename, "internal.config.kubernetes.io/path": $filename, "config.kubernetes.io/index": "0", "internal.config.kubernetes.io/index": "0" } } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", annotations: { "config.kubernetes.io/path": $filename, "internal.config.kubernetes.io/path": $filename, "config.kubernetes.io/index": "0", "internal.config.kubernetes.io/index": "0" } } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource filename set by apiVersion" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let filename: string = "example.yaml"
+ let actual: record = $resourcelist | resource filename set $filename "apps/v1"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { "config.kubernetes.io/path": $filename, "internal.config.kubernetes.io/path": $filename } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", annotations: { "config.kubernetes.io/path": $filename, "internal.config.kubernetes.io/path": $filename } } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource filename set by kind" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let filename: string = "example.yaml"
+ let actual: record = $resourcelist | resource filename set $filename '' "Deployment"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { "config.kubernetes.io/path": $filename, "internal.config.kubernetes.io/path": $filename } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", annotations: { "config.kubernetes.io/path": $filename, "internal.config.kubernetes.io/path": $filename } } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource filename set by name" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let filename: string = "example.yaml"
+ let actual: record = $resourcelist | resource filename set $filename '' '' "example1"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { "config.kubernetes.io/path": $filename, "internal.config.kubernetes.io/path": $filename } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource filename set by namespace" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let filename: string = "example.yaml"
+ let actual: record = $resourcelist | resource filename set $filename '' '' '' "default"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { "config.kubernetes.io/path": $filename, "internal.config.kubernetes.io/path": $filename } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default", annotations: { "config.kubernetes.io/path": $filename, "internal.config.kubernetes.io/path": $filename } } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch resource filename set multiple filters" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let filename: string = "example.yaml"
+ let actual: record = $resourcelist | resource filename set $filename "apps/v1" "Deployment" '' "default"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { "config.kubernetes.io/path": $filename, "internal.config.kubernetes.io/path": $filename } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+
+# TODO:
+
+# --- list append item tests ---
+
+export def "test patch list append item no filters" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let key_path: cell-path = $.metadata.annotations.example
+ let value: any = "example-value"
+ let actual: record = $resourcelist | list append item $key_path $value
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { example: ["example-value"] } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default", annotations: { example: ["example-value"] } } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", annotations: { example: ["example-value"] } } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch list append item existing list" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { example: ["initial-value"] } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default", annotations: { example: ["initial-value"] } } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", annotations: { example: ["initial-value"] } } }
+ ]
+ }
+
+ let key_path: cell-path = $.metadata.annotations.example
+ let value: any = "example-value"
+ let actual: record = $resourcelist | list append item $key_path $value
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { example: ["initial-value", "example-value"] } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default", annotations: { example: ["initial-value", "example-value"] } } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", annotations: { example: ["initial-value", "example-value"] } } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch list append item existing non-list value" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { example: "initial-value" } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default", annotations: { example: "initial-value" } } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", annotations: { example: "initial-value" } } }
+ ]
+ }
+
+ let key_path: cell-path = $.metadata.annotations.example
+ let value: any = "example-value"
+
+ let error_occurred: any = try {
+ $resourcelist | list append item $key_path $value
+ } catch {
+ |err| $err.msg
+ }
+
+ assert ($error_occurred | str starts-with "Error: Some matching keys are not lists. Non conformant:")
+}
+
+
+export def "test patch list append item by apiVersion" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let key_path: cell-path = $.metadata.annotations.example
+ let value: any = "example-value"
+ let actual: record = $resourcelist | list append item $key_path $value "apps/v1"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { example: ["example-value"] } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", annotations: { example: ["example-value"] } } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch list append item by kind" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let key_path: cell-path = $.metadata.annotations.example
+ let value: any = "example-value"
+ let actual: record = $resourcelist | list append item $key_path $value '' "Deployment"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { example: ["example-value"] } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", annotations: { example: ["example-value"] } } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch list append item by name" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let key_path: cell-path = $.metadata.annotations.example
+ let value: any = "example-value"
+ let actual: record = $resourcelist | list append item $key_path $value '' '' "example1"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { example: ["example-value"] } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch list append item by namespace" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let key_path: cell-path = $.metadata.annotations.example
+ let value: any = "example-value"
+ let actual: record = $resourcelist | list append item $key_path $value '' '' '' "default"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { example: ["example-value"] } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default", annotations: { example: ["example-value"] } } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch list append item multiple filters" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default" } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ let key_path: cell-path = $.metadata.annotations.example
+ let value: any = "example-value"
+ let actual: record = $resourcelist | list append item $key_path $value "apps/v1" 'Deployment' '' "default"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { example: ["example-value"] } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default" } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other" } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+# TODO:
+
+# --- list drop item tests ---
+
+export def "test patch list drop item no filters" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { example: ["value1", "value2"] } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default", annotations: { example: ["value1", "value2"] } } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", annotations: { example: ["value1", "value2"] } } }
+ ]
+ }
+
+ let key_path: cell-path = $.metadata.annotations.example
+ let value: any = "value1"
+ let actual: record = $resourcelist | list drop item $key_path $value
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { example: ["value2"] } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default", annotations: { example: ["value2"] } } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", annotations: { example: ["value2"] } } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch list drop item existing list with multiple values" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { example: ["value1", "value2", "value3"] } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default", annotations: { example: ["value1", "value2", "value3"] } } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", annotations: { example: ["value1", "value2", "value3"] } } }
+ ]
+ }
+
+ let key_path: cell-path = $.metadata.annotations.example
+ let value: any = "value2"
+ let actual: record = $resourcelist | list drop item $key_path $value
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { example: ["value1", "value3"] } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default", annotations: { example: ["value1", "value3"] } } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", annotations: { example: ["value1", "value3"] } } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch list drop item existing non-list value" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { example: "value1" } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default", annotations: { example: "value1" } } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", annotations: { example: "value1" } } }
+ ]
+ }
+
+ let key_path: cell-path = $.metadata.annotations.example
+ let value: any = "value1"
+
+ let error_occurred: any = try {
+ $resourcelist | list drop item $key_path $value
+ } catch {
+ |err| $err.msg
+ }
+
+ assert ($error_occurred | str starts-with "Error: Some matching keys are not lists. Non conformant:")
+}
+
+
+export def "test patch list drop item by apiVersion" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { example: ["value1", "value2"] } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default", annotations: { example: ["value1", "value2"] } } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", annotations: { example: ["value1", "value2"] } } }
+ ]
+ }
+
+ let key_path: cell-path = $.metadata.annotations.example
+ let value: any = "value1"
+ let actual: record = $resourcelist | list drop item $key_path $value "apps/v1"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { example: ["value2"] } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default", annotations: { example: ["value1", "value2"] } } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", annotations: { example: ["value2"] } } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch list drop item by kind" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { example: ["value1", "value2"] } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default", annotations: { example: ["value1", "value2"] } } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", annotations: { example: ["value1", "value2"] } } }
+ ]
+ }
+
+ let key_path: cell-path = $.metadata.annotations.example
+ let value: any = "value1"
+ let actual: record = $resourcelist | list drop item $key_path $value '' "Deployment"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { example: ["value2"] } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default", annotations: { example: ["value1", "value2"] } } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", annotations: { example: ["value2"] } } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch list drop item by name" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { example: ["value1", "value2"] } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default", annotations: { example: ["value1", "value2"] } } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", annotations: { example: ["value1", "value2"] } } }
+ ]
+ }
+
+ let key_path: cell-path = $.metadata.annotations.example
+ let value: any = "value1"
+ let actual: record = $resourcelist | list drop item $key_path $value '' '' "example1"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { example: ["value2"] } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default", annotations: { example: ["value1", "value2"] } } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", annotations: { example: ["value1", "value2"] } } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
+
+
+export def "test patch list drop item by namespace" []: [
+ nothing -> nothing
+] {
+ let resourcelist: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { example: ["value1", "value2"] } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default", annotations: { example: ["value1", "value2"] } } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", annotations: { example: ["value1", "value2"] } } }
+ ]
+ }
+
+ let key_path: cell-path = $.metadata.annotations.example
+ let value: any = "value1"
+ let actual: record = $resourcelist | list drop item $key_path $value '' '' '' "default"
+ let expected: record = {
+ apiVersion: "config.kubernetes.io/v1"
+ kind: "ResourceList"
+ items: [
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example1", namespace: "default", annotations: { example: ["value2"] } } }
+ { apiVersion: "v1", kind: "Pod", metadata: { name: "example2", namespace: "default", annotations: { example: ["value2"] } } }
+ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "example3", namespace: "other", annotations: { example: ["value1", "value2"] } } }
+ ]
+ }
+
+ assert equal $actual $expected
+}
diff --git a/docker/osm-nushell-krm-functions/krm/tests/strategicmergepatch.nu b/docker/osm-nushell-krm-functions/krm/tests/strategicmergepatch.nu
new file mode 100644
index 0000000..095c60a
--- /dev/null
+++ b/docker/osm-nushell-krm-functions/krm/tests/strategicmergepatch.nu
@@ -0,0 +1,144 @@
+#!/usr/bin/env -S nu --stdin
+#######################################################################################
+# Copyright ETSI Contributors and Others.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#######################################################################################
+
+
+use std assert
+use ../../krm/strategicmergepatch.nu *
+
+
+# --- create strategic merge patch tests ---
+
+export def "test strategicmergepatch create strategic merge patch basic" []: [
+ nothing -> nothing
+] {
+ let target: record = {
+ kind: "Deployment"
+ name: "podinfo"
+ }
+ let patch: record = {
+ apiVersion: "apps/v1"
+ kind: "Deployment"
+ metadata: {
+ name: "not-used"
+ }
+ spec: {
+ template: {
+ metadata: {
+ annotations: {
+ "cluster-autoscaler.kubernetes.io/safe-to-evict": "true"
+ }
+ }
+ }
+ }
+ }
+
+ let actual: record = create $target $patch
+ let expected: record = {
+ target: $target,
+ patch: ($patch | to yaml)
+ }
+
+ assert equal $actual.target $expected.target
+ assert equal $actual.patch $expected.patch
+}
+
+
+export def "test strategicmergepatch create strategic merge patch with dollar-patch directives" []: [
+ nothing -> nothing
+] {
+ let target: record = {
+ kind: "Deployment"
+ name: "podinfo"
+ }
+ let patch: record = {
+ apiVersion: "apps/v1"
+ kind: "Deployment"
+ metadata: {
+ name: "not-used"
+ }
+ spec: {
+ template: {
+ metadata: {
+ annotations: {
+ "cluster-autoscaler.kubernetes.io/safe-to-evict": "true"
+ }
+ }
+ }
+ }
+ "\$patch": "replace"
+ }
+
+ let actual: record = create $target $patch
+ let expected: record = {
+ target: $target,
+ patch: ($patch | to yaml)
+ }
+
+ assert equal $actual.target $expected.target
+ assert equal $actual.patch $expected.patch
+}
+
+
+# export def "test strategicmergepatch create strategic merge patch invalid target" []: [
+# nothing -> nothing
+# ] {
+# let target: record = {"Invalid target": "Invalid value"}
+# let patch: record = {
+# apiVersion: "apps/v1"
+# kind: "Deployment"
+# metadata: {
+# name: "not-used"
+# }
+# spec: {
+# template: {
+# metadata: {
+# annotations: {
+# "cluster-autoscaler.kubernetes.io/safe-to-evict": "true"
+# }
+# }
+# }
+# }
+# }
+
+# let error_occurred: error = try {
+# create $target $patch
+# } catch {
+# |err| $err.msg
+# }
+
+# assert equal $error_occurred "Expected a record"
+# }
+
+
+# export def "test strategicmergepatch create strategic merge patch invalid patch" []: [
+# nothing -> nothing
+# ] {
+# let target: record = {
+# kind: "Deployment"
+# name: "podinfo"
+# }
+# let patch: record = {"Invalid patch": "Invalid value"}
+
+# let error_occurred: error = try {
+# create $target $patch
+# } catch {
+# |err| $err.msg
+# }
+
+# assert equal $error_occurred "Expected a record"
+# }