From 8cbf41887b42100d093816ad4bc3e83c0da4620b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?=
Date: Sat, 24 Feb 2024 11:08:40 +0100
Subject: [PATCH] feat: add env vars support in command and script (#963)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat: add env vars support in command and script
Signed-off-by: Charles-Edouard Brétéché
* feat: add env vars support in command and script
Signed-off-by: Charles-Edouard Brétéché
---------
Signed-off-by: Charles-Edouard Brétéché
---
.crds/chainsaw.kyverno.io_tests.yaml | 102 +++++++++++
.crds/chainsaw.kyverno.io_teststeps.yaml | 102 +++++++++++
.schemas/json/test-chainsaw-v1alpha1.json | 168 ++++++++++++++++++
pkg/apis/v1alpha1/binding.go | 39 ++++
pkg/apis/v1alpha1/command.go | 4 +
pkg/apis/v1alpha1/script.go | 4 +
pkg/apis/v1alpha1/zz_generated.deepcopy.go | 14 ++
pkg/data/crds/chainsaw.kyverno.io_tests.yaml | 102 +++++++++++
.../crds/chainsaw.kyverno.io_teststeps.yaml | 102 +++++++++++
.../schemas/json/test-chainsaw-v1alpha1.json | 168 ++++++++++++++++++
pkg/runner/operations/command/operation.go | 22 +--
pkg/runner/operations/internal/env.go | 35 ++++
pkg/runner/operations/script/operation.go | 16 +-
pkg/runner/processors/bindings.go | 22 +--
testdata/e2e/examples/CATALOG.md | 1 +
testdata/e2e/examples/script-env/README.md | 20 +++
.../examples/script-env/chainsaw-test.yaml | 32 ++++
website/docs/apis/chainsaw.v1alpha1.md | 4 +
18 files changed, 920 insertions(+), 37 deletions(-)
create mode 100644 pkg/runner/operations/internal/env.go
create mode 100644 testdata/e2e/examples/script-env/README.md
create mode 100644 testdata/e2e/examples/script-env/chainsaw-test.yaml
diff --git a/.crds/chainsaw.kyverno.io_tests.yaml b/.crds/chainsaw.kyverno.io_tests.yaml
index 0fa51c427..aabc3f97b 100644
--- a/.crds/chainsaw.kyverno.io_tests.yaml
+++ b/.crds/chainsaw.kyverno.io_tests.yaml
@@ -136,6 +136,23 @@ spec:
description: Entrypoint is the command entry point
to run.
type: string
+ env:
+ description: Env defines additional environment variables.
+ items:
+ description: Binding represents a key/value set
+ as a binding in an executing test.
+ properties:
+ name:
+ description: Name the name of the binding.
+ type: string
+ value:
+ description: Value value of the binding.
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - name
+ - value
+ type: object
+ type: array
skipLogOutput:
description: SkipLogOutput removes the output from
the command. Useful for sensitive logs or to reduce
@@ -385,6 +402,23 @@ spec:
description: Content defines a shell script (run with
"sh -c ...").
type: string
+ env:
+ description: Env defines additional environment variables.
+ items:
+ description: Binding represents a key/value set
+ as a binding in an executing test.
+ properties:
+ name:
+ description: Name the name of the binding.
+ type: string
+ value:
+ description: Value value of the binding.
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - name
+ - value
+ type: object
+ type: array
skipLogOutput:
description: SkipLogOutput removes the output from
the command. Useful for sensitive logs or to reduce
@@ -442,6 +476,23 @@ spec:
description: Entrypoint is the command entry point
to run.
type: string
+ env:
+ description: Env defines additional environment variables.
+ items:
+ description: Binding represents a key/value set
+ as a binding in an executing test.
+ properties:
+ name:
+ description: Name the name of the binding.
+ type: string
+ value:
+ description: Value value of the binding.
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - name
+ - value
+ type: object
+ type: array
skipLogOutput:
description: SkipLogOutput removes the output from
the command. Useful for sensitive logs or to reduce
@@ -691,6 +742,23 @@ spec:
description: Content defines a shell script (run with
"sh -c ...").
type: string
+ env:
+ description: Env defines additional environment variables.
+ items:
+ description: Binding represents a key/value set
+ as a binding in an executing test.
+ properties:
+ name:
+ description: Name the name of the binding.
+ type: string
+ value:
+ description: Value value of the binding.
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - name
+ - value
+ type: object
+ type: array
skipLogOutput:
description: SkipLogOutput removes the output from
the command. Useful for sensitive logs or to reduce
@@ -893,6 +961,23 @@ spec:
description: Entrypoint is the command entry point
to run.
type: string
+ env:
+ description: Env defines additional environment variables.
+ items:
+ description: Binding represents a key/value set
+ as a binding in an executing test.
+ properties:
+ name:
+ description: Name the name of the binding.
+ type: string
+ value:
+ description: Value value of the binding.
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - name
+ - value
+ type: object
+ type: array
skipLogOutput:
description: SkipLogOutput removes the output from
the command. Useful for sensitive logs or to reduce
@@ -1203,6 +1288,23 @@ spec:
description: Content defines a shell script (run with
"sh -c ...").
type: string
+ env:
+ description: Env defines additional environment variables.
+ items:
+ description: Binding represents a key/value set
+ as a binding in an executing test.
+ properties:
+ name:
+ description: Name the name of the binding.
+ type: string
+ value:
+ description: Value value of the binding.
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - name
+ - value
+ type: object
+ type: array
skipLogOutput:
description: SkipLogOutput removes the output from
the command. Useful for sensitive logs or to reduce
diff --git a/.crds/chainsaw.kyverno.io_teststeps.yaml b/.crds/chainsaw.kyverno.io_teststeps.yaml
index 9f87038fe..65ea21658 100644
--- a/.crds/chainsaw.kyverno.io_teststeps.yaml
+++ b/.crds/chainsaw.kyverno.io_teststeps.yaml
@@ -78,6 +78,23 @@ spec:
entrypoint:
description: Entrypoint is the command entry point to run.
type: string
+ env:
+ description: Env defines additional environment variables.
+ items:
+ description: Binding represents a key/value set as a binding
+ in an executing test.
+ properties:
+ name:
+ description: Name the name of the binding.
+ type: string
+ value:
+ description: Value value of the binding.
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - name
+ - value
+ type: object
+ type: array
skipLogOutput:
description: SkipLogOutput removes the output from the command.
Useful for sensitive logs or to reduce noise.
@@ -311,6 +328,23 @@ spec:
description: Content defines a shell script (run with "sh
-c ...").
type: string
+ env:
+ description: Env defines additional environment variables.
+ items:
+ description: Binding represents a key/value set as a binding
+ in an executing test.
+ properties:
+ name:
+ description: Name the name of the binding.
+ type: string
+ value:
+ description: Value value of the binding.
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - name
+ - value
+ type: object
+ type: array
skipLogOutput:
description: SkipLogOutput removes the output from the command.
Useful for sensitive logs or to reduce noise.
@@ -365,6 +399,23 @@ spec:
entrypoint:
description: Entrypoint is the command entry point to run.
type: string
+ env:
+ description: Env defines additional environment variables.
+ items:
+ description: Binding represents a key/value set as a binding
+ in an executing test.
+ properties:
+ name:
+ description: Name the name of the binding.
+ type: string
+ value:
+ description: Value value of the binding.
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - name
+ - value
+ type: object
+ type: array
skipLogOutput:
description: SkipLogOutput removes the output from the command.
Useful for sensitive logs or to reduce noise.
@@ -598,6 +649,23 @@ spec:
description: Content defines a shell script (run with "sh
-c ...").
type: string
+ env:
+ description: Env defines additional environment variables.
+ items:
+ description: Binding represents a key/value set as a binding
+ in an executing test.
+ properties:
+ name:
+ description: Name the name of the binding.
+ type: string
+ value:
+ description: Value value of the binding.
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - name
+ - value
+ type: object
+ type: array
skipLogOutput:
description: SkipLogOutput removes the output from the command.
Useful for sensitive logs or to reduce noise.
@@ -791,6 +859,23 @@ spec:
entrypoint:
description: Entrypoint is the command entry point to run.
type: string
+ env:
+ description: Env defines additional environment variables.
+ items:
+ description: Binding represents a key/value set as a binding
+ in an executing test.
+ properties:
+ name:
+ description: Name the name of the binding.
+ type: string
+ value:
+ description: Value value of the binding.
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - name
+ - value
+ type: object
+ type: array
skipLogOutput:
description: SkipLogOutput removes the output from the command.
Useful for sensitive logs or to reduce noise.
@@ -1089,6 +1174,23 @@ spec:
description: Content defines a shell script (run with "sh
-c ...").
type: string
+ env:
+ description: Env defines additional environment variables.
+ items:
+ description: Binding represents a key/value set as a binding
+ in an executing test.
+ properties:
+ name:
+ description: Name the name of the binding.
+ type: string
+ value:
+ description: Value value of the binding.
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - name
+ - value
+ type: object
+ type: array
skipLogOutput:
description: SkipLogOutput removes the output from the command.
Useful for sensitive logs or to reduce noise.
diff --git a/.schemas/json/test-chainsaw-v1alpha1.json b/.schemas/json/test-chainsaw-v1alpha1.json
index d52dfdd60..2770a328d 100644
--- a/.schemas/json/test-chainsaw-v1alpha1.json
+++ b/.schemas/json/test-chainsaw-v1alpha1.json
@@ -459,6 +459,34 @@
"description": "Entrypoint is the command entry point to run.",
"type": "string"
},
+ "env": {
+ "description": "Env defines additional environment variables.",
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "description": "Binding represents a key/value set as a binding in an executing test.",
+ "type": [
+ "object",
+ "null"
+ ],
+ "required": [
+ "name",
+ "value"
+ ],
+ "properties": {
+ "name": {
+ "description": "Name the name of the binding.",
+ "type": "string"
+ },
+ "value": {
+ "description": "Value value of the binding.",
+ "x-kubernetes-preserve-unknown-fields": true
+ }
+ }
+ }
+ },
"skipLogOutput": {
"description": "SkipLogOutput removes the output from the command. Useful for sensitive logs or to reduce noise.",
"type": [
@@ -873,6 +901,34 @@
"null"
]
},
+ "env": {
+ "description": "Env defines additional environment variables.",
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "description": "Binding represents a key/value set as a binding in an executing test.",
+ "type": [
+ "object",
+ "null"
+ ],
+ "required": [
+ "name",
+ "value"
+ ],
+ "properties": {
+ "name": {
+ "description": "Name the name of the binding.",
+ "type": "string"
+ },
+ "value": {
+ "description": "Value value of the binding.",
+ "x-kubernetes-preserve-unknown-fields": true
+ }
+ }
+ }
+ },
"skipLogOutput": {
"description": "SkipLogOutput removes the output from the command. Useful for sensitive logs or to reduce noise.",
"type": [
@@ -973,6 +1029,34 @@
"description": "Entrypoint is the command entry point to run.",
"type": "string"
},
+ "env": {
+ "description": "Env defines additional environment variables.",
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "description": "Binding represents a key/value set as a binding in an executing test.",
+ "type": [
+ "object",
+ "null"
+ ],
+ "required": [
+ "name",
+ "value"
+ ],
+ "properties": {
+ "name": {
+ "description": "Name the name of the binding.",
+ "type": "string"
+ },
+ "value": {
+ "description": "Value value of the binding.",
+ "x-kubernetes-preserve-unknown-fields": true
+ }
+ }
+ }
+ },
"skipLogOutput": {
"description": "SkipLogOutput removes the output from the command. Useful for sensitive logs or to reduce noise.",
"type": [
@@ -1387,6 +1471,34 @@
"null"
]
},
+ "env": {
+ "description": "Env defines additional environment variables.",
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "description": "Binding represents a key/value set as a binding in an executing test.",
+ "type": [
+ "object",
+ "null"
+ ],
+ "required": [
+ "name",
+ "value"
+ ],
+ "properties": {
+ "name": {
+ "description": "Name the name of the binding.",
+ "type": "string"
+ },
+ "value": {
+ "description": "Value value of the binding.",
+ "x-kubernetes-preserve-unknown-fields": true
+ }
+ }
+ }
+ },
"skipLogOutput": {
"description": "SkipLogOutput removes the output from the command. Useful for sensitive logs or to reduce noise.",
"type": [
@@ -1716,6 +1828,34 @@
"description": "Entrypoint is the command entry point to run.",
"type": "string"
},
+ "env": {
+ "description": "Env defines additional environment variables.",
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "description": "Binding represents a key/value set as a binding in an executing test.",
+ "type": [
+ "object",
+ "null"
+ ],
+ "required": [
+ "name",
+ "value"
+ ],
+ "properties": {
+ "name": {
+ "description": "Name the name of the binding.",
+ "type": "string"
+ },
+ "value": {
+ "description": "Value value of the binding.",
+ "x-kubernetes-preserve-unknown-fields": true
+ }
+ }
+ }
+ },
"skipLogOutput": {
"description": "SkipLogOutput removes the output from the command. Useful for sensitive logs or to reduce noise.",
"type": [
@@ -2181,6 +2321,34 @@
"null"
]
},
+ "env": {
+ "description": "Env defines additional environment variables.",
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "description": "Binding represents a key/value set as a binding in an executing test.",
+ "type": [
+ "object",
+ "null"
+ ],
+ "required": [
+ "name",
+ "value"
+ ],
+ "properties": {
+ "name": {
+ "description": "Name the name of the binding.",
+ "type": "string"
+ },
+ "value": {
+ "description": "Value value of the binding.",
+ "x-kubernetes-preserve-unknown-fields": true
+ }
+ }
+ }
+ },
"skipLogOutput": {
"description": "SkipLogOutput removes the output from the command. Useful for sensitive logs or to reduce noise.",
"type": [
diff --git a/pkg/apis/v1alpha1/binding.go b/pkg/apis/v1alpha1/binding.go
index 26256f910..b6eb183e8 100644
--- a/pkg/apis/v1alpha1/binding.go
+++ b/pkg/apis/v1alpha1/binding.go
@@ -1,5 +1,19 @@
package v1alpha1
+import (
+ "fmt"
+ "regexp"
+ "strings"
+
+ "k8s.io/apimachinery/pkg/util/sets"
+)
+
+var (
+ identifier = regexp.MustCompile(`^\w+$`)
+ forbiddenNames = []string{"namespace", "client", "error", "values", "stdout", "stderr"}
+ forbidden = sets.New(forbiddenNames...)
+)
+
// Binding represents a key/value set as a binding in an executing test.
type Binding struct {
// Name the name of the binding.
@@ -10,3 +24,28 @@ type Binding struct {
// +kubebuilder:pruning:PreserveUnknownFields
Value Any `json:"value"`
}
+
+func (b Binding) CheckName() error {
+ return CheckBindingName(b.Name)
+}
+
+func (b Binding) CheckEnvName() error {
+ return CheckBindingEnvName(b.Name)
+}
+
+func CheckBindingName(name string) error {
+ if forbidden.Has(name) {
+ return fmt.Errorf("name is forbidden (%s), it must not be (%s)", name, strings.Join(forbiddenNames, ", "))
+ }
+ if !identifier.MatchString(name) {
+ return fmt.Errorf("invalid name %s", name)
+ }
+ return nil
+}
+
+func CheckBindingEnvName(name string) error {
+ if !identifier.MatchString(name) {
+ return fmt.Errorf("invalid name %s", name)
+ }
+ return nil
+}
diff --git a/pkg/apis/v1alpha1/command.go b/pkg/apis/v1alpha1/command.go
index 3fff7b4ce..376e22e6a 100644
--- a/pkg/apis/v1alpha1/command.go
+++ b/pkg/apis/v1alpha1/command.go
@@ -10,6 +10,10 @@ type Command struct {
// +optional
Timeout *metav1.Duration `json:"timeout,omitempty"`
+ // Env defines additional environment variables.
+ // +optional
+ Env []Binding `json:"env,omitempty"`
+
// Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).
// +optional
Cluster string `json:"cluster,omitempty"`
diff --git a/pkg/apis/v1alpha1/script.go b/pkg/apis/v1alpha1/script.go
index edc83236a..5fa1e3628 100644
--- a/pkg/apis/v1alpha1/script.go
+++ b/pkg/apis/v1alpha1/script.go
@@ -10,6 +10,10 @@ type Script struct {
// +optional
Timeout *metav1.Duration `json:"timeout,omitempty"`
+ // Env defines additional environment variables.
+ // +optional
+ Env []Binding `json:"env,omitempty"`
+
// Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).
// +optional
Cluster string `json:"cluster,omitempty"`
diff --git a/pkg/apis/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/v1alpha1/zz_generated.deepcopy.go
index 4fede44f5..04fb3e707 100644
--- a/pkg/apis/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/v1alpha1/zz_generated.deepcopy.go
@@ -203,6 +203,13 @@ func (in *Command) DeepCopyInto(out *Command) {
*out = new(v1.Duration)
**out = **in
}
+ if in.Env != nil {
+ in, out := &in.Env, &out.Env
+ *out = make([]Binding, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
if in.Args != nil {
in, out := &in.Args, &out.Args
*out = make([]string, len(*in))
@@ -834,6 +841,13 @@ func (in *Script) DeepCopyInto(out *Script) {
*out = new(v1.Duration)
**out = **in
}
+ if in.Env != nil {
+ in, out := &in.Env, &out.Env
+ *out = make([]Binding, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
if in.Check != nil {
in, out := &in.Check, &out.Check
*out = (*in).DeepCopy()
diff --git a/pkg/data/crds/chainsaw.kyverno.io_tests.yaml b/pkg/data/crds/chainsaw.kyverno.io_tests.yaml
index 0fa51c427..aabc3f97b 100644
--- a/pkg/data/crds/chainsaw.kyverno.io_tests.yaml
+++ b/pkg/data/crds/chainsaw.kyverno.io_tests.yaml
@@ -136,6 +136,23 @@ spec:
description: Entrypoint is the command entry point
to run.
type: string
+ env:
+ description: Env defines additional environment variables.
+ items:
+ description: Binding represents a key/value set
+ as a binding in an executing test.
+ properties:
+ name:
+ description: Name the name of the binding.
+ type: string
+ value:
+ description: Value value of the binding.
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - name
+ - value
+ type: object
+ type: array
skipLogOutput:
description: SkipLogOutput removes the output from
the command. Useful for sensitive logs or to reduce
@@ -385,6 +402,23 @@ spec:
description: Content defines a shell script (run with
"sh -c ...").
type: string
+ env:
+ description: Env defines additional environment variables.
+ items:
+ description: Binding represents a key/value set
+ as a binding in an executing test.
+ properties:
+ name:
+ description: Name the name of the binding.
+ type: string
+ value:
+ description: Value value of the binding.
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - name
+ - value
+ type: object
+ type: array
skipLogOutput:
description: SkipLogOutput removes the output from
the command. Useful for sensitive logs or to reduce
@@ -442,6 +476,23 @@ spec:
description: Entrypoint is the command entry point
to run.
type: string
+ env:
+ description: Env defines additional environment variables.
+ items:
+ description: Binding represents a key/value set
+ as a binding in an executing test.
+ properties:
+ name:
+ description: Name the name of the binding.
+ type: string
+ value:
+ description: Value value of the binding.
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - name
+ - value
+ type: object
+ type: array
skipLogOutput:
description: SkipLogOutput removes the output from
the command. Useful for sensitive logs or to reduce
@@ -691,6 +742,23 @@ spec:
description: Content defines a shell script (run with
"sh -c ...").
type: string
+ env:
+ description: Env defines additional environment variables.
+ items:
+ description: Binding represents a key/value set
+ as a binding in an executing test.
+ properties:
+ name:
+ description: Name the name of the binding.
+ type: string
+ value:
+ description: Value value of the binding.
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - name
+ - value
+ type: object
+ type: array
skipLogOutput:
description: SkipLogOutput removes the output from
the command. Useful for sensitive logs or to reduce
@@ -893,6 +961,23 @@ spec:
description: Entrypoint is the command entry point
to run.
type: string
+ env:
+ description: Env defines additional environment variables.
+ items:
+ description: Binding represents a key/value set
+ as a binding in an executing test.
+ properties:
+ name:
+ description: Name the name of the binding.
+ type: string
+ value:
+ description: Value value of the binding.
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - name
+ - value
+ type: object
+ type: array
skipLogOutput:
description: SkipLogOutput removes the output from
the command. Useful for sensitive logs or to reduce
@@ -1203,6 +1288,23 @@ spec:
description: Content defines a shell script (run with
"sh -c ...").
type: string
+ env:
+ description: Env defines additional environment variables.
+ items:
+ description: Binding represents a key/value set
+ as a binding in an executing test.
+ properties:
+ name:
+ description: Name the name of the binding.
+ type: string
+ value:
+ description: Value value of the binding.
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - name
+ - value
+ type: object
+ type: array
skipLogOutput:
description: SkipLogOutput removes the output from
the command. Useful for sensitive logs or to reduce
diff --git a/pkg/data/crds/chainsaw.kyverno.io_teststeps.yaml b/pkg/data/crds/chainsaw.kyverno.io_teststeps.yaml
index 9f87038fe..65ea21658 100644
--- a/pkg/data/crds/chainsaw.kyverno.io_teststeps.yaml
+++ b/pkg/data/crds/chainsaw.kyverno.io_teststeps.yaml
@@ -78,6 +78,23 @@ spec:
entrypoint:
description: Entrypoint is the command entry point to run.
type: string
+ env:
+ description: Env defines additional environment variables.
+ items:
+ description: Binding represents a key/value set as a binding
+ in an executing test.
+ properties:
+ name:
+ description: Name the name of the binding.
+ type: string
+ value:
+ description: Value value of the binding.
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - name
+ - value
+ type: object
+ type: array
skipLogOutput:
description: SkipLogOutput removes the output from the command.
Useful for sensitive logs or to reduce noise.
@@ -311,6 +328,23 @@ spec:
description: Content defines a shell script (run with "sh
-c ...").
type: string
+ env:
+ description: Env defines additional environment variables.
+ items:
+ description: Binding represents a key/value set as a binding
+ in an executing test.
+ properties:
+ name:
+ description: Name the name of the binding.
+ type: string
+ value:
+ description: Value value of the binding.
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - name
+ - value
+ type: object
+ type: array
skipLogOutput:
description: SkipLogOutput removes the output from the command.
Useful for sensitive logs or to reduce noise.
@@ -365,6 +399,23 @@ spec:
entrypoint:
description: Entrypoint is the command entry point to run.
type: string
+ env:
+ description: Env defines additional environment variables.
+ items:
+ description: Binding represents a key/value set as a binding
+ in an executing test.
+ properties:
+ name:
+ description: Name the name of the binding.
+ type: string
+ value:
+ description: Value value of the binding.
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - name
+ - value
+ type: object
+ type: array
skipLogOutput:
description: SkipLogOutput removes the output from the command.
Useful for sensitive logs or to reduce noise.
@@ -598,6 +649,23 @@ spec:
description: Content defines a shell script (run with "sh
-c ...").
type: string
+ env:
+ description: Env defines additional environment variables.
+ items:
+ description: Binding represents a key/value set as a binding
+ in an executing test.
+ properties:
+ name:
+ description: Name the name of the binding.
+ type: string
+ value:
+ description: Value value of the binding.
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - name
+ - value
+ type: object
+ type: array
skipLogOutput:
description: SkipLogOutput removes the output from the command.
Useful for sensitive logs or to reduce noise.
@@ -791,6 +859,23 @@ spec:
entrypoint:
description: Entrypoint is the command entry point to run.
type: string
+ env:
+ description: Env defines additional environment variables.
+ items:
+ description: Binding represents a key/value set as a binding
+ in an executing test.
+ properties:
+ name:
+ description: Name the name of the binding.
+ type: string
+ value:
+ description: Value value of the binding.
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - name
+ - value
+ type: object
+ type: array
skipLogOutput:
description: SkipLogOutput removes the output from the command.
Useful for sensitive logs or to reduce noise.
@@ -1089,6 +1174,23 @@ spec:
description: Content defines a shell script (run with "sh
-c ...").
type: string
+ env:
+ description: Env defines additional environment variables.
+ items:
+ description: Binding represents a key/value set as a binding
+ in an executing test.
+ properties:
+ name:
+ description: Name the name of the binding.
+ type: string
+ value:
+ description: Value value of the binding.
+ x-kubernetes-preserve-unknown-fields: true
+ required:
+ - name
+ - value
+ type: object
+ type: array
skipLogOutput:
description: SkipLogOutput removes the output from the command.
Useful for sensitive logs or to reduce noise.
diff --git a/pkg/data/schemas/json/test-chainsaw-v1alpha1.json b/pkg/data/schemas/json/test-chainsaw-v1alpha1.json
index d52dfdd60..2770a328d 100644
--- a/pkg/data/schemas/json/test-chainsaw-v1alpha1.json
+++ b/pkg/data/schemas/json/test-chainsaw-v1alpha1.json
@@ -459,6 +459,34 @@
"description": "Entrypoint is the command entry point to run.",
"type": "string"
},
+ "env": {
+ "description": "Env defines additional environment variables.",
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "description": "Binding represents a key/value set as a binding in an executing test.",
+ "type": [
+ "object",
+ "null"
+ ],
+ "required": [
+ "name",
+ "value"
+ ],
+ "properties": {
+ "name": {
+ "description": "Name the name of the binding.",
+ "type": "string"
+ },
+ "value": {
+ "description": "Value value of the binding.",
+ "x-kubernetes-preserve-unknown-fields": true
+ }
+ }
+ }
+ },
"skipLogOutput": {
"description": "SkipLogOutput removes the output from the command. Useful for sensitive logs or to reduce noise.",
"type": [
@@ -873,6 +901,34 @@
"null"
]
},
+ "env": {
+ "description": "Env defines additional environment variables.",
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "description": "Binding represents a key/value set as a binding in an executing test.",
+ "type": [
+ "object",
+ "null"
+ ],
+ "required": [
+ "name",
+ "value"
+ ],
+ "properties": {
+ "name": {
+ "description": "Name the name of the binding.",
+ "type": "string"
+ },
+ "value": {
+ "description": "Value value of the binding.",
+ "x-kubernetes-preserve-unknown-fields": true
+ }
+ }
+ }
+ },
"skipLogOutput": {
"description": "SkipLogOutput removes the output from the command. Useful for sensitive logs or to reduce noise.",
"type": [
@@ -973,6 +1029,34 @@
"description": "Entrypoint is the command entry point to run.",
"type": "string"
},
+ "env": {
+ "description": "Env defines additional environment variables.",
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "description": "Binding represents a key/value set as a binding in an executing test.",
+ "type": [
+ "object",
+ "null"
+ ],
+ "required": [
+ "name",
+ "value"
+ ],
+ "properties": {
+ "name": {
+ "description": "Name the name of the binding.",
+ "type": "string"
+ },
+ "value": {
+ "description": "Value value of the binding.",
+ "x-kubernetes-preserve-unknown-fields": true
+ }
+ }
+ }
+ },
"skipLogOutput": {
"description": "SkipLogOutput removes the output from the command. Useful for sensitive logs or to reduce noise.",
"type": [
@@ -1387,6 +1471,34 @@
"null"
]
},
+ "env": {
+ "description": "Env defines additional environment variables.",
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "description": "Binding represents a key/value set as a binding in an executing test.",
+ "type": [
+ "object",
+ "null"
+ ],
+ "required": [
+ "name",
+ "value"
+ ],
+ "properties": {
+ "name": {
+ "description": "Name the name of the binding.",
+ "type": "string"
+ },
+ "value": {
+ "description": "Value value of the binding.",
+ "x-kubernetes-preserve-unknown-fields": true
+ }
+ }
+ }
+ },
"skipLogOutput": {
"description": "SkipLogOutput removes the output from the command. Useful for sensitive logs or to reduce noise.",
"type": [
@@ -1716,6 +1828,34 @@
"description": "Entrypoint is the command entry point to run.",
"type": "string"
},
+ "env": {
+ "description": "Env defines additional environment variables.",
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "description": "Binding represents a key/value set as a binding in an executing test.",
+ "type": [
+ "object",
+ "null"
+ ],
+ "required": [
+ "name",
+ "value"
+ ],
+ "properties": {
+ "name": {
+ "description": "Name the name of the binding.",
+ "type": "string"
+ },
+ "value": {
+ "description": "Value value of the binding.",
+ "x-kubernetes-preserve-unknown-fields": true
+ }
+ }
+ }
+ },
"skipLogOutput": {
"description": "SkipLogOutput removes the output from the command. Useful for sensitive logs or to reduce noise.",
"type": [
@@ -2181,6 +2321,34 @@
"null"
]
},
+ "env": {
+ "description": "Env defines additional environment variables.",
+ "type": [
+ "array",
+ "null"
+ ],
+ "items": {
+ "description": "Binding represents a key/value set as a binding in an executing test.",
+ "type": [
+ "object",
+ "null"
+ ],
+ "required": [
+ "name",
+ "value"
+ ],
+ "properties": {
+ "name": {
+ "description": "Name the name of the binding.",
+ "type": "string"
+ },
+ "value": {
+ "description": "Value value of the binding.",
+ "x-kubernetes-preserve-unknown-fields": true
+ }
+ }
+ }
+ },
"skipLogOutput": {
"description": "SkipLogOutput removes the output from the command. Useful for sensitive logs or to reduce noise.",
"type": [
diff --git a/pkg/runner/operations/command/operation.go b/pkg/runner/operations/command/operation.go
index 2b382e302..226959077 100644
--- a/pkg/runner/operations/command/operation.go
+++ b/pkg/runner/operations/command/operation.go
@@ -10,7 +10,7 @@ import (
"github.com/jmespath-community/go-jmespath/pkg/binding"
"github.com/kyverno/chainsaw/pkg/apis/v1alpha1"
"github.com/kyverno/chainsaw/pkg/runner/check"
- "github.com/kyverno/chainsaw/pkg/runner/env"
+ environment "github.com/kyverno/chainsaw/pkg/runner/env"
"github.com/kyverno/chainsaw/pkg/runner/logging"
"github.com/kyverno/chainsaw/pkg/runner/operations"
"github.com/kyverno/chainsaw/pkg/runner/operations/internal"
@@ -48,7 +48,7 @@ func (o *operation) Exec(ctx context.Context, bindings binding.Bindings) (_err e
defer func() {
internal.LogEnd(logger, logging.Command, _err)
}()
- cmd, cancel, err := o.createCommand(ctx)
+ cmd, cancel, err := o.createCommand(ctx, bindings)
if cancel != nil {
defer cancel()
}
@@ -59,17 +59,19 @@ func (o *operation) Exec(ctx context.Context, bindings binding.Bindings) (_err e
return o.execute(ctx, bindings, cmd)
}
-func (o *operation) createCommand(ctx context.Context) (*exec.Cmd, context.CancelFunc, error) {
- var cancel context.CancelFunc
- args := env.Expand(map[string]string{"NAMESPACE": o.namespace}, o.command.Args...)
- cmd := exec.CommandContext(ctx, o.command.Entrypoint, args...) //nolint:gosec
- env := os.Environ()
+func (o *operation) createCommand(ctx context.Context, bindings binding.Bindings) (*exec.Cmd, context.CancelFunc, error) {
cwd, err := os.Getwd()
if err != nil {
return nil, nil, fmt.Errorf("failed to get current working directory (%w)", err)
}
+ maps, envs, err := internal.RegisterEnvs(ctx, o.namespace, bindings, o.command.Env...)
+ if err != nil {
+ return nil, nil, err
+ }
+ env := os.Environ()
+ env = append(env, envs...)
env = append(env, fmt.Sprintf("PATH=%s/bin/:%s", cwd, os.Getenv("PATH")))
- env = append(env, fmt.Sprintf("NAMESPACE=%s", o.namespace))
+ var cancel context.CancelFunc
if o.cfg != nil {
f, err := os.CreateTemp(o.basePath, "chainsaw-kubeconfig-")
if err != nil {
@@ -87,10 +89,10 @@ func (o *operation) createCommand(ctx context.Context) (*exec.Cmd, context.Cance
if err := restutils.Save(o.cfg, f); err != nil {
return nil, cancel, err
}
- fmt.Println(f.Name())
- fmt.Println(o.basePath)
env = append(env, fmt.Sprintf("KUBECONFIG=%s", filepath.Join(cwd, path)))
}
+ args := environment.Expand(maps, o.command.Args...)
+ cmd := exec.CommandContext(ctx, o.command.Entrypoint, args...) //nolint:gosec
cmd.Env = env
cmd.Dir = o.basePath
return cmd, cancel, nil
diff --git a/pkg/runner/operations/internal/env.go b/pkg/runner/operations/internal/env.go
new file mode 100644
index 000000000..248c07acd
--- /dev/null
+++ b/pkg/runner/operations/internal/env.go
@@ -0,0 +1,35 @@
+package internal
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/jmespath-community/go-jmespath/pkg/binding"
+ "github.com/kyverno/chainsaw/pkg/apis/v1alpha1"
+ mutation "github.com/kyverno/chainsaw/pkg/mutate"
+ "github.com/kyverno/chainsaw/pkg/runner/functions"
+ "github.com/kyverno/kyverno-json/pkg/engine/template"
+)
+
+func RegisterEnvs(ctx context.Context, namespace string, bindings binding.Bindings, envs ...v1alpha1.Binding) (map[string]string, []string, error) {
+ mapOut := map[string]string{}
+ var envsOut []string
+ for _, env := range envs {
+ if err := env.CheckEnvName(); err != nil {
+ return mapOut, envsOut, err
+ }
+ patched, err := mutation.Mutate(ctx, nil, mutation.Parse(ctx, env.Value.Value), nil, bindings, template.WithFunctionCaller(functions.Caller))
+ if err != nil {
+ return mapOut, envsOut, err
+ }
+ if patched, ok := patched.(string); !ok {
+ return mapOut, envsOut, fmt.Errorf("value must be a string (%s)", env.Name)
+ } else {
+ mapOut[env.Name] = patched
+ envsOut = append(envsOut, env.Name+"="+patched)
+ }
+ }
+ mapOut["NAMESPACE"] = namespace
+ envsOut = append(envsOut, "NAMESPACE="+namespace)
+ return mapOut, envsOut, nil
+}
diff --git a/pkg/runner/operations/script/operation.go b/pkg/runner/operations/script/operation.go
index be1741e48..ab25d0448 100644
--- a/pkg/runner/operations/script/operation.go
+++ b/pkg/runner/operations/script/operation.go
@@ -47,7 +47,7 @@ func (o *operation) Exec(ctx context.Context, bindings binding.Bindings) (err er
defer func() {
internal.LogEnd(logger, logging.Script, err)
}()
- cmd, cancel, err := o.createCommand(ctx)
+ cmd, cancel, err := o.createCommand(ctx, bindings)
if cancel != nil {
defer cancel()
}
@@ -58,16 +58,19 @@ func (o *operation) Exec(ctx context.Context, bindings binding.Bindings) (err er
return o.execute(ctx, bindings, cmd)
}
-func (o *operation) createCommand(ctx context.Context) (*exec.Cmd, context.CancelFunc, error) {
- var cancel context.CancelFunc
- cmd := exec.CommandContext(ctx, "sh", "-c", o.script.Content) //nolint:gosec
- env := os.Environ()
+func (o *operation) createCommand(ctx context.Context, bindings binding.Bindings) (*exec.Cmd, context.CancelFunc, error) {
cwd, err := os.Getwd()
if err != nil {
return nil, nil, fmt.Errorf("failed to get current working directory (%w)", err)
}
+ _, envs, err := internal.RegisterEnvs(ctx, o.namespace, bindings, o.script.Env...)
+ if err != nil {
+ return nil, nil, err
+ }
+ env := os.Environ()
+ env = append(env, envs...)
env = append(env, fmt.Sprintf("PATH=%s/bin/:%s", cwd, os.Getenv("PATH")))
- env = append(env, fmt.Sprintf("NAMESPACE=%s", o.namespace))
+ var cancel context.CancelFunc
if o.cfg != nil {
f, err := os.CreateTemp(o.basePath, "chainsaw-kubeconfig-")
if err != nil {
@@ -87,6 +90,7 @@ func (o *operation) createCommand(ctx context.Context) (*exec.Cmd, context.Cance
}
env = append(env, fmt.Sprintf("KUBECONFIG=%s", filepath.Join(cwd, path)))
}
+ cmd := exec.CommandContext(ctx, "sh", "-c", o.script.Content) //nolint:gosec
cmd.Env = env
cmd.Dir = o.basePath
return cmd, cancel, nil
diff --git a/pkg/runner/processors/bindings.go b/pkg/runner/processors/bindings.go
index 17fe6ad5f..f17c687dd 100644
--- a/pkg/runner/processors/bindings.go
+++ b/pkg/runner/processors/bindings.go
@@ -2,22 +2,12 @@ package processors
import (
"context"
- "fmt"
- "regexp"
- "strings"
"github.com/jmespath-community/go-jmespath/pkg/binding"
"github.com/kyverno/chainsaw/pkg/apis/v1alpha1"
mutation "github.com/kyverno/chainsaw/pkg/mutate"
"github.com/kyverno/chainsaw/pkg/runner/functions"
"github.com/kyverno/kyverno-json/pkg/engine/template"
- "k8s.io/apimachinery/pkg/util/sets"
-)
-
-var (
- identifier = regexp.MustCompile(`^\w+$`)
- forbiddenNames = []string{"namespace", "client", "error", "values", "stdout", "stderr"}
- forbidden = sets.New(forbiddenNames...)
)
func registerBindings(ctx context.Context, bindings binding.Bindings, variables ...v1alpha1.Binding) (binding.Bindings, error) {
@@ -25,7 +15,7 @@ func registerBindings(ctx context.Context, bindings binding.Bindings, variables
bindings = binding.NewBindings()
}
for _, variable := range variables {
- if err := checkBindingName(variable.Name); err != nil {
+ if err := variable.CheckName(); err != nil {
return bindings, err
}
patched, err := mutation.Mutate(ctx, nil, mutation.Parse(ctx, variable.Value.Value), nil, bindings, template.WithFunctionCaller(functions.Caller))
@@ -36,13 +26,3 @@ func registerBindings(ctx context.Context, bindings binding.Bindings, variables
}
return bindings, nil
}
-
-func checkBindingName(name string) error {
- if forbidden.Has(name) {
- return fmt.Errorf("binding name is forbidden (%s), it must not be (%s)", name, strings.Join(forbiddenNames, ", "))
- }
- if !identifier.MatchString(name) {
- return fmt.Errorf("invalid binding name %s", name)
- }
- return nil
-}
diff --git a/testdata/e2e/examples/CATALOG.md b/testdata/e2e/examples/CATALOG.md
index 8ebad8a38..a835d1266 100644
--- a/testdata/e2e/examples/CATALOG.md
+++ b/testdata/e2e/examples/CATALOG.md
@@ -14,6 +14,7 @@
- [namespace-template](namespace-template/README.md)
- [non-resource-assertion](non-resource-assertion/README.md)
- [patch](patch/README.md)
+- [script-env](script-env/README.md)
- [sleep](sleep/README.md)
- [template](template/README.md)
- [timeout](timeout/README.md)
diff --git a/testdata/e2e/examples/script-env/README.md b/testdata/e2e/examples/script-env/README.md
new file mode 100644
index 000000000..98c304dc2
--- /dev/null
+++ b/testdata/e2e/examples/script-env/README.md
@@ -0,0 +1,20 @@
+# Test: `script-env`
+
+*No description*
+
+### Steps
+
+| # | Name | Try | Catch | Finally |
+|:-:|---|:-:|:-:|:-:|
+| 1 | [step-1](#step-step-1) | 2 | 0 | 0 |
+
+## Step: `step-1`
+
+*No description*
+
+### Try
+
+| # | Operation | Description |
+|:-:|---|---|
+| 1 | `script` | *No description* |
+| 2 | `command` | *No description* |
diff --git a/testdata/e2e/examples/script-env/chainsaw-test.yaml b/testdata/e2e/examples/script-env/chainsaw-test.yaml
new file mode 100644
index 000000000..2ac88810d
--- /dev/null
+++ b/testdata/e2e/examples/script-env/chainsaw-test.yaml
@@ -0,0 +1,32 @@
+# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+ name: script-env
+spec:
+ bindings:
+ - name: chainsaw
+ value: chainsaw
+ steps:
+ - bindings:
+ - name: hello
+ value: hello
+ try:
+ - script:
+ env:
+ - name: GREETINGS
+ value: (join(' ', [$hello, $chainsaw]))
+ content: echo $GREETINGS
+ check:
+ ($error): ~
+ ($stdout): hello chainsaw
+ - command:
+ env:
+ - name: GREETINGS
+ value: (join(' ', [$hello, $chainsaw]))
+ entrypoint: echo
+ args:
+ - $GREETINGS
+ check:
+ ($error): ~
+ ($stdout): hello chainsaw
diff --git a/website/docs/apis/chainsaw.v1alpha1.md b/website/docs/apis/chainsaw.v1alpha1.md
index 32dca2589..f6fc562a9 100644
--- a/website/docs/apis/chainsaw.v1alpha1.md
+++ b/website/docs/apis/chainsaw.v1alpha1.md
@@ -94,10 +94,12 @@ during the testing process.
- [Apply](#chainsaw-kyverno-io-v1alpha1-Apply)
- [Assert](#chainsaw-kyverno-io-v1alpha1-Assert)
+- [Command](#chainsaw-kyverno-io-v1alpha1-Command)
- [Create](#chainsaw-kyverno-io-v1alpha1-Create)
- [Delete](#chainsaw-kyverno-io-v1alpha1-Delete)
- [Error](#chainsaw-kyverno-io-v1alpha1-Error)
- [Patch](#chainsaw-kyverno-io-v1alpha1-Patch)
+- [Script](#chainsaw-kyverno-io-v1alpha1-Script)
- [TestSpec](#chainsaw-kyverno-io-v1alpha1-TestSpec)
- [TestStepSpec](#chainsaw-kyverno-io-v1alpha1-TestStepSpec)
@@ -155,6 +157,7 @@ during the testing process.
| Field | Type | Required | Inline | Description |
|---|---|---|---|---|
| `timeout` | [`meta/v1.Duration`](https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration) | | | Timeout for the operation. Overrides the global timeout set in the Configuration.
|
+| `env` | [`[]Binding`](#chainsaw-kyverno-io-v1alpha1-Binding) | | | Env defines additional environment variables.
|
| `cluster` | `string` | | | Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).
|
| `entrypoint` | `string` | :white_check_mark: | | Entrypoint is the command entry point to run.
|
| `args` | `[]string` | | | Args is the command arguments.
|
@@ -536,6 +539,7 @@ If a resource doesn't exist yet in the cluster it will fail.
| Field | Type | Required | Inline | Description |
|---|---|---|---|---|
| `timeout` | [`meta/v1.Duration`](https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration) | | | Timeout for the operation. Overrides the global timeout set in the Configuration.
|
+| `env` | [`[]Binding`](#chainsaw-kyverno-io-v1alpha1-Binding) | | | Env defines additional environment variables.
|
| `cluster` | `string` | | | Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).
|
| `content` | `string` | | | Content defines a shell script (run with "sh -c ...").
|
| `skipLogOutput` | `bool` | | | SkipLogOutput removes the output from the command. Useful for sensitive logs or to reduce noise.
|