From a19c56d449faab50d10e1faefe3f401c565207cf Mon Sep 17 00:00:00 2001 From: cannarelladev Date: Thu, 12 Mar 2026 15:59:54 +0100 Subject: [PATCH] test(e2e): add chainsaw e2e test suite with reusable templates and full artifact lifecycle coverage Signed-off-by: c2ndev --- .github/workflows/test-e2e.yml | 48 +- .gitignore | 3 + Makefile | 45 +- config/manager/kustomization.yaml | 2 +- go.mod | 9 +- go.sum | 22 - test/e2e/chainsaw/.chainsaw.yaml | 37 ++ test/e2e/chainsaw/README.md | 244 ++++++++++ .../apply-assert-component.yaml | 44 ++ .../apply-assert-falco-daemonset.yaml | 42 ++ .../apply-assert-falco-deployment.yaml | 44 ++ .../assert-artifact-resolved-refs.yaml | 21 + .../assert-artifact-status.yaml | 21 + .../assert-component-status.yaml | 29 ++ .../_step_templates/assert-falco-status.yaml | 27 ++ .../verify-content-update.yaml | 31 ++ .../_step_templates/verify-dir-listing.yaml | 28 ++ .../_step_templates/verify-file-contains.yaml | 28 ++ .../_step_templates/verify-file-deleted.yaml | 25 + .../_step_templates/verify-file-rename.yaml | 28 ++ .../_step_templates/verify-file-size.yaml | 28 ++ .../_step_templates/verify-plugin-config.yaml | 25 + .../common/_step_templates/verify-plugin.yaml | 28 ++ .../_step_templates/wait-falco-pod-ready.yaml | 20 + test/e2e/chainsaw/common/scripts/common.sh | 211 +++++++++ .../chainsaw/common/scripts/debug_artifact.sh | 53 +++ .../common/scripts/verify_content_update.sh | 29 ++ .../common/scripts/verify_dir_listing.sh | 69 +++ .../common/scripts/verify_file_contains.sh | 28 ++ .../common/scripts/verify_file_deleted.sh | 35 ++ .../common/scripts/verify_file_rename.sh | 32 ++ .../common/scripts/verify_file_size.sh | 37 ++ .../common/scripts/verify_plugin_config.sh | 30 ++ .../common/scripts/wait_for_plugin.sh | 33 ++ .../lifecycle/chainsaw-test.yaml | 101 ++++ .../lifecycle/chainsaw-test.yaml | 136 ++++++ .../podtemplate/chainsaw-test.yaml | 94 ++++ .../metacollector/scale/chainsaw-test.yaml | 65 +++ .../config/lifecycle/chainsaw-test.yaml | 301 ++++++++++++ .../lifecycle/config-boundary-high.yaml | 8 + .../config/lifecycle/config-boundary-low.yaml | 8 + .../config/lifecycle/config-high.yaml | 9 + .../lifecycle/config-inline-updated.yaml | 11 + .../config/lifecycle/config-inline.yaml | 11 + .../chainsaw/config/lifecycle/config-low.yaml | 9 + .../config/lifecycle/config-matching.yaml | 11 + .../config/lifecycle/config-nonmatching.yaml | 11 + .../config/lifecycle/config-priority.yaml | 9 + .../falco/deployment/chainsaw-test.yaml | 53 +++ .../falco/lifecycle/chainsaw-test.yaml | 175 +++++++ .../falco/podtemplate/chainsaw-test.yaml | 73 +++ .../chainsaw/falco/version/chainsaw-test.yaml | 103 +++++ .../integration/full-stack/chainsaw-test.yaml | 172 +++++++ .../integration/full-stack/component.yaml | 7 + .../integration/full-stack/config.yaml | 10 + .../integration/full-stack/plugin.yaml | 12 + .../integration/full-stack/rulesfile.yaml | 13 + .../plugin/lifecycle/chainsaw-test.yaml | 187 ++++++++ .../plugin/lifecycle/plugin-dummy.yaml | 8 + .../plugin/lifecycle/plugin-json.yaml | 11 + .../plugin/lifecycle/plugin-nonmatching.yaml | 13 + .../rulesfile/edge-cases/chainsaw-test.yaml | 62 +++ .../rulesfile/edge-cases/rulesfile.yaml | 15 + .../rulesfile/lifecycle/chainsaw-test.yaml | 432 ++++++++++++++++++ .../rulesfile/lifecycle/configmap-all.yaml | 12 + .../lifecycle/configmap-updated.yaml | 12 + .../rulesfile/lifecycle/configmap.yaml | 12 + .../rulesfile/lifecycle/plugin-container.yaml | 11 + .../lifecycle/rulesfile-all-sources.yaml | 19 + .../lifecycle/rulesfile-configmap.yaml | 8 + .../lifecycle/rulesfile-inline-updated.yaml | 13 + .../rulesfile/lifecycle/rulesfile-inline.yaml | 13 + .../lifecycle/rulesfile-matching.yaml | 16 + .../lifecycle/rulesfile-multi-source.yaml | 19 + .../lifecycle/rulesfile-nonmatching.yaml | 16 + .../lifecycle/rulesfile-oci-updated.yaml | 11 + .../rulesfile/lifecycle/rulesfile-oci.yaml | 11 + .../lifecycle/rulesfile-remove-source.yaml | 19 + .../chainsaw/validation/chainsaw-test.yaml | 173 +++++++ test/e2e/e2e_suite_test.go | 55 --- test/e2e/e2e_test.go | 172 ------- test/utils/doc.go | 18 - test/utils/utils.go | 255 ----------- 83 files changed, 3885 insertions(+), 546 deletions(-) create mode 100644 test/e2e/chainsaw/.chainsaw.yaml create mode 100644 test/e2e/chainsaw/README.md create mode 100644 test/e2e/chainsaw/common/_step_templates/apply-assert-component.yaml create mode 100644 test/e2e/chainsaw/common/_step_templates/apply-assert-falco-daemonset.yaml create mode 100644 test/e2e/chainsaw/common/_step_templates/apply-assert-falco-deployment.yaml create mode 100644 test/e2e/chainsaw/common/_step_templates/assert-artifact-resolved-refs.yaml create mode 100644 test/e2e/chainsaw/common/_step_templates/assert-artifact-status.yaml create mode 100644 test/e2e/chainsaw/common/_step_templates/assert-component-status.yaml create mode 100644 test/e2e/chainsaw/common/_step_templates/assert-falco-status.yaml create mode 100644 test/e2e/chainsaw/common/_step_templates/verify-content-update.yaml create mode 100644 test/e2e/chainsaw/common/_step_templates/verify-dir-listing.yaml create mode 100644 test/e2e/chainsaw/common/_step_templates/verify-file-contains.yaml create mode 100644 test/e2e/chainsaw/common/_step_templates/verify-file-deleted.yaml create mode 100644 test/e2e/chainsaw/common/_step_templates/verify-file-rename.yaml create mode 100644 test/e2e/chainsaw/common/_step_templates/verify-file-size.yaml create mode 100644 test/e2e/chainsaw/common/_step_templates/verify-plugin-config.yaml create mode 100644 test/e2e/chainsaw/common/_step_templates/verify-plugin.yaml create mode 100644 test/e2e/chainsaw/common/_step_templates/wait-falco-pod-ready.yaml create mode 100755 test/e2e/chainsaw/common/scripts/common.sh create mode 100755 test/e2e/chainsaw/common/scripts/debug_artifact.sh create mode 100755 test/e2e/chainsaw/common/scripts/verify_content_update.sh create mode 100755 test/e2e/chainsaw/common/scripts/verify_dir_listing.sh create mode 100755 test/e2e/chainsaw/common/scripts/verify_file_contains.sh create mode 100755 test/e2e/chainsaw/common/scripts/verify_file_deleted.sh create mode 100755 test/e2e/chainsaw/common/scripts/verify_file_rename.sh create mode 100755 test/e2e/chainsaw/common/scripts/verify_file_size.sh create mode 100755 test/e2e/chainsaw/common/scripts/verify_plugin_config.sh create mode 100755 test/e2e/chainsaw/common/scripts/wait_for_plugin.sh create mode 100644 test/e2e/chainsaw/component/falcosidekick/lifecycle/chainsaw-test.yaml create mode 100644 test/e2e/chainsaw/component/metacollector/lifecycle/chainsaw-test.yaml create mode 100644 test/e2e/chainsaw/component/metacollector/podtemplate/chainsaw-test.yaml create mode 100644 test/e2e/chainsaw/component/metacollector/scale/chainsaw-test.yaml create mode 100644 test/e2e/chainsaw/config/lifecycle/chainsaw-test.yaml create mode 100644 test/e2e/chainsaw/config/lifecycle/config-boundary-high.yaml create mode 100644 test/e2e/chainsaw/config/lifecycle/config-boundary-low.yaml create mode 100644 test/e2e/chainsaw/config/lifecycle/config-high.yaml create mode 100644 test/e2e/chainsaw/config/lifecycle/config-inline-updated.yaml create mode 100644 test/e2e/chainsaw/config/lifecycle/config-inline.yaml create mode 100644 test/e2e/chainsaw/config/lifecycle/config-low.yaml create mode 100644 test/e2e/chainsaw/config/lifecycle/config-matching.yaml create mode 100644 test/e2e/chainsaw/config/lifecycle/config-nonmatching.yaml create mode 100644 test/e2e/chainsaw/config/lifecycle/config-priority.yaml create mode 100644 test/e2e/chainsaw/falco/deployment/chainsaw-test.yaml create mode 100644 test/e2e/chainsaw/falco/lifecycle/chainsaw-test.yaml create mode 100644 test/e2e/chainsaw/falco/podtemplate/chainsaw-test.yaml create mode 100644 test/e2e/chainsaw/falco/version/chainsaw-test.yaml create mode 100644 test/e2e/chainsaw/integration/full-stack/chainsaw-test.yaml create mode 100644 test/e2e/chainsaw/integration/full-stack/component.yaml create mode 100644 test/e2e/chainsaw/integration/full-stack/config.yaml create mode 100644 test/e2e/chainsaw/integration/full-stack/plugin.yaml create mode 100644 test/e2e/chainsaw/integration/full-stack/rulesfile.yaml create mode 100644 test/e2e/chainsaw/plugin/lifecycle/chainsaw-test.yaml create mode 100644 test/e2e/chainsaw/plugin/lifecycle/plugin-dummy.yaml create mode 100644 test/e2e/chainsaw/plugin/lifecycle/plugin-json.yaml create mode 100644 test/e2e/chainsaw/plugin/lifecycle/plugin-nonmatching.yaml create mode 100644 test/e2e/chainsaw/rulesfile/edge-cases/chainsaw-test.yaml create mode 100644 test/e2e/chainsaw/rulesfile/edge-cases/rulesfile.yaml create mode 100644 test/e2e/chainsaw/rulesfile/lifecycle/chainsaw-test.yaml create mode 100644 test/e2e/chainsaw/rulesfile/lifecycle/configmap-all.yaml create mode 100644 test/e2e/chainsaw/rulesfile/lifecycle/configmap-updated.yaml create mode 100644 test/e2e/chainsaw/rulesfile/lifecycle/configmap.yaml create mode 100644 test/e2e/chainsaw/rulesfile/lifecycle/plugin-container.yaml create mode 100644 test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-all-sources.yaml create mode 100644 test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-configmap.yaml create mode 100644 test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-inline-updated.yaml create mode 100644 test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-inline.yaml create mode 100644 test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-matching.yaml create mode 100644 test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-multi-source.yaml create mode 100644 test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-nonmatching.yaml create mode 100644 test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-oci-updated.yaml create mode 100644 test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-oci.yaml create mode 100644 test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-remove-source.yaml create mode 100644 test/e2e/chainsaw/validation/chainsaw-test.yaml delete mode 100644 test/e2e/e2e_suite_test.go delete mode 100644 test/e2e/e2e_test.go delete mode 100644 test/utils/doc.go delete mode 100644 test/utils/utils.go diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index c86d13e3..776b85db 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -8,7 +8,7 @@ on: jobs: test-e2e: - name: tests-e2e + name: Tests E2E runs-on: ubuntu-latest steps: - name: Clone the code @@ -19,19 +19,49 @@ jobs: with: go-version-file: go.mod - - name: Install the latest version of kind + - name: Install chainsaw + uses: kyverno/action-install-chainsaw@v0.2.12 + + - name: Install kind run: | curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64 chmod +x ./kind sudo mv ./kind /usr/local/bin/kind - - name: Verify kind installation - run: kind version - - name: Create kind cluster - run: kind create cluster + run: kind create cluster --wait 60s + + - name: Build artifact-operator image + run: make docker-build OPERATOR=artifact IMG=falcosecurity/artifact-operator:e2e - - name: Running Test e2e + - name: Build falco-operator image + run: make docker-build OPERATOR=instance IMG=falcosecurity/falco-operator:e2e ARTIFACT_OPERATOR_IMAGE=falcosecurity/artifact-operator:e2e + + - name: Load images into kind run: | - go mod tidy - make test-e2e + kind load docker-image falcosecurity/artifact-operator:e2e + kind load docker-image falcosecurity/falco-operator:e2e + + - name: Install CRDs + run: make install + + - name: Deploy operator + run: make deploy IMG=falcosecurity/falco-operator:e2e + + - name: Wait for operator + run: kubectl wait --for=condition=Available deployment/falco-operator -n falco-operator --timeout=120s + + - name: Run e2e tests + run: chainsaw test --config test/e2e/chainsaw/.chainsaw.yaml --test-dir test/e2e/chainsaw/ + + - name: Collect operator logs + if: failure() + run: | + echo "=== Falco Operator Logs ===" + kubectl logs -n falco-operator deployment/falco-operator --tail=200 || true + echo "=== Cluster Events ===" + kubectl get events --all-namespaces --sort-by='.lastTimestamp' | tail -50 || true + + - name: Cleanup + if: always() + run: kind delete cluster || true diff --git a/.gitignore b/.gitignore index ada68ff0..d587704a 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ go.work *.swp *.swo *~ + +# Chainsaw test reports +chainsaw-report.json diff --git a/Makefile b/Makefile index 115d23c7..b8c279f0 100644 --- a/Makefile +++ b/Makefile @@ -79,13 +79,18 @@ vet: ## Run go vet against code. test: manifests generate fmt vet setup-envtest ## Run tests. KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out -# TODO(user): To use a different vendor for e2e tests, modify the setup under 'tests/e2e'. -# The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally. -# Prometheus and CertManager are installed by default; skip with: -# - PROMETHEUS_INSTALL_SKIP=true -# - CERT_MANAGER_INSTALL_SKIP=true +# E2E test configuration using Kind cluster +E2E_FALCO_IMG ?= falcosecurity/falco-operator:e2e +E2E_ARTIFACT_IMG ?= falcosecurity/artifact-operator:e2e +CHAINSAW_TEST_DIR ?= ./test/e2e/chainsaw +CHAINSAW_CONFIG ?= ./test/e2e/chainsaw/.chainsaw.yaml + .PHONY: test-e2e -test-e2e: manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind. +test-e2e: chainsaw ## Run chainsaw e2e tests (requires running cluster with operator deployed). + $(CHAINSAW) test --config $(CHAINSAW_CONFIG) --test-dir $(CHAINSAW_TEST_DIR) + +.PHONY: test-e2e-setup +test-e2e-setup: manifests generate fmt vet ## Build images and deploy operator to Kind cluster for e2e testing. @command -v kind >/dev/null 2>&1 || { \ echo "Kind is not installed. Please install Kind manually."; \ exit 1; \ @@ -94,7 +99,26 @@ test-e2e: manifests generate fmt vet ## Run the e2e tests. Expected an isolated echo "No Kind cluster is running. Please start a Kind cluster before running the e2e tests."; \ exit 1; \ } - go test ./test/e2e/ -v -ginkgo.v + @echo "=== Building artifact-operator image ===" + @$(MAKE) docker-build OPERATOR=artifact IMG=$(E2E_ARTIFACT_IMG) + @echo "=== Building falco-operator image ===" + @$(MAKE) docker-build OPERATOR=instance IMG=$(E2E_FALCO_IMG) ARTIFACT_OPERATOR_IMAGE=$(E2E_ARTIFACT_IMG) + @echo "=== Loading images into Kind ===" + @kind load docker-image $(E2E_ARTIFACT_IMG) + @kind load docker-image $(E2E_FALCO_IMG) + @echo "=== Installing CRDs ===" + @$(MAKE) install + @echo "=== Deploying operator ===" + @$(MAKE) deploy IMG=$(E2E_FALCO_IMG) + @echo "=== Waiting for operator to be ready ===" + @kubectl wait --for=condition=Available deployment/falco-operator -n falco-operator --timeout=120s + +.PHONY: test-e2e-teardown +test-e2e-teardown: ## Undeploy operator after e2e testing. + @$(MAKE) undeploy ignore-not-found=true || true + +.PHONY: test-e2e-all +test-e2e-all: test-e2e-setup test-e2e test-e2e-teardown ## Full e2e test lifecycle: setup, test, teardown. .PHONY: lint lint: golangci-lint ## Run golangci-lint linter @@ -210,6 +234,7 @@ ENVTEST ?= $(LOCALBIN)/setup-envtest GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint GCI ?= $(LOCALBIN)/gci ADD_LICENSE ?= $(LOCALBIN)/addlicense +CHAINSAW ?= $(LOCALBIN)/chainsaw ## Tool Versions KUSTOMIZE_VERSION ?= v5.5.0 @@ -221,6 +246,7 @@ ENVTEST_K8S_VERSION ?= $(shell go list -m -f "{{ .Version }}" k8s.io/api | awk - GOLANGCI_LINT_VERSION ?= v2.11.1 GCI_VERSION ?= v0.13.5 ADD_LICENSE_VERSION ?= v1.1.1 +CHAINSAW_VERSION ?= v0.2.12 .PHONY: kustomize kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. @@ -259,6 +285,11 @@ $(GCI): $(LOCALBIN) addlicense: $(ADD_LICENSE) ## Download addlicense locally if necessary $(ADD_LICENSE): $(LOCALBIN) $(call go-install-tool,$(ADD_LICENSE),github.com/google/addlicense,$(ADD_LICENSE_VERSION)) + +.PHONY: chainsaw +chainsaw: $(CHAINSAW) ## Download chainsaw locally if necessary. +$(CHAINSAW): $(LOCALBIN) + $(call go-install-tool,$(CHAINSAW),github.com/kyverno/chainsaw,$(CHAINSAW_VERSION)) # go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist # $1 - target path with name of binary # $2 - package url which can be installed diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 8f3f52d9..e8222a8d 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -5,4 +5,4 @@ kind: Kustomization images: - name: falcosecurity/falco-operator newName: falcosecurity/falco-operator - newTag: latest + newTag: e2e diff --git a/go.mod b/go.mod index 3420640b..bb7fe7de 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,6 @@ module github.com/falcosecurity/falco-operator go 1.26.0 require ( - github.com/onsi/ginkgo/v2 v2.28.1 - github.com/onsi/gomega v1.39.1 github.com/opencontainers/image-spec v1.1.1 github.com/stretchr/testify v1.11.1 gopkg.in/yaml.v3 v3.0.1 @@ -21,7 +19,6 @@ require ( require ( cel.dev/expr v0.25.1 // indirect - github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect @@ -40,12 +37,10 @@ require ( github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect - github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/cel-go v0.26.0 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -55,6 +50,8 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/onsi/ginkgo/v2 v2.28.1 // indirect + github.com/onsi/gomega v1.39.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect @@ -79,7 +76,6 @@ require ( go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect - golang.org/x/mod v0.32.0 // indirect golang.org/x/net v0.49.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sync v0.19.0 // indirect @@ -87,7 +83,6 @@ require ( golang.org/x/term v0.39.0 // indirect golang.org/x/text v0.33.0 // indirect golang.org/x/time v0.11.0 // indirect - golang.org/x/tools v0.41.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect diff --git a/go.sum b/go.sum index 33c1bc61..f576c9ce 100644 --- a/go.sum +++ b/go.sum @@ -29,12 +29,6 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= -github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= -github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= -github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= -github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE= -github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -50,8 +44,6 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= -github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= @@ -76,8 +68,6 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= -github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= @@ -90,10 +80,6 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= -github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= -github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= -github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -144,14 +130,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= -github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= -github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= -github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= diff --git a/test/e2e/chainsaw/.chainsaw.yaml b/test/e2e/chainsaw/.chainsaw.yaml new file mode 100644 index 00000000..f587c6c7 --- /dev/null +++ b/test/e2e/chainsaw/.chainsaw.yaml @@ -0,0 +1,37 @@ +# Global Chainsaw configuration file. +# This file configures default timeouts and settings for all chainsaw e2e tests. +# Individual tests can override these settings if needed. +# +# Note: Some settings like --quiet are CLI-only and configured in the Makefile. + +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Configuration +metadata: + name: falco-operator-e2e +spec: + # Default timeouts for all operations. + # These are generous defaults to account for: + # - Slow CI environments + # - Falco pod startup time + # - OCI artifact download delays + timeouts: + apply: 30s + assert: 5m + cleanup: 2m + delete: 30s + error: 30s + exec: 3m + + # Test execution settings. + # Can be overridden via CLI: chainsaw test --parallel N + parallel: 10 + failFast: false + fullName: true + skipDelete: false + + # Debug collection on failure. + catch: + - events: {} + - podLogs: + selector: app.kubernetes.io/instance=falco-test + tail: 100 diff --git a/test/e2e/chainsaw/README.md b/test/e2e/chainsaw/README.md new file mode 100644 index 00000000..2e6986ba --- /dev/null +++ b/test/e2e/chainsaw/README.md @@ -0,0 +1,244 @@ +# Chainsaw E2E Tests + +This directory contains end-to-end tests for the Falco Operator using [Chainsaw](https://kyverno.github.io/chainsaw/) (Kyverno's declarative Kubernetes testing framework). + +## Running Tests + +### Prerequisites + +- A running Kubernetes cluster (Kind recommended) +- The operator deployed to the cluster +- [Chainsaw](https://kyverno.github.io/chainsaw/) installed + +### Quick Start (Full Lifecycle) + +```bash +# Setup + test + teardown in one command +make test-e2e-all +``` + +### Step-by-Step + +```bash +# 1. Setup: build images, deploy operator to Kind cluster +make test-e2e-setup + +# 2. Run all e2e tests +make test-e2e + +# 3. Run a specific test suite +make test-e2e CHAINSAW_TEST_DIR=./test/e2e/chainsaw/falco/lifecycle + +# 4. Teardown: undeploy operator +make test-e2e-teardown +``` + +### Makefile Targets + +| Target | Description | +|--------|-------------| +| `test-e2e-setup` | Build images, load into Kind, install CRDs, deploy operator | +| `test-e2e` | Run chainsaw e2e tests (requires running cluster with operator) | +| `test-e2e-teardown` | Undeploy operator | +| `test-e2e-all` | Full lifecycle: setup, test, teardown | + +## Global Configuration + +The [.chainsaw.yaml](.chainsaw.yaml) file centralizes default timeouts and settings for all tests. Individual tests inherit these defaults unless they override them. + +```yaml +spec: + timeouts: + apply: 30s # Time to apply resources + assert: 5m # Time for assertions to succeed (retries until timeout) + cleanup: 2m # Time for cleanup operations + delete: 30s # Time for deletion operations + error: 30s # Time before error timeout + exec: 3m # Time for script execution + parallel: 10 # Run 10 tests in parallel + failFast: false # Continue running tests even if one fails + fullName: true # Use full test names in output +``` + +When adjusting timeouts, prefer updating the global configuration over setting per-test or per-step overrides. This keeps behavior consistent and easy to reason about. + +## Directory Structure + +``` +test/e2e/chainsaw/ +├── .chainsaw.yaml # Global config +├── README.md # This file +├── TEST_MATRIX.md # Full test matrix and coverage tracking +├── common/ +│ ├── _step_templates/ # Reusable step templates (16) +│ │ ├── apply-assert-component.yaml +│ │ ├── apply-assert-falco-daemonset.yaml +│ │ ├── apply-assert-falco-deployment.yaml +│ │ ├── assert-artifact-resolved-refs.yaml +│ │ ├── assert-artifact-status.yaml +│ │ ├── assert-component-status.yaml +│ │ ├── assert-falco-status.yaml +│ │ ├── verify-content-update.yaml +│ │ ├── verify-dir-listing.yaml +│ │ ├── verify-file-contains.yaml +│ │ ├── verify-file-deleted.yaml +│ │ ├── verify-file-rename.yaml +│ │ ├── verify-file-size.yaml +│ │ ├── verify-plugin-config.yaml +│ │ ├── verify-plugin.yaml +│ │ └── wait-falco-pod-ready.yaml +│ └── scripts/ # Standalone verification scripts (10) +│ ├── common.sh # Shared utility functions (pod lookup, exec, retry) +│ ├── debug_artifact.sh # Diagnostic dump on test failure +│ ├── verify_content_update.sh +│ ├── verify_dir_listing.sh +│ ├── verify_file_contains.sh +│ ├── verify_file_deleted.sh +│ ├── verify_file_rename.sh +│ ├── verify_file_size.sh +│ ├── verify_plugin_config.sh +│ └── wait_for_plugin.sh +├── falco/ # Falco CRD tests +│ ├── lifecycle/ # DaemonSet CRUD, idempotent, type-switch, delete +│ ├── deployment/ # Deployment CRUD, status, scale +│ ├── podtemplate/ # Custom PodTemplateSpec +│ └── version/ # Version upgrade, image override +├── component/ # Component CRD tests +│ ├── metacollector/ +│ │ ├── lifecycle/ # Metacollector CRUD, idempotent, sub-resources, delete +│ │ ├── scale/ # Create replicas=2, scale down to 1 +│ │ └── podtemplate/ # Custom labels, deployment strategy +│ └── falcosidekick/ +│ └── lifecycle/ # Falcosidekick lifecycle, sub-resources, delete +├── config/ +│ └── lifecycle/ # Inline CRUD, priority, selector, boundary tests +├── rulesfile/ +│ ├── lifecycle/ # All sources, priority, selector, multi-source, delete +│ └── edge-cases/ # Missing ConfigMap handling +├── plugin/ +│ └── lifecycle/ # OCI create, multiple, update, selector, delete +├── integration/ +│ └── full-stack/ # Falco + Config + Rulesfile + Plugin + Component + type switch +└── validation/ # CRD validation (invalid type, priority bounds, Component) +``` + +**14 test suites** running in parallel via `make test-e2e`. + +## Best Practices + +### 1. Use Step Templates for Reusable Operations + +Step templates in `common/_step_templates/` encapsulate common operations shared across tests. Always prefer using an existing template over duplicating YAML. + +**Using a template:** +```yaml +- name: Create Falco instance + use: + template: ../../common/_step_templates/apply-assert-falco-daemonset.yaml +``` + +**Overriding bindings when needed:** +```yaml +- name: Verify config file + use: + template: ../../common/_step_templates/verify-file-contains.yaml + bindings: + - name: file_path + value: "/etc/falco/config.d/50-03-config-test-inline.yaml" + - name: expected_content + value: "json_output" +``` + +### 2. Define Test-Wide Bindings + +Define shared values at the top of the test spec to avoid repetition: + +```yaml +spec: + bindings: + - name: falco_name + value: falco-test + - name: falco_version + value: "0.43.0" +``` + +Step-level bindings override test-level bindings when templates need different values. + +### 3. Script Conventions + +Scripts in `common/scripts/` follow these conventions: + +**Standalone**: Every script is self-contained and runnable from the command line. All inputs come from environment variables: + +```bash +NAMESPACE=default \ +FILE_PATH=/etc/falco/config.d/50-03-config-test-inline.yaml \ +EXPECTED_CONTENT=json_output \ +bash common/scripts/verify_file_contains.sh +``` + +All environment variables are documented in a header comment at the top of each script. + +**Safety flags**: Every script starts with `set -o errexit`, `set -o nounset`, `set -o pipefail`. + +**Structured output**: Scripts output JSON on failure for debugging: +```json +{ + "error": "Pattern not found in file", + "file_path": "/etc/falco/config.d/50-03-config-test-inline.yaml", + "pattern": "json_output" +} +``` + +**Modular**: Scripts source `common.sh` for shared utility functions. Each script does one thing: +- `verify_file_contains.sh` — Verify file exists and contains a pattern +- `verify_content_update.sh` — Verify file content was updated (new present, old absent) +- `verify_file_size.sh` — Verify file has minimum size (OCI artifacts) +- `verify_file_rename.sh` — Verify file was renamed (priority changes) +- `verify_file_deleted.sh` — Verify file was removed +- `verify_dir_listing.sh` — Verify directory contains expected files +- `verify_plugin_config.sh` — Verify plugin entry in config +- `wait_for_plugin.sh` — Wait for plugin .so download +- `debug_artifact.sh` — Diagnostic dump on failure (pod status, logs, events) + +### 4. Adding New Tests + +1. Create a new directory under the appropriate CRD category +2. Create a `chainsaw-test.yaml` with test-level bindings +3. Reuse existing step templates where possible +4. For test-specific resources, create separate YAML files in the test directory +5. If a new common pattern emerges, extract it into a step template + +## Test Coverage + + +| Suite | CRD | Scenarios | +|-------|-----|-----------| +| `falco/lifecycle` | Falco | DaemonSet create, status, idempotent, type-switch, delete | +| `falco/deployment` | Falco | Deployment create, status, scale | +| `falco/version` | Falco | Version upgrade, image override | +| `falco/podtemplate` | Falco | Custom labels, tolerations, resources | +| `component/metacollector/lifecycle` | Component | Metacollector create, idempotent, sub-resources, delete | +| `component/metacollector/scale` | Component | Create replicas=2, scale down to 1, status verification | +| `component/metacollector/podtemplate` | Component | Custom labels/annotations, deployment strategy | +| `component/falcosidekick/lifecycle` | Component | Falcosidekick create, sub-resources (port 2801), status, delete | +| `config/lifecycle` | Config | Inline CRUD, priority rename, selector, boundary, status, delete | +| `rulesfile/lifecycle` | Rulesfile | Inline, OCI, ConfigMap, multi-source, selector, status, delete | +| `rulesfile/edge-cases` | Rulesfile | Missing ConfigMap handling, status failure assertions | +| `plugin/lifecycle` | Plugin | OCI create, multiple, update, selector, status, delete | +| `integration/full-stack` | All | Full stack + status assertions + type switch + Component coexistence | +| `validation` | All | CRD validation: type, priority bounds, OCIArtifact fields, plainHTTP/tls, Component type/replicas, Falco replicas | + +## Chainsaw Gotchas + +### Unbound Variables + +Chainsaw expressions like `($myvar)` fail if `myvar` is not bound. Always explicitly bind every variable used in expressions at either the test or step level. + +### Script Runs Once, Assert Retries + +In a step with a `script:` followed by an `assert:`, the script executes once and its output is captured. The `assert:` block retries independently. Structure scripts to handle retries internally when needed. + +### Relative Paths in Templates + +Script paths in templates use paths relative to the **test directory**, not the template directory. Templates in `common/_step_templates/` reference scripts as `../../common/scripts/foo.sh`, which resolves correctly when used from a test two levels deep (e.g., `falco/lifecycle/`). diff --git a/test/e2e/chainsaw/common/_step_templates/apply-assert-component.yaml b/test/e2e/chainsaw/common/_step_templates/apply-assert-component.yaml new file mode 100644 index 00000000..cec789e6 --- /dev/null +++ b/test/e2e/chainsaw/common/_step_templates/apply-assert-component.yaml @@ -0,0 +1,44 @@ +# Step template for creating a Component instance and asserting readiness. +# +# Required bindings: +# - component_name: Name of the Component instance. +# - component_type: Component type (e.g. metacollector). +# - component_replicas: Number of replicas to deploy. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: StepTemplate +metadata: + name: apply-assert-component +spec: + try: + # 1. Apply the Component CR. + - apply: + resource: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Component + metadata: + name: ($component_name) + spec: + component: + type: ($component_type) + replicas: ($component_replicas) + + # 2. Assert the Deployment is created and ready. + - assert: + resource: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: ($component_name) + status: + availableReplicas: ($component_replicas) + readyReplicas: ($component_replicas) + + # 3. Assert Component CR reflects Deployment type. + - assert: + resource: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Component + metadata: + name: ($component_name) + status: + resourceType: Deployment diff --git a/test/e2e/chainsaw/common/_step_templates/apply-assert-falco-daemonset.yaml b/test/e2e/chainsaw/common/_step_templates/apply-assert-falco-daemonset.yaml new file mode 100644 index 00000000..7fc46931 --- /dev/null +++ b/test/e2e/chainsaw/common/_step_templates/apply-assert-falco-daemonset.yaml @@ -0,0 +1,42 @@ +# Step template for creating a Falco instance as DaemonSet and asserting readiness. +# +# Required bindings: +# - falco_name: Name of the Falco instance. +# - falco_version: Falco version to deploy. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: StepTemplate +metadata: + name: apply-assert-falco-daemonset +spec: + try: + # 1. Apply the Falco CR as DaemonSet. + - apply: + resource: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Falco + metadata: + name: ($falco_name) + spec: + type: DaemonSet + version: ($falco_version) + + # 2. Assert the DaemonSet is created and ready. + - assert: + resource: + apiVersion: apps/v1 + kind: DaemonSet + metadata: + name: ($falco_name) + status: + numberReady: 1 + desiredNumberScheduled: 1 + + # 3. Assert Falco CR reflects DaemonSet type. + - assert: + resource: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Falco + metadata: + name: ($falco_name) + status: + resourceType: DaemonSet diff --git a/test/e2e/chainsaw/common/_step_templates/apply-assert-falco-deployment.yaml b/test/e2e/chainsaw/common/_step_templates/apply-assert-falco-deployment.yaml new file mode 100644 index 00000000..fd32df96 --- /dev/null +++ b/test/e2e/chainsaw/common/_step_templates/apply-assert-falco-deployment.yaml @@ -0,0 +1,44 @@ +# Step template for creating a Falco instance as Deployment and asserting readiness. +# +# Required bindings: +# - falco_name: Name of the Falco instance. +# - falco_version: Falco version to deploy. +# - falco_replicas: Number of replicas to deploy. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: StepTemplate +metadata: + name: apply-assert-falco-deployment +spec: + try: + # 1. Apply the Falco CR as Deployment. + - apply: + resource: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Falco + metadata: + name: ($falco_name) + spec: + type: Deployment + replicas: ($falco_replicas) + version: ($falco_version) + + # 2. Assert the Deployment is created and ready. + - assert: + resource: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: ($falco_name) + status: + availableReplicas: ($falco_replicas) + readyReplicas: ($falco_replicas) + + # 3. Assert Falco CR reflects Deployment type. + - assert: + resource: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Falco + metadata: + name: ($falco_name) + status: + resourceType: Deployment diff --git a/test/e2e/chainsaw/common/_step_templates/assert-artifact-resolved-refs.yaml b/test/e2e/chainsaw/common/_step_templates/assert-artifact-resolved-refs.yaml new file mode 100644 index 00000000..2d724683 --- /dev/null +++ b/test/e2e/chainsaw/common/_step_templates/assert-artifact-resolved-refs.yaml @@ -0,0 +1,21 @@ +# Step template for asserting ResolvedRefs condition on artifact CRDs. +# +# Required bindings: +# - artifact_kind: Kind of the artifact (Rulesfile, Plugin). +# - artifact_name: Name of the artifact instance. +# - expected_resolved_refs: Expected status of the ResolvedRefs condition (True/False). +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: StepTemplate +metadata: + name: assert-artifact-resolved-refs +spec: + try: + - assert: + resource: + apiVersion: artifact.falcosecurity.dev/v1alpha1 + kind: ($artifact_kind) + metadata: + name: ($artifact_name) + status: + (conditions[?type == 'ResolvedRefs']): + - status: ($expected_resolved_refs) diff --git a/test/e2e/chainsaw/common/_step_templates/assert-artifact-status.yaml b/test/e2e/chainsaw/common/_step_templates/assert-artifact-status.yaml new file mode 100644 index 00000000..0e9ce63a --- /dev/null +++ b/test/e2e/chainsaw/common/_step_templates/assert-artifact-status.yaml @@ -0,0 +1,21 @@ +# Step template for asserting artifact CRD status conditions. +# +# Required bindings: +# - artifact_kind: Kind of the artifact (Config, Rulesfile, Plugin). +# - artifact_name: Name of the artifact instance. +# - expected_programmed: Expected status of the Programmed condition (True/False). +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: StepTemplate +metadata: + name: assert-artifact-status +spec: + try: + - assert: + resource: + apiVersion: artifact.falcosecurity.dev/v1alpha1 + kind: ($artifact_kind) + metadata: + name: ($artifact_name) + status: + (conditions[?type == 'Programmed']): + - status: ($expected_programmed) diff --git a/test/e2e/chainsaw/common/_step_templates/assert-component-status.yaml b/test/e2e/chainsaw/common/_step_templates/assert-component-status.yaml new file mode 100644 index 00000000..13dba8fd --- /dev/null +++ b/test/e2e/chainsaw/common/_step_templates/assert-component-status.yaml @@ -0,0 +1,29 @@ +# Step template for asserting Component CR status conditions. +# +# Required bindings: +# - component_name: Name of the Component instance. +# - expected_reconciled: Expected status of the Reconciled condition (True/False). +# - expected_available: Expected status of the Available condition (True/False). +# - expected_replicas: Expected desiredReplicas and availableReplicas count. +# - expected_version: Expected version in status. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: StepTemplate +metadata: + name: assert-component-status +spec: + try: + - assert: + resource: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Component + metadata: + name: ($component_name) + status: + resourceType: Deployment + version: ($expected_version) + desiredReplicas: ($expected_replicas) + availableReplicas: ($expected_replicas) + (conditions[?type == 'Reconciled']): + - status: ($expected_reconciled) + (conditions[?type == 'Available']): + - status: ($expected_available) diff --git a/test/e2e/chainsaw/common/_step_templates/assert-falco-status.yaml b/test/e2e/chainsaw/common/_step_templates/assert-falco-status.yaml new file mode 100644 index 00000000..726d7331 --- /dev/null +++ b/test/e2e/chainsaw/common/_step_templates/assert-falco-status.yaml @@ -0,0 +1,27 @@ +# Step template for asserting Falco CR status conditions. +# +# Required bindings: +# - falco_name: Name of the Falco instance. +# - expected_reconciled: Expected status of the Reconciled condition (True/False). +# - expected_available: Expected status of the Available condition (True/False). +# - expected_resource_type: Expected resourceType in status (Deployment/DaemonSet). +# - expected_version: Expected version in status. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: StepTemplate +metadata: + name: assert-falco-status +spec: + try: + - assert: + resource: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Falco + metadata: + name: ($falco_name) + status: + resourceType: ($expected_resource_type) + version: ($expected_version) + (conditions[?type == 'Reconciled']): + - status: ($expected_reconciled) + (conditions[?type == 'Available']): + - status: ($expected_available) diff --git a/test/e2e/chainsaw/common/_step_templates/verify-content-update.yaml b/test/e2e/chainsaw/common/_step_templates/verify-content-update.yaml new file mode 100644 index 00000000..8b85d74b --- /dev/null +++ b/test/e2e/chainsaw/common/_step_templates/verify-content-update.yaml @@ -0,0 +1,31 @@ +# Step template for verifying a file content was updated in the Falco pod. +# +# Required bindings: +# - file_path: The file path to check inside the Falco pod. +# - new_content: The grep pattern for the new expected content. +# - old_content: The grep pattern for the old content that should be absent. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: StepTemplate +metadata: + name: verify-content-update +spec: + try: + - script: + env: + - name: NAMESPACE + value: ($namespace) + - name: FILE_PATH + value: ($file_path) + - name: NEW_CONTENT + value: ($new_content) + - name: OLD_CONTENT + value: ($old_content) + content: | + bash ../../common/scripts/verify_content_update.sh + catch: + - script: + env: + - name: NAMESPACE + value: ($namespace) + content: | + bash ../../common/scripts/debug_artifact.sh diff --git a/test/e2e/chainsaw/common/_step_templates/verify-dir-listing.yaml b/test/e2e/chainsaw/common/_step_templates/verify-dir-listing.yaml new file mode 100644 index 00000000..24a50f66 --- /dev/null +++ b/test/e2e/chainsaw/common/_step_templates/verify-dir-listing.yaml @@ -0,0 +1,28 @@ +# Step template for listing files in a directory and verifying expected files exist. +# +# Required bindings: +# - dir_path: The directory path to list inside the Falco pod. +# - expected_files: Comma-separated list of filenames expected in the directory. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: StepTemplate +metadata: + name: verify-dir-listing +spec: + try: + - script: + env: + - name: NAMESPACE + value: ($namespace) + - name: DIR_PATH + value: ($dir_path) + - name: EXPECTED_FILES + value: ($expected_files) + content: | + bash ../../common/scripts/verify_dir_listing.sh + catch: + - script: + env: + - name: NAMESPACE + value: ($namespace) + content: | + bash ../../common/scripts/debug_artifact.sh diff --git a/test/e2e/chainsaw/common/_step_templates/verify-file-contains.yaml b/test/e2e/chainsaw/common/_step_templates/verify-file-contains.yaml new file mode 100644 index 00000000..c36c8a53 --- /dev/null +++ b/test/e2e/chainsaw/common/_step_templates/verify-file-contains.yaml @@ -0,0 +1,28 @@ +# Step template for verifying a file exists in the Falco pod and contains expected content. +# +# Required bindings: +# - file_path: The file path to check inside the Falco pod. +# - expected_content: The grep pattern to search for in the file. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: StepTemplate +metadata: + name: verify-file-contains +spec: + try: + - script: + env: + - name: NAMESPACE + value: ($namespace) + - name: FILE_PATH + value: ($file_path) + - name: EXPECTED_CONTENT + value: ($expected_content) + content: | + bash ../../common/scripts/verify_file_contains.sh + catch: + - script: + env: + - name: NAMESPACE + value: ($namespace) + content: | + bash ../../common/scripts/debug_artifact.sh diff --git a/test/e2e/chainsaw/common/_step_templates/verify-file-deleted.yaml b/test/e2e/chainsaw/common/_step_templates/verify-file-deleted.yaml new file mode 100644 index 00000000..71a95c76 --- /dev/null +++ b/test/e2e/chainsaw/common/_step_templates/verify-file-deleted.yaml @@ -0,0 +1,25 @@ +# Step template for verifying a file has been removed from the Falco pod. +# +# Required bindings: +# - file_path: The file path that should no longer exist inside the Falco pod. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: StepTemplate +metadata: + name: verify-file-deleted +spec: + try: + - script: + env: + - name: NAMESPACE + value: ($namespace) + - name: FILE_PATH + value: ($file_path) + content: | + bash ../../common/scripts/verify_file_deleted.sh + catch: + - script: + env: + - name: NAMESPACE + value: ($namespace) + content: | + bash ../../common/scripts/debug_artifact.sh diff --git a/test/e2e/chainsaw/common/_step_templates/verify-file-rename.yaml b/test/e2e/chainsaw/common/_step_templates/verify-file-rename.yaml new file mode 100644 index 00000000..399ab765 --- /dev/null +++ b/test/e2e/chainsaw/common/_step_templates/verify-file-rename.yaml @@ -0,0 +1,28 @@ +# Step template for verifying a file was renamed (e.g., priority change). +# +# Required bindings: +# - old_file_path: The old file path that should no longer exist. +# - new_file_path: The new file path that should now exist. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: StepTemplate +metadata: + name: verify-file-rename +spec: + try: + - script: + env: + - name: NAMESPACE + value: ($namespace) + - name: OLD_FILE_PATH + value: ($old_file_path) + - name: NEW_FILE_PATH + value: ($new_file_path) + content: | + bash ../../common/scripts/verify_file_rename.sh + catch: + - script: + env: + - name: NAMESPACE + value: ($namespace) + content: | + bash ../../common/scripts/debug_artifact.sh diff --git a/test/e2e/chainsaw/common/_step_templates/verify-file-size.yaml b/test/e2e/chainsaw/common/_step_templates/verify-file-size.yaml new file mode 100644 index 00000000..faee8843 --- /dev/null +++ b/test/e2e/chainsaw/common/_step_templates/verify-file-size.yaml @@ -0,0 +1,28 @@ +# Step template for verifying a file exists with a minimum size (for OCI artifacts). +# +# Required bindings: +# - file_path: The file path to check inside the Falco pod. +# - min_size: The minimum expected file size in bytes. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: StepTemplate +metadata: + name: verify-file-size +spec: + try: + - script: + env: + - name: NAMESPACE + value: ($namespace) + - name: FILE_PATH + value: ($file_path) + - name: MIN_SIZE + value: ($min_size) + content: | + bash ../../common/scripts/verify_file_size.sh + catch: + - script: + env: + - name: NAMESPACE + value: ($namespace) + content: | + bash ../../common/scripts/debug_artifact.sh diff --git a/test/e2e/chainsaw/common/_step_templates/verify-plugin-config.yaml b/test/e2e/chainsaw/common/_step_templates/verify-plugin-config.yaml new file mode 100644 index 00000000..f72bff50 --- /dev/null +++ b/test/e2e/chainsaw/common/_step_templates/verify-plugin-config.yaml @@ -0,0 +1,25 @@ +# Step template for verifying the plugin config file contains expected plugin entries. +# +# Required bindings: +# - plugin_name: The plugin name to search for in the config. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: StepTemplate +metadata: + name: verify-plugin-config +spec: + try: + - script: + env: + - name: NAMESPACE + value: ($namespace) + - name: PLUGIN_NAME + value: ($plugin_name) + content: | + bash ../../common/scripts/verify_plugin_config.sh + catch: + - script: + env: + - name: NAMESPACE + value: ($namespace) + content: | + bash ../../common/scripts/debug_artifact.sh diff --git a/test/e2e/chainsaw/common/_step_templates/verify-plugin.yaml b/test/e2e/chainsaw/common/_step_templates/verify-plugin.yaml new file mode 100644 index 00000000..f665c6db --- /dev/null +++ b/test/e2e/chainsaw/common/_step_templates/verify-plugin.yaml @@ -0,0 +1,28 @@ +# Step template for verifying a plugin .so file was downloaded. +# +# Required bindings: +# - plugin_dir: The directory where plugins are stored. +# - min_size: (Optional) Minimum plugin file size in bytes. Default used by script: 1000. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: StepTemplate +metadata: + name: verify-plugin +spec: + try: + - script: + env: + - name: NAMESPACE + value: ($namespace) + - name: PLUGIN_DIR + value: ($plugin_dir) + - name: MIN_SIZE + value: ($min_size) + content: | + bash ../../common/scripts/wait_for_plugin.sh + catch: + - script: + env: + - name: NAMESPACE + value: ($namespace) + content: | + bash ../../common/scripts/debug_artifact.sh diff --git a/test/e2e/chainsaw/common/_step_templates/wait-falco-pod-ready.yaml b/test/e2e/chainsaw/common/_step_templates/wait-falco-pod-ready.yaml new file mode 100644 index 00000000..f117950c --- /dev/null +++ b/test/e2e/chainsaw/common/_step_templates/wait-falco-pod-ready.yaml @@ -0,0 +1,20 @@ +# Step template for waiting until a Falco pod is Ready. +# Since each test runs in its own ephemeral namespace with a single Falco instance, +# we wait for all pods in the namespace to be ready. +# +# Note: The `wait` operation does NOT support bindings or templating. +# Omitting `name` and `selector` makes it wait for ALL pods in the test namespace. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: StepTemplate +metadata: + name: wait-falco-pod-ready +spec: + try: + - wait: + apiVersion: v1 + kind: Pod + timeout: 90s + for: + condition: + name: Ready + value: "true" diff --git a/test/e2e/chainsaw/common/scripts/common.sh b/test/e2e/chainsaw/common/scripts/common.sh new file mode 100755 index 00000000..fd46caff --- /dev/null +++ b/test/e2e/chainsaw/common/scripts/common.sh @@ -0,0 +1,211 @@ +#!/bin/bash +# Common utility functions for Falco operator e2e test scripts. +# +# This library provides shared functions for pod operations, file verification, +# and Falco process checks. All scripts in this directory source this file. +# +# Variables (from environment): +# NAMESPACE: (Required) The namespace where the Falco pod is running. +# FALCO_LABEL: (Optional) Label selector for the Falco pod. Default: app.kubernetes.io/instance=falco-test. +# RETRY_COUNT: (Optional) Number of retries. Default: 30. +# RETRY_DELAY: (Optional) Delay between retries in seconds. Default: 2. + +set -o errexit +set -o nounset +set -o pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +FALCO_LABEL="${FALCO_LABEL:-app.kubernetes.io/instance=falco-test}" +RETRY_COUNT="${RETRY_COUNT:-30}" +RETRY_DELAY="${RETRY_DELAY:-2}" +_CACHED_POD="" + +# get_pod returns the name of the Falco pod matching the label selector. +# The result is cached for the lifetime of the script (one shell invocation). +get_pod() { + if [ -n "${_CACHED_POD}" ]; then + echo "${_CACHED_POD}" + return 0 + fi + local pod + pod=$(kubectl get pods -n "${NAMESPACE}" -l "${FALCO_LABEL}" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + if [ -z "${pod}" ]; then + echo '{"error": "No Falco pod found", "namespace": "'"${NAMESPACE}"'", "label": "'"${FALCO_LABEL}"'"}' >&2 + return 1 + fi + _CACHED_POD="${pod}" + echo "${pod}" +} + +# exec_in_falco runs a command inside the Falco container. +exec_in_falco() { + local pod + pod=$(get_pod) + kubectl exec -n "${NAMESPACE}" "${pod}" -c falco -- sh -c "$1" +} + +# wait_for_file waits for a file to exist in the Falco pod. +# Arguments: $1 = file path, $2 = description (for logging) +wait_for_file() { + local file_path="$1" + local description="${2:-file}" + local pod + pod=$(get_pod) + + echo "Waiting for ${description} at ${file_path}..." + for i in $(seq 1 "${RETRY_COUNT}"); do + if kubectl exec -n "${NAMESPACE}" "${pod}" -c falco -- test -f "${file_path}" 2>/dev/null; then + echo "OK: ${description} found (attempt ${i}/${RETRY_COUNT})" + return 0 + fi + sleep "${RETRY_DELAY}" + done + + echo '{"error": "File not found after '"${RETRY_COUNT}"' attempts", "file_path": "'"${file_path}"'", "description": "'"${description}"'", "kubectl_command": "kubectl exec -n '"${NAMESPACE}"' '"${pod}"' -c falco -- test -f '"${file_path}"'"}' >&2 + return 1 +} + +# verify_file_contains checks that a file in the Falco pod contains a pattern. +# Arguments: $1 = file path, $2 = grep pattern, $3 = description +verify_file_contains() { + local file_path="$1" + local pattern="$2" + local description="${3:-expected content}" + local pod + pod=$(get_pod) + + echo "Verifying ${file_path} contains ${description}..." + if kubectl exec -n "${NAMESPACE}" "${pod}" -c falco -- grep -q "${pattern}" "${file_path}" 2>/dev/null; then + echo "OK: ${description} found in ${file_path}" + return 0 + fi + + local actual_content + actual_content=$(kubectl exec -n "${NAMESPACE}" "${pod}" -c falco -- cat "${file_path}" 2>/dev/null || echo "") + echo '{"error": "Pattern not found in file", "file_path": "'"${file_path}"'", "pattern": "'"${pattern}"'", "actual_content": "'"$(echo "${actual_content}" | head -20)"'"}' >&2 + return 1 +} + +# verify_falco_running waits for the Falco process to be running in the pod. +# This uses the same retry pattern as wait_for_file and wait_for_content_update +# to handle container restarts (e.g., liveness probe kills during hot reload). +verify_falco_running() { + local pod + pod=$(get_pod) + + echo "Waiting for Falco process to be running..." + for i in $(seq 1 "${RETRY_COUNT}"); do + if kubectl exec -n "${NAMESPACE}" "${pod}" -c falco -- pgrep falco >/dev/null 2>&1; then + echo "OK: Falco process is running (attempt ${i}/${RETRY_COUNT})" + return 0 + fi + sleep "${RETRY_DELAY}" + done + + echo '{"error": "Falco process not running after '"${RETRY_COUNT}"' attempts", "namespace": "'"${NAMESPACE}"'", "pod": "'"${pod}"'"}' >&2 + return 1 +} + +# verify_falco_loaded_rules checks that Falco loaded a specific rules file. +# Arguments: $1 = rules filename +verify_falco_loaded_rules() { + local rules_file="$1" + local pod + pod=$(get_pod) + + echo "Verifying Falco loaded rules from ${rules_file}..." + if kubectl logs -n "${NAMESPACE}" "${pod}" -c falco 2>/dev/null | grep -q "${rules_file}"; then + echo "OK: Falco loaded rules from ${rules_file}" + return 0 + fi + + echo '{"error": "Rules file not found in Falco logs", "rules_file": "'"${rules_file}"'", "kubectl_command": "kubectl logs -n '"${NAMESPACE}"' '"${pod}"' -c falco"}' >&2 + return 1 +} + +# get_file_size returns the size of a file in the Falco pod in bytes. +# Arguments: $1 = file path +get_file_size() { + local file_path="$1" + local pod + pod=$(get_pod) + kubectl exec -n "${NAMESPACE}" "${pod}" -c falco -- stat -c%s "${file_path}" 2>/dev/null || echo "0" +} + +# wait_for_content_update waits for a file to contain new content and not old content. +# Arguments: $1 = file path, $2 = new content pattern, $3 = old content pattern +wait_for_content_update() { + local file_path="$1" + local new_content="$2" + local old_content="$3" + local pod + pod=$(get_pod) + + echo "Waiting for content update in ${file_path}..." + for i in $(seq 1 "${RETRY_COUNT}"); do + local content + content=$(kubectl exec -n "${NAMESPACE}" "${pod}" -c falco -- cat "${file_path}" 2>/dev/null || echo "") + if echo "${content}" | grep -q "${new_content}" && ! echo "${content}" | grep -q "${old_content}"; then + echo "OK: Content updated (attempt ${i}/${RETRY_COUNT})" + return 0 + fi + sleep "${RETRY_DELAY}" + done + + echo '{"error": "Content not updated after '"${RETRY_COUNT}"' attempts", "file_path": "'"${file_path}"'", "expected_new": "'"${new_content}"'", "expected_absent": "'"${old_content}"'"}' >&2 + return 1 +} + +# wait_for_file_rename waits for a file to be renamed (old file gone, new file exists). +# Arguments: $1 = old file path, $2 = new file path +wait_for_file_rename() { + local old_path="$1" + local new_path="$2" + local pod + pod=$(get_pod) + + echo "Waiting for file rename from ${old_path} to ${new_path}..." + for i in $(seq 1 "${RETRY_COUNT}"); do + local old_exists new_exists + old_exists=$(kubectl exec -n "${NAMESPACE}" "${pod}" -c falco -- test -f "${old_path}" 2>/dev/null && echo "yes" || echo "no") + new_exists=$(kubectl exec -n "${NAMESPACE}" "${pod}" -c falco -- test -f "${new_path}" 2>/dev/null && echo "yes" || echo "no") + + if [ "${old_exists}" = "no" ] && [ "${new_exists}" = "yes" ]; then + echo "OK: File renamed successfully (attempt ${i}/${RETRY_COUNT})" + return 0 + fi + sleep "${RETRY_DELAY}" + done + + echo '{"error": "File rename not detected after '"${RETRY_COUNT}"' attempts", "old_path": "'"${old_path}"'", "new_path": "'"${new_path}"'"}' >&2 + return 1 +} + +# wait_for_plugin waits for a .so plugin file to appear in a directory and exceed min_size. +# Arguments: $1 = plugin directory, $2 = minimum file size in bytes (optional, default: 0) +wait_for_plugin() { + local plugin_dir="$1" + local min_size="${2:-0}" + local pod + pod=$(get_pod) + + echo "Waiting for plugin in ${plugin_dir} (min size: ${min_size} bytes)..." >&2 + for i in $(seq 1 "${RETRY_COUNT}"); do + local plugin_file + plugin_file=$(kubectl exec -n "${NAMESPACE}" "${pod}" -c falco -- find "${plugin_dir}" -name "*.so" -type f 2>/dev/null | head -1) + if [ -n "${plugin_file}" ]; then + local file_size + file_size=$(kubectl exec -n "${NAMESPACE}" "${pod}" -c falco -- stat -c%s "${plugin_file}" 2>/dev/null || echo "0") + if [ "${file_size}" -gt "${min_size}" ]; then + echo "OK: Plugin found at ${plugin_file} (${file_size} bytes, attempt ${i}/${RETRY_COUNT})" >&2 + echo "${plugin_file}" + return 0 + fi + echo "Plugin found at ${plugin_file} but too small (${file_size} bytes), retrying... (attempt ${i}/${RETRY_COUNT})" >&2 + fi + sleep "${RETRY_DELAY}" + done + + echo '{"error": "Plugin not found after '"${RETRY_COUNT}"' attempts", "plugin_dir": "'"${plugin_dir}"'", "min_size": '"${min_size}"'}' >&2 + return 1 +} diff --git a/test/e2e/chainsaw/common/scripts/debug_artifact.sh b/test/e2e/chainsaw/common/scripts/debug_artifact.sh new file mode 100755 index 00000000..a27c70d7 --- /dev/null +++ b/test/e2e/chainsaw/common/scripts/debug_artifact.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# Debug helper for artifact operator e2e tests. +# Outputs diagnostic information when a test step fails. +# +# Variables (from environment): +# NAMESPACE: (Required) The namespace to debug. + +set -o errexit +set -o nounset +set -o pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/common.sh" + +echo "=== Debug Information ===" +echo "Namespace: ${NAMESPACE}" + +echo "" +echo "=== Pod Status ===" +kubectl get pods -n "${NAMESPACE}" -o wide 2>/dev/null || echo "No pods found" + +echo "" +echo "=== Rules Directory ===" +exec_in_falco "ls -la /etc/falco/rules.d/ 2>/dev/null" || echo "Unable to list rules directory" + +echo "" +echo "=== Config Directory ===" +exec_in_falco "ls -la /etc/falco/config.d/ 2>/dev/null" || echo "Unable to list config directory" + +echo "" +echo "=== Plugin Directory ===" +exec_in_falco "ls -la /usr/share/falco/plugins/ 2>/dev/null" || echo "Unable to list plugin directory" + +echo "" +echo "=== Artifact Operator Logs ===" +falco_pod=$(get_pod 2>/dev/null || echo "") +if [ -n "${falco_pod}" ]; then + kubectl logs -n "${NAMESPACE}" "${falco_pod}" -c artifact-operator --tail=50 2>/dev/null || echo "Unable to get artifact operator logs" +else + echo "No Falco pod found for artifact operator logs" +fi + +echo "" +echo "=== Falco Logs ===" +if [ -n "${falco_pod}" ]; then + kubectl logs -n "${NAMESPACE}" "${falco_pod}" -c falco --tail=50 2>/dev/null || echo "Unable to get Falco logs" +else + echo "No Falco pod found" +fi + +echo "" +echo "=== Events ===" +kubectl get events -n "${NAMESPACE}" --sort-by='.lastTimestamp' 2>/dev/null | tail -20 || echo "No events found" diff --git a/test/e2e/chainsaw/common/scripts/verify_content_update.sh b/test/e2e/chainsaw/common/scripts/verify_content_update.sh new file mode 100755 index 00000000..00cf2056 --- /dev/null +++ b/test/e2e/chainsaw/common/scripts/verify_content_update.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Verify that a file content was updated (new content present, old content absent). +# Can be run standalone for debugging. +# +# Variables (from environment): +# NAMESPACE: (Required) The namespace where the Falco pod is running. +# FILE_PATH: (Required) The file path to check inside the Falco pod. +# NEW_CONTENT: (Required) The grep pattern for the new expected content. +# OLD_CONTENT: (Required) The grep pattern for the old content that should be absent. +# FALCO_LABEL: (Optional) Label selector for the Falco pod. Default: app.kubernetes.io/instance=falco-test. +# RETRY_COUNT: (Optional) Number of retries. Default: 30. +# RETRY_DELAY: (Optional) Delay between retries in seconds. Default: 2. +# +# Example: +# NAMESPACE=default FILE_PATH=/etc/falco/config.d/50-03-config-test-inline.yaml \ +# NEW_CONTENT="json_output: false" OLD_CONTENT="json_output: true" \ +# bash verify_content_update.sh + +set -o errexit +set -o nounset +set -o pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/common.sh" + +wait_for_content_update "${FILE_PATH}" "${NEW_CONTENT}" "${OLD_CONTENT}" +verify_falco_running + +echo '{"status": "ok", "file_path": "'"${FILE_PATH}"'", "new_content": "'"${NEW_CONTENT}"'"}' diff --git a/test/e2e/chainsaw/common/scripts/verify_dir_listing.sh b/test/e2e/chainsaw/common/scripts/verify_dir_listing.sh new file mode 100755 index 00000000..bc4126a5 --- /dev/null +++ b/test/e2e/chainsaw/common/scripts/verify_dir_listing.sh @@ -0,0 +1,69 @@ +#!/bin/bash +# List files in a directory inside the Falco pod and verify expected files exist. +# Can be run standalone for debugging. +# +# Variables (from environment): +# NAMESPACE: (Required) The namespace where the Falco pod is running. +# DIR_PATH: (Required) The directory path to list inside the Falco pod. +# EXPECTED_FILES: (Required) Comma-separated list of filenames expected in the directory. +# FALCO_LABEL: (Optional) Label selector for the Falco pod. Default: app.kubernetes.io/instance=falco-test. +# RETRY_COUNT: (Optional) Number of retries. Default: 30. +# RETRY_DELAY: (Optional) Delay between retries in seconds. Default: 2. +# +# Example: +# NAMESPACE=default DIR_PATH=/etc/falco/rules.d \ +# EXPECTED_FILES="50-01-rules.yaml,50-02-rules.yaml" \ +# bash verify_dir_listing.sh + +set -o errexit +set -o nounset +set -o pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/common.sh" + +echo "Waiting for directory ${DIR_PATH} to exist..." +for i in $(seq 1 "${RETRY_COUNT}"); do + if exec_in_falco "test -d '${DIR_PATH}'" 2>/dev/null; then + echo "OK: Directory found (attempt ${i}/${RETRY_COUNT})" + break + fi + if [ "${i}" -eq "${RETRY_COUNT}" ]; then + echo '{"error": "Directory not found after '"${RETRY_COUNT}"' attempts", "dir_path": "'"${DIR_PATH}"'"}' >&2 + exit 1 + fi + sleep "${RETRY_DELAY}" +done + +echo "Listing files in ${DIR_PATH}..." +MISSING_FILES="" +IFS=',' read -ra FILES <<< "${EXPECTED_FILES}" + +for attempt in $(seq 1 "${RETRY_COUNT}"); do + FILE_LIST=$(exec_in_falco "ls -1 '${DIR_PATH}'" 2>/dev/null || echo "") + MISSING_FILES="" + for expected in "${FILES[@]}"; do + expected=$(echo "${expected}" | xargs) + if ! echo "${FILE_LIST}" | grep -Fxq "${expected}"; then + if [ -n "${MISSING_FILES}" ]; then + MISSING_FILES="${MISSING_FILES}, ${expected}" + else + MISSING_FILES="${expected}" + fi + fi + done + + if [ -z "${MISSING_FILES}" ]; then + break + fi + + if [ "${attempt}" -eq "${RETRY_COUNT}" ]; then + echo '{"error": "Expected files missing", "dir_path": "'"${DIR_PATH}"'", "missing_files": "'"${MISSING_FILES}"'", "actual_files": "'"$(echo "${FILE_LIST}" | tr '\n' ',')"'"}' >&2 + exit 1 + fi + sleep "${RETRY_DELAY}" +done + +verify_falco_running + +echo '{"status": "ok", "dir_path": "'"${DIR_PATH}"'", "files": "'"$(echo "${FILE_LIST}" | tr '\n' ',')"'"}' diff --git a/test/e2e/chainsaw/common/scripts/verify_file_contains.sh b/test/e2e/chainsaw/common/scripts/verify_file_contains.sh new file mode 100755 index 00000000..37179f84 --- /dev/null +++ b/test/e2e/chainsaw/common/scripts/verify_file_contains.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Verify that a file exists in the Falco pod and contains an expected pattern. +# Can be run standalone for debugging. +# +# Variables (from environment): +# NAMESPACE: (Required) The namespace where the Falco pod is running. +# FILE_PATH: (Required) The file path to check inside the Falco pod. +# EXPECTED_CONTENT: (Required) The grep pattern to search for in the file. +# FALCO_LABEL: (Optional) Label selector for the Falco pod. Default: app.kubernetes.io/instance=falco-test. +# RETRY_COUNT: (Optional) Number of retries. Default: 30. +# RETRY_DELAY: (Optional) Delay between retries in seconds. Default: 2. +# +# Example: +# NAMESPACE=default FILE_PATH=/etc/falco/config.d/50-03-config-test-inline.yaml \ +# EXPECTED_CONTENT=json_output bash verify_file_contains.sh + +set -o errexit +set -o nounset +set -o pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/common.sh" + +wait_for_file "${FILE_PATH}" "file at ${FILE_PATH}" +verify_file_contains "${FILE_PATH}" "${EXPECTED_CONTENT}" "expected content '${EXPECTED_CONTENT}'" +verify_falco_running + +echo '{"status": "ok", "file_path": "'"${FILE_PATH}"'", "pattern_found": "'"${EXPECTED_CONTENT}"'"}' diff --git a/test/e2e/chainsaw/common/scripts/verify_file_deleted.sh b/test/e2e/chainsaw/common/scripts/verify_file_deleted.sh new file mode 100755 index 00000000..5a9f5c3b --- /dev/null +++ b/test/e2e/chainsaw/common/scripts/verify_file_deleted.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# Verify that a file has been removed from the Falco pod. +# Can be run standalone for debugging. +# +# Variables (from environment): +# NAMESPACE: (Required) The namespace where the Falco pod is running. +# FILE_PATH: (Required) The file path that should no longer exist inside the Falco pod. +# FALCO_LABEL: (Optional) Label selector for the Falco pod. Default: app.kubernetes.io/instance=falco-test. +# RETRY_COUNT: (Optional) Number of retries. Default: 30. +# RETRY_DELAY: (Optional) Delay between retries in seconds. Default: 2. +# +# Example: +# NAMESPACE=default FILE_PATH=/etc/falco/rules.d/50-01-rulesfile-oci-oci.yaml \ +# bash verify_file_deleted.sh + +set -o errexit +set -o nounset +set -o pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/common.sh" + +echo "Waiting for file ${FILE_PATH} to be deleted..." +for i in $(seq 1 "${RETRY_COUNT}"); do + if ! exec_in_falco "test -f '${FILE_PATH}'" 2>/dev/null; then + echo "OK: File deleted (attempt ${i}/${RETRY_COUNT})" + verify_falco_running + echo '{"status": "ok", "message": "File deleted", "file": "'"${FILE_PATH}"'"}' + exit 0 + fi + sleep "${RETRY_DELAY}" +done + +echo '{"error": "File still exists after '"${RETRY_COUNT}"' attempts", "file": "'"${FILE_PATH}"'"}' >&2 +exit 1 diff --git a/test/e2e/chainsaw/common/scripts/verify_file_rename.sh b/test/e2e/chainsaw/common/scripts/verify_file_rename.sh new file mode 100755 index 00000000..7a8be91f --- /dev/null +++ b/test/e2e/chainsaw/common/scripts/verify_file_rename.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Verify that a file was renamed (old path gone, new path exists). +# Used when changing artifact priority causes a file rename. +# Can be run standalone for debugging. +# +# Variables (from environment): +# NAMESPACE: (Required) The namespace where the Falco pod is running. +# OLD_FILE_PATH: (Required) The old file path that should no longer exist. +# NEW_FILE_PATH: (Required) The new file path that should exist. +# FALCO_LABEL: (Optional) Label selector for the Falco pod. Default: app.kubernetes.io/instance=falco-test. +# RETRY_COUNT: (Optional) Number of retries. Default: 30. +# RETRY_DELAY: (Optional) Delay between retries in seconds. Default: 2. +# +# Example: +# NAMESPACE=default OLD_FILE_PATH=/etc/falco/rules.d/50-01-rulesfile-oci-oci.yaml \ +# NEW_FILE_PATH=/etc/falco/rules.d/60-01-rulesfile-oci-oci.yaml \ +# bash verify_file_rename.sh + +set -o errexit +set -o nounset +set -o pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/common.sh" + +wait_for_file_rename "${OLD_FILE_PATH}" "${NEW_FILE_PATH}" +verify_falco_running + +echo "Rules directory after rename:" +exec_in_falco "ls -la /etc/falco/rules.d/" 2>/dev/null || true + +echo '{"status": "ok", "old_path": "'"${OLD_FILE_PATH}"'", "new_path": "'"${NEW_FILE_PATH}"'"}' diff --git a/test/e2e/chainsaw/common/scripts/verify_file_size.sh b/test/e2e/chainsaw/common/scripts/verify_file_size.sh new file mode 100755 index 00000000..d3fec405 --- /dev/null +++ b/test/e2e/chainsaw/common/scripts/verify_file_size.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# Verify that a file exists and has a minimum size (for OCI artifact downloads). +# Can be run standalone for debugging. +# +# Variables (from environment): +# NAMESPACE: (Required) The namespace where the Falco pod is running. +# FILE_PATH: (Required) The file path to check inside the Falco pod. +# MIN_SIZE: (Required) The minimum expected file size in bytes. +# FALCO_LABEL: (Optional) Label selector for the Falco pod. Default: app.kubernetes.io/instance=falco-test. +# RETRY_COUNT: (Optional) Number of retries. Default: 30. +# RETRY_DELAY: (Optional) Delay between retries in seconds. Default: 2. +# +# Example: +# NAMESPACE=default FILE_PATH=/etc/falco/rules.d/50-01-rulesfile-oci-oci.yaml \ +# MIN_SIZE=100 bash verify_file_size.sh + +set -o errexit +set -o nounset +set -o pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/common.sh" + +wait_for_file "${FILE_PATH}" "file at ${FILE_PATH}" + +echo "Verifying file size..." +FILE_SIZE=$(get_file_size "${FILE_PATH}") +if [ "${FILE_SIZE}" -gt "${MIN_SIZE}" ]; then + echo "OK: File has valid size (${FILE_SIZE} bytes > ${MIN_SIZE} bytes)" +else + echo '{"error": "File too small", "file_path": "'"${FILE_PATH}"'", "actual_size": '"${FILE_SIZE}"', "min_size": '"${MIN_SIZE}"'}' >&2 + exit 1 +fi + +verify_falco_running + +echo '{"status": "ok", "file_path": "'"${FILE_PATH}"'", "file_size": '"${FILE_SIZE}"'}' diff --git a/test/e2e/chainsaw/common/scripts/verify_plugin_config.sh b/test/e2e/chainsaw/common/scripts/verify_plugin_config.sh new file mode 100755 index 00000000..2250f1b8 --- /dev/null +++ b/test/e2e/chainsaw/common/scripts/verify_plugin_config.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# Verify that the plugin config file contains expected plugin entries. +# Can be run standalone for debugging. +# +# Variables (from environment): +# NAMESPACE: (Required) The namespace where the Falco pod is running. +# PLUGIN_NAME: (Required) The plugin name to search for in the config. +# PLUGIN_CONFIG_PATH: (Optional) Path to the plugin config file. Default: /etc/falco/config.d/99-03-plugins-config-inline.yaml. +# FALCO_LABEL: (Optional) Label selector for the Falco pod. Default: app.kubernetes.io/instance=falco-test. +# RETRY_COUNT: (Optional) Number of retries. Default: 30. +# RETRY_DELAY: (Optional) Delay between retries in seconds. Default: 2. +# +# Example: +# NAMESPACE=default PLUGIN_NAME=json \ +# bash verify_plugin_config.sh + +set -o errexit +set -o nounset +set -o pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/common.sh" + +PLUGIN_CONFIG_PATH="${PLUGIN_CONFIG_PATH:-/etc/falco/config.d/99-03-plugins-config-inline.yaml}" + +wait_for_file "${PLUGIN_CONFIG_PATH}" "plugin config at ${PLUGIN_CONFIG_PATH}" +verify_file_contains "${PLUGIN_CONFIG_PATH}" "${PLUGIN_NAME}" "plugin '${PLUGIN_NAME}'" +verify_falco_running + +echo '{"status": "ok", "plugin_name": "'"${PLUGIN_NAME}"'", "config_path": "'"${PLUGIN_CONFIG_PATH}"'"}' diff --git a/test/e2e/chainsaw/common/scripts/wait_for_plugin.sh b/test/e2e/chainsaw/common/scripts/wait_for_plugin.sh new file mode 100755 index 00000000..f4ae7c17 --- /dev/null +++ b/test/e2e/chainsaw/common/scripts/wait_for_plugin.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# Wait for a plugin .so file to be downloaded and verify its size. +# Can be run standalone for debugging. +# +# Variables (from environment): +# NAMESPACE: (Required) The namespace where the Falco pod is running. +# PLUGIN_DIR: (Required) The directory where plugins are stored. +# MIN_SIZE: (Optional) Minimum plugin file size in bytes. Default: 1000. +# FALCO_LABEL: (Optional) Label selector for the Falco pod. Default: app.kubernetes.io/instance=falco-test. +# RETRY_COUNT: (Optional) Number of retries. Default: 30. +# RETRY_DELAY: (Optional) Delay between retries in seconds. Default: 2. +# +# Example: +# NAMESPACE=default PLUGIN_DIR=/usr/share/falco/plugins bash wait_for_plugin.sh + +set -o errexit +set -o nounset +set -o pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/common.sh" + +MIN_SIZE="${MIN_SIZE:-1000}" + +PLUGIN_FILE=$(wait_for_plugin "${PLUGIN_DIR}" "${MIN_SIZE}") + +verify_falco_running + +echo "Plugin directory contents:" +exec_in_falco "ls -la ${PLUGIN_DIR}" 2>/dev/null || true + +FILE_SIZE=$(get_file_size "${PLUGIN_FILE}") +echo '{"status": "ok", "plugin_file": "'"${PLUGIN_FILE}"'", "file_size": '"${FILE_SIZE}"'}' diff --git a/test/e2e/chainsaw/component/falcosidekick/lifecycle/chainsaw-test.yaml b/test/e2e/chainsaw/component/falcosidekick/lifecycle/chainsaw-test.yaml new file mode 100644 index 00000000..e515f15e --- /dev/null +++ b/test/e2e/chainsaw/component/falcosidekick/lifecycle/chainsaw-test.yaml @@ -0,0 +1,101 @@ +# Test: Falcosidekick Component lifecycle — create, status, sub-resources, delete. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: falcosidekick-lifecycle +spec: + description: "Component (falcosidekick) lifecycle: create → sub-resources → delete" + bindings: + - name: component_name + value: sk-test + - name: component_type + value: falcosidekick + - name: component_replicas + value: 2 + steps: + # --- create --- + - name: Create Component as falcosidekick + use: + template: ../../../common/_step_templates/apply-assert-component.yaml + - name: Assert Component status conditions + use: + template: ../../../common/_step_templates/assert-component-status.yaml + bindings: + - name: expected_reconciled + value: "True" + - name: expected_available + value: "True" + - name: expected_replicas + value: 2 + - name: expected_version + value: "2.32.0" + # --- sub-resources --- + - name: Verify sub-resources exist + try: + - assert: + resource: + apiVersion: v1 + kind: ServiceAccount + metadata: + name: ($component_name) + - assert: + resource: + apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: + name: ($component_name) + - assert: + resource: + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + name: ($component_name) + - assert: + resource: + apiVersion: v1 + kind: Service + metadata: + name: ($component_name) + spec: + ports: + - port: 2801 + # --- delete --- + - name: Delete Component CR + try: + - delete: + ref: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Component + name: ($component_name) + - name: Verify namespaced resources are gone + try: + - error: + resource: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: ($component_name) + - error: + resource: + apiVersion: v1 + kind: ServiceAccount + metadata: + name: ($component_name) + - error: + resource: + apiVersion: v1 + kind: Service + metadata: + name: ($component_name) + - error: + resource: + apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: + name: ($component_name) + - error: + resource: + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + name: ($component_name) diff --git a/test/e2e/chainsaw/component/metacollector/lifecycle/chainsaw-test.yaml b/test/e2e/chainsaw/component/metacollector/lifecycle/chainsaw-test.yaml new file mode 100644 index 00000000..481ec311 --- /dev/null +++ b/test/e2e/chainsaw/component/metacollector/lifecycle/chainsaw-test.yaml @@ -0,0 +1,136 @@ +# Test: Component CRD lifecycle — create, idempotent, sub-resources, delete. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: metacollector-lifecycle +spec: + description: "Component (metacollector) lifecycle: create → idempotent → sub-resources → delete" + bindings: + - name: component_name + value: mc-test + - name: component_type + value: metacollector + - name: component_replicas + value: 1 + steps: + # --- component-create --- + - name: Create Component as metacollector + use: + template: ../../../common/_step_templates/apply-assert-component.yaml + - name: Assert Component status conditions + use: + template: ../../../common/_step_templates/assert-component-status.yaml + bindings: + - name: expected_reconciled + value: "True" + - name: expected_available + value: "True" + - name: expected_replicas + value: 1 + - name: expected_version + value: "0.1.2" + # --- component-sub-resources --- + - name: Verify sub-resources exist + try: + - assert: + resource: + apiVersion: v1 + kind: ServiceAccount + metadata: + name: ($component_name) + - assert: + resource: + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + name: (join('--', [$component_name, $namespace])) + - assert: + resource: + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + name: (join('--', [$component_name, $namespace])) + - assert: + resource: + apiVersion: v1 + kind: Service + metadata: + name: ($component_name) + # --- component-idempotent --- + - name: Re-apply the same Component resource + try: + - apply: + resource: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Component + metadata: + name: ($component_name) + spec: + component: + type: ($component_type) + replicas: ($component_replicas) + - name: Assert status stable after re-apply + use: + template: ../../../common/_step_templates/assert-component-status.yaml + bindings: + - name: expected_reconciled + value: "True" + - name: expected_available + value: "True" + - name: expected_replicas + value: 1 + - name: expected_version + value: "0.1.2" + - name: Verify Deployment stable after re-apply + try: + - assert: + resource: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: ($component_name) + status: + availableReplicas: 1 + readyReplicas: 1 + # --- component-delete --- + - name: Delete Component CR + try: + - delete: + ref: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Component + name: ($component_name) + - name: Verify namespaced resources are gone + try: + - error: + resource: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: ($component_name) + - error: + resource: + apiVersion: v1 + kind: ServiceAccount + metadata: + name: ($component_name) + - error: + resource: + apiVersion: v1 + kind: Service + metadata: + name: ($component_name) + - name: Verify cluster-scoped resources are gone + try: + - error: + resource: + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + name: (join('--', [$component_name, $namespace])) + - error: + resource: + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + name: (join('--', [$component_name, $namespace])) diff --git a/test/e2e/chainsaw/component/metacollector/podtemplate/chainsaw-test.yaml b/test/e2e/chainsaw/component/metacollector/podtemplate/chainsaw-test.yaml new file mode 100644 index 00000000..6f1f7341 --- /dev/null +++ b/test/e2e/chainsaw/component/metacollector/podtemplate/chainsaw-test.yaml @@ -0,0 +1,94 @@ +# Test: Component podtemplate — custom labels and deployment strategy. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: metacollector-podtemplate +spec: + description: "Component (metacollector) podtemplate: custom labels → deployment strategy" + bindings: + - name: component_name + value: mc-custom + - name: component_type + value: metacollector + - name: component_replicas + value: 1 + steps: + # --- custom-labels --- + - name: Create Component with custom pod labels + try: + - apply: + resource: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Component + metadata: + name: ($component_name) + spec: + component: + type: ($component_type) + replicas: ($component_replicas) + podTemplateSpec: + metadata: + labels: + custom-label: test-value + annotations: + custom-annotation: test-annotation + - assert: + resource: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: ($component_name) + status: + availableReplicas: ($component_replicas) + readyReplicas: ($component_replicas) + - name: Assert pod has custom label + try: + - assert: + resource: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: ($component_name) + spec: + template: + metadata: + labels: + custom-label: test-value + annotations: + custom-annotation: test-annotation + - name: Assert Component status + use: + template: ../../../common/_step_templates/assert-component-status.yaml + bindings: + - name: expected_reconciled + value: "True" + - name: expected_available + value: "True" + - name: expected_replicas + value: 1 + - name: expected_version + value: "0.1.2" + # --- deployment-strategy --- + - name: Patch deployment strategy to Recreate + try: + - patch: + resource: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Component + metadata: + name: ($component_name) + spec: + strategy: + type: Recreate + - assert: + resource: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: ($component_name) + spec: + strategy: + type: Recreate + status: + availableReplicas: ($component_replicas) + readyReplicas: ($component_replicas) diff --git a/test/e2e/chainsaw/component/metacollector/scale/chainsaw-test.yaml b/test/e2e/chainsaw/component/metacollector/scale/chainsaw-test.yaml new file mode 100644 index 00000000..638d799c --- /dev/null +++ b/test/e2e/chainsaw/component/metacollector/scale/chainsaw-test.yaml @@ -0,0 +1,65 @@ +# Test: Component scale — create with replicas=2, scale down to 1. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: metacollector-scale +spec: + description: "Component (metacollector) scale: create replicas=2 → scale down to 1" + bindings: + - name: component_name + value: mc-scale + - name: component_type + value: metacollector + - name: component_replicas + value: 2 + steps: + # --- component-create-scaled --- + - name: Create Component with replicas=2 + use: + template: ../../../common/_step_templates/apply-assert-component.yaml + - name: Assert Component status with 2 replicas + use: + template: ../../../common/_step_templates/assert-component-status.yaml + bindings: + - name: expected_reconciled + value: "True" + - name: expected_available + value: "True" + - name: expected_replicas + value: 2 + - name: expected_version + value: "0.1.2" + # --- component-scale-down --- + - name: Scale down to 1 replica + try: + - patch: + resource: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Component + metadata: + name: ($component_name) + spec: + replicas: 1 + - assert: + resource: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: ($component_name) + spec: + replicas: 1 + status: + availableReplicas: 1 + readyReplicas: 1 + - name: Assert Component status after scale down + use: + template: ../../../common/_step_templates/assert-component-status.yaml + bindings: + - name: expected_reconciled + value: "True" + - name: expected_available + value: "True" + - name: expected_replicas + value: 1 + - name: expected_version + value: "0.1.2" diff --git a/test/e2e/chainsaw/config/lifecycle/chainsaw-test.yaml b/test/e2e/chainsaw/config/lifecycle/chainsaw-test.yaml new file mode 100644 index 00000000..38a76703 --- /dev/null +++ b/test/e2e/chainsaw/config/lifecycle/chainsaw-test.yaml @@ -0,0 +1,301 @@ +# Test: Config CRD lifecycle — inline CRUD, priority rename, selector, multi-priority, delete. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: config-lifecycle +spec: + description: "Config lifecycle: inline → priority → selector → multi-priority → delete" + bindings: + - name: falco_name + value: falco-test + - name: falco_version + value: "0.43.0" + steps: + # --- Shared setup --- + - name: Create Falco instance + use: + template: ../../common/_step_templates/apply-assert-falco-daemonset.yaml + - name: Wait for Falco pod + use: + template: ../../common/_step_templates/wait-falco-pod-ready.yaml + + # --- config-inline: create --- + - name: Create Config with inline configuration + try: + - apply: + file: config-inline.yaml + + - name: Verify inline config file exists + use: + template: ../../common/_step_templates/verify-file-contains.yaml + bindings: + - name: file_path + value: "/etc/falco/config.d/50-03-config-test-inline.yaml" + - name: expected_content + value: "json_output" + + - name: Verify config-test status after create + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Config + - name: artifact_name + value: config-test + - name: expected_programmed + value: "True" + + # --- config-inline: update --- + - name: Update Config with new content + try: + - apply: + file: config-inline-updated.yaml + + - name: Verify config file was updated + use: + template: ../../common/_step_templates/verify-content-update.yaml + bindings: + - name: file_path + value: "/etc/falco/config.d/50-03-config-test-inline.yaml" + - name: new_content + value: "json_output: false" + - name: old_content + value: "json_output: true" + + - name: Verify config-test status after update + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Config + - name: artifact_name + value: config-test + - name: expected_programmed + value: "True" + + # --- config-priority: create + rename --- + - name: Create Config with priority 50 + try: + - apply: + file: config-priority.yaml + + - name: Verify config at priority-50 path + use: + template: ../../common/_step_templates/verify-file-contains.yaml + bindings: + - name: file_path + value: "/etc/falco/config.d/50-03-config-priority-inline.yaml" + - name: expected_content + value: "json_output" + + - name: Update Config priority to 30 + try: + - patch: + resource: + apiVersion: artifact.falcosecurity.dev/v1alpha1 + kind: Config + metadata: + name: config-priority + spec: + priority: 30 + + - name: Verify old config file deleted + use: + template: ../../common/_step_templates/verify-file-deleted.yaml + bindings: + - name: file_path + value: "/etc/falco/config.d/50-03-config-priority-inline.yaml" + + - name: Verify new config file at renamed path + use: + template: ../../common/_step_templates/verify-file-contains.yaml + bindings: + - name: file_path + value: "/etc/falco/config.d/30-03-config-priority-inline.yaml" + - name: expected_content + value: "json_output" + + - name: Verify config-priority status after rename + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Config + - name: artifact_name + value: config-priority + - name: expected_programmed + value: "True" + + # --- config-selector: matching --- + - name: Apply Config with matching selector + try: + - apply: + file: config-matching.yaml + + - name: Verify matching config file exists + use: + template: ../../common/_step_templates/verify-file-contains.yaml + bindings: + - name: file_path + value: "/etc/falco/config.d/50-03-config-matching-inline.yaml" + - name: expected_content + value: "json_output" + + - name: Verify config-matching status + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Config + - name: artifact_name + value: config-matching + - name: expected_programmed + value: "True" + + # --- config-selector: non-matching --- + - name: Apply Config with non-matching selector + try: + - apply: + file: config-nonmatching.yaml + + - name: Wait for potential file write + try: + - sleep: + duration: 5s + + - name: Verify non-matching config file does NOT exist + use: + template: ../../common/_step_templates/verify-file-deleted.yaml + bindings: + - name: file_path + value: "/etc/falco/config.d/50-03-config-nonmatching-inline.yaml" + + # --- config-multiple-priorities --- + - name: Apply both priority configs + try: + - apply: + file: config-low.yaml + - apply: + file: config-high.yaml + + - name: Verify both config files exist + use: + template: ../../common/_step_templates/verify-dir-listing.yaml + bindings: + - name: dir_path + value: "/etc/falco/config.d" + - name: expected_files + value: "30-03-config-low-inline.yaml,70-03-config-high-inline.yaml" + + - name: Verify low-priority config content + use: + template: ../../common/_step_templates/verify-file-contains.yaml + bindings: + - name: file_path + value: "/etc/falco/config.d/30-03-config-low-inline.yaml" + - name: expected_content + value: "json_output" + + - name: Verify high-priority config content + use: + template: ../../common/_step_templates/verify-file-contains.yaml + bindings: + - name: file_path + value: "/etc/falco/config.d/70-03-config-high-inline.yaml" + - name: expected_content + value: "log_level: debug" + + - name: Verify config-low status + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Config + - name: artifact_name + value: config-low + - name: expected_programmed + value: "True" + + - name: Verify config-high status + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Config + - name: artifact_name + value: config-high + - name: expected_programmed + value: "True" + + # --- config-priority-boundaries --- + - name: Apply Config with minimum priority (0) + try: + - apply: + file: config-boundary-low.yaml + + - name: Verify priority-0 config file exists + use: + template: ../../common/_step_templates/verify-file-contains.yaml + bindings: + - name: file_path + value: "/etc/falco/config.d/00-03-config-boundary-low-inline.yaml" + - name: expected_content + value: "json_output" + + - name: Verify config-boundary-low status + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Config + - name: artifact_name + value: config-boundary-low + - name: expected_programmed + value: "True" + + - name: Apply Config with maximum priority (99) + try: + - apply: + file: config-boundary-high.yaml + + - name: Verify priority-99 config file exists + use: + template: ../../common/_step_templates/verify-file-contains.yaml + bindings: + - name: file_path + value: "/etc/falco/config.d/99-03-config-boundary-high-inline.yaml" + - name: expected_content + value: "log_level" + + - name: Verify config-boundary-high status + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Config + - name: artifact_name + value: config-boundary-high + - name: expected_programmed + value: "True" + + # --- config-delete --- + - name: Delete Config CR + try: + - delete: + ref: + apiVersion: artifact.falcosecurity.dev/v1alpha1 + kind: Config + name: config-test + - error: + resource: + apiVersion: artifact.falcosecurity.dev/v1alpha1 + kind: Config + metadata: + name: config-test + + - name: Verify config file removed + use: + template: ../../common/_step_templates/verify-file-deleted.yaml + bindings: + - name: file_path + value: "/etc/falco/config.d/50-03-config-test-inline.yaml" diff --git a/test/e2e/chainsaw/config/lifecycle/config-boundary-high.yaml b/test/e2e/chainsaw/config/lifecycle/config-boundary-high.yaml new file mode 100644 index 00000000..cc056545 --- /dev/null +++ b/test/e2e/chainsaw/config/lifecycle/config-boundary-high.yaml @@ -0,0 +1,8 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Config +metadata: + name: config-boundary-high +spec: + priority: 99 + config: + log_level: debug diff --git a/test/e2e/chainsaw/config/lifecycle/config-boundary-low.yaml b/test/e2e/chainsaw/config/lifecycle/config-boundary-low.yaml new file mode 100644 index 00000000..d016300b --- /dev/null +++ b/test/e2e/chainsaw/config/lifecycle/config-boundary-low.yaml @@ -0,0 +1,8 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Config +metadata: + name: config-boundary-low +spec: + priority: 0 + config: + json_output: true diff --git a/test/e2e/chainsaw/config/lifecycle/config-high.yaml b/test/e2e/chainsaw/config/lifecycle/config-high.yaml new file mode 100644 index 00000000..dae21f9d --- /dev/null +++ b/test/e2e/chainsaw/config/lifecycle/config-high.yaml @@ -0,0 +1,9 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Config +metadata: + name: config-high +spec: + priority: 70 + config: + log_level: debug + log_stderr: true diff --git a/test/e2e/chainsaw/config/lifecycle/config-inline-updated.yaml b/test/e2e/chainsaw/config/lifecycle/config-inline-updated.yaml new file mode 100644 index 00000000..b671799b --- /dev/null +++ b/test/e2e/chainsaw/config/lifecycle/config-inline-updated.yaml @@ -0,0 +1,11 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Config +metadata: + name: config-test +spec: + config: + json_output: false + json_include_output_property: false + log_stderr: true + log_syslog: true + log_level: debug diff --git a/test/e2e/chainsaw/config/lifecycle/config-inline.yaml b/test/e2e/chainsaw/config/lifecycle/config-inline.yaml new file mode 100644 index 00000000..d8fe970d --- /dev/null +++ b/test/e2e/chainsaw/config/lifecycle/config-inline.yaml @@ -0,0 +1,11 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Config +metadata: + name: config-test +spec: + config: + json_output: true + json_include_output_property: true + log_stderr: true + log_syslog: false + log_level: info diff --git a/test/e2e/chainsaw/config/lifecycle/config-low.yaml b/test/e2e/chainsaw/config/lifecycle/config-low.yaml new file mode 100644 index 00000000..7b25c11e --- /dev/null +++ b/test/e2e/chainsaw/config/lifecycle/config-low.yaml @@ -0,0 +1,9 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Config +metadata: + name: config-low +spec: + priority: 30 + config: + json_output: true + log_level: info diff --git a/test/e2e/chainsaw/config/lifecycle/config-matching.yaml b/test/e2e/chainsaw/config/lifecycle/config-matching.yaml new file mode 100644 index 00000000..21c404d9 --- /dev/null +++ b/test/e2e/chainsaw/config/lifecycle/config-matching.yaml @@ -0,0 +1,11 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Config +metadata: + name: config-matching +spec: + priority: 50 + config: + json_output: true + selector: + matchLabels: + kubernetes.io/os: linux diff --git a/test/e2e/chainsaw/config/lifecycle/config-nonmatching.yaml b/test/e2e/chainsaw/config/lifecycle/config-nonmatching.yaml new file mode 100644 index 00000000..e81bda4d --- /dev/null +++ b/test/e2e/chainsaw/config/lifecycle/config-nonmatching.yaml @@ -0,0 +1,11 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Config +metadata: + name: config-nonmatching +spec: + priority: 50 + config: + log_level: debug + selector: + matchLabels: + non-existent-label: does-not-exist diff --git a/test/e2e/chainsaw/config/lifecycle/config-priority.yaml b/test/e2e/chainsaw/config/lifecycle/config-priority.yaml new file mode 100644 index 00000000..161cc46a --- /dev/null +++ b/test/e2e/chainsaw/config/lifecycle/config-priority.yaml @@ -0,0 +1,9 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Config +metadata: + name: config-priority +spec: + priority: 50 + config: + json_output: true + log_level: info diff --git a/test/e2e/chainsaw/falco/deployment/chainsaw-test.yaml b/test/e2e/chainsaw/falco/deployment/chainsaw-test.yaml new file mode 100644 index 00000000..824cbe4d --- /dev/null +++ b/test/e2e/chainsaw/falco/deployment/chainsaw-test.yaml @@ -0,0 +1,53 @@ +# Test: Falco Deployment lifecycle — create, status, scale. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: falco-deployment +spec: + description: "Falco Deployment: create → status → scale replicas" + bindings: + - name: falco_name + value: falco-test + - name: falco_version + value: "0.43.0" + - name: falco_replicas + value: 1 + steps: + - name: Create Falco instance as Deployment + use: + template: ../../common/_step_templates/apply-assert-falco-deployment.yaml + - name: Wait for Falco pod to be ready + use: + template: ../../common/_step_templates/wait-falco-pod-ready.yaml + - name: Assert Falco Deployment status conditions + use: + template: ../../common/_step_templates/assert-falco-status.yaml + bindings: + - name: expected_reconciled + value: "True" + - name: expected_available + value: "True" + - name: expected_resource_type + value: Deployment + - name: expected_version + value: "0.43.0" + - name: Scale Deployment replicas + try: + - patch: + resource: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Falco + metadata: + name: ($falco_name) + spec: + replicas: 2 + - assert: + resource: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: ($falco_name) + spec: + replicas: 2 + status: + readyReplicas: 2 diff --git a/test/e2e/chainsaw/falco/lifecycle/chainsaw-test.yaml b/test/e2e/chainsaw/falco/lifecycle/chainsaw-test.yaml new file mode 100644 index 00000000..a166ed60 --- /dev/null +++ b/test/e2e/chainsaw/falco/lifecycle/chainsaw-test.yaml @@ -0,0 +1,175 @@ +# Test: Falco CRD lifecycle — create, idempotent, type-switch, delete. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: falco-lifecycle +spec: + description: "Falco DaemonSet lifecycle: create → idempotent → type-switch → delete" + bindings: + - name: falco_name + value: falco-test + - name: falco_version + value: "0.43.0" + steps: + # --- falco-daemonset --- + - name: Create Falco as DaemonSet + use: + template: ../../common/_step_templates/apply-assert-falco-daemonset.yaml + - name: Wait for Falco pod + use: + template: ../../common/_step_templates/wait-falco-pod-ready.yaml + - name: Assert Falco status conditions + use: + template: ../../common/_step_templates/assert-falco-status.yaml + bindings: + - name: expected_reconciled + value: "True" + - name: expected_available + value: "True" + - name: expected_resource_type + value: DaemonSet + - name: expected_version + value: "0.43.0" + # --- falco-idempotent --- + - name: Re-apply the same Falco resource + try: + - apply: + resource: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Falco + metadata: + name: ($falco_name) + spec: + type: DaemonSet + version: ($falco_version) + - name: Wait for reconciliation after re-apply + use: + template: ../../common/_step_templates/assert-falco-status.yaml + bindings: + - name: expected_reconciled + value: "True" + - name: expected_available + value: "True" + - name: expected_resource_type + value: DaemonSet + - name: expected_version + value: "0.43.0" + - name: Verify DaemonSet stable after re-apply + try: + - script: + env: + - name: NAMESPACE + value: ($namespace) + content: | + RESTARTS=$(kubectl get pods -n "$NAMESPACE" -l app.kubernetes.io/instance=falco-test -o jsonpath='{.items[0].status.containerStatuses[?(@.name=="falco")].restartCount}') + RESTARTS="${RESTARTS:-0}" + if [ "$RESTARTS" != "0" ]; then + echo "{\"error\": \"Pod restarted\", \"restartCount\": \"$RESTARTS\"}" + exit 1 + fi + echo "{\"status\": \"ok\", \"restartCount\": \"$RESTARTS\"}" + check: + ($error == null): true + - assert: + resource: + apiVersion: apps/v1 + kind: DaemonSet + metadata: + name: ($falco_name) + status: + numberReady: 1 + desiredNumberScheduled: 1 + # --- falco-type-switch --- + - name: Patch Falco type to Deployment + try: + - patch: + resource: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Falco + metadata: + name: ($falco_name) + spec: + type: Deployment + replicas: 1 + - name: Assert DaemonSet no longer exists + try: + - error: + resource: + apiVersion: apps/v1 + kind: DaemonSet + metadata: + name: ($falco_name) + - name: Assert Deployment exists and is ready + try: + - assert: + resource: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: ($falco_name) + status: + availableReplicas: 1 + readyReplicas: 1 + - name: Wait for new pod after type switch + use: + template: ../../common/_step_templates/wait-falco-pod-ready.yaml + - name: Assert Falco status after type switch + use: + template: ../../common/_step_templates/assert-falco-status.yaml + bindings: + - name: expected_reconciled + value: "True" + - name: expected_available + value: "True" + - name: expected_resource_type + value: Deployment + - name: expected_version + value: "0.43.0" + # --- falco-delete --- + - name: Delete Falco CR + try: + - delete: + ref: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Falco + name: ($falco_name) + - name: Verify namespaced resources are gone + try: + - error: + resource: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: ($falco_name) + - error: + resource: + apiVersion: v1 + kind: ServiceAccount + metadata: + name: ($falco_name) + - error: + resource: + apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: + name: ($falco_name) + - error: + resource: + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + name: ($falco_name) + - name: Verify cluster-scoped resources are gone + try: + - error: + resource: + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + name: (join('--', [$falco_name, $namespace])) + - error: + resource: + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + name: (join('--', [$falco_name, $namespace])) diff --git a/test/e2e/chainsaw/falco/podtemplate/chainsaw-test.yaml b/test/e2e/chainsaw/falco/podtemplate/chainsaw-test.yaml new file mode 100644 index 00000000..cbc77f20 --- /dev/null +++ b/test/e2e/chainsaw/falco/podtemplate/chainsaw-test.yaml @@ -0,0 +1,73 @@ +# Test: Falco instance with custom podTemplateSpec. +# Verifies custom labels, tolerations, and resource limits are applied to pods. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: falco-custom-podtemplate +spec: + description: Test Falco instance with custom podTemplateSpec fields + bindings: + - name: falco_name + value: falco-test + - name: falco_version + value: "0.43.0" + steps: + - name: Create Falco instance with custom podTemplateSpec + try: + - apply: + resource: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Falco + metadata: + name: ($falco_name) + spec: + type: DaemonSet + version: ($falco_version) + podTemplateSpec: + metadata: + labels: + custom-label: test-value + spec: + tolerations: + - key: test-tol + operator: Exists + effect: NoSchedule + containers: + - name: falco + resources: + limits: + cpu: 500m + memory: 1Gi + - assert: + resource: + apiVersion: apps/v1 + kind: DaemonSet + metadata: + name: ($falco_name) + status: + numberReady: 1 + desiredNumberScheduled: 1 + - name: Wait for Falco pod to be ready + use: + template: ../../common/_step_templates/wait-falco-pod-ready.yaml + - name: Assert pod has custom label + try: + - assert: + resource: + apiVersion: v1 + kind: Pod + metadata: + labels: + custom-label: test-value + status: + phase: Running + - name: Assert pod has toleration + try: + - script: + env: + - name: NAMESPACE + value: ($namespace) + content: | + kubectl get pods -n "$NAMESPACE" -l app.kubernetes.io/instance=falco-test -o jsonpath='{.items[0].spec.tolerations[*].key}' | grep -q "test-tol" + check: + ($error == null): true diff --git a/test/e2e/chainsaw/falco/version/chainsaw-test.yaml b/test/e2e/chainsaw/falco/version/chainsaw-test.yaml new file mode 100644 index 00000000..78b7b90e --- /dev/null +++ b/test/e2e/chainsaw/falco/version/chainsaw-test.yaml @@ -0,0 +1,103 @@ +# Test: Falco version management — upgrade and image override. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: falco-version +spec: + description: "Falco version: upgrade → image override" + bindings: + - name: falco_name + value: falco-test + - name: initial_version + value: "0.40.0" + - name: upgraded_version + value: "0.43.0" + - name: initial_image + value: "docker.io/falcosecurity/falco:0.40.0" + - name: upgraded_image + value: "docker.io/falcosecurity/falco:0.43.0" + steps: + # --- version-upgrade --- + - name: Create Falco with initial version + try: + - apply: + resource: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Falco + metadata: + name: ($falco_name) + spec: + type: DaemonSet + version: ($initial_version) + - assert: + resource: + apiVersion: apps/v1 + kind: DaemonSet + metadata: + name: ($falco_name) + status: + numberReady: 1 + desiredNumberScheduled: 1 + - name: Wait for Falco pod + use: + template: ../../common/_step_templates/wait-falco-pod-ready.yaml + - name: Patch Falco version to upgraded version + try: + - patch: + resource: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Falco + metadata: + name: ($falco_name) + spec: + version: ($upgraded_version) + - name: Verify DaemonSet has updated image and rollout complete + try: + - assert: + resource: + apiVersion: apps/v1 + kind: DaemonSet + metadata: + name: ($falco_name) + spec: + template: + spec: + # Use JMESPath filter to match the specific container by name. + # Direct array assertion fails because chainsaw does strict element-count matching + # and the DaemonSet has 2 containers (falco + artifact-operator sidecar). + (containers[?name == 'falco']): + - image: ($upgraded_image) + status: + numberReady: 1 + updatedNumberScheduled: 1 + # --- version-override --- + - name: Apply image override via podTemplateSpec + try: + - patch: + resource: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Falco + metadata: + name: ($falco_name) + spec: + podTemplateSpec: + spec: + containers: + - name: falco + image: ($initial_image) + - name: Verify DaemonSet has overridden image and rollout complete + try: + - assert: + resource: + apiVersion: apps/v1 + kind: DaemonSet + metadata: + name: ($falco_name) + spec: + template: + spec: + (containers[?name == 'falco']): + - image: ($initial_image) + status: + numberReady: 1 + updatedNumberScheduled: 1 diff --git a/test/e2e/chainsaw/integration/full-stack/chainsaw-test.yaml b/test/e2e/chainsaw/integration/full-stack/chainsaw-test.yaml new file mode 100644 index 00000000..046a1f99 --- /dev/null +++ b/test/e2e/chainsaw/integration/full-stack/chainsaw-test.yaml @@ -0,0 +1,172 @@ +# Test: Full stack — Falco + Config + Rulesfile + Plugin all together. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: full-stack +spec: + description: Test Falco with Config, Rulesfile, and Plugin together + bindings: + - name: falco_name + value: falco-test + - name: falco_version + value: "0.43.0" + - name: plugin_dir + value: "/usr/share/falco/plugins" + - name: min_size + value: "1000" + - name: falco_replicas + value: 1 + steps: + - name: Create Falco instance + use: + template: ../../common/_step_templates/apply-assert-falco-daemonset.yaml + - name: Wait for Falco pod to be ready + use: + template: ../../common/_step_templates/wait-falco-pod-ready.yaml + - name: Create Config CR + try: + - apply: + file: config.yaml + - name: Create Rulesfile CR + try: + - apply: + file: rulesfile.yaml + - name: Create Plugin CR + try: + - apply: + file: plugin.yaml + - name: Verify config file exists + use: + template: ../../common/_step_templates/verify-file-contains.yaml + bindings: + - name: file_path + value: /etc/falco/config.d/50-03-full-stack-config-inline.yaml + - name: expected_content + value: "json_output" + - name: Verify full-stack-config status + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Config + - name: artifact_name + value: full-stack-config + - name: expected_programmed + value: "True" + - name: Verify rules file exists + use: + template: ../../common/_step_templates/verify-file-contains.yaml + bindings: + - name: file_path + value: /etc/falco/rules.d/50-03-full-stack-rules-inline.yaml + - name: expected_content + value: "Full Stack Test Rule" + - name: Verify full-stack-rules status + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Rulesfile + - name: artifact_name + value: full-stack-rules + - name: expected_programmed + value: "True" + - name: Verify plugin file downloaded + use: + template: ../../common/_step_templates/verify-plugin.yaml + - name: Verify plugin config generated + use: + template: ../../common/_step_templates/verify-plugin-config.yaml + bindings: + - name: plugin_name + value: json + - name: Verify json plugin status + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Plugin + - name: artifact_name + value: json + - name: expected_programmed + value: "True" + # --- Phase 2: Verify artifacts survive type switch to Deployment --- + - name: Switch Falco to Deployment + try: + - patch: + resource: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Falco + metadata: + name: ($falco_name) + spec: + type: Deployment + replicas: ($falco_replicas) + - assert: + resource: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: ($falco_name) + status: + availableReplicas: ($falco_replicas) + readyReplicas: ($falco_replicas) + - name: Wait for Deployment pod ready + use: + template: ../../common/_step_templates/wait-falco-pod-ready.yaml + - name: Verify config file survives type switch + use: + template: ../../common/_step_templates/verify-file-contains.yaml + bindings: + - name: file_path + value: /etc/falco/config.d/50-03-full-stack-config-inline.yaml + - name: expected_content + value: "json_output" + - name: Verify rules file survives type switch + use: + template: ../../common/_step_templates/verify-file-contains.yaml + bindings: + - name: file_path + value: /etc/falco/rules.d/50-03-full-stack-rules-inline.yaml + - name: expected_content + value: "Full Stack Test Rule" + - name: Verify full-stack-rules status after type switch + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Rulesfile + - name: artifact_name + value: full-stack-rules + - name: expected_programmed + value: "True" + # --- Phase 3: Component (metacollector) coexists with Falco --- + - name: Create Component (metacollector) + try: + - apply: + file: component.yaml + - name: Assert Component Deployment ready + try: + - assert: + resource: + apiVersion: apps/v1 + kind: Deployment + metadata: + name: mc-fullstack + status: + availableReplicas: 1 + readyReplicas: 1 + - name: Assert Component status + try: + - assert: + resource: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Component + metadata: + name: mc-fullstack + status: + resourceType: Deployment + (conditions[?type == 'Reconciled']): + - status: "True" + (conditions[?type == 'Available']): + - status: "True" diff --git a/test/e2e/chainsaw/integration/full-stack/component.yaml b/test/e2e/chainsaw/integration/full-stack/component.yaml new file mode 100644 index 00000000..a2a0e0d1 --- /dev/null +++ b/test/e2e/chainsaw/integration/full-stack/component.yaml @@ -0,0 +1,7 @@ +apiVersion: instance.falcosecurity.dev/v1alpha1 +kind: Component +metadata: + name: mc-fullstack +spec: + component: + type: metacollector diff --git a/test/e2e/chainsaw/integration/full-stack/config.yaml b/test/e2e/chainsaw/integration/full-stack/config.yaml new file mode 100644 index 00000000..4df9f876 --- /dev/null +++ b/test/e2e/chainsaw/integration/full-stack/config.yaml @@ -0,0 +1,10 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Config +metadata: + name: full-stack-config +spec: + priority: 50 + config: + json_output: true + log_stderr: true + log_level: info diff --git a/test/e2e/chainsaw/integration/full-stack/plugin.yaml b/test/e2e/chainsaw/integration/full-stack/plugin.yaml new file mode 100644 index 00000000..be642bb5 --- /dev/null +++ b/test/e2e/chainsaw/integration/full-stack/plugin.yaml @@ -0,0 +1,12 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Plugin +metadata: + name: json +spec: + ociArtifact: + image: + repository: falcosecurity/plugins/plugin/json + registry: + name: ghcr.io + config: + name: json diff --git a/test/e2e/chainsaw/integration/full-stack/rulesfile.yaml b/test/e2e/chainsaw/integration/full-stack/rulesfile.yaml new file mode 100644 index 00000000..52fd24c9 --- /dev/null +++ b/test/e2e/chainsaw/integration/full-stack/rulesfile.yaml @@ -0,0 +1,13 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Rulesfile +metadata: + name: full-stack-rules +spec: + priority: 50 + inlineRules: + - rule: Full Stack Test Rule + desc: A test rule for full stack e2e testing + condition: evt.type = open + output: "Full stack test rule triggered" + priority: DEBUG + enabled: false diff --git a/test/e2e/chainsaw/plugin/lifecycle/chainsaw-test.yaml b/test/e2e/chainsaw/plugin/lifecycle/chainsaw-test.yaml new file mode 100644 index 00000000..e5da4181 --- /dev/null +++ b/test/e2e/chainsaw/plugin/lifecycle/chainsaw-test.yaml @@ -0,0 +1,187 @@ +# Test: Plugin CRD lifecycle — create, multiple, update, delete. +# NOTE: The "update initConfig" step fails due to a known operator bug where +# addConfig uses isSame() (Name + InitConfig equality) instead of name-based +# replacement, preventing initConfig updates from being reflected. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: plugin-lifecycle +spec: + description: "Plugin lifecycle: create → multiple → update → delete" + bindings: + - name: falco_name + value: falco-test + - name: falco_version + value: "0.43.0" + - name: plugin_dir + value: "/usr/share/falco/plugins" + - name: min_size + value: "1000" + steps: + # --- Shared setup --- + - name: Create Falco instance + use: + template: ../../common/_step_templates/apply-assert-falco-daemonset.yaml + - name: Wait for Falco pod + use: + template: ../../common/_step_templates/wait-falco-pod-ready.yaml + + # --- plugin-oci: create --- + - name: Create Plugin from OCI registry + try: + - apply: + file: plugin-json.yaml + + - name: Verify plugin binary downloaded + use: + template: ../../common/_step_templates/verify-plugin.yaml + + - name: Verify plugin config generated + use: + template: ../../common/_step_templates/verify-plugin-config.yaml + bindings: + - name: plugin_name + value: json + + - name: Verify json plugin status after create + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Plugin + - name: artifact_name + value: json + - name: expected_programmed + value: "True" + + # --- plugin-multiple --- + - name: Create second Plugin CR + try: + - apply: + file: plugin-dummy.yaml + + - name: Verify dummy plugin binary + use: + template: ../../common/_step_templates/verify-file-size.yaml + bindings: + - name: file_path + value: /usr/share/falco/plugins/dummy.so + - name: min_size + value: "1000" + + - name: Verify dummy plugin in config + use: + template: ../../common/_step_templates/verify-plugin-config.yaml + bindings: + - name: plugin_name + value: dummy + + - name: Verify dummy plugin status after create + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Plugin + - name: artifact_name + value: dummy + - name: expected_programmed + value: "True" + + - name: Verify json plugin still in config + use: + template: ../../common/_step_templates/verify-plugin-config.yaml + bindings: + - name: plugin_name + value: json + + # --- plugin-update (known operator bug) --- + - name: Verify plugin config has initial value + use: + template: ../../common/_step_templates/verify-file-contains.yaml + bindings: + - name: file_path + value: /etc/falco/config.d/99-03-plugins-config-inline.yaml + - name: expected_content + value: "initial.example.com" + + - name: Update Plugin initConfig + try: + - patch: + resource: + apiVersion: artifact.falcosecurity.dev/v1alpha1 + kind: Plugin + metadata: + name: json + spec: + config: + initConfig: + sssURL: "https://updated.example.com" + + - name: Verify plugin config updated + use: + template: ../../common/_step_templates/verify-content-update.yaml + bindings: + - name: file_path + value: /etc/falco/config.d/99-03-plugins-config-inline.yaml + - name: new_content + value: "updated.example.com" + - name: old_content + value: "initial.example.com" + + - name: Verify json plugin status after update + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Plugin + - name: artifact_name + value: json + - name: expected_programmed + value: "True" + + # --- plugin-selector: non-matching --- + - name: Apply Plugin with non-matching selector + try: + - apply: + file: plugin-nonmatching.yaml + + - name: Wait for potential plugin download + try: + - sleep: + duration: 5s + + - name: Verify non-matching plugin NOT downloaded + use: + template: ../../common/_step_templates/verify-file-deleted.yaml + bindings: + - name: file_path + value: /usr/share/falco/plugins/plugin-nonmatching.so + + # --- plugin-delete --- + - name: Delete json Plugin CR + try: + - delete: + ref: + apiVersion: artifact.falcosecurity.dev/v1alpha1 + kind: Plugin + name: json + - error: + resource: + apiVersion: artifact.falcosecurity.dev/v1alpha1 + kind: Plugin + metadata: + name: json + + - name: Verify json plugin binary removed + use: + template: ../../common/_step_templates/verify-file-deleted.yaml + bindings: + - name: file_path + value: /usr/share/falco/plugins/json.so + + - name: Verify dummy still in config + use: + template: ../../common/_step_templates/verify-plugin-config.yaml + bindings: + - name: plugin_name + value: dummy diff --git a/test/e2e/chainsaw/plugin/lifecycle/plugin-dummy.yaml b/test/e2e/chainsaw/plugin/lifecycle/plugin-dummy.yaml new file mode 100644 index 00000000..359fda50 --- /dev/null +++ b/test/e2e/chainsaw/plugin/lifecycle/plugin-dummy.yaml @@ -0,0 +1,8 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Plugin +metadata: + name: dummy +spec: + ociArtifact: + image: + repository: falcosecurity/plugins/plugin/dummy diff --git a/test/e2e/chainsaw/plugin/lifecycle/plugin-json.yaml b/test/e2e/chainsaw/plugin/lifecycle/plugin-json.yaml new file mode 100644 index 00000000..a4711f7e --- /dev/null +++ b/test/e2e/chainsaw/plugin/lifecycle/plugin-json.yaml @@ -0,0 +1,11 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Plugin +metadata: + name: json +spec: + ociArtifact: + image: + repository: falcosecurity/plugins/plugin/json + config: + initConfig: + sssURL: "https://initial.example.com" diff --git a/test/e2e/chainsaw/plugin/lifecycle/plugin-nonmatching.yaml b/test/e2e/chainsaw/plugin/lifecycle/plugin-nonmatching.yaml new file mode 100644 index 00000000..d526789a --- /dev/null +++ b/test/e2e/chainsaw/plugin/lifecycle/plugin-nonmatching.yaml @@ -0,0 +1,13 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Plugin +metadata: + name: plugin-nonmatching +spec: + ociArtifact: + image: + repository: falcosecurity/plugins/plugin/json + registry: + name: ghcr.io + selector: + matchLabels: + non-existent-label: does-not-exist diff --git a/test/e2e/chainsaw/rulesfile/edge-cases/chainsaw-test.yaml b/test/e2e/chainsaw/rulesfile/edge-cases/chainsaw-test.yaml new file mode 100644 index 00000000..fbd4879b --- /dev/null +++ b/test/e2e/chainsaw/rulesfile/edge-cases/chainsaw-test.yaml @@ -0,0 +1,62 @@ +# Test: Rulesfile with missing ConfigMap — graceful handling. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: rulesfile-edge-cases +spec: + description: Test Rulesfile edge cases (missing ConfigMap) + bindings: + - name: falco_name + value: falco-test + - name: falco_version + value: "0.43.0" + - name: inline_file_path + value: "/etc/falco/rules.d/50-03-rulesfile-cm-missing-inline.yaml" + steps: + - name: Create Falco instance + use: + template: ../../common/_step_templates/apply-assert-falco-daemonset.yaml + - name: Wait for Falco pod + use: + template: ../../common/_step_templates/wait-falco-pod-ready.yaml + + - name: Create Rulesfile with missing ConfigMap and inline rules + try: + - apply: + file: rulesfile.yaml + + - name: Verify inline rules file does NOT exist + use: + template: ../../common/_step_templates/verify-file-deleted.yaml + bindings: + - name: file_path + value: ($inline_file_path) + + - name: Verify configmap rules file does NOT exist + use: + template: ../../common/_step_templates/verify-file-deleted.yaml + bindings: + - name: file_path + value: "/etc/falco/rules.d/50-02-rulesfile-cm-missing-configmap.yaml" + + - name: Verify artifact status reflects failure + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Rulesfile + - name: artifact_name + value: rulesfile-cm-missing + - name: expected_programmed + value: "False" + + - name: Verify ResolvedRefs reflects failure + use: + template: ../../common/_step_templates/assert-artifact-resolved-refs.yaml + bindings: + - name: artifact_kind + value: Rulesfile + - name: artifact_name + value: rulesfile-cm-missing + - name: expected_resolved_refs + value: "False" diff --git a/test/e2e/chainsaw/rulesfile/edge-cases/rulesfile.yaml b/test/e2e/chainsaw/rulesfile/edge-cases/rulesfile.yaml new file mode 100644 index 00000000..5bbcc268 --- /dev/null +++ b/test/e2e/chainsaw/rulesfile/edge-cases/rulesfile.yaml @@ -0,0 +1,15 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Rulesfile +metadata: + name: rulesfile-cm-missing +spec: + priority: 50 + configMapRef: + name: nonexistent-configmap + inlineRules: + - rule: Inline Fallback Rule + desc: A test rule that should still work when ConfigMap is missing + condition: evt.type = open + output: "Inline fallback rule triggered" + priority: DEBUG + enabled: false diff --git a/test/e2e/chainsaw/rulesfile/lifecycle/chainsaw-test.yaml b/test/e2e/chainsaw/rulesfile/lifecycle/chainsaw-test.yaml new file mode 100644 index 00000000..64ccce06 --- /dev/null +++ b/test/e2e/chainsaw/rulesfile/lifecycle/chainsaw-test.yaml @@ -0,0 +1,432 @@ +# Test: Rulesfile CRD lifecycle — inline, OCI, ConfigMap, multi-source, all-sources, remove-source, delete. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: rulesfile-lifecycle +spec: + description: "Rulesfile lifecycle: inline → OCI → ConfigMap → multi-source → all-sources → remove-source → delete" + bindings: + - name: falco_name + value: falco-test + - name: falco_version + value: "0.43.0" + steps: + # --- Shared setup --- + - name: Create Falco instance + use: + template: ../../common/_step_templates/apply-assert-falco-daemonset.yaml + - name: Wait for Falco pod + use: + template: ../../common/_step_templates/wait-falco-pod-ready.yaml + + # --- rulesfile-inline: create --- + - name: Create Rulesfile with inline rules + try: + - apply: + file: rulesfile-inline.yaml + + - name: Verify inline rules file exists + use: + template: ../../common/_step_templates/verify-file-contains.yaml + bindings: + - name: file_path + value: "/etc/falco/rules.d/50-03-rulesfile-inline-inline.yaml" + - name: expected_content + value: "Test Inline Rule" + + - name: Verify rulesfile-inline status after create + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Rulesfile + - name: artifact_name + value: rulesfile-inline + - name: expected_programmed + value: "True" + + # --- rulesfile-inline: update --- + - name: Update Rulesfile with new inline content + try: + - apply: + file: rulesfile-inline-updated.yaml + + - name: Verify inline rules file updated + use: + template: ../../common/_step_templates/verify-content-update.yaml + bindings: + - name: file_path + value: "/etc/falco/rules.d/50-03-rulesfile-inline-inline.yaml" + - name: new_content + value: "Updated Inline Rule" + - name: old_content + value: "Test Inline Rule" + + - name: Verify rulesfile-inline status after update + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Rulesfile + - name: artifact_name + value: rulesfile-inline + - name: expected_programmed + value: "True" + + # --- container-plugin --- + - name: Install container plugin for OCI rules + try: + - apply: + file: plugin-container.yaml + + - name: Verify container plugin downloaded + use: + template: ../../common/_step_templates/verify-plugin.yaml + bindings: + - name: plugin_dir + value: "/usr/share/falco/plugins" + - name: min_size + value: "1000" + + - name: Wait for Falco pod ready after plugin install + use: + template: ../../common/_step_templates/wait-falco-pod-ready.yaml + + # --- rulesfile-oci: create --- + - name: Create Rulesfile from OCI registry + try: + - apply: + file: rulesfile-oci.yaml + + - name: Verify OCI rules file downloaded + use: + template: ../../common/_step_templates/verify-file-size.yaml + bindings: + - name: file_path + value: "/etc/falco/rules.d/50-01-rulesfile-oci-oci.yaml" + - name: min_size + value: "100" + + - name: Verify rulesfile-oci status after create + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Rulesfile + - name: artifact_name + value: rulesfile-oci + - name: expected_programmed + value: "True" + + # --- rulesfile-oci: priority update --- + - name: Update Rulesfile OCI priority + try: + - apply: + file: rulesfile-oci-updated.yaml + + - name: Verify rules file renamed after priority change + use: + template: ../../common/_step_templates/verify-file-rename.yaml + bindings: + - name: old_file_path + value: "/etc/falco/rules.d/50-01-rulesfile-oci-oci.yaml" + - name: new_file_path + value: "/etc/falco/rules.d/60-01-rulesfile-oci-oci.yaml" + + - name: Verify rulesfile-oci status after priority change + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Rulesfile + - name: artifact_name + value: rulesfile-oci + - name: expected_programmed + value: "True" + + # --- rulesfile-configmap: create --- + - name: Create ConfigMap with rules content + try: + - apply: + file: configmap.yaml + + - name: Create Rulesfile with ConfigMap reference + try: + - apply: + file: rulesfile-configmap.yaml + + - name: Verify ConfigMap rules file exists + use: + template: ../../common/_step_templates/verify-file-contains.yaml + bindings: + - name: file_path + value: "/etc/falco/rules.d/50-02-rulesfile-cm-configmap.yaml" + - name: expected_content + value: "Test ConfigMap Rule" + + - name: Verify rulesfile-cm status after create + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Rulesfile + - name: artifact_name + value: rulesfile-cm + - name: expected_programmed + value: "True" + + - name: Verify rulesfile-cm resolved refs after create + use: + template: ../../common/_step_templates/assert-artifact-resolved-refs.yaml + bindings: + - name: artifact_kind + value: Rulesfile + - name: artifact_name + value: rulesfile-cm + - name: expected_resolved_refs + value: "True" + + # --- rulesfile-configmap: update --- + - name: Update ConfigMap with new content + try: + - apply: + file: configmap-updated.yaml + + - name: Verify ConfigMap rules file updated + use: + template: ../../common/_step_templates/verify-content-update.yaml + bindings: + - name: file_path + value: "/etc/falco/rules.d/50-02-rulesfile-cm-configmap.yaml" + - name: new_content + value: "Updated ConfigMap Rule" + - name: old_content + value: "Test ConfigMap Rule" + + - name: Verify rulesfile-cm status after update + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Rulesfile + - name: artifact_name + value: rulesfile-cm + - name: expected_programmed + value: "True" + + - name: Verify rulesfile-cm resolved refs after update + use: + template: ../../common/_step_templates/assert-artifact-resolved-refs.yaml + bindings: + - name: artifact_kind + value: Rulesfile + - name: artifact_name + value: rulesfile-cm + - name: expected_resolved_refs + value: "True" + + # --- rulesfile-multi-source --- + - name: Create Rulesfile with OCI and inline sources + try: + - apply: + file: rulesfile-multi-source.yaml + + - name: Verify multi-source OCI file exists + use: + template: ../../common/_step_templates/verify-file-size.yaml + bindings: + - name: file_path + value: "/etc/falco/rules.d/50-01-rulesfile-multi-oci.yaml" + - name: min_size + value: "100" + + - name: Verify multi-source inline file exists + use: + template: ../../common/_step_templates/verify-file-contains.yaml + bindings: + - name: file_path + value: "/etc/falco/rules.d/50-03-rulesfile-multi-inline.yaml" + - name: expected_content + value: "Inline Test Rule" + + - name: Verify rulesfile-multi status + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Rulesfile + - name: artifact_name + value: rulesfile-multi + - name: expected_programmed + value: "True" + + # --- rulesfile-all-sources --- + - name: Create ConfigMap for all-sources test + try: + - apply: + file: configmap-all.yaml + + - name: Create Rulesfile with all three sources + try: + - apply: + file: rulesfile-all-sources.yaml + + - name: Verify all three rules files exist + use: + template: ../../common/_step_templates/verify-dir-listing.yaml + bindings: + - name: dir_path + value: "/etc/falco/rules.d" + - name: expected_files + value: "50-01-rulesfile-all-oci.yaml,50-02-rulesfile-all-configmap.yaml,50-03-rulesfile-all-inline.yaml" + + - name: Verify rulesfile-all status + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Rulesfile + - name: artifact_name + value: rulesfile-all + - name: expected_programmed + value: "True" + + - name: Verify rulesfile-all resolved refs + use: + template: ../../common/_step_templates/assert-artifact-resolved-refs.yaml + bindings: + - name: artifact_kind + value: Rulesfile + - name: artifact_name + value: rulesfile-all + - name: expected_resolved_refs + value: "True" + + # --- rulesfile-remove-source --- + - name: Create Rulesfile with OCI and inline for removal test + try: + - apply: + file: rulesfile-remove-source.yaml + + - name: Verify OCI file exists for removal test + use: + template: ../../common/_step_templates/verify-file-size.yaml + bindings: + - name: file_path + value: "/etc/falco/rules.d/50-01-rulesfile-rm-oci.yaml" + - name: min_size + value: "100" + + - name: Verify inline file exists for removal test + use: + template: ../../common/_step_templates/verify-file-contains.yaml + bindings: + - name: file_path + value: "/etc/falco/rules.d/50-03-rulesfile-rm-inline.yaml" + - name: expected_content + value: "Removable Inline Rule" + + - name: Remove inline source from Rulesfile + try: + - patch: + resource: + apiVersion: artifact.falcosecurity.dev/v1alpha1 + kind: Rulesfile + metadata: + name: rulesfile-rm + spec: + inlineRules: null + + - name: Verify inline rules file deleted + use: + template: ../../common/_step_templates/verify-file-deleted.yaml + bindings: + - name: file_path + value: "/etc/falco/rules.d/50-03-rulesfile-rm-inline.yaml" + + - name: Verify OCI rules file still exists + use: + template: ../../common/_step_templates/verify-file-size.yaml + bindings: + - name: file_path + value: "/etc/falco/rules.d/50-01-rulesfile-rm-oci.yaml" + - name: min_size + value: "100" + + - name: Verify rulesfile-rm status after source removal + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Rulesfile + - name: artifact_name + value: rulesfile-rm + - name: expected_programmed + value: "True" + + # --- rulesfile-selector: matching --- + - name: Apply Rulesfile with matching selector + try: + - apply: + file: rulesfile-matching.yaml + + - name: Verify matching rulesfile exists + use: + template: ../../common/_step_templates/verify-file-contains.yaml + bindings: + - name: file_path + value: "/etc/falco/rules.d/50-03-rulesfile-matching-inline.yaml" + - name: expected_content + value: "Matching Selector Rule" + + - name: Verify rulesfile-matching status + use: + template: ../../common/_step_templates/assert-artifact-status.yaml + bindings: + - name: artifact_kind + value: Rulesfile + - name: artifact_name + value: rulesfile-matching + - name: expected_programmed + value: "True" + + # --- rulesfile-selector: non-matching --- + - name: Apply Rulesfile with non-matching selector + try: + - apply: + file: rulesfile-nonmatching.yaml + + - name: Wait for potential file write + try: + # No positive signal exists for "controller decided not to create file". + - sleep: + duration: 5s + + - name: Verify non-matching rulesfile does NOT exist + use: + template: ../../common/_step_templates/verify-file-deleted.yaml + bindings: + - name: file_path + value: "/etc/falco/rules.d/50-03-rulesfile-nonmatching-inline.yaml" + + # --- rulesfile-delete --- + - name: Delete Rulesfile CR + try: + - delete: + ref: + apiVersion: artifact.falcosecurity.dev/v1alpha1 + kind: Rulesfile + name: rulesfile-inline + - error: + resource: + apiVersion: artifact.falcosecurity.dev/v1alpha1 + kind: Rulesfile + metadata: + name: rulesfile-inline + + - name: Verify rules file removed + use: + template: ../../common/_step_templates/verify-file-deleted.yaml + bindings: + - name: file_path + value: "/etc/falco/rules.d/50-03-rulesfile-inline-inline.yaml" diff --git a/test/e2e/chainsaw/rulesfile/lifecycle/configmap-all.yaml b/test/e2e/chainsaw/rulesfile/lifecycle/configmap-all.yaml new file mode 100644 index 00000000..9c9ec171 --- /dev/null +++ b/test/e2e/chainsaw/rulesfile/lifecycle/configmap-all.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-rules-all-cm +data: + rules.yaml: | + - rule: Test All Sources ConfigMap Rule + desc: A test rule from ConfigMap for all-sources test + condition: evt.type = open + output: "All sources ConfigMap rule triggered" + priority: WARNING + source: syscall diff --git a/test/e2e/chainsaw/rulesfile/lifecycle/configmap-updated.yaml b/test/e2e/chainsaw/rulesfile/lifecycle/configmap-updated.yaml new file mode 100644 index 00000000..ce64ddc9 --- /dev/null +++ b/test/e2e/chainsaw/rulesfile/lifecycle/configmap-updated.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-rules-cm +data: + rules.yaml: | + - rule: Updated ConfigMap Rule + desc: An updated test rule from ConfigMap + condition: evt.type = openat + output: "Updated ConfigMap rule triggered" + priority: INFO + source: syscall diff --git a/test/e2e/chainsaw/rulesfile/lifecycle/configmap.yaml b/test/e2e/chainsaw/rulesfile/lifecycle/configmap.yaml new file mode 100644 index 00000000..2bd64e5c --- /dev/null +++ b/test/e2e/chainsaw/rulesfile/lifecycle/configmap.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-rules-cm +data: + rules.yaml: | + - rule: Test ConfigMap Rule + desc: A test rule from ConfigMap + condition: evt.type = open + output: "Test ConfigMap rule triggered" + priority: WARNING + source: syscall diff --git a/test/e2e/chainsaw/rulesfile/lifecycle/plugin-container.yaml b/test/e2e/chainsaw/rulesfile/lifecycle/plugin-container.yaml new file mode 100644 index 00000000..7223a5a2 --- /dev/null +++ b/test/e2e/chainsaw/rulesfile/lifecycle/plugin-container.yaml @@ -0,0 +1,11 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Plugin +metadata: + name: container +spec: + ociArtifact: + image: + repository: falcosecurity/plugins/plugin/container + tag: "0.6.1" + registry: + name: ghcr.io diff --git a/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-all-sources.yaml b/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-all-sources.yaml new file mode 100644 index 00000000..4f1ddca0 --- /dev/null +++ b/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-all-sources.yaml @@ -0,0 +1,19 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Rulesfile +metadata: + name: rulesfile-all +spec: + priority: 50 + ociArtifact: + image: + repository: falcosecurity/rules/falco-rules + tag: "3" + configMapRef: + name: test-rules-all-cm + inlineRules: + - rule: Test All Sources Inline Rule + desc: A test rule from inline for all-sources test + condition: evt.type = open + output: "All sources inline rule triggered" + priority: WARNING + source: syscall diff --git a/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-configmap.yaml b/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-configmap.yaml new file mode 100644 index 00000000..d4e4d2c5 --- /dev/null +++ b/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-configmap.yaml @@ -0,0 +1,8 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Rulesfile +metadata: + name: rulesfile-cm +spec: + priority: 50 + configMapRef: + name: test-rules-cm diff --git a/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-inline-updated.yaml b/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-inline-updated.yaml new file mode 100644 index 00000000..ed862253 --- /dev/null +++ b/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-inline-updated.yaml @@ -0,0 +1,13 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Rulesfile +metadata: + name: rulesfile-inline +spec: + priority: 50 + inlineRules: + - rule: Updated Inline Rule + desc: An updated test rule for e2e testing + condition: evt.type = openat + output: "Updated rule triggered" + priority: INFO + enabled: false diff --git a/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-inline.yaml b/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-inline.yaml new file mode 100644 index 00000000..0ae2774d --- /dev/null +++ b/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-inline.yaml @@ -0,0 +1,13 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Rulesfile +metadata: + name: rulesfile-inline +spec: + priority: 50 + inlineRules: + - rule: Test Inline Rule + desc: A simple test rule for e2e testing + condition: evt.type = open + output: "Test rule triggered" + priority: DEBUG + enabled: false diff --git a/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-matching.yaml b/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-matching.yaml new file mode 100644 index 00000000..ceacd579 --- /dev/null +++ b/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-matching.yaml @@ -0,0 +1,16 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Rulesfile +metadata: + name: rulesfile-matching +spec: + priority: 50 + inlineRules: + - rule: Matching Selector Rule + desc: Rule applied only to matching nodes + condition: evt.type = open + output: "Matching selector rule triggered" + priority: DEBUG + enabled: false + selector: + matchLabels: + kubernetes.io/os: linux diff --git a/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-multi-source.yaml b/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-multi-source.yaml new file mode 100644 index 00000000..a24168cf --- /dev/null +++ b/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-multi-source.yaml @@ -0,0 +1,19 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Rulesfile +metadata: + name: rulesfile-multi +spec: + priority: 50 + ociArtifact: + image: + repository: falcosecurity/rules/falco-rules + tag: "3" + registry: + name: ghcr.io + inlineRules: + - rule: Inline Test Rule + desc: Test rule from inline + condition: evt.type = open + output: "Inline test" + priority: WARNING + source: syscall diff --git a/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-nonmatching.yaml b/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-nonmatching.yaml new file mode 100644 index 00000000..0c12f6d4 --- /dev/null +++ b/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-nonmatching.yaml @@ -0,0 +1,16 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Rulesfile +metadata: + name: rulesfile-nonmatching +spec: + priority: 50 + inlineRules: + - rule: Non-Matching Selector Rule + desc: Rule that should NOT be applied + condition: evt.type = open + output: "Non-matching selector rule triggered" + priority: DEBUG + enabled: false + selector: + matchLabels: + non-existent-label: does-not-exist diff --git a/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-oci-updated.yaml b/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-oci-updated.yaml new file mode 100644 index 00000000..011f3556 --- /dev/null +++ b/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-oci-updated.yaml @@ -0,0 +1,11 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Rulesfile +metadata: + name: rulesfile-oci +spec: + priority: 60 + ociArtifact: + image: + repository: falcosecurity/rules/falco-rules + registry: + name: ghcr.io diff --git a/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-oci.yaml b/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-oci.yaml new file mode 100644 index 00000000..c512a1af --- /dev/null +++ b/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-oci.yaml @@ -0,0 +1,11 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Rulesfile +metadata: + name: rulesfile-oci +spec: + priority: 50 + ociArtifact: + image: + repository: falcosecurity/rules/falco-rules + registry: + name: ghcr.io diff --git a/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-remove-source.yaml b/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-remove-source.yaml new file mode 100644 index 00000000..c9ae5cc6 --- /dev/null +++ b/test/e2e/chainsaw/rulesfile/lifecycle/rulesfile-remove-source.yaml @@ -0,0 +1,19 @@ +apiVersion: artifact.falcosecurity.dev/v1alpha1 +kind: Rulesfile +metadata: + name: rulesfile-rm +spec: + priority: 50 + ociArtifact: + image: + repository: falcosecurity/rules/falco-rules + tag: "3" + registry: + name: ghcr.io + inlineRules: + - rule: Removable Inline Rule + desc: A test rule that will be removed + condition: evt.type = open + output: "Removable inline rule triggered" + priority: WARNING + source: syscall diff --git a/test/e2e/chainsaw/validation/chainsaw-test.yaml b/test/e2e/chainsaw/validation/chainsaw-test.yaml new file mode 100644 index 00000000..08549260 --- /dev/null +++ b/test/e2e/chainsaw/validation/chainsaw-test.yaml @@ -0,0 +1,173 @@ +# Test: CRD validation rejects invalid resources. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: validation +spec: + description: Test CRD validation rejects invalid resources + bindings: + - name: falco_name + value: falco-test + - name: falco_version + value: "0.43.0" + steps: + - name: Reject invalid Falco type + try: + - create: + resource: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Falco + metadata: + name: invalid-type + spec: + type: StatefulSet + expect: + - check: + ($error != null): true + - name: Reject Config priority over 99 + try: + - create: + resource: + apiVersion: artifact.falcosecurity.dev/v1alpha1 + kind: Config + metadata: + name: invalid-priority-high + spec: + priority: 100 + config: "test: true" + expect: + - check: + ($error != null): true + - name: Reject Config priority below 0 + try: + - create: + resource: + apiVersion: artifact.falcosecurity.dev/v1alpha1 + kind: Config + metadata: + name: invalid-priority-low + spec: + priority: -1 + config: "test: true" + expect: + - check: + ($error != null): true + + # --- OCIArtifact API validation --- + - name: Reject Rulesfile ociArtifact without image.repository + try: + - create: + resource: + apiVersion: artifact.falcosecurity.dev/v1alpha1 + kind: Rulesfile + metadata: + name: invalid-oci-no-repo + spec: + priority: 50 + ociArtifact: + image: {} + expect: + - check: + ($error != null): true + + - name: Reject Plugin ociArtifact without image + try: + - create: + resource: + apiVersion: artifact.falcosecurity.dev/v1alpha1 + kind: Plugin + metadata: + name: invalid-oci-no-image + spec: + ociArtifact: {} + expect: + - check: + ($error != null): true + + - name: Reject Rulesfile with plainHTTP and tls both set + try: + - create: + resource: + apiVersion: artifact.falcosecurity.dev/v1alpha1 + kind: Rulesfile + metadata: + name: invalid-oci-plainhttp-tls + spec: + priority: 50 + ociArtifact: + image: + repository: falcosecurity/rules/falco-rules + registry: + plainHTTP: true + tls: + insecureSkipVerify: true + expect: + - check: + ($error != null): true + + - name: Reject Plugin with plainHTTP and tls both set + try: + - create: + resource: + apiVersion: artifact.falcosecurity.dev/v1alpha1 + kind: Plugin + metadata: + name: invalid-plugin-plainhttp-tls + spec: + ociArtifact: + image: + repository: falcosecurity/plugins/plugin/json + registry: + plainHTTP: true + tls: + insecureSkipVerify: true + expect: + - check: + ($error != null): true + + # --- Component CRD validation --- + - name: Reject invalid Component type + try: + - create: + resource: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Component + metadata: + name: invalid-type + spec: + component: + type: invalid + expect: + - check: + ($error != null): true + + - name: Reject Component replicas zero + try: + - create: + resource: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Component + metadata: + name: invalid-replicas + spec: + component: + type: metacollector + replicas: 0 + expect: + - check: + ($error != null): true + + - name: Reject Falco replicas zero + try: + - create: + resource: + apiVersion: instance.falcosecurity.dev/v1alpha1 + kind: Falco + metadata: + name: invalid-replicas + spec: + type: Deployment + replicas: 0 + expect: + - check: + ($error != null): true diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go deleted file mode 100644 index 1637caaf..00000000 --- a/test/e2e/e2e_suite_test.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (C) 2026 The Falco Authors -// -// 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. -// -// SPDX-License-Identifier: Apache-2.0 - -package e2e - -import ( - "fmt" - "os/exec" - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/falcosecurity/falco-operator/test/utils" -) - -var ( - projectImage = "falcosecurity/falco-operator:latest" -) - -// TestE2E runs the end-to-end (e2e) test suite for the project. These tests execute in an isolated, -// temporary environment to validate project changes with the purposed to be used in CI jobs. -// The default setup requires Kind, builds/loads the Manager Docker image locally, and installs -// CertManager and Prometheus. -func TestE2E(t *testing.T) { - RegisterFailHandler(Fail) - _, _ = fmt.Fprintf(GinkgoWriter, "Starting falco-operator integration test suite\n") - RunSpecs(t, "e2e suite") -} - -var _ = BeforeSuite(func() { - By("building the manager(Operator) image") - cmd := exec.Command("make", "docker-build", "OPERATOR=instance", fmt.Sprintf("IMG=%s", projectImage)) - _, err := utils.Run(cmd) - ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to build the manager(Operator) image") - - // TODO(user): If you want to change the e2e test vendor from Kind, ensure the image is - // built and available before running the tests. Also, remove the following block. - By("loading the manager(Operator) image on Kind") - err = utils.LoadImageToKindClusterWithName(projectImage) - ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to load the manager(Operator) image into Kind") -}) diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go deleted file mode 100644 index d428c778..00000000 --- a/test/e2e/e2e_test.go +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright (C) 2026 The Falco Authors -// -// 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. -// -// SPDX-License-Identifier: Apache-2.0 - -package e2e - -import ( - "fmt" - "os/exec" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/falcosecurity/falco-operator/test/utils" -) - -// namespace where the project is deployed in. -const namespace = "falco-operator" - -var _ = Describe("Manager", Ordered, func() { - var controllerPodName string - - // Before running the tests, set up the environment by creating the namespace, - // enforce the restricted security policy to the namespace, installing CRDs, - // and deploying the controller. - BeforeAll(func() { - By("creating manager namespace") - cmd := exec.Command("kubectl", "create", "ns", namespace) - _, err := utils.Run(cmd) - Expect(err).NotTo(HaveOccurred(), "Failed to create namespace") - - By("labeling the namespace to enforce the restricted security policy") - cmd = exec.Command("kubectl", "label", "--overwrite", "ns", namespace, - "pod-security.kubernetes.io/enforce=restricted") - _, err = utils.Run(cmd) - Expect(err).NotTo(HaveOccurred(), "Failed to label namespace with restricted policy") - - By("installing CRDs") - cmd = exec.Command("make", "install") - _, err = utils.Run(cmd) - Expect(err).NotTo(HaveOccurred(), "Failed to install CRDs") - - By("deploying the falco-operator") - cmd = exec.Command("make", "deploy", fmt.Sprintf("IMG=%s", projectImage)) - _, err = utils.Run(cmd) - Expect(err).NotTo(HaveOccurred(), "Failed to deploy the falco-operator") - }) - - // After all tests have been executed, clean up by undeploying the controller, uninstalling CRDs, - // and deleting the namespace. - AfterAll(func() { - By("cleaning up the curl pod for metrics") - cmd := exec.Command("kubectl", "delete", "pod", "curl-metrics", "-n", namespace) - _, _ = utils.Run(cmd) - - By("undeploying the falco-operator") - cmd = exec.Command("make", "undeploy") - _, _ = utils.Run(cmd) - - By("uninstalling CRDs") - cmd = exec.Command("make", "uninstall") - _, _ = utils.Run(cmd) - - By("removing manager namespace") - cmd = exec.Command("kubectl", "delete", "ns", namespace) - _, _ = utils.Run(cmd) - }) - - // After each test, check for failures and collect logs, events, - // and pod descriptions for debugging. - AfterEach(func() { - specReport := CurrentSpecReport() - if specReport.Failed() { - By("Fetching controller manager pod logs") - cmd := exec.Command("kubectl", "logs", controllerPodName, "-n", namespace) - controllerLogs, err := utils.Run(cmd) - if err == nil { - _, _ = fmt.Fprintf(GinkgoWriter, "Controller logs:\n %s", controllerLogs) - } else { - _, _ = fmt.Fprintf(GinkgoWriter, "Failed to get Controller logs: %s", err) - } - - By("Fetching Kubernetes events") - cmd = exec.Command("kubectl", "get", "events", "-n", namespace, "--sort-by=.lastTimestamp") - eventsOutput, err := utils.Run(cmd) - if err == nil { - _, _ = fmt.Fprintf(GinkgoWriter, "Kubernetes events:\n%s", eventsOutput) - } else { - _, _ = fmt.Fprintf(GinkgoWriter, "Failed to get Kubernetes events: %s", err) - } - - By("Fetching curl-metrics logs") - cmd = exec.Command("kubectl", "logs", "curl-metrics", "-n", namespace) - metricsOutput, err := utils.Run(cmd) - if err == nil { - _, _ = fmt.Fprintf(GinkgoWriter, "Metrics logs:\n %s", metricsOutput) - } else { - _, _ = fmt.Fprintf(GinkgoWriter, "Failed to get curl-metrics logs: %s", err) - } - - By("Fetching controller manager pod description") - cmd = exec.Command("kubectl", "describe", "pod", controllerPodName, "-n", namespace) - podDescription, err := utils.Run(cmd) - if err == nil { - fmt.Println("Pod description:\n", podDescription) - } else { - fmt.Println("Failed to describe controller pod") - } - } - }) - - SetDefaultEventuallyTimeout(2 * time.Minute) - SetDefaultEventuallyPollingInterval(time.Second) - - Context("Manager", func() { - It("should run successfully", func() { - By("validating that the falco-operator pod is running as expected") - verifyControllerUp := func(g Gomega) { - // Get the name of the falco-operator pod - cmd := exec.Command("kubectl", "get", - "pods", "-l", "control-plane=falco-operator", - "-o", "go-template={{ range .items }}"+ - "{{ if not .metadata.deletionTimestamp }}"+ - "{{ .metadata.name }}"+ - "{{ \"\\n\" }}{{ end }}{{ end }}", - "-n", namespace, - ) - - podOutput, err := utils.Run(cmd) - g.Expect(err).NotTo(HaveOccurred(), "Failed to retrieve falco-operator pod information") - podNames := utils.GetNonEmptyLines(podOutput) - g.Expect(podNames).To(HaveLen(1), "expected 1 controller pod running") - controllerPodName = podNames[0] - g.Expect(controllerPodName).To(ContainSubstring("falco-operator")) - - // Validate the pod's status - cmd = exec.Command("kubectl", "get", - "pods", controllerPodName, "-o", "jsonpath={.status.phase}", - "-n", namespace, - ) - output, err := utils.Run(cmd) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(output).To(Equal("Running"), "Incorrect falco-operator pod status") - } - Eventually(verifyControllerUp).Should(Succeed()) - }) - - // +kubebuilder:scaffold:e2e-webhooks-checks - - // TODO: Customize the e2e test suite with scenarios specific to your project. - // Consider applying sample/CR(s) and check their status and/or verifying - // the reconciliation by using the metrics, i.e.: - // metricsOutput := getMetricsOutput() - // Expect(metricsOutput).To(ContainSubstring( - // fmt.Sprintf(`controller_runtime_reconcile_total{controller="%s",result="success"} 1`, - // strings.ToLower(), - // )) - }) -}) diff --git a/test/utils/doc.go b/test/utils/doc.go deleted file mode 100644 index cd721849..00000000 --- a/test/utils/doc.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (C) 2026 The Falco Authors -// -// 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. -// -// SPDX-License-Identifier: Apache-2.0 - -// Package utils defines helper functions used to write tests. -package utils diff --git a/test/utils/utils.go b/test/utils/utils.go deleted file mode 100644 index 2f5ade3c..00000000 --- a/test/utils/utils.go +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright (C) 2026 The Falco Authors -// -// 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. -// -// SPDX-License-Identifier: Apache-2.0 - -package utils - -import ( - "bufio" - "bytes" - "fmt" - "os" - "os/exec" - "strings" - - . "github.com/onsi/ginkgo/v2" //nolint:staticcheck // no need to name the import -) - -const ( - prometheusOperatorVersion = "v0.77.1" - prometheusOperatorURL = "https://github.com/prometheus-operator/prometheus-operator/" + - "releases/download/%s/bundle.yaml" - - certmanagerVersion = "v1.16.0" - certmanagerURLTmpl = "https://github.com/jetstack/cert-manager/releases/download/%s/cert-manager.yaml" -) - -func warnError(err error) { - _, _ = fmt.Fprintf(GinkgoWriter, "warning: %v\n", err) -} - -// Run executes the provided command within this context. -func Run(cmd *exec.Cmd) (string, error) { - dir, _ := GetProjectDir() - cmd.Dir = dir - - if err := os.Chdir(cmd.Dir); err != nil { - _, _ = fmt.Fprintf(GinkgoWriter, "chdir dir: %s\n", err) - } - - cmd.Env = append(os.Environ(), "GO111MODULE=on") - command := strings.Join(cmd.Args, " ") - _, _ = fmt.Fprintf(GinkgoWriter, "running: %s\n", command) - output, err := cmd.CombinedOutput() - if err != nil { - return string(output), fmt.Errorf("%s failed with error: (%w) %s", command, err, string(output)) - } - - return string(output), nil -} - -// InstallPrometheusOperator installs the prometheus Operator to be used to export the enabled metrics. -func InstallPrometheusOperator() error { - url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion) - //nolint:gosec // false positive - cmd := exec.Command("kubectl", "create", "-f", url) - _, err := Run(cmd) - return err -} - -// UninstallPrometheusOperator uninstalls the prometheus. -func UninstallPrometheusOperator() { - url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion) - //nolint:gosec // false positive - cmd := exec.Command("kubectl", "delete", "-f", url) - if _, err := Run(cmd); err != nil { - warnError(err) - } -} - -// IsPrometheusCRDsInstalled checks if any Prometheus CRDs are installed -// by verifying the existence of key CRDs related to Prometheus. -func IsPrometheusCRDsInstalled() bool { - // List of common Prometheus CRDs - prometheusCRDs := []string{ - "prometheuses.monitoring.coreos.com", - "prometheusrules.monitoring.coreos.com", - "prometheusagents.monitoring.coreos.com", - } - - cmd := exec.Command("kubectl", "get", "crds", "-o", "custom-columns=NAME:.metadata.name") - output, err := Run(cmd) - if err != nil { - return false - } - crdList := GetNonEmptyLines(output) - for _, crd := range prometheusCRDs { - for _, line := range crdList { - if strings.Contains(line, crd) { - return true - } - } - } - - return false -} - -// UninstallCertManager uninstalls the cert manager. -func UninstallCertManager() { - url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion) - //nolint:gosec // false positive - cmd := exec.Command("kubectl", "delete", "-f", url) - if _, err := Run(cmd); err != nil { - warnError(err) - } -} - -// InstallCertManager installs the cert manager bundle. -func InstallCertManager() error { - url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion) - //nolint:gosec // false positive - cmd := exec.Command("kubectl", "apply", "-f", url) - if _, err := Run(cmd); err != nil { - return err - } - // Wait for cert-manager-webhook to be ready, which can take time if cert-manager - // was re-installed after uninstalling on a cluster. - cmd = exec.Command("kubectl", "wait", "deployment.apps/cert-manager-webhook", - "--for", "condition=Available", - "--namespace", "cert-manager", - "--timeout", "5m", - ) - - _, err := Run(cmd) - return err -} - -// IsCertManagerCRDsInstalled checks if any Cert Manager CRDs are installed -// by verifying the existence of key CRDs related to Cert Manager. -func IsCertManagerCRDsInstalled() bool { - // List of common Cert Manager CRDs - certManagerCRDs := []string{ - "certificates.cert-manager.io", - "issuers.cert-manager.io", - "clusterissuers.cert-manager.io", - "certificaterequests.cert-manager.io", - "orders.acme.cert-manager.io", - "challenges.acme.cert-manager.io", - } - - // Execute the kubectl command to get all CRDs - cmd := exec.Command("kubectl", "get", "crds") - output, err := Run(cmd) - if err != nil { - return false - } - - // Check if any of the Cert Manager CRDs are present - crdList := GetNonEmptyLines(output) - for _, crd := range certManagerCRDs { - for _, line := range crdList { - if strings.Contains(line, crd) { - return true - } - } - } - - return false -} - -// LoadImageToKindClusterWithName loads a local docker image to the kind cluster. -func LoadImageToKindClusterWithName(name string) error { - cluster := "kind" - if v, ok := os.LookupEnv("KIND_CLUSTER"); ok { - cluster = v - } - kindOptions := []string{"load", "docker-image", name, "--name", cluster} - //nolint:gosec // false positive - cmd := exec.Command("kind", kindOptions...) - _, err := Run(cmd) - return err -} - -// GetNonEmptyLines converts given command output string into individual objects -// according to line breakers, and ignores the empty elements in it. -func GetNonEmptyLines(output string) []string { - var res []string - elements := strings.SplitSeq(output, "\n") - for element := range elements { - if element != "" { - res = append(res, element) - } - } - - return res -} - -// GetProjectDir will return the directory where the project is. -func GetProjectDir() (string, error) { - wd, err := os.Getwd() - if err != nil { - return wd, err - } - wd = strings.ReplaceAll(wd, "/test/e2e", "") - return wd, nil -} - -// UncommentCode searches for target in the file and remove the comment prefix -// of the target content. The target content may span multiple lines. -func UncommentCode(filename, target, prefix string) error { - //nolint:gosec // false positive - content, err := os.ReadFile(filename) - if err != nil { - return err - } - strContent := string(content) - - idx := strings.Index(strContent, target) - if idx < 0 { - return fmt.Errorf("unable to find the code %s to be uncomment", target) - } - - out := new(bytes.Buffer) - _, err = out.Write(content[:idx]) - if err != nil { - return err - } - - scanner := bufio.NewScanner(bytes.NewBufferString(target)) - if !scanner.Scan() { - return nil - } - for { - _, err := out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)) - if err != nil { - return err - } - // Avoid writing a newline in case the previous line was the last in target. - if !scanner.Scan() { - break - } - if _, err := out.WriteString("\n"); err != nil { - return err - } - } - - _, err = out.Write(content[idx+len(target):]) - if err != nil { - return err - } - - //nolint:gosec // false positive - return os.WriteFile(filename, out.Bytes(), 0o644) -}