diff --git a/0.2.3/404.html b/0.2.3/404.html new file mode 100644 index 000000000..52eb01c76 --- /dev/null +++ b/0.2.3/404.html @@ -0,0 +1 @@ + Chainsaw

404 - Not found

\ No newline at end of file diff --git a/0.2.3/assets/images/favicon.png b/0.2.3/assets/images/favicon.png new file mode 100644 index 000000000..1cf13b9f9 Binary files /dev/null and b/0.2.3/assets/images/favicon.png differ diff --git a/0.2.3/assets/images/social/cicd/gh-action.png b/0.2.3/assets/images/social/cicd/gh-action.png new file mode 100644 index 000000000..427a8589c Binary files /dev/null and b/0.2.3/assets/images/social/cicd/gh-action.png differ diff --git a/0.2.3/assets/images/social/community/index.png b/0.2.3/assets/images/social/community/index.png new file mode 100644 index 000000000..58eed4346 Binary files /dev/null and b/0.2.3/assets/images/social/community/index.png differ diff --git a/0.2.3/assets/images/social/configuration/file.png b/0.2.3/assets/images/social/configuration/file.png new file mode 100644 index 000000000..b3458bad7 Binary files /dev/null and b/0.2.3/assets/images/social/configuration/file.png differ diff --git a/0.2.3/assets/images/social/configuration/flags.png b/0.2.3/assets/images/social/configuration/flags.png new file mode 100644 index 000000000..0ce825e91 Binary files /dev/null and b/0.2.3/assets/images/social/configuration/flags.png differ diff --git a/0.2.3/assets/images/social/configuration/index.png b/0.2.3/assets/images/social/configuration/index.png new file mode 100644 index 000000000..a40b1e581 Binary files /dev/null and b/0.2.3/assets/images/social/configuration/index.png differ diff --git a/0.2.3/assets/images/social/configuration/options/cleanup.png b/0.2.3/assets/images/social/configuration/options/cleanup.png new file mode 100644 index 000000000..463d08287 Binary files /dev/null and b/0.2.3/assets/images/social/configuration/options/cleanup.png differ diff --git a/0.2.3/assets/images/social/configuration/options/clusters.png b/0.2.3/assets/images/social/configuration/options/clusters.png new file mode 100644 index 000000000..634462018 Binary files /dev/null and b/0.2.3/assets/images/social/configuration/options/clusters.png differ diff --git a/0.2.3/assets/images/social/configuration/options/deletion.png b/0.2.3/assets/images/social/configuration/options/deletion.png new file mode 100644 index 000000000..6d0e02dcc Binary files /dev/null and b/0.2.3/assets/images/social/configuration/options/deletion.png differ diff --git a/0.2.3/assets/images/social/configuration/options/discovery.png b/0.2.3/assets/images/social/configuration/options/discovery.png new file mode 100644 index 000000000..938d3fe93 Binary files /dev/null and b/0.2.3/assets/images/social/configuration/options/discovery.png differ diff --git a/0.2.3/assets/images/social/configuration/options/error.png b/0.2.3/assets/images/social/configuration/options/error.png new file mode 100644 index 000000000..a6995e0f5 Binary files /dev/null and b/0.2.3/assets/images/social/configuration/options/error.png differ diff --git a/0.2.3/assets/images/social/configuration/options/execution.png b/0.2.3/assets/images/social/configuration/options/execution.png new file mode 100644 index 000000000..51965abb5 Binary files /dev/null and b/0.2.3/assets/images/social/configuration/options/execution.png differ diff --git a/0.2.3/assets/images/social/configuration/options/label-selectors.png b/0.2.3/assets/images/social/configuration/options/label-selectors.png new file mode 100644 index 000000000..553712218 Binary files /dev/null and b/0.2.3/assets/images/social/configuration/options/label-selectors.png differ diff --git a/0.2.3/assets/images/social/configuration/options/namespace.png b/0.2.3/assets/images/social/configuration/options/namespace.png new file mode 100644 index 000000000..70644541f Binary files /dev/null and b/0.2.3/assets/images/social/configuration/options/namespace.png differ diff --git a/0.2.3/assets/images/social/configuration/options/no-cluster.png b/0.2.3/assets/images/social/configuration/options/no-cluster.png new file mode 100644 index 000000000..e60d22852 Binary files /dev/null and b/0.2.3/assets/images/social/configuration/options/no-cluster.png differ diff --git a/0.2.3/assets/images/social/configuration/options/pause.png b/0.2.3/assets/images/social/configuration/options/pause.png new file mode 100644 index 000000000..f77943b96 Binary files /dev/null and b/0.2.3/assets/images/social/configuration/options/pause.png differ diff --git a/0.2.3/assets/images/social/configuration/options/report.png b/0.2.3/assets/images/social/configuration/options/report.png new file mode 100644 index 000000000..ee6f14264 Binary files /dev/null and b/0.2.3/assets/images/social/configuration/options/report.png differ diff --git a/0.2.3/assets/images/social/configuration/options/templating.png b/0.2.3/assets/images/social/configuration/options/templating.png new file mode 100644 index 000000000..b90648446 Binary files /dev/null and b/0.2.3/assets/images/social/configuration/options/templating.png differ diff --git a/0.2.3/assets/images/social/configuration/options/timeouts.png b/0.2.3/assets/images/social/configuration/options/timeouts.png new file mode 100644 index 000000000..a374cc2bc Binary files /dev/null and b/0.2.3/assets/images/social/configuration/options/timeouts.png differ diff --git a/0.2.3/assets/images/social/configuration/options/values.png b/0.2.3/assets/images/social/configuration/options/values.png new file mode 100644 index 000000000..1a2c5adf7 Binary files /dev/null and b/0.2.3/assets/images/social/configuration/options/values.png differ diff --git a/0.2.3/assets/images/social/examples/concurrency.png b/0.2.3/assets/images/social/examples/concurrency.png new file mode 100644 index 000000000..2621023f2 Binary files /dev/null and b/0.2.3/assets/images/social/examples/concurrency.png differ diff --git a/0.2.3/assets/images/social/examples/crds.png b/0.2.3/assets/images/social/examples/crds.png new file mode 100644 index 000000000..609cdcf1f Binary files /dev/null and b/0.2.3/assets/images/social/examples/crds.png differ diff --git a/0.2.3/assets/images/social/examples/events.png b/0.2.3/assets/images/social/examples/events.png new file mode 100644 index 000000000..6e19339f1 Binary files /dev/null and b/0.2.3/assets/images/social/examples/events.png differ diff --git a/0.2.3/assets/images/social/examples/index.png b/0.2.3/assets/images/social/examples/index.png new file mode 100644 index 000000000..8146d4d94 Binary files /dev/null and b/0.2.3/assets/images/social/examples/index.png differ diff --git a/0.2.3/assets/images/social/examples/inline.png b/0.2.3/assets/images/social/examples/inline.png new file mode 100644 index 000000000..4e9e9d5d8 Binary files /dev/null and b/0.2.3/assets/images/social/examples/inline.png differ diff --git a/0.2.3/assets/images/social/examples/kube-version.png b/0.2.3/assets/images/social/examples/kube-version.png new file mode 100644 index 000000000..5cfc404f3 Binary files /dev/null and b/0.2.3/assets/images/social/examples/kube-version.png differ diff --git a/0.2.3/assets/images/social/examples/label-selectors.png b/0.2.3/assets/images/social/examples/label-selectors.png new file mode 100644 index 000000000..d07b12c69 Binary files /dev/null and b/0.2.3/assets/images/social/examples/label-selectors.png differ diff --git a/0.2.3/assets/images/social/examples/multi-cluster.png b/0.2.3/assets/images/social/examples/multi-cluster.png new file mode 100644 index 000000000..fce4a83f2 Binary files /dev/null and b/0.2.3/assets/images/social/examples/multi-cluster.png differ diff --git a/0.2.3/assets/images/social/examples/negative-testing.png b/0.2.3/assets/images/social/examples/negative-testing.png new file mode 100644 index 000000000..b3bfdb3d6 Binary files /dev/null and b/0.2.3/assets/images/social/examples/negative-testing.png differ diff --git a/0.2.3/assets/images/social/examples/non-resource-assertions.png b/0.2.3/assets/images/social/examples/non-resource-assertions.png new file mode 100644 index 000000000..005b1a087 Binary files /dev/null and b/0.2.3/assets/images/social/examples/non-resource-assertions.png differ diff --git a/0.2.3/assets/images/social/examples/test-output.png b/0.2.3/assets/images/social/examples/test-output.png new file mode 100644 index 000000000..514bfd48b Binary files /dev/null and b/0.2.3/assets/images/social/examples/test-output.png differ diff --git a/0.2.3/assets/images/social/examples/values.png b/0.2.3/assets/images/social/examples/values.png new file mode 100644 index 000000000..9a8a29d33 Binary files /dev/null and b/0.2.3/assets/images/social/examples/values.png differ diff --git a/0.2.3/assets/images/social/general/bindings.png b/0.2.3/assets/images/social/general/bindings.png new file mode 100644 index 000000000..3dcd5656f Binary files /dev/null and b/0.2.3/assets/images/social/general/bindings.png differ diff --git a/0.2.3/assets/images/social/general/checks.png b/0.2.3/assets/images/social/general/checks.png new file mode 100644 index 000000000..2cfa38e59 Binary files /dev/null and b/0.2.3/assets/images/social/general/checks.png differ diff --git a/0.2.3/assets/images/social/general/inheritance.png b/0.2.3/assets/images/social/general/inheritance.png new file mode 100644 index 000000000..b5912e838 Binary files /dev/null and b/0.2.3/assets/images/social/general/inheritance.png differ diff --git a/0.2.3/assets/images/social/general/namespace.png b/0.2.3/assets/images/social/general/namespace.png new file mode 100644 index 000000000..d6137b361 Binary files /dev/null and b/0.2.3/assets/images/social/general/namespace.png differ diff --git a/0.2.3/assets/images/social/general/outputs.png b/0.2.3/assets/images/social/general/outputs.png new file mode 100644 index 000000000..8acf31155 Binary files /dev/null and b/0.2.3/assets/images/social/general/outputs.png differ diff --git a/0.2.3/assets/images/social/general/references.png b/0.2.3/assets/images/social/general/references.png new file mode 100644 index 000000000..97e4bf4e2 Binary files /dev/null and b/0.2.3/assets/images/social/general/references.png differ diff --git a/0.2.3/assets/images/social/general/templating.png b/0.2.3/assets/images/social/general/templating.png new file mode 100644 index 000000000..d6ad7bc50 Binary files /dev/null and b/0.2.3/assets/images/social/general/templating.png differ diff --git a/0.2.3/assets/images/social/guides/kuttl-migration.png b/0.2.3/assets/images/social/guides/kuttl-migration.png new file mode 100644 index 000000000..fe2d134a3 Binary files /dev/null and b/0.2.3/assets/images/social/guides/kuttl-migration.png differ diff --git a/0.2.3/assets/images/social/guides/lint.png b/0.2.3/assets/images/social/guides/lint.png new file mode 100644 index 000000000..d9f970bb4 Binary files /dev/null and b/0.2.3/assets/images/social/guides/lint.png differ diff --git a/0.2.3/assets/images/social/guides/test-docs.png b/0.2.3/assets/images/social/guides/test-docs.png new file mode 100644 index 000000000..508c2dffd Binary files /dev/null and b/0.2.3/assets/images/social/guides/test-docs.png differ diff --git a/0.2.3/assets/images/social/index.png b/0.2.3/assets/images/social/index.png new file mode 100644 index 000000000..62aff4368 Binary files /dev/null and b/0.2.3/assets/images/social/index.png differ diff --git a/0.2.3/assets/images/social/operations/apply.png b/0.2.3/assets/images/social/operations/apply.png new file mode 100644 index 000000000..54c7bbf37 Binary files /dev/null and b/0.2.3/assets/images/social/operations/apply.png differ diff --git a/0.2.3/assets/images/social/operations/assert.png b/0.2.3/assets/images/social/operations/assert.png new file mode 100644 index 000000000..0ad5d14ef Binary files /dev/null and b/0.2.3/assets/images/social/operations/assert.png differ diff --git a/0.2.3/assets/images/social/operations/command.png b/0.2.3/assets/images/social/operations/command.png new file mode 100644 index 000000000..9b217c60b Binary files /dev/null and b/0.2.3/assets/images/social/operations/command.png differ diff --git a/0.2.3/assets/images/social/operations/create.png b/0.2.3/assets/images/social/operations/create.png new file mode 100644 index 000000000..a75932042 Binary files /dev/null and b/0.2.3/assets/images/social/operations/create.png differ diff --git a/0.2.3/assets/images/social/operations/delete.png b/0.2.3/assets/images/social/operations/delete.png new file mode 100644 index 000000000..9f4122e81 Binary files /dev/null and b/0.2.3/assets/images/social/operations/delete.png differ diff --git a/0.2.3/assets/images/social/operations/error.png b/0.2.3/assets/images/social/operations/error.png new file mode 100644 index 000000000..a3c864223 Binary files /dev/null and b/0.2.3/assets/images/social/operations/error.png differ diff --git a/0.2.3/assets/images/social/operations/helpers/describe.png b/0.2.3/assets/images/social/operations/helpers/describe.png new file mode 100644 index 000000000..2401611c3 Binary files /dev/null and b/0.2.3/assets/images/social/operations/helpers/describe.png differ diff --git a/0.2.3/assets/images/social/operations/helpers/events.png b/0.2.3/assets/images/social/operations/helpers/events.png new file mode 100644 index 000000000..7a294726e Binary files /dev/null and b/0.2.3/assets/images/social/operations/helpers/events.png differ diff --git a/0.2.3/assets/images/social/operations/helpers/get.png b/0.2.3/assets/images/social/operations/helpers/get.png new file mode 100644 index 000000000..11710ed9b Binary files /dev/null and b/0.2.3/assets/images/social/operations/helpers/get.png differ diff --git a/0.2.3/assets/images/social/operations/helpers/index.png b/0.2.3/assets/images/social/operations/helpers/index.png new file mode 100644 index 000000000..0a35bf36a Binary files /dev/null and b/0.2.3/assets/images/social/operations/helpers/index.png differ diff --git a/0.2.3/assets/images/social/operations/helpers/logs.png b/0.2.3/assets/images/social/operations/helpers/logs.png new file mode 100644 index 000000000..1694dfdc1 Binary files /dev/null and b/0.2.3/assets/images/social/operations/helpers/logs.png differ diff --git a/0.2.3/assets/images/social/operations/helpers/wait.png b/0.2.3/assets/images/social/operations/helpers/wait.png new file mode 100644 index 000000000..83f6fbf64 Binary files /dev/null and b/0.2.3/assets/images/social/operations/helpers/wait.png differ diff --git a/0.2.3/assets/images/social/operations/index.png b/0.2.3/assets/images/social/operations/index.png new file mode 100644 index 000000000..225ac2df7 Binary files /dev/null and b/0.2.3/assets/images/social/operations/index.png differ diff --git a/0.2.3/assets/images/social/operations/patch.png b/0.2.3/assets/images/social/operations/patch.png new file mode 100644 index 000000000..27c165a9c Binary files /dev/null and b/0.2.3/assets/images/social/operations/patch.png differ diff --git a/0.2.3/assets/images/social/operations/script.png b/0.2.3/assets/images/social/operations/script.png new file mode 100644 index 000000000..57664e121 Binary files /dev/null and b/0.2.3/assets/images/social/operations/script.png differ diff --git a/0.2.3/assets/images/social/operations/sleep.png b/0.2.3/assets/images/social/operations/sleep.png new file mode 100644 index 000000000..2b42a2051 Binary files /dev/null and b/0.2.3/assets/images/social/operations/sleep.png differ diff --git a/0.2.3/assets/images/social/operations/update.png b/0.2.3/assets/images/social/operations/update.png new file mode 100644 index 000000000..20ba58988 Binary files /dev/null and b/0.2.3/assets/images/social/operations/update.png differ diff --git a/0.2.3/assets/images/social/quick-start/assertion-trees.png b/0.2.3/assets/images/social/quick-start/assertion-trees.png new file mode 100644 index 000000000..d46d38eb9 Binary files /dev/null and b/0.2.3/assets/images/social/quick-start/assertion-trees.png differ diff --git a/0.2.3/assets/images/social/quick-start/bindings.png b/0.2.3/assets/images/social/quick-start/bindings.png new file mode 100644 index 000000000..6bf7820b5 Binary files /dev/null and b/0.2.3/assets/images/social/quick-start/bindings.png differ diff --git a/0.2.3/assets/images/social/quick-start/cleanup.png b/0.2.3/assets/images/social/quick-start/cleanup.png new file mode 100644 index 000000000..2cc3a1c45 Binary files /dev/null and b/0.2.3/assets/images/social/quick-start/cleanup.png differ diff --git a/0.2.3/assets/images/social/quick-start/completion.png b/0.2.3/assets/images/social/quick-start/completion.png new file mode 100644 index 000000000..231c73ab5 Binary files /dev/null and b/0.2.3/assets/images/social/quick-start/completion.png differ diff --git a/0.2.3/assets/images/social/quick-start/first-test.png b/0.2.3/assets/images/social/quick-start/first-test.png new file mode 100644 index 000000000..cee655500 Binary files /dev/null and b/0.2.3/assets/images/social/quick-start/first-test.png differ diff --git a/0.2.3/assets/images/social/quick-start/index.png b/0.2.3/assets/images/social/quick-start/index.png new file mode 100644 index 000000000..f2c8b3f3a Binary files /dev/null and b/0.2.3/assets/images/social/quick-start/index.png differ diff --git a/0.2.3/assets/images/social/quick-start/install.png b/0.2.3/assets/images/social/quick-start/install.png new file mode 100644 index 000000000..a9b5a1774 Binary files /dev/null and b/0.2.3/assets/images/social/quick-start/install.png differ diff --git a/0.2.3/assets/images/social/quick-start/next-steps.png b/0.2.3/assets/images/social/quick-start/next-steps.png new file mode 100644 index 000000000..baf412e8e Binary files /dev/null and b/0.2.3/assets/images/social/quick-start/next-steps.png differ diff --git a/0.2.3/assets/images/social/quick-start/operation-outputs.png b/0.2.3/assets/images/social/quick-start/operation-outputs.png new file mode 100644 index 000000000..acc0373df Binary files /dev/null and b/0.2.3/assets/images/social/quick-start/operation-outputs.png differ diff --git a/0.2.3/assets/images/social/quick-start/resource-templating.png b/0.2.3/assets/images/social/quick-start/resource-templating.png new file mode 100644 index 000000000..6ec2a59a2 Binary files /dev/null and b/0.2.3/assets/images/social/quick-start/resource-templating.png differ diff --git a/0.2.3/assets/images/social/quick-start/resources.png b/0.2.3/assets/images/social/quick-start/resources.png new file mode 100644 index 000000000..91e970616 Binary files /dev/null and b/0.2.3/assets/images/social/quick-start/resources.png differ diff --git a/0.2.3/assets/images/social/quick-start/run-tests.png b/0.2.3/assets/images/social/quick-start/run-tests.png new file mode 100644 index 000000000..10eca4603 Binary files /dev/null and b/0.2.3/assets/images/social/quick-start/run-tests.png differ diff --git a/0.2.3/assets/images/social/quick-start/timeouts.png b/0.2.3/assets/images/social/quick-start/timeouts.png new file mode 100644 index 000000000..abaa3e4f1 Binary files /dev/null and b/0.2.3/assets/images/social/quick-start/timeouts.png differ diff --git a/0.2.3/assets/images/social/quick-start/try-catch.png b/0.2.3/assets/images/social/quick-start/try-catch.png new file mode 100644 index 000000000..61dbc599e Binary files /dev/null and b/0.2.3/assets/images/social/quick-start/try-catch.png differ diff --git a/0.2.3/assets/images/social/reference/apis/chainsaw.v1alpha1.png b/0.2.3/assets/images/social/reference/apis/chainsaw.v1alpha1.png new file mode 100644 index 000000000..53b53a146 Binary files /dev/null and b/0.2.3/assets/images/social/reference/apis/chainsaw.v1alpha1.png differ diff --git a/0.2.3/assets/images/social/reference/apis/chainsaw.v1alpha2.png b/0.2.3/assets/images/social/reference/apis/chainsaw.v1alpha2.png new file mode 100644 index 000000000..232d3288c Binary files /dev/null and b/0.2.3/assets/images/social/reference/apis/chainsaw.v1alpha2.png differ diff --git a/0.2.3/assets/images/social/reference/builtins.png b/0.2.3/assets/images/social/reference/builtins.png new file mode 100644 index 000000000..154105906 Binary files /dev/null and b/0.2.3/assets/images/social/reference/builtins.png differ diff --git a/0.2.3/assets/images/social/reference/commands/chainsaw.png b/0.2.3/assets/images/social/reference/commands/chainsaw.png new file mode 100644 index 000000000..1447175b1 Binary files /dev/null and b/0.2.3/assets/images/social/reference/commands/chainsaw.png differ diff --git a/0.2.3/assets/images/social/reference/commands/chainsaw_assert.png b/0.2.3/assets/images/social/reference/commands/chainsaw_assert.png new file mode 100644 index 000000000..acf773d50 Binary files /dev/null and b/0.2.3/assets/images/social/reference/commands/chainsaw_assert.png differ diff --git a/0.2.3/assets/images/social/reference/commands/chainsaw_build.png b/0.2.3/assets/images/social/reference/commands/chainsaw_build.png new file mode 100644 index 000000000..f0233c2a1 Binary files /dev/null and b/0.2.3/assets/images/social/reference/commands/chainsaw_build.png differ diff --git a/0.2.3/assets/images/social/reference/commands/chainsaw_build_docs.png b/0.2.3/assets/images/social/reference/commands/chainsaw_build_docs.png new file mode 100644 index 000000000..dd2cb41b3 Binary files /dev/null and b/0.2.3/assets/images/social/reference/commands/chainsaw_build_docs.png differ diff --git a/0.2.3/assets/images/social/reference/commands/chainsaw_completion.png b/0.2.3/assets/images/social/reference/commands/chainsaw_completion.png new file mode 100644 index 000000000..b64f7d1ac Binary files /dev/null and b/0.2.3/assets/images/social/reference/commands/chainsaw_completion.png differ diff --git a/0.2.3/assets/images/social/reference/commands/chainsaw_completion_bash.png b/0.2.3/assets/images/social/reference/commands/chainsaw_completion_bash.png new file mode 100644 index 000000000..67a3886ae Binary files /dev/null and b/0.2.3/assets/images/social/reference/commands/chainsaw_completion_bash.png differ diff --git a/0.2.3/assets/images/social/reference/commands/chainsaw_completion_fish.png b/0.2.3/assets/images/social/reference/commands/chainsaw_completion_fish.png new file mode 100644 index 000000000..3d6cf6a66 Binary files /dev/null and b/0.2.3/assets/images/social/reference/commands/chainsaw_completion_fish.png differ diff --git a/0.2.3/assets/images/social/reference/commands/chainsaw_completion_powershell.png b/0.2.3/assets/images/social/reference/commands/chainsaw_completion_powershell.png new file mode 100644 index 000000000..0108f4c77 Binary files /dev/null and b/0.2.3/assets/images/social/reference/commands/chainsaw_completion_powershell.png differ diff --git a/0.2.3/assets/images/social/reference/commands/chainsaw_completion_zsh.png b/0.2.3/assets/images/social/reference/commands/chainsaw_completion_zsh.png new file mode 100644 index 000000000..7a90a865d Binary files /dev/null and b/0.2.3/assets/images/social/reference/commands/chainsaw_completion_zsh.png differ diff --git a/0.2.3/assets/images/social/reference/commands/chainsaw_create.png b/0.2.3/assets/images/social/reference/commands/chainsaw_create.png new file mode 100644 index 000000000..8412f7db4 Binary files /dev/null and b/0.2.3/assets/images/social/reference/commands/chainsaw_create.png differ diff --git a/0.2.3/assets/images/social/reference/commands/chainsaw_create_test.png b/0.2.3/assets/images/social/reference/commands/chainsaw_create_test.png new file mode 100644 index 000000000..38e8ce42c Binary files /dev/null and b/0.2.3/assets/images/social/reference/commands/chainsaw_create_test.png differ diff --git a/0.2.3/assets/images/social/reference/commands/chainsaw_docs.png b/0.2.3/assets/images/social/reference/commands/chainsaw_docs.png new file mode 100644 index 000000000..c6b233ccc Binary files /dev/null and b/0.2.3/assets/images/social/reference/commands/chainsaw_docs.png differ diff --git a/0.2.3/assets/images/social/reference/commands/chainsaw_export.png b/0.2.3/assets/images/social/reference/commands/chainsaw_export.png new file mode 100644 index 000000000..b4d97176b Binary files /dev/null and b/0.2.3/assets/images/social/reference/commands/chainsaw_export.png differ diff --git a/0.2.3/assets/images/social/reference/commands/chainsaw_export_schemas.png b/0.2.3/assets/images/social/reference/commands/chainsaw_export_schemas.png new file mode 100644 index 000000000..76b190796 Binary files /dev/null and b/0.2.3/assets/images/social/reference/commands/chainsaw_export_schemas.png differ diff --git a/0.2.3/assets/images/social/reference/commands/chainsaw_lint.png b/0.2.3/assets/images/social/reference/commands/chainsaw_lint.png new file mode 100644 index 000000000..be1ad3b16 Binary files /dev/null and b/0.2.3/assets/images/social/reference/commands/chainsaw_lint.png differ diff --git a/0.2.3/assets/images/social/reference/commands/chainsaw_migrate.png b/0.2.3/assets/images/social/reference/commands/chainsaw_migrate.png new file mode 100644 index 000000000..aeec23ff5 Binary files /dev/null and b/0.2.3/assets/images/social/reference/commands/chainsaw_migrate.png differ diff --git a/0.2.3/assets/images/social/reference/commands/chainsaw_migrate_kuttl.png b/0.2.3/assets/images/social/reference/commands/chainsaw_migrate_kuttl.png new file mode 100644 index 000000000..8e4a7ea74 Binary files /dev/null and b/0.2.3/assets/images/social/reference/commands/chainsaw_migrate_kuttl.png differ diff --git a/0.2.3/assets/images/social/reference/commands/chainsaw_migrate_kuttl_config.png b/0.2.3/assets/images/social/reference/commands/chainsaw_migrate_kuttl_config.png new file mode 100644 index 000000000..8fe94f5cb Binary files /dev/null and b/0.2.3/assets/images/social/reference/commands/chainsaw_migrate_kuttl_config.png differ diff --git a/0.2.3/assets/images/social/reference/commands/chainsaw_migrate_kuttl_tests.png b/0.2.3/assets/images/social/reference/commands/chainsaw_migrate_kuttl_tests.png new file mode 100644 index 000000000..b9762867a Binary files /dev/null and b/0.2.3/assets/images/social/reference/commands/chainsaw_migrate_kuttl_tests.png differ diff --git a/0.2.3/assets/images/social/reference/commands/chainsaw_test.png b/0.2.3/assets/images/social/reference/commands/chainsaw_test.png new file mode 100644 index 000000000..c9f69339a Binary files /dev/null and b/0.2.3/assets/images/social/reference/commands/chainsaw_test.png differ diff --git a/0.2.3/assets/images/social/reference/commands/chainsaw_version.png b/0.2.3/assets/images/social/reference/commands/chainsaw_version.png new file mode 100644 index 000000000..bb4b949a6 Binary files /dev/null and b/0.2.3/assets/images/social/reference/commands/chainsaw_version.png differ diff --git a/0.2.3/assets/images/social/reference/index.png b/0.2.3/assets/images/social/reference/index.png new file mode 100644 index 000000000..93d076c0a Binary files /dev/null and b/0.2.3/assets/images/social/reference/index.png differ diff --git a/0.2.3/assets/images/social/reference/jp/functions.png b/0.2.3/assets/images/social/reference/jp/functions.png new file mode 100644 index 000000000..b5f03746d Binary files /dev/null and b/0.2.3/assets/images/social/reference/jp/functions.png differ diff --git a/0.2.3/assets/images/social/reference/json-schemas.png b/0.2.3/assets/images/social/reference/json-schemas.png new file mode 100644 index 000000000..ee2328e81 Binary files /dev/null and b/0.2.3/assets/images/social/reference/json-schemas.png differ diff --git a/0.2.3/assets/images/social/step/catch.png b/0.2.3/assets/images/social/step/catch.png new file mode 100644 index 000000000..6a6427e0f Binary files /dev/null and b/0.2.3/assets/images/social/step/catch.png differ diff --git a/0.2.3/assets/images/social/step/cleanup.png b/0.2.3/assets/images/social/step/cleanup.png new file mode 100644 index 000000000..167659e2d Binary files /dev/null and b/0.2.3/assets/images/social/step/cleanup.png differ diff --git a/0.2.3/assets/images/social/step/finally.png b/0.2.3/assets/images/social/step/finally.png new file mode 100644 index 000000000..352028754 Binary files /dev/null and b/0.2.3/assets/images/social/step/finally.png differ diff --git a/0.2.3/assets/images/social/step/index.png b/0.2.3/assets/images/social/step/index.png new file mode 100644 index 000000000..413f1e1eb Binary files /dev/null and b/0.2.3/assets/images/social/step/index.png differ diff --git a/0.2.3/assets/images/social/step/try.png b/0.2.3/assets/images/social/step/try.png new file mode 100644 index 000000000..9c63ff7a4 Binary files /dev/null and b/0.2.3/assets/images/social/step/try.png differ diff --git a/0.2.3/assets/images/social/test/conventional.png b/0.2.3/assets/images/social/test/conventional.png new file mode 100644 index 000000000..7fefdb9e2 Binary files /dev/null and b/0.2.3/assets/images/social/test/conventional.png differ diff --git a/0.2.3/assets/images/social/test/explicit.png b/0.2.3/assets/images/social/test/explicit.png new file mode 100644 index 000000000..c4aced153 Binary files /dev/null and b/0.2.3/assets/images/social/test/explicit.png differ diff --git a/0.2.3/assets/images/social/test/index.png b/0.2.3/assets/images/social/test/index.png new file mode 100644 index 000000000..35d445963 Binary files /dev/null and b/0.2.3/assets/images/social/test/index.png differ diff --git a/0.2.3/assets/images/social/test/spec/index.png b/0.2.3/assets/images/social/test/spec/index.png new file mode 100644 index 000000000..9fec30f6c Binary files /dev/null and b/0.2.3/assets/images/social/test/spec/index.png differ diff --git a/0.2.3/assets/javascripts/bundle.081f42fc.min.js b/0.2.3/assets/javascripts/bundle.081f42fc.min.js new file mode 100644 index 000000000..32734cd37 --- /dev/null +++ b/0.2.3/assets/javascripts/bundle.081f42fc.min.js @@ -0,0 +1,29 @@ +"use strict";(()=>{var Fi=Object.create;var gr=Object.defineProperty;var ji=Object.getOwnPropertyDescriptor;var Wi=Object.getOwnPropertyNames,Dt=Object.getOwnPropertySymbols,Ui=Object.getPrototypeOf,xr=Object.prototype.hasOwnProperty,no=Object.prototype.propertyIsEnumerable;var oo=(e,t,r)=>t in e?gr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,R=(e,t)=>{for(var r in t||(t={}))xr.call(t,r)&&oo(e,r,t[r]);if(Dt)for(var r of Dt(t))no.call(t,r)&&oo(e,r,t[r]);return e};var io=(e,t)=>{var r={};for(var o in e)xr.call(e,o)&&t.indexOf(o)<0&&(r[o]=e[o]);if(e!=null&&Dt)for(var o of Dt(e))t.indexOf(o)<0&&no.call(e,o)&&(r[o]=e[o]);return r};var yr=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Di=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Wi(t))!xr.call(e,n)&&n!==r&&gr(e,n,{get:()=>t[n],enumerable:!(o=ji(t,n))||o.enumerable});return e};var Vt=(e,t,r)=>(r=e!=null?Fi(Ui(e)):{},Di(t||!e||!e.__esModule?gr(r,"default",{value:e,enumerable:!0}):r,e));var ao=(e,t,r)=>new Promise((o,n)=>{var i=p=>{try{s(r.next(p))}catch(c){n(c)}},a=p=>{try{s(r.throw(p))}catch(c){n(c)}},s=p=>p.done?o(p.value):Promise.resolve(p.value).then(i,a);s((r=r.apply(e,t)).next())});var co=yr((Er,so)=>{(function(e,t){typeof Er=="object"&&typeof so!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(Er,function(){"use strict";function e(r){var o=!0,n=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(H){return!!(H&&H!==document&&H.nodeName!=="HTML"&&H.nodeName!=="BODY"&&"classList"in H&&"contains"in H.classList)}function p(H){var mt=H.type,ze=H.tagName;return!!(ze==="INPUT"&&a[mt]&&!H.readOnly||ze==="TEXTAREA"&&!H.readOnly||H.isContentEditable)}function c(H){H.classList.contains("focus-visible")||(H.classList.add("focus-visible"),H.setAttribute("data-focus-visible-added",""))}function l(H){H.hasAttribute("data-focus-visible-added")&&(H.classList.remove("focus-visible"),H.removeAttribute("data-focus-visible-added"))}function f(H){H.metaKey||H.altKey||H.ctrlKey||(s(r.activeElement)&&c(r.activeElement),o=!0)}function u(H){o=!1}function h(H){s(H.target)&&(o||p(H.target))&&c(H.target)}function w(H){s(H.target)&&(H.target.classList.contains("focus-visible")||H.target.hasAttribute("data-focus-visible-added"))&&(n=!0,window.clearTimeout(i),i=window.setTimeout(function(){n=!1},100),l(H.target))}function A(H){document.visibilityState==="hidden"&&(n&&(o=!0),te())}function te(){document.addEventListener("mousemove",J),document.addEventListener("mousedown",J),document.addEventListener("mouseup",J),document.addEventListener("pointermove",J),document.addEventListener("pointerdown",J),document.addEventListener("pointerup",J),document.addEventListener("touchmove",J),document.addEventListener("touchstart",J),document.addEventListener("touchend",J)}function ie(){document.removeEventListener("mousemove",J),document.removeEventListener("mousedown",J),document.removeEventListener("mouseup",J),document.removeEventListener("pointermove",J),document.removeEventListener("pointerdown",J),document.removeEventListener("pointerup",J),document.removeEventListener("touchmove",J),document.removeEventListener("touchstart",J),document.removeEventListener("touchend",J)}function J(H){H.target.nodeName&&H.target.nodeName.toLowerCase()==="html"||(o=!1,ie())}document.addEventListener("keydown",f,!0),document.addEventListener("mousedown",u,!0),document.addEventListener("pointerdown",u,!0),document.addEventListener("touchstart",u,!0),document.addEventListener("visibilitychange",A,!0),te(),r.addEventListener("focus",h,!0),r.addEventListener("blur",w,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var Yr=yr((Rt,Kr)=>{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof Rt=="object"&&typeof Kr=="object"?Kr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Rt=="object"?Rt.ClipboardJS=r():t.ClipboardJS=r()})(Rt,function(){return function(){var e={686:function(o,n,i){"use strict";i.d(n,{default:function(){return Ii}});var a=i(279),s=i.n(a),p=i(370),c=i.n(p),l=i(817),f=i.n(l);function u(V){try{return document.execCommand(V)}catch(_){return!1}}var h=function(_){var O=f()(_);return u("cut"),O},w=h;function A(V){var _=document.documentElement.getAttribute("dir")==="rtl",O=document.createElement("textarea");O.style.fontSize="12pt",O.style.border="0",O.style.padding="0",O.style.margin="0",O.style.position="absolute",O.style[_?"right":"left"]="-9999px";var j=window.pageYOffset||document.documentElement.scrollTop;return O.style.top="".concat(j,"px"),O.setAttribute("readonly",""),O.value=V,O}var te=function(_,O){var j=A(_);O.container.appendChild(j);var D=f()(j);return u("copy"),j.remove(),D},ie=function(_){var O=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},j="";return typeof _=="string"?j=te(_,O):_ instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(_==null?void 0:_.type)?j=te(_.value,O):(j=f()(_),u("copy")),j},J=ie;function H(V){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?H=function(O){return typeof O}:H=function(O){return O&&typeof Symbol=="function"&&O.constructor===Symbol&&O!==Symbol.prototype?"symbol":typeof O},H(V)}var mt=function(){var _=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},O=_.action,j=O===void 0?"copy":O,D=_.container,Y=_.target,ke=_.text;if(j!=="copy"&&j!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(Y!==void 0)if(Y&&H(Y)==="object"&&Y.nodeType===1){if(j==="copy"&&Y.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(j==="cut"&&(Y.hasAttribute("readonly")||Y.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(ke)return J(ke,{container:D});if(Y)return j==="cut"?w(Y):J(Y,{container:D})},ze=mt;function Ie(V){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Ie=function(O){return typeof O}:Ie=function(O){return O&&typeof Symbol=="function"&&O.constructor===Symbol&&O!==Symbol.prototype?"symbol":typeof O},Ie(V)}function _i(V,_){if(!(V instanceof _))throw new TypeError("Cannot call a class as a function")}function ro(V,_){for(var O=0;O<_.length;O++){var j=_[O];j.enumerable=j.enumerable||!1,j.configurable=!0,"value"in j&&(j.writable=!0),Object.defineProperty(V,j.key,j)}}function Ai(V,_,O){return _&&ro(V.prototype,_),O&&ro(V,O),V}function Ci(V,_){if(typeof _!="function"&&_!==null)throw new TypeError("Super expression must either be null or a function");V.prototype=Object.create(_&&_.prototype,{constructor:{value:V,writable:!0,configurable:!0}}),_&&br(V,_)}function br(V,_){return br=Object.setPrototypeOf||function(j,D){return j.__proto__=D,j},br(V,_)}function Hi(V){var _=Pi();return function(){var j=Wt(V),D;if(_){var Y=Wt(this).constructor;D=Reflect.construct(j,arguments,Y)}else D=j.apply(this,arguments);return ki(this,D)}}function ki(V,_){return _&&(Ie(_)==="object"||typeof _=="function")?_:$i(V)}function $i(V){if(V===void 0)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return V}function Pi(){if(typeof Reflect=="undefined"||!Reflect.construct||Reflect.construct.sham)return!1;if(typeof Proxy=="function")return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],function(){})),!0}catch(V){return!1}}function Wt(V){return Wt=Object.setPrototypeOf?Object.getPrototypeOf:function(O){return O.__proto__||Object.getPrototypeOf(O)},Wt(V)}function vr(V,_){var O="data-clipboard-".concat(V);if(_.hasAttribute(O))return _.getAttribute(O)}var Ri=function(V){Ci(O,V);var _=Hi(O);function O(j,D){var Y;return _i(this,O),Y=_.call(this),Y.resolveOptions(D),Y.listenClick(j),Y}return Ai(O,[{key:"resolveOptions",value:function(){var D=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof D.action=="function"?D.action:this.defaultAction,this.target=typeof D.target=="function"?D.target:this.defaultTarget,this.text=typeof D.text=="function"?D.text:this.defaultText,this.container=Ie(D.container)==="object"?D.container:document.body}},{key:"listenClick",value:function(D){var Y=this;this.listener=c()(D,"click",function(ke){return Y.onClick(ke)})}},{key:"onClick",value:function(D){var Y=D.delegateTarget||D.currentTarget,ke=this.action(Y)||"copy",Ut=ze({action:ke,container:this.container,target:this.target(Y),text:this.text(Y)});this.emit(Ut?"success":"error",{action:ke,text:Ut,trigger:Y,clearSelection:function(){Y&&Y.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(D){return vr("action",D)}},{key:"defaultTarget",value:function(D){var Y=vr("target",D);if(Y)return document.querySelector(Y)}},{key:"defaultText",value:function(D){return vr("text",D)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(D){var Y=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return J(D,Y)}},{key:"cut",value:function(D){return w(D)}},{key:"isSupported",value:function(){var D=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],Y=typeof D=="string"?[D]:D,ke=!!document.queryCommandSupported;return Y.forEach(function(Ut){ke=ke&&!!document.queryCommandSupported(Ut)}),ke}}]),O}(s()),Ii=Ri},828:function(o){var n=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,p){for(;s&&s.nodeType!==n;){if(typeof s.matches=="function"&&s.matches(p))return s;s=s.parentNode}}o.exports=a},438:function(o,n,i){var a=i(828);function s(l,f,u,h,w){var A=c.apply(this,arguments);return l.addEventListener(u,A,w),{destroy:function(){l.removeEventListener(u,A,w)}}}function p(l,f,u,h,w){return typeof l.addEventListener=="function"?s.apply(null,arguments):typeof u=="function"?s.bind(null,document).apply(null,arguments):(typeof l=="string"&&(l=document.querySelectorAll(l)),Array.prototype.map.call(l,function(A){return s(A,f,u,h,w)}))}function c(l,f,u,h){return function(w){w.delegateTarget=a(w.target,f),w.delegateTarget&&h.call(l,w)}}o.exports=p},879:function(o,n){n.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},n.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||n.node(i[0]))},n.string=function(i){return typeof i=="string"||i instanceof String},n.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}},370:function(o,n,i){var a=i(879),s=i(438);function p(u,h,w){if(!u&&!h&&!w)throw new Error("Missing required arguments");if(!a.string(h))throw new TypeError("Second argument must be a String");if(!a.fn(w))throw new TypeError("Third argument must be a Function");if(a.node(u))return c(u,h,w);if(a.nodeList(u))return l(u,h,w);if(a.string(u))return f(u,h,w);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(u,h,w){return u.addEventListener(h,w),{destroy:function(){u.removeEventListener(h,w)}}}function l(u,h,w){return Array.prototype.forEach.call(u,function(A){A.addEventListener(h,w)}),{destroy:function(){Array.prototype.forEach.call(u,function(A){A.removeEventListener(h,w)})}}}function f(u,h,w){return s(document.body,u,h,w)}o.exports=p},817:function(o){function n(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var p=window.getSelection(),c=document.createRange();c.selectNodeContents(i),p.removeAllRanges(),p.addRange(c),a=p.toString()}return a}o.exports=n},279:function(o){function n(){}n.prototype={on:function(i,a,s){var p=this.e||(this.e={});return(p[i]||(p[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var p=this;function c(){p.off(i,c),a.apply(s,arguments)}return c._=a,this.on(i,c,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),p=0,c=s.length;for(p;p{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var ts=/["'&<>]/;ei.exports=rs;function rs(e){var t=""+e,r=ts.exec(t);if(!r)return t;var o,n="",i=0,a=0;for(i=r.index;i0&&i[i.length-1])&&(c[0]===6||c[0]===2)){r=0;continue}if(c[0]===3&&(!i||c[1]>i[0]&&c[1]=e.length&&(e=void 0),{value:e&&e[o++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function N(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var o=r.call(e),n,i=[],a;try{for(;(t===void 0||t-- >0)&&!(n=o.next()).done;)i.push(n.value)}catch(s){a={error:s}}finally{try{n&&!n.done&&(r=o.return)&&r.call(o)}finally{if(a)throw a.error}}return i}function q(e,t,r){if(r||arguments.length===2)for(var o=0,n=t.length,i;o1||s(u,h)})})}function s(u,h){try{p(o[u](h))}catch(w){f(i[0][3],w)}}function p(u){u.value instanceof nt?Promise.resolve(u.value.v).then(c,l):f(i[0][2],u)}function c(u){s("next",u)}function l(u){s("throw",u)}function f(u,h){u(h),i.shift(),i.length&&s(i[0][0],i[0][1])}}function mo(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof de=="function"?de(e):e[Symbol.iterator](),r={},o("next"),o("throw"),o("return"),r[Symbol.asyncIterator]=function(){return this},r);function o(i){r[i]=e[i]&&function(a){return new Promise(function(s,p){a=e[i](a),n(s,p,a.done,a.value)})}}function n(i,a,s,p){Promise.resolve(p).then(function(c){i({value:c,done:s})},a)}}function k(e){return typeof e=="function"}function ft(e){var t=function(o){Error.call(o),o.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var zt=ft(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(o,n){return n+1+") "+o.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function qe(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Fe=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,o,n,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(a)if(this._parentage=null,Array.isArray(a))try{for(var s=de(a),p=s.next();!p.done;p=s.next()){var c=p.value;c.remove(this)}}catch(A){t={error:A}}finally{try{p&&!p.done&&(r=s.return)&&r.call(s)}finally{if(t)throw t.error}}else a.remove(this);var l=this.initialTeardown;if(k(l))try{l()}catch(A){i=A instanceof zt?A.errors:[A]}var f=this._finalizers;if(f){this._finalizers=null;try{for(var u=de(f),h=u.next();!h.done;h=u.next()){var w=h.value;try{fo(w)}catch(A){i=i!=null?i:[],A instanceof zt?i=q(q([],N(i)),N(A.errors)):i.push(A)}}}catch(A){o={error:A}}finally{try{h&&!h.done&&(n=u.return)&&n.call(u)}finally{if(o)throw o.error}}}if(i)throw new zt(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)fo(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&qe(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&qe(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Tr=Fe.EMPTY;function qt(e){return e instanceof Fe||e&&"closed"in e&&k(e.remove)&&k(e.add)&&k(e.unsubscribe)}function fo(e){k(e)?e():e.unsubscribe()}var $e={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var ut={setTimeout:function(e,t){for(var r=[],o=2;o0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var o=this,n=this,i=n.hasError,a=n.isStopped,s=n.observers;return i||a?Tr:(this.currentObservers=null,s.push(r),new Fe(function(){o.currentObservers=null,qe(s,r)}))},t.prototype._checkFinalizedStatuses=function(r){var o=this,n=o.hasError,i=o.thrownError,a=o.isStopped;n?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new F;return r.source=this,r},t.create=function(r,o){return new Eo(r,o)},t}(F);var Eo=function(e){re(t,e);function t(r,o){var n=e.call(this)||this;return n.destination=r,n.source=o,n}return t.prototype.next=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.next)===null||n===void 0||n.call(o,r)},t.prototype.error=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.error)===null||n===void 0||n.call(o,r)},t.prototype.complete=function(){var r,o;(o=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||o===void 0||o.call(r)},t.prototype._subscribe=function(r){var o,n;return(n=(o=this.source)===null||o===void 0?void 0:o.subscribe(r))!==null&&n!==void 0?n:Tr},t}(g);var _r=function(e){re(t,e);function t(r){var o=e.call(this)||this;return o._value=r,o}return Object.defineProperty(t.prototype,"value",{get:function(){return this.getValue()},enumerable:!1,configurable:!0}),t.prototype._subscribe=function(r){var o=e.prototype._subscribe.call(this,r);return!o.closed&&r.next(this._value),o},t.prototype.getValue=function(){var r=this,o=r.hasError,n=r.thrownError,i=r._value;if(o)throw n;return this._throwIfClosed(),i},t.prototype.next=function(r){e.prototype.next.call(this,this._value=r)},t}(g);var Lt={now:function(){return(Lt.delegate||Date).now()},delegate:void 0};var _t=function(e){re(t,e);function t(r,o,n){r===void 0&&(r=1/0),o===void 0&&(o=1/0),n===void 0&&(n=Lt);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=o,i._timestampProvider=n,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=o===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,o),i}return t.prototype.next=function(r){var o=this,n=o.isStopped,i=o._buffer,a=o._infiniteTimeWindow,s=o._timestampProvider,p=o._windowTime;n||(i.push(r),!a&&i.push(s.now()+p)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var o=this._innerSubscribe(r),n=this,i=n._infiniteTimeWindow,a=n._buffer,s=a.slice(),p=0;p0?e.prototype.schedule.call(this,r,o):(this.delay=o,this.state=r,this.scheduler.flush(this),this)},t.prototype.execute=function(r,o){return o>0||this.closed?e.prototype.execute.call(this,r,o):this._execute(r,o)},t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!=null&&n>0||n==null&&this.delay>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.flush(this),0)},t}(vt);var So=function(e){re(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t}(gt);var Hr=new So(To);var Oo=function(e){re(t,e);function t(r,o){var n=e.call(this,r,o)||this;return n.scheduler=r,n.work=o,n}return t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!==null&&n>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.actions.push(this),r._scheduled||(r._scheduled=bt.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,o,n){var i;if(n===void 0&&(n=0),n!=null?n>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,o,n);var a=r.actions;o!=null&&((i=a[a.length-1])===null||i===void 0?void 0:i.id)!==o&&(bt.cancelAnimationFrame(o),r._scheduled=void 0)},t}(vt);var Mo=function(e){re(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var o=this._scheduled;this._scheduled=void 0;var n=this.actions,i;r=r||n.shift();do if(i=r.execute(r.state,r.delay))break;while((r=n[0])&&r.id===o&&n.shift());if(this._active=!1,i){for(;(r=n[0])&&r.id===o&&n.shift();)r.unsubscribe();throw i}},t}(gt);var me=new Mo(Oo);var M=new F(function(e){return e.complete()});function Yt(e){return e&&k(e.schedule)}function kr(e){return e[e.length-1]}function Xe(e){return k(kr(e))?e.pop():void 0}function He(e){return Yt(kr(e))?e.pop():void 0}function Bt(e,t){return typeof kr(e)=="number"?e.pop():t}var xt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function Gt(e){return k(e==null?void 0:e.then)}function Jt(e){return k(e[ht])}function Xt(e){return Symbol.asyncIterator&&k(e==null?void 0:e[Symbol.asyncIterator])}function Zt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Gi(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var er=Gi();function tr(e){return k(e==null?void 0:e[er])}function rr(e){return lo(this,arguments,function(){var r,o,n,i;return Nt(this,function(a){switch(a.label){case 0:r=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,nt(r.read())];case 3:return o=a.sent(),n=o.value,i=o.done,i?[4,nt(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,nt(n)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function or(e){return k(e==null?void 0:e.getReader)}function W(e){if(e instanceof F)return e;if(e!=null){if(Jt(e))return Ji(e);if(xt(e))return Xi(e);if(Gt(e))return Zi(e);if(Xt(e))return Lo(e);if(tr(e))return ea(e);if(or(e))return ta(e)}throw Zt(e)}function Ji(e){return new F(function(t){var r=e[ht]();if(k(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function Xi(e){return new F(function(t){for(var r=0;r=2;return function(o){return o.pipe(e?v(function(n,i){return e(n,i,o)}):le,Te(1),r?Be(t):zo(function(){return new ir}))}}function Fr(e){return e<=0?function(){return M}:y(function(t,r){var o=[];t.subscribe(T(r,function(n){o.push(n),e=2,!0))}function pe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new g}:t,o=e.resetOnError,n=o===void 0?!0:o,i=e.resetOnComplete,a=i===void 0?!0:i,s=e.resetOnRefCountZero,p=s===void 0?!0:s;return function(c){var l,f,u,h=0,w=!1,A=!1,te=function(){f==null||f.unsubscribe(),f=void 0},ie=function(){te(),l=u=void 0,w=A=!1},J=function(){var H=l;ie(),H==null||H.unsubscribe()};return y(function(H,mt){h++,!A&&!w&&te();var ze=u=u!=null?u:r();mt.add(function(){h--,h===0&&!A&&!w&&(f=Wr(J,p))}),ze.subscribe(mt),!l&&h>0&&(l=new at({next:function(Ie){return ze.next(Ie)},error:function(Ie){A=!0,te(),f=Wr(ie,n,Ie),ze.error(Ie)},complete:function(){w=!0,te(),f=Wr(ie,a),ze.complete()}}),W(H).subscribe(l))})(c)}}function Wr(e,t){for(var r=[],o=2;oe.next(document)),e}function $(e,t=document){return Array.from(t.querySelectorAll(e))}function P(e,t=document){let r=fe(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function fe(e,t=document){return t.querySelector(e)||void 0}function Re(){var e,t,r,o;return(o=(r=(t=(e=document.activeElement)==null?void 0:e.shadowRoot)==null?void 0:t.activeElement)!=null?r:document.activeElement)!=null?o:void 0}var xa=S(d(document.body,"focusin"),d(document.body,"focusout")).pipe(_e(1),Q(void 0),m(()=>Re()||document.body),B(1));function et(e){return xa.pipe(m(t=>e.contains(t)),K())}function kt(e,t){return C(()=>S(d(e,"mouseenter").pipe(m(()=>!0)),d(e,"mouseleave").pipe(m(()=>!1))).pipe(t?Ht(r=>Me(+!r*t)):le,Q(e.matches(":hover"))))}function Bo(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)Bo(e,r)}function x(e,t,...r){let o=document.createElement(e);if(t)for(let n of Object.keys(t))typeof t[n]!="undefined"&&(typeof t[n]!="boolean"?o.setAttribute(n,t[n]):o.setAttribute(n,""));for(let n of r)Bo(o,n);return o}function sr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function wt(e){let t=x("script",{src:e});return C(()=>(document.head.appendChild(t),S(d(t,"load"),d(t,"error").pipe(b(()=>$r(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(m(()=>{}),L(()=>document.head.removeChild(t)),Te(1))))}var Go=new g,ya=C(()=>typeof ResizeObserver=="undefined"?wt("https://unpkg.com/resize-observer-polyfill"):I(void 0)).pipe(m(()=>new ResizeObserver(e=>e.forEach(t=>Go.next(t)))),b(e=>S(Ke,I(e)).pipe(L(()=>e.disconnect()))),B(1));function ce(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ge(e){let t=e;for(;t.clientWidth===0&&t.parentElement;)t=t.parentElement;return ya.pipe(E(r=>r.observe(t)),b(r=>Go.pipe(v(o=>o.target===t),L(()=>r.unobserve(t)))),m(()=>ce(e)),Q(ce(e)))}function Tt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function cr(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}function Jo(e){let t=[],r=e.parentElement;for(;r;)(e.clientWidth>r.clientWidth||e.clientHeight>r.clientHeight)&&t.push(r),r=(e=r).parentElement;return t.length===0&&t.push(document.documentElement),t}function Ue(e){return{x:e.offsetLeft,y:e.offsetTop}}function Xo(e){let t=e.getBoundingClientRect();return{x:t.x+window.scrollX,y:t.y+window.scrollY}}function Zo(e){return S(d(window,"load"),d(window,"resize")).pipe(Le(0,me),m(()=>Ue(e)),Q(Ue(e)))}function pr(e){return{x:e.scrollLeft,y:e.scrollTop}}function De(e){return S(d(e,"scroll"),d(window,"scroll"),d(window,"resize")).pipe(Le(0,me),m(()=>pr(e)),Q(pr(e)))}var en=new g,Ea=C(()=>I(new IntersectionObserver(e=>{for(let t of e)en.next(t)},{threshold:0}))).pipe(b(e=>S(Ke,I(e)).pipe(L(()=>e.disconnect()))),B(1));function tt(e){return Ea.pipe(E(t=>t.observe(e)),b(t=>en.pipe(v(({target:r})=>r===e),L(()=>t.unobserve(e)),m(({isIntersecting:r})=>r))))}function tn(e,t=16){return De(e).pipe(m(({y:r})=>{let o=ce(e),n=Tt(e);return r>=n.height-o.height-t}),K())}var lr={drawer:P("[data-md-toggle=drawer]"),search:P("[data-md-toggle=search]")};function rn(e){return lr[e].checked}function Je(e,t){lr[e].checked!==t&&lr[e].click()}function Ve(e){let t=lr[e];return d(t,"change").pipe(m(()=>t.checked),Q(t.checked))}function wa(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function Ta(){return S(d(window,"compositionstart").pipe(m(()=>!0)),d(window,"compositionend").pipe(m(()=>!1))).pipe(Q(!1))}function on(){let e=d(window,"keydown").pipe(v(t=>!(t.metaKey||t.ctrlKey)),m(t=>({mode:rn("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),v(({mode:t,type:r})=>{if(t==="global"){let o=Re();if(typeof o!="undefined")return!wa(o,r)}return!0}),pe());return Ta().pipe(b(t=>t?M:e))}function xe(){return new URL(location.href)}function pt(e,t=!1){if(G("navigation.instant")&&!t){let r=x("a",{href:e.href});document.body.appendChild(r),r.click(),r.remove()}else location.href=e.href}function nn(){return new g}function an(){return location.hash.slice(1)}function sn(e){let t=x("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Sa(e){return S(d(window,"hashchange"),e).pipe(m(an),Q(an()),v(t=>t.length>0),B(1))}function cn(e){return Sa(e).pipe(m(t=>fe(`[id="${t}"]`)),v(t=>typeof t!="undefined"))}function $t(e){let t=matchMedia(e);return ar(r=>t.addListener(()=>r(t.matches))).pipe(Q(t.matches))}function pn(){let e=matchMedia("print");return S(d(window,"beforeprint").pipe(m(()=>!0)),d(window,"afterprint").pipe(m(()=>!1))).pipe(Q(e.matches))}function Nr(e,t){return e.pipe(b(r=>r?t():M))}function zr(e,t){return new F(r=>{let o=new XMLHttpRequest;return o.open("GET",`${e}`),o.responseType="blob",o.addEventListener("load",()=>{o.status>=200&&o.status<300?(r.next(o.response),r.complete()):r.error(new Error(o.statusText))}),o.addEventListener("error",()=>{r.error(new Error("Network error"))}),o.addEventListener("abort",()=>{r.complete()}),typeof(t==null?void 0:t.progress$)!="undefined"&&(o.addEventListener("progress",n=>{var i;if(n.lengthComputable)t.progress$.next(n.loaded/n.total*100);else{let a=(i=o.getResponseHeader("Content-Length"))!=null?i:0;t.progress$.next(n.loaded/+a*100)}}),t.progress$.next(5)),o.send(),()=>o.abort()})}function Ne(e,t){return zr(e,t).pipe(b(r=>r.text()),m(r=>JSON.parse(r)),B(1))}function ln(e,t){let r=new DOMParser;return zr(e,t).pipe(b(o=>o.text()),m(o=>r.parseFromString(o,"text/html")),B(1))}function mn(e,t){let r=new DOMParser;return zr(e,t).pipe(b(o=>o.text()),m(o=>r.parseFromString(o,"text/xml")),B(1))}function fn(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function un(){return S(d(window,"scroll",{passive:!0}),d(window,"resize",{passive:!0})).pipe(m(fn),Q(fn()))}function dn(){return{width:innerWidth,height:innerHeight}}function hn(){return d(window,"resize",{passive:!0}).pipe(m(dn),Q(dn()))}function bn(){return z([un(),hn()]).pipe(m(([e,t])=>({offset:e,size:t})),B(1))}function mr(e,{viewport$:t,header$:r}){let o=t.pipe(Z("size")),n=z([o,r]).pipe(m(()=>Ue(e)));return z([r,t,n]).pipe(m(([{height:i},{offset:a,size:s},{x:p,y:c}])=>({offset:{x:a.x-p,y:a.y-c+i},size:s})))}function Oa(e){return d(e,"message",t=>t.data)}function Ma(e){let t=new g;return t.subscribe(r=>e.postMessage(r)),t}function vn(e,t=new Worker(e)){let r=Oa(t),o=Ma(t),n=new g;n.subscribe(o);let i=o.pipe(X(),ne(!0));return n.pipe(X(),Pe(r.pipe(U(i))),pe())}var La=P("#__config"),St=JSON.parse(La.textContent);St.base=`${new URL(St.base,xe())}`;function ye(){return St}function G(e){return St.features.includes(e)}function Ee(e,t){return typeof t!="undefined"?St.translations[e].replace("#",t.toString()):St.translations[e]}function Se(e,t=document){return P(`[data-md-component=${e}]`,t)}function ae(e,t=document){return $(`[data-md-component=${e}]`,t)}function _a(e){let t=P(".md-typeset > :first-child",e);return d(t,"click",{once:!0}).pipe(m(()=>P(".md-typeset",e)),m(r=>({hash:__md_hash(r.innerHTML)})))}function gn(e){if(!G("announce.dismiss")||!e.childElementCount)return M;if(!e.hidden){let t=P(".md-typeset",e);__md_hash(t.innerHTML)===__md_get("__announce")&&(e.hidden=!0)}return C(()=>{let t=new g;return t.subscribe(({hash:r})=>{e.hidden=!0,__md_set("__announce",r)}),_a(e).pipe(E(r=>t.next(r)),L(()=>t.complete()),m(r=>R({ref:e},r)))})}function Aa(e,{target$:t}){return t.pipe(m(r=>({hidden:r!==e})))}function xn(e,t){let r=new g;return r.subscribe(({hidden:o})=>{e.hidden=o}),Aa(e,t).pipe(E(o=>r.next(o)),L(()=>r.complete()),m(o=>R({ref:e},o)))}function Pt(e,t){return t==="inline"?x("div",{class:"md-tooltip md-tooltip--inline",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"})):x("div",{class:"md-tooltip",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"}))}function yn(...e){return x("div",{class:"md-tooltip2",role:"tooltip"},x("div",{class:"md-tooltip2__inner md-typeset"},e))}function En(e,t){if(t=t?`${t}_annotation_${e}`:void 0,t){let r=t?`#${t}`:void 0;return x("aside",{class:"md-annotation",tabIndex:0},Pt(t),x("a",{href:r,class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}else return x("aside",{class:"md-annotation",tabIndex:0},Pt(t),x("span",{class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}function wn(e){return x("button",{class:"md-clipboard md-icon",title:Ee("clipboard.copy"),"data-clipboard-target":`#${e} > code`})}function qr(e,t){let r=t&2,o=t&1,n=Object.keys(e.terms).filter(p=>!e.terms[p]).reduce((p,c)=>[...p,x("del",null,c)," "],[]).slice(0,-1),i=ye(),a=new URL(e.location,i.base);G("search.highlight")&&a.searchParams.set("h",Object.entries(e.terms).filter(([,p])=>p).reduce((p,[c])=>`${p} ${c}`.trim(),""));let{tags:s}=ye();return x("a",{href:`${a}`,class:"md-search-result__link",tabIndex:-1},x("article",{class:"md-search-result__article md-typeset","data-md-score":e.score.toFixed(2)},r>0&&x("div",{class:"md-search-result__icon md-icon"}),r>0&&x("h1",null,e.title),r<=0&&x("h2",null,e.title),o>0&&e.text.length>0&&e.text,e.tags&&e.tags.map(p=>{let c=s?p in s?`md-tag-icon md-tag--${s[p]}`:"md-tag-icon":"";return x("span",{class:`md-tag ${c}`},p)}),o>0&&n.length>0&&x("p",{class:"md-search-result__terms"},Ee("search.result.term.missing"),": ",...n)))}function Tn(e){let t=e[0].score,r=[...e],o=ye(),n=r.findIndex(l=>!`${new URL(l.location,o.base)}`.includes("#")),[i]=r.splice(n,1),a=r.findIndex(l=>l.scoreqr(l,1)),...p.length?[x("details",{class:"md-search-result__more"},x("summary",{tabIndex:-1},x("div",null,p.length>0&&p.length===1?Ee("search.result.more.one"):Ee("search.result.more.other",p.length))),...p.map(l=>qr(l,1)))]:[]];return x("li",{class:"md-search-result__item"},c)}function Sn(e){return x("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>x("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?sr(r):r)))}function Qr(e){let t=`tabbed-control tabbed-control--${e}`;return x("div",{class:t,hidden:!0},x("button",{class:"tabbed-button",tabIndex:-1,"aria-hidden":"true"}))}function On(e){return x("div",{class:"md-typeset__scrollwrap"},x("div",{class:"md-typeset__table"},e))}function Ca(e){var o;let t=ye(),r=new URL(`../${e.version}/`,t.base);return x("li",{class:"md-version__item"},x("a",{href:`${r}`,class:"md-version__link"},e.title,((o=t.version)==null?void 0:o.alias)&&e.aliases.length>0&&x("span",{class:"md-version__alias"},e.aliases[0])))}function Mn(e,t){var o;let r=ye();return e=e.filter(n=>{var i;return!((i=n.properties)!=null&&i.hidden)}),x("div",{class:"md-version"},x("button",{class:"md-version__current","aria-label":Ee("select.version")},t.title,((o=r.version)==null?void 0:o.alias)&&t.aliases.length>0&&x("span",{class:"md-version__alias"},t.aliases[0])),x("ul",{class:"md-version__list"},e.map(Ca)))}var Ha=0;function ka(e){let t=z([et(e),kt(e)]).pipe(m(([o,n])=>o||n),K()),r=C(()=>Jo(e)).pipe(oe(De),ct(1),m(()=>Xo(e)));return t.pipe(Ae(o=>o),b(()=>z([t,r])),m(([o,n])=>({active:o,offset:n})),pe())}function $a(e,t){let{content$:r,viewport$:o}=t,n=`__tooltip2_${Ha++}`;return C(()=>{let i=new g,a=new _r(!1);i.pipe(X(),ne(!1)).subscribe(a);let s=a.pipe(Ht(c=>Me(+!c*250,Hr)),K(),b(c=>c?r:M),E(c=>c.id=n),pe());z([i.pipe(m(({active:c})=>c)),s.pipe(b(c=>kt(c,250)),Q(!1))]).pipe(m(c=>c.some(l=>l))).subscribe(a);let p=a.pipe(v(c=>c),ee(s,o),m(([c,l,{size:f}])=>{let u=e.getBoundingClientRect(),h=u.width/2;if(l.role==="tooltip")return{x:h,y:8+u.height};if(u.y>=f.height/2){let{height:w}=ce(l);return{x:h,y:-16-w}}else return{x:h,y:16+u.height}}));return z([s,i,p]).subscribe(([c,{offset:l},f])=>{c.style.setProperty("--md-tooltip-host-x",`${l.x}px`),c.style.setProperty("--md-tooltip-host-y",`${l.y}px`),c.style.setProperty("--md-tooltip-x",`${f.x}px`),c.style.setProperty("--md-tooltip-y",`${f.y}px`),c.classList.toggle("md-tooltip2--top",f.y<0),c.classList.toggle("md-tooltip2--bottom",f.y>=0)}),a.pipe(v(c=>c),ee(s,(c,l)=>l),v(c=>c.role==="tooltip")).subscribe(c=>{let l=ce(P(":scope > *",c));c.style.setProperty("--md-tooltip-width",`${l.width}px`),c.style.setProperty("--md-tooltip-tail","0px")}),a.pipe(K(),be(me),ee(s)).subscribe(([c,l])=>{l.classList.toggle("md-tooltip2--active",c)}),z([a.pipe(v(c=>c)),s]).subscribe(([c,l])=>{l.role==="dialog"?(e.setAttribute("aria-controls",n),e.setAttribute("aria-haspopup","dialog")):e.setAttribute("aria-describedby",n)}),a.pipe(v(c=>!c)).subscribe(()=>{e.removeAttribute("aria-controls"),e.removeAttribute("aria-describedby"),e.removeAttribute("aria-haspopup")}),ka(e).pipe(E(c=>i.next(c)),L(()=>i.complete()),m(c=>R({ref:e},c)))})}function lt(e,{viewport$:t},r=document.body){return $a(e,{content$:new F(o=>{let n=e.title,i=yn(n);return o.next(i),e.removeAttribute("title"),r.append(i),()=>{i.remove(),e.setAttribute("title",n)}}),viewport$:t})}function Pa(e,t){let r=C(()=>z([Zo(e),De(t)])).pipe(m(([{x:o,y:n},i])=>{let{width:a,height:s}=ce(e);return{x:o-i.x+a/2,y:n-i.y+s/2}}));return et(e).pipe(b(o=>r.pipe(m(n=>({active:o,offset:n})),Te(+!o||1/0))))}function Ln(e,t,{target$:r}){let[o,n]=Array.from(e.children);return C(()=>{let i=new g,a=i.pipe(X(),ne(!0));return i.subscribe({next({offset:s}){e.style.setProperty("--md-tooltip-x",`${s.x}px`),e.style.setProperty("--md-tooltip-y",`${s.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}}),tt(e).pipe(U(a)).subscribe(s=>{e.toggleAttribute("data-md-visible",s)}),S(i.pipe(v(({active:s})=>s)),i.pipe(_e(250),v(({active:s})=>!s))).subscribe({next({active:s}){s?e.prepend(o):o.remove()},complete(){e.prepend(o)}}),i.pipe(Le(16,me)).subscribe(({active:s})=>{o.classList.toggle("md-tooltip--active",s)}),i.pipe(ct(125,me),v(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:s})=>s)).subscribe({next(s){s?e.style.setProperty("--md-tooltip-0",`${-s}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}}),d(n,"click").pipe(U(a),v(s=>!(s.metaKey||s.ctrlKey))).subscribe(s=>{s.stopPropagation(),s.preventDefault()}),d(n,"mousedown").pipe(U(a),ee(i)).subscribe(([s,{active:p}])=>{var c;if(s.button!==0||s.metaKey||s.ctrlKey)s.preventDefault();else if(p){s.preventDefault();let l=e.parentElement.closest(".md-annotation");l instanceof HTMLElement?l.focus():(c=Re())==null||c.blur()}}),r.pipe(U(a),v(s=>s===o),Ge(125)).subscribe(()=>e.focus()),Pa(e,t).pipe(E(s=>i.next(s)),L(()=>i.complete()),m(s=>R({ref:e},s)))})}function Ra(e){return e.tagName==="CODE"?$(".c, .c1, .cm",e):[e]}function Ia(e){let t=[];for(let r of Ra(e)){let o=[],n=document.createNodeIterator(r,NodeFilter.SHOW_TEXT);for(let i=n.nextNode();i;i=n.nextNode())o.push(i);for(let i of o){let a;for(;a=/(\(\d+\))(!)?/.exec(i.textContent);){let[,s,p]=a;if(typeof p=="undefined"){let c=i.splitText(a.index);i=c.splitText(s.length),t.push(c)}else{i.textContent=s,t.push(i);break}}}}return t}function _n(e,t){t.append(...Array.from(e.childNodes))}function fr(e,t,{target$:r,print$:o}){let n=t.closest("[id]"),i=n==null?void 0:n.id,a=new Map;for(let s of Ia(t)){let[,p]=s.textContent.match(/\((\d+)\)/);fe(`:scope > li:nth-child(${p})`,e)&&(a.set(p,En(p,i)),s.replaceWith(a.get(p)))}return a.size===0?M:C(()=>{let s=new g,p=s.pipe(X(),ne(!0)),c=[];for(let[l,f]of a)c.push([P(".md-typeset",f),P(`:scope > li:nth-child(${l})`,e)]);return o.pipe(U(p)).subscribe(l=>{e.hidden=!l,e.classList.toggle("md-annotation-list",l);for(let[f,u]of c)l?_n(f,u):_n(u,f)}),S(...[...a].map(([,l])=>Ln(l,t,{target$:r}))).pipe(L(()=>s.complete()),pe())})}function An(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return An(t)}}function Cn(e,t){return C(()=>{let r=An(e);return typeof r!="undefined"?fr(r,e,t):M})}var Hn=Vt(Yr());var Fa=0;function kn(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return kn(t)}}function ja(e){return ge(e).pipe(m(({width:t})=>({scrollable:Tt(e).width>t})),Z("scrollable"))}function $n(e,t){let{matches:r}=matchMedia("(hover)"),o=C(()=>{let n=new g,i=n.pipe(Fr(1));n.subscribe(({scrollable:c})=>{c&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")});let a=[];if(Hn.default.isSupported()&&(e.closest(".copy")||G("content.code.copy")&&!e.closest(".no-copy"))){let c=e.closest("pre");c.id=`__code_${Fa++}`;let l=wn(c.id);c.insertBefore(l,e),G("content.tooltips")&&a.push(lt(l,{viewport$}))}let s=e.closest(".highlight");if(s instanceof HTMLElement){let c=kn(s);if(typeof c!="undefined"&&(s.classList.contains("annotate")||G("content.code.annotate"))){let l=fr(c,e,t);a.push(ge(s).pipe(U(i),m(({width:f,height:u})=>f&&u),K(),b(f=>f?l:M)))}}return $(":scope > span[id]",e).length&&e.classList.add("md-code__content"),ja(e).pipe(E(c=>n.next(c)),L(()=>n.complete()),m(c=>R({ref:e},c)),Pe(...a))});return G("content.lazy")?tt(e).pipe(v(n=>n),Te(1),b(()=>o)):o}function Wa(e,{target$:t,print$:r}){let o=!0;return S(t.pipe(m(n=>n.closest("details:not([open])")),v(n=>e===n),m(()=>({action:"open",reveal:!0}))),r.pipe(v(n=>n||!o),E(()=>o=e.open),m(n=>({action:n?"open":"close"}))))}function Pn(e,t){return C(()=>{let r=new g;return r.subscribe(({action:o,reveal:n})=>{e.toggleAttribute("open",o==="open"),n&&e.scrollIntoView()}),Wa(e,t).pipe(E(o=>r.next(o)),L(()=>r.complete()),m(o=>R({ref:e},o)))})}var Rn=".node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}marker{fill:var(--md-mermaid-edge-color)!important}.edgeLabel .label rect{fill:#0000}.label{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--md-mermaid-label-fg-color)}.edgeLabel,.edgeLabel rect,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel rect{fill:var(--md-mermaid-label-bg-color);color:var(--md-mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--md-mermaid-edge-color);stroke-width:.05rem}.edgePath .arrowheadPath{fill:var(--md-mermaid-edge-color);stroke:none}.cluster rect{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}.cluster span{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}g #flowchart-circleEnd,g #flowchart-circleStart,g #flowchart-crossEnd,g #flowchart-crossStart,g #flowchart-pointEnd,g #flowchart-pointStart{stroke:none}g.classGroup line,g.classGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.classGroup text{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.classLabel .box{fill:var(--md-mermaid-label-bg-color);background-color:var(--md-mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node .divider{stroke:var(--md-mermaid-node-fg-color)}.relation{stroke:var(--md-mermaid-edge-color)}.cardinality{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.cardinality text{fill:inherit!important}defs #classDiagram-compositionEnd,defs #classDiagram-compositionStart,defs #classDiagram-dependencyEnd,defs #classDiagram-dependencyStart,defs #classDiagram-extensionEnd,defs #classDiagram-extensionStart{fill:var(--md-mermaid-edge-color)!important;stroke:var(--md-mermaid-edge-color)!important}defs #classDiagram-aggregationEnd,defs #classDiagram-aggregationStart{fill:var(--md-mermaid-label-bg-color)!important;stroke:var(--md-mermaid-edge-color)!important}g.stateGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--md-mermaid-label-fg-color)!important;font-family:var(--md-mermaid-font-family)}g.stateGroup .composit{fill:var(--md-mermaid-label-bg-color)}.nodeLabel,.nodeLabel p{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}a .nodeLabel{text-decoration:underline}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--md-mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--md-mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--md-mermaid-label-bg-color)}.transition{stroke:var(--md-mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--md-mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--md-default-bg-color)}.statediagram-cluster rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--md-mermaid-edge-color)}.attributeBoxEven,.attributeBoxOdd{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityBox{fill:var(--md-mermaid-label-bg-color);stroke:var(--md-mermaid-node-fg-color)}.entityLabel{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.relationshipLabelBox{fill:var(--md-mermaid-label-bg-color);fill-opacity:1;background-color:var(--md-mermaid-label-bg-color);opacity:1}.relationshipLabel{fill:var(--md-mermaid-label-fg-color)}.relationshipLine{stroke:var(--md-mermaid-edge-color)}defs #ONE_OR_MORE_END *,defs #ONE_OR_MORE_START *,defs #ONLY_ONE_END *,defs #ONLY_ONE_START *,defs #ZERO_OR_MORE_END *,defs #ZERO_OR_MORE_START *,defs #ZERO_OR_ONE_END *,defs #ZERO_OR_ONE_START *{stroke:var(--md-mermaid-edge-color)!important}defs #ZERO_OR_MORE_END circle,defs #ZERO_OR_MORE_START circle{fill:var(--md-mermaid-label-bg-color)}.actor{fill:var(--md-mermaid-sequence-actor-bg-color);stroke:var(--md-mermaid-sequence-actor-border-color)}text.actor>tspan{fill:var(--md-mermaid-sequence-actor-fg-color);font-family:var(--md-mermaid-font-family)}line{stroke:var(--md-mermaid-sequence-actor-line-color)}.actor-man circle,.actor-man line{fill:var(--md-mermaid-sequence-actorman-bg-color);stroke:var(--md-mermaid-sequence-actorman-line-color)}.messageLine0,.messageLine1{stroke:var(--md-mermaid-sequence-message-line-color)}.note{fill:var(--md-mermaid-sequence-note-bg-color);stroke:var(--md-mermaid-sequence-note-border-color)}.loopText,.loopText>tspan,.messageText,.noteText>tspan{stroke:none;font-family:var(--md-mermaid-font-family)!important}.messageText{fill:var(--md-mermaid-sequence-message-fg-color)}.loopText,.loopText>tspan{fill:var(--md-mermaid-sequence-loop-fg-color)}.noteText>tspan{fill:var(--md-mermaid-sequence-note-fg-color)}#arrowhead path{fill:var(--md-mermaid-sequence-message-line-color);stroke:none}.loopLine{fill:var(--md-mermaid-sequence-loop-bg-color);stroke:var(--md-mermaid-sequence-loop-border-color)}.labelBox{fill:var(--md-mermaid-sequence-label-bg-color);stroke:none}.labelText,.labelText>span{fill:var(--md-mermaid-sequence-label-fg-color);font-family:var(--md-mermaid-font-family)}.sequenceNumber{fill:var(--md-mermaid-sequence-number-fg-color)}rect.rect{fill:var(--md-mermaid-sequence-box-bg-color);stroke:none}rect.rect+text.text{fill:var(--md-mermaid-sequence-box-fg-color)}defs #sequencenumber{fill:var(--md-mermaid-sequence-number-bg-color)!important}";var Br,Da=0;function Va(){return typeof mermaid=="undefined"||mermaid instanceof Element?wt("https://unpkg.com/mermaid@10/dist/mermaid.min.js"):I(void 0)}function In(e){return e.classList.remove("mermaid"),Br||(Br=Va().pipe(E(()=>mermaid.initialize({startOnLoad:!1,themeCSS:Rn,sequence:{actorFontSize:"16px",messageFontSize:"16px",noteFontSize:"16px"}})),m(()=>{}),B(1))),Br.subscribe(()=>ao(this,null,function*(){e.classList.add("mermaid");let t=`__mermaid_${Da++}`,r=x("div",{class:"mermaid"}),o=e.textContent,{svg:n,fn:i}=yield mermaid.render(t,o),a=r.attachShadow({mode:"closed"});a.innerHTML=n,e.replaceWith(r),i==null||i(a)})),Br.pipe(m(()=>({ref:e})))}var Fn=x("table");function jn(e){return e.replaceWith(Fn),Fn.replaceWith(On(e)),I({ref:e})}function Na(e){let t=e.find(r=>r.checked)||e[0];return S(...e.map(r=>d(r,"change").pipe(m(()=>P(`label[for="${r.id}"]`))))).pipe(Q(P(`label[for="${t.id}"]`)),m(r=>({active:r})))}function Wn(e,{viewport$:t,target$:r}){let o=P(".tabbed-labels",e),n=$(":scope > input",e),i=Qr("prev");e.append(i);let a=Qr("next");return e.append(a),C(()=>{let s=new g,p=s.pipe(X(),ne(!0));z([s,ge(e)]).pipe(U(p),Le(1,me)).subscribe({next([{active:c},l]){let f=Ue(c),{width:u}=ce(c);e.style.setProperty("--md-indicator-x",`${f.x}px`),e.style.setProperty("--md-indicator-width",`${u}px`);let h=pr(o);(f.xh.x+l.width)&&o.scrollTo({left:Math.max(0,f.x-16),behavior:"smooth"})},complete(){e.style.removeProperty("--md-indicator-x"),e.style.removeProperty("--md-indicator-width")}}),z([De(o),ge(o)]).pipe(U(p)).subscribe(([c,l])=>{let f=Tt(o);i.hidden=c.x<16,a.hidden=c.x>f.width-l.width-16}),S(d(i,"click").pipe(m(()=>-1)),d(a,"click").pipe(m(()=>1))).pipe(U(p)).subscribe(c=>{let{width:l}=ce(o);o.scrollBy({left:l*c,behavior:"smooth"})}),r.pipe(U(p),v(c=>n.includes(c))).subscribe(c=>c.click()),o.classList.add("tabbed-labels--linked");for(let c of n){let l=P(`label[for="${c.id}"]`);l.replaceChildren(x("a",{href:`#${l.htmlFor}`,tabIndex:-1},...Array.from(l.childNodes))),d(l.firstElementChild,"click").pipe(U(p),v(f=>!(f.metaKey||f.ctrlKey)),E(f=>{f.preventDefault(),f.stopPropagation()})).subscribe(()=>{history.replaceState({},"",`#${l.htmlFor}`),l.click()})}return G("content.tabs.link")&&s.pipe(Ce(1),ee(t)).subscribe(([{active:c},{offset:l}])=>{let f=c.innerText.trim();if(c.hasAttribute("data-md-switching"))c.removeAttribute("data-md-switching");else{let u=e.offsetTop-l.y;for(let w of $("[data-tabs]"))for(let A of $(":scope > input",w)){let te=P(`label[for="${A.id}"]`);if(te!==c&&te.innerText.trim()===f){te.setAttribute("data-md-switching",""),A.click();break}}window.scrollTo({top:e.offsetTop-u});let h=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([f,...h])])}}),s.pipe(U(p)).subscribe(()=>{for(let c of $("audio, video",e))c.pause()}),tt(e).pipe(b(()=>Na(n)),E(c=>s.next(c)),L(()=>s.complete()),m(c=>R({ref:e},c)))}).pipe(Qe(se))}function Un(e,{viewport$:t,target$:r,print$:o}){return S(...$(".annotate:not(.highlight)",e).map(n=>Cn(n,{target$:r,print$:o})),...$("pre:not(.mermaid) > code",e).map(n=>$n(n,{target$:r,print$:o})),...$("pre.mermaid",e).map(n=>In(n)),...$("table:not([class])",e).map(n=>jn(n)),...$("details",e).map(n=>Pn(n,{target$:r,print$:o})),...$("[data-tabs]",e).map(n=>Wn(n,{viewport$:t,target$:r})),...$("[title]",e).filter(()=>G("content.tooltips")).map(n=>lt(n,{viewport$:t})))}function za(e,{alert$:t}){return t.pipe(b(r=>S(I(!0),I(!1).pipe(Ge(2e3))).pipe(m(o=>({message:r,active:o})))))}function Dn(e,t){let r=P(".md-typeset",e);return C(()=>{let o=new g;return o.subscribe(({message:n,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=n}),za(e,t).pipe(E(n=>o.next(n)),L(()=>o.complete()),m(n=>R({ref:e},n)))})}var qa=0;function Qa(e,t){document.body.append(e);let{width:r}=ce(e);e.style.setProperty("--md-tooltip-width",`${r}px`),e.remove();let o=cr(t),n=typeof o!="undefined"?De(o):I({x:0,y:0}),i=S(et(t),kt(t)).pipe(K());return z([i,n]).pipe(m(([a,s])=>{let{x:p,y:c}=Ue(t),l=ce(t),f=t.closest("table");return f&&t.parentElement&&(p+=f.offsetLeft+t.parentElement.offsetLeft,c+=f.offsetTop+t.parentElement.offsetTop),{active:a,offset:{x:p-s.x+l.width/2-r/2,y:c-s.y+l.height+8}}}))}function Vn(e){let t=e.title;if(!t.length)return M;let r=`__tooltip_${qa++}`,o=Pt(r,"inline"),n=P(".md-typeset",o);return n.innerHTML=t,C(()=>{let i=new g;return i.subscribe({next({offset:a}){o.style.setProperty("--md-tooltip-x",`${a.x}px`),o.style.setProperty("--md-tooltip-y",`${a.y}px`)},complete(){o.style.removeProperty("--md-tooltip-x"),o.style.removeProperty("--md-tooltip-y")}}),S(i.pipe(v(({active:a})=>a)),i.pipe(_e(250),v(({active:a})=>!a))).subscribe({next({active:a}){a?(e.insertAdjacentElement("afterend",o),e.setAttribute("aria-describedby",r),e.removeAttribute("title")):(o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t))},complete(){o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t)}}),i.pipe(Le(16,me)).subscribe(({active:a})=>{o.classList.toggle("md-tooltip--active",a)}),i.pipe(ct(125,me),v(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:a})=>a)).subscribe({next(a){a?o.style.setProperty("--md-tooltip-0",`${-a}px`):o.style.removeProperty("--md-tooltip-0")},complete(){o.style.removeProperty("--md-tooltip-0")}}),Qa(o,e).pipe(E(a=>i.next(a)),L(()=>i.complete()),m(a=>R({ref:e},a)))}).pipe(Qe(se))}function Ka({viewport$:e}){if(!G("header.autohide"))return I(!1);let t=e.pipe(m(({offset:{y:n}})=>n),Ye(2,1),m(([n,i])=>[nMath.abs(i-n.y)>100),m(([,[n]])=>n),K()),o=Ve("search");return z([e,o]).pipe(m(([{offset:n},i])=>n.y>400&&!i),K(),b(n=>n?r:I(!1)),Q(!1))}function Nn(e,t){return C(()=>z([ge(e),Ka(t)])).pipe(m(([{height:r},o])=>({height:r,hidden:o})),K((r,o)=>r.height===o.height&&r.hidden===o.hidden),B(1))}function zn(e,{header$:t,main$:r}){return C(()=>{let o=new g,n=o.pipe(X(),ne(!0));o.pipe(Z("active"),We(t)).subscribe(([{active:a},{hidden:s}])=>{e.classList.toggle("md-header--shadow",a&&!s),e.hidden=s});let i=ue($("[title]",e)).pipe(v(()=>G("content.tooltips")),oe(a=>Vn(a)));return r.subscribe(o),t.pipe(U(n),m(a=>R({ref:e},a)),Pe(i.pipe(U(n))))})}function Ya(e,{viewport$:t,header$:r}){return mr(e,{viewport$:t,header$:r}).pipe(m(({offset:{y:o}})=>{let{height:n}=ce(e);return{active:o>=n}}),Z("active"))}function qn(e,t){return C(()=>{let r=new g;r.subscribe({next({active:n}){e.classList.toggle("md-header__title--active",n)},complete(){e.classList.remove("md-header__title--active")}});let o=fe(".md-content h1");return typeof o=="undefined"?M:Ya(o,t).pipe(E(n=>r.next(n)),L(()=>r.complete()),m(n=>R({ref:e},n)))})}function Qn(e,{viewport$:t,header$:r}){let o=r.pipe(m(({height:i})=>i),K()),n=o.pipe(b(()=>ge(e).pipe(m(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),Z("bottom"))));return z([o,n,t]).pipe(m(([i,{top:a,bottom:s},{offset:{y:p},size:{height:c}}])=>(c=Math.max(0,c-Math.max(0,a-p,i)-Math.max(0,c+p-s)),{offset:a-i,height:c,active:a-i<=p})),K((i,a)=>i.offset===a.offset&&i.height===a.height&&i.active===a.active))}function Ba(e){let t=__md_get("__palette")||{index:e.findIndex(o=>matchMedia(o.getAttribute("data-md-color-media")).matches)},r=Math.max(0,Math.min(t.index,e.length-1));return I(...e).pipe(oe(o=>d(o,"change").pipe(m(()=>o))),Q(e[r]),m(o=>({index:e.indexOf(o),color:{media:o.getAttribute("data-md-color-media"),scheme:o.getAttribute("data-md-color-scheme"),primary:o.getAttribute("data-md-color-primary"),accent:o.getAttribute("data-md-color-accent")}})),B(1))}function Kn(e){let t=$("input",e),r=x("meta",{name:"theme-color"});document.head.appendChild(r);let o=x("meta",{name:"color-scheme"});document.head.appendChild(o);let n=$t("(prefers-color-scheme: light)");return C(()=>{let i=new g;return i.subscribe(a=>{if(document.body.setAttribute("data-md-color-switching",""),a.color.media==="(prefers-color-scheme)"){let s=matchMedia("(prefers-color-scheme: light)"),p=document.querySelector(s.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");a.color.scheme=p.getAttribute("data-md-color-scheme"),a.color.primary=p.getAttribute("data-md-color-primary"),a.color.accent=p.getAttribute("data-md-color-accent")}for(let[s,p]of Object.entries(a.color))document.body.setAttribute(`data-md-color-${s}`,p);for(let s=0;sa.key==="Enter"),ee(i,(a,s)=>s)).subscribe(({index:a})=>{a=(a+1)%t.length,t[a].click(),t[a].focus()}),i.pipe(m(()=>{let a=Se("header"),s=window.getComputedStyle(a);return o.content=s.colorScheme,s.backgroundColor.match(/\d+/g).map(p=>(+p).toString(16).padStart(2,"0")).join("")})).subscribe(a=>r.content=`#${a}`),i.pipe(be(se)).subscribe(()=>{document.body.removeAttribute("data-md-color-switching")}),Ba(t).pipe(U(n.pipe(Ce(1))),st(),E(a=>i.next(a)),L(()=>i.complete()),m(a=>R({ref:e},a)))})}function Yn(e,{progress$:t}){return C(()=>{let r=new g;return r.subscribe(({value:o})=>{e.style.setProperty("--md-progress-value",`${o}`)}),t.pipe(E(o=>r.next({value:o})),L(()=>r.complete()),m(o=>({ref:e,value:o})))})}var Gr=Vt(Yr());function Ga(e){e.setAttribute("data-md-copying","");let t=e.closest("[data-copy]"),r=t?t.getAttribute("data-copy"):e.innerText;return e.removeAttribute("data-md-copying"),r.trimEnd()}function Bn({alert$:e}){Gr.default.isSupported()&&new F(t=>{new Gr.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||Ga(P(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe(E(t=>{t.trigger.focus()}),m(()=>Ee("clipboard.copied"))).subscribe(e)}function Gn(e,t){return e.protocol=t.protocol,e.hostname=t.hostname,e}function Ja(e,t){let r=new Map;for(let o of $("url",e)){let n=P("loc",o),i=[Gn(new URL(n.textContent),t)];r.set(`${i[0]}`,i);for(let a of $("[rel=alternate]",o)){let s=a.getAttribute("href");s!=null&&i.push(Gn(new URL(s),t))}}return r}function ur(e){return mn(new URL("sitemap.xml",e)).pipe(m(t=>Ja(t,new URL(e))),ve(()=>I(new Map)))}function Xa(e,t){if(!(e.target instanceof Element))return M;let r=e.target.closest("a");if(r===null)return M;if(r.target||e.metaKey||e.ctrlKey)return M;let o=new URL(r.href);return o.search=o.hash="",t.has(`${o}`)?(e.preventDefault(),I(new URL(r.href))):M}function Jn(e){let t=new Map;for(let r of $(":scope > *",e.head))t.set(r.outerHTML,r);return t}function Xn(e){for(let t of $("[href], [src]",e))for(let r of["href","src"]){let o=t.getAttribute(r);if(o&&!/^(?:[a-z]+:)?\/\//i.test(o)){t[r]=t[r];break}}return I(e)}function Za(e){for(let o of["[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=outdated]","[data-md-component=logo]","[data-md-component=skip]",...G("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let n=fe(o),i=fe(o,e);typeof n!="undefined"&&typeof i!="undefined"&&n.replaceWith(i)}let t=Jn(document);for(let[o,n]of Jn(e))t.has(o)?t.delete(o):document.head.appendChild(n);for(let o of t.values()){let n=o.getAttribute("name");n!=="theme-color"&&n!=="color-scheme"&&o.remove()}let r=Se("container");return je($("script",r)).pipe(b(o=>{let n=e.createElement("script");if(o.src){for(let i of o.getAttributeNames())n.setAttribute(i,o.getAttribute(i));return o.replaceWith(n),new F(i=>{n.onload=()=>i.complete()})}else return n.textContent=o.textContent,o.replaceWith(n),M}),X(),ne(document))}function Zn({location$:e,viewport$:t,progress$:r}){let o=ye();if(location.protocol==="file:")return M;let n=ur(o.base);I(document).subscribe(Xn);let i=d(document.body,"click").pipe(We(n),b(([p,c])=>Xa(p,c)),pe()),a=d(window,"popstate").pipe(m(xe),pe());i.pipe(ee(t)).subscribe(([p,{offset:c}])=>{history.replaceState(c,""),history.pushState(null,"",p)}),S(i,a).subscribe(e);let s=e.pipe(Z("pathname"),b(p=>ln(p,{progress$:r}).pipe(ve(()=>(pt(p,!0),M)))),b(Xn),b(Za),pe());return S(s.pipe(ee(e,(p,c)=>c)),s.pipe(b(()=>e),Z("pathname"),b(()=>e),Z("hash")),e.pipe(K((p,c)=>p.pathname===c.pathname&&p.hash===c.hash),b(()=>i),E(()=>history.back()))).subscribe(p=>{var c,l;history.state!==null||!p.hash?window.scrollTo(0,(l=(c=history.state)==null?void 0:c.y)!=null?l:0):(history.scrollRestoration="auto",sn(p.hash),history.scrollRestoration="manual")}),e.subscribe(()=>{history.scrollRestoration="manual"}),d(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}),t.pipe(Z("offset"),_e(100)).subscribe(({offset:p})=>{history.replaceState(p,"")}),s}var ri=Vt(ti());function oi(e){let t=e.separator.split("|").map(n=>n.replace(/(\(\?[!=<][^)]+\))/g,"").length===0?"\uFFFD":n).join("|"),r=new RegExp(t,"img"),o=(n,i,a)=>`${i}${a}`;return n=>{n=n.replace(/[\s*+\-:~^]+/g," ").trim();let i=new RegExp(`(^|${e.separator}|)(${n.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(r,"|")})`,"img");return a=>(0,ri.default)(a).replace(i,o).replace(/<\/mark>(\s+)]*>/img,"$1")}}function It(e){return e.type===1}function dr(e){return e.type===3}function ni(e,t){let r=vn(e);return S(I(location.protocol!=="file:"),Ve("search")).pipe(Ae(o=>o),b(()=>t)).subscribe(({config:o,docs:n})=>r.next({type:0,data:{config:o,docs:n,options:{suggest:G("search.suggest")}}})),r}function ii({document$:e}){let t=ye(),r=Ne(new URL("../versions.json",t.base)).pipe(ve(()=>M)),o=r.pipe(m(n=>{let[,i]=t.base.match(/([^/]+)\/?$/);return n.find(({version:a,aliases:s})=>a===i||s.includes(i))||n[0]}));r.pipe(m(n=>new Map(n.map(i=>[`${new URL(`../${i.version}/`,t.base)}`,i]))),b(n=>d(document.body,"click").pipe(v(i=>!i.metaKey&&!i.ctrlKey),ee(o),b(([i,a])=>{if(i.target instanceof Element){let s=i.target.closest("a");if(s&&!s.target&&n.has(s.href)){let p=s.href;return!i.target.closest(".md-version")&&n.get(p)===a?M:(i.preventDefault(),I(p))}}return M}),b(i=>ur(new URL(i)).pipe(m(a=>{let p=xe().href.replace(t.base,i);return a.has(p.split("#")[0])?new URL(p):new URL(i)})))))).subscribe(n=>pt(n,!0)),z([r,o]).subscribe(([n,i])=>{P(".md-header__topic").appendChild(Mn(n,i))}),e.pipe(b(()=>o)).subscribe(n=>{var a;let i=__md_get("__outdated",sessionStorage);if(i===null){i=!0;let s=((a=t.version)==null?void 0:a.default)||"latest";Array.isArray(s)||(s=[s]);e:for(let p of s)for(let c of n.aliases.concat(n.version))if(new RegExp(p,"i").test(c)){i=!1;break e}__md_set("__outdated",i,sessionStorage)}if(i)for(let s of ae("outdated"))s.hidden=!1})}function ns(e,{worker$:t}){let{searchParams:r}=xe();r.has("q")&&(Je("search",!0),e.value=r.get("q"),e.focus(),Ve("search").pipe(Ae(i=>!i)).subscribe(()=>{let i=xe();i.searchParams.delete("q"),history.replaceState({},"",`${i}`)}));let o=et(e),n=S(t.pipe(Ae(It)),d(e,"keyup"),o).pipe(m(()=>e.value),K());return z([n,o]).pipe(m(([i,a])=>({value:i,focus:a})),B(1))}function ai(e,{worker$:t}){let r=new g,o=r.pipe(X(),ne(!0));z([t.pipe(Ae(It)),r],(i,a)=>a).pipe(Z("value")).subscribe(({value:i})=>t.next({type:2,data:i})),r.pipe(Z("focus")).subscribe(({focus:i})=>{i&&Je("search",i)}),d(e.form,"reset").pipe(U(o)).subscribe(()=>e.focus());let n=P("header [for=__search]");return d(n,"click").subscribe(()=>e.focus()),ns(e,{worker$:t}).pipe(E(i=>r.next(i)),L(()=>r.complete()),m(i=>R({ref:e},i)),B(1))}function si(e,{worker$:t,query$:r}){let o=new g,n=tn(e.parentElement).pipe(v(Boolean)),i=e.parentElement,a=P(":scope > :first-child",e),s=P(":scope > :last-child",e);Ve("search").subscribe(l=>s.setAttribute("role",l?"list":"presentation")),o.pipe(ee(r),Ur(t.pipe(Ae(It)))).subscribe(([{items:l},{value:f}])=>{switch(l.length){case 0:a.textContent=f.length?Ee("search.result.none"):Ee("search.result.placeholder");break;case 1:a.textContent=Ee("search.result.one");break;default:let u=sr(l.length);a.textContent=Ee("search.result.other",u)}});let p=o.pipe(E(()=>s.innerHTML=""),b(({items:l})=>S(I(...l.slice(0,10)),I(...l.slice(10)).pipe(Ye(4),Vr(n),b(([f])=>f)))),m(Tn),pe());return p.subscribe(l=>s.appendChild(l)),p.pipe(oe(l=>{let f=fe("details",l);return typeof f=="undefined"?M:d(f,"toggle").pipe(U(o),m(()=>f))})).subscribe(l=>{l.open===!1&&l.offsetTop<=i.scrollTop&&i.scrollTo({top:l.offsetTop})}),t.pipe(v(dr),m(({data:l})=>l)).pipe(E(l=>o.next(l)),L(()=>o.complete()),m(l=>R({ref:e},l)))}function is(e,{query$:t}){return t.pipe(m(({value:r})=>{let o=xe();return o.hash="",r=r.replace(/\s+/g,"+").replace(/&/g,"%26").replace(/=/g,"%3D"),o.search=`q=${r}`,{url:o}}))}function ci(e,t){let r=new g,o=r.pipe(X(),ne(!0));return r.subscribe(({url:n})=>{e.setAttribute("data-clipboard-text",e.href),e.href=`${n}`}),d(e,"click").pipe(U(o)).subscribe(n=>n.preventDefault()),is(e,t).pipe(E(n=>r.next(n)),L(()=>r.complete()),m(n=>R({ref:e},n)))}function pi(e,{worker$:t,keyboard$:r}){let o=new g,n=Se("search-query"),i=S(d(n,"keydown"),d(n,"focus")).pipe(be(se),m(()=>n.value),K());return o.pipe(We(i),m(([{suggest:s},p])=>{let c=p.split(/([\s-]+)/);if(s!=null&&s.length&&c[c.length-1]){let l=s[s.length-1];l.startsWith(c[c.length-1])&&(c[c.length-1]=l)}else c.length=0;return c})).subscribe(s=>e.innerHTML=s.join("").replace(/\s/g," ")),r.pipe(v(({mode:s})=>s==="search")).subscribe(s=>{switch(s.type){case"ArrowRight":e.innerText.length&&n.selectionStart===n.value.length&&(n.value=e.innerText);break}}),t.pipe(v(dr),m(({data:s})=>s)).pipe(E(s=>o.next(s)),L(()=>o.complete()),m(()=>({ref:e})))}function li(e,{index$:t,keyboard$:r}){let o=ye();try{let n=ni(o.search,t),i=Se("search-query",e),a=Se("search-result",e);d(e,"click").pipe(v(({target:p})=>p instanceof Element&&!!p.closest("a"))).subscribe(()=>Je("search",!1)),r.pipe(v(({mode:p})=>p==="search")).subscribe(p=>{let c=Re();switch(p.type){case"Enter":if(c===i){let l=new Map;for(let f of $(":first-child [href]",a)){let u=f.firstElementChild;l.set(f,parseFloat(u.getAttribute("data-md-score")))}if(l.size){let[[f]]=[...l].sort(([,u],[,h])=>h-u);f.click()}p.claim()}break;case"Escape":case"Tab":Je("search",!1),i.blur();break;case"ArrowUp":case"ArrowDown":if(typeof c=="undefined")i.focus();else{let l=[i,...$(":not(details) > [href], summary, details[open] [href]",a)],f=Math.max(0,(Math.max(0,l.indexOf(c))+l.length+(p.type==="ArrowUp"?-1:1))%l.length);l[f].focus()}p.claim();break;default:i!==Re()&&i.focus()}}),r.pipe(v(({mode:p})=>p==="global")).subscribe(p=>{switch(p.type){case"f":case"s":case"/":i.focus(),i.select(),p.claim();break}});let s=ai(i,{worker$:n});return S(s,si(a,{worker$:n,query$:s})).pipe(Pe(...ae("search-share",e).map(p=>ci(p,{query$:s})),...ae("search-suggest",e).map(p=>pi(p,{worker$:n,keyboard$:r}))))}catch(n){return e.hidden=!0,Ke}}function mi(e,{index$:t,location$:r}){return z([t,r.pipe(Q(xe()),v(o=>!!o.searchParams.get("h")))]).pipe(m(([o,n])=>oi(o.config)(n.searchParams.get("h"))),m(o=>{var a;let n=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let s=i.nextNode();s;s=i.nextNode())if((a=s.parentElement)!=null&&a.offsetHeight){let p=s.textContent,c=o(p);c.length>p.length&&n.set(s,c)}for(let[s,p]of n){let{childNodes:c}=x("span",null,p);s.replaceWith(...Array.from(c))}return{ref:e,nodes:n}}))}function as(e,{viewport$:t,main$:r}){let o=e.closest(".md-grid"),n=o.offsetTop-o.parentElement.offsetTop;return z([r,t]).pipe(m(([{offset:i,height:a},{offset:{y:s}}])=>(a=a+Math.min(n,Math.max(0,s-i))-n,{height:a,locked:s>=i+n})),K((i,a)=>i.height===a.height&&i.locked===a.locked))}function Jr(e,o){var n=o,{header$:t}=n,r=io(n,["header$"]);let i=P(".md-sidebar__scrollwrap",e),{y:a}=Ue(i);return C(()=>{let s=new g,p=s.pipe(X(),ne(!0)),c=s.pipe(Le(0,me));return c.pipe(ee(t)).subscribe({next([{height:l},{height:f}]){i.style.height=`${l-2*a}px`,e.style.top=`${f}px`},complete(){i.style.height="",e.style.top=""}}),c.pipe(Ae()).subscribe(()=>{for(let l of $(".md-nav__link--active[href]",e)){if(!l.clientHeight)continue;let f=l.closest(".md-sidebar__scrollwrap");if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:h}=ce(f);f.scrollTo({top:u-h/2})}}}),ue($("label[tabindex]",e)).pipe(oe(l=>d(l,"click").pipe(be(se),m(()=>l),U(p)))).subscribe(l=>{let f=P(`[id="${l.htmlFor}"]`);P(`[aria-labelledby="${l.id}"]`).setAttribute("aria-expanded",`${f.checked}`)}),as(e,r).pipe(E(l=>s.next(l)),L(()=>s.complete()),m(l=>R({ref:e},l)))})}function fi(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return Ct(Ne(`${r}/releases/latest`).pipe(ve(()=>M),m(o=>({version:o.tag_name})),Be({})),Ne(r).pipe(ve(()=>M),m(o=>({stars:o.stargazers_count,forks:o.forks_count})),Be({}))).pipe(m(([o,n])=>R(R({},o),n)))}else{let r=`https://api.github.com/users/${e}`;return Ne(r).pipe(m(o=>({repositories:o.public_repos})),Be({}))}}function ui(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return Ne(r).pipe(ve(()=>M),m(({star_count:o,forks_count:n})=>({stars:o,forks:n})),Be({}))}function di(e){let t=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);if(t){let[,r,o]=t;return fi(r,o)}if(t=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i),t){let[,r,o]=t;return ui(r,o)}return M}var ss;function cs(e){return ss||(ss=C(()=>{let t=__md_get("__source",sessionStorage);if(t)return I(t);if(ae("consent").length){let o=__md_get("__consent");if(!(o&&o.github))return M}return di(e.href).pipe(E(o=>__md_set("__source",o,sessionStorage)))}).pipe(ve(()=>M),v(t=>Object.keys(t).length>0),m(t=>({facts:t})),B(1)))}function hi(e){let t=P(":scope > :last-child",e);return C(()=>{let r=new g;return r.subscribe(({facts:o})=>{t.appendChild(Sn(o)),t.classList.add("md-source__repository--active")}),cs(e).pipe(E(o=>r.next(o)),L(()=>r.complete()),m(o=>R({ref:e},o)))})}function ps(e,{viewport$:t,header$:r}){return ge(document.body).pipe(b(()=>mr(e,{header$:r,viewport$:t})),m(({offset:{y:o}})=>({hidden:o>=10})),Z("hidden"))}function bi(e,t){return C(()=>{let r=new g;return r.subscribe({next({hidden:o}){e.hidden=o},complete(){e.hidden=!1}}),(G("navigation.tabs.sticky")?I({hidden:!1}):ps(e,t)).pipe(E(o=>r.next(o)),L(()=>r.complete()),m(o=>R({ref:e},o)))})}function ls(e,{viewport$:t,header$:r}){let o=new Map,n=$(".md-nav__link",e);for(let s of n){let p=decodeURIComponent(s.hash.substring(1)),c=fe(`[id="${p}"]`);typeof c!="undefined"&&o.set(s,c)}let i=r.pipe(Z("height"),m(({height:s})=>{let p=Se("main"),c=P(":scope > :first-child",p);return s+.8*(c.offsetTop-p.offsetTop)}),pe());return ge(document.body).pipe(Z("height"),b(s=>C(()=>{let p=[];return I([...o].reduce((c,[l,f])=>{for(;p.length&&o.get(p[p.length-1]).tagName>=f.tagName;)p.pop();let u=f.offsetTop;for(;!u&&f.parentElement;)f=f.parentElement,u=f.offsetTop;let h=f.offsetParent;for(;h;h=h.offsetParent)u+=h.offsetTop;return c.set([...p=[...p,l]].reverse(),u)},new Map))}).pipe(m(p=>new Map([...p].sort(([,c],[,l])=>c-l))),We(i),b(([p,c])=>t.pipe(jr(([l,f],{offset:{y:u},size:h})=>{let w=u+h.height>=Math.floor(s.height);for(;f.length;){let[,A]=f[0];if(A-c=u&&!w)f=[l.pop(),...f];else break}return[l,f]},[[],[...p]]),K((l,f)=>l[0]===f[0]&&l[1]===f[1])))))).pipe(m(([s,p])=>({prev:s.map(([c])=>c),next:p.map(([c])=>c)})),Q({prev:[],next:[]}),Ye(2,1),m(([s,p])=>s.prev.length{let i=new g,a=i.pipe(X(),ne(!0));if(i.subscribe(({prev:s,next:p})=>{for(let[c]of p)c.classList.remove("md-nav__link--passed"),c.classList.remove("md-nav__link--active");for(let[c,[l]]of s.entries())l.classList.add("md-nav__link--passed"),l.classList.toggle("md-nav__link--active",c===s.length-1)}),G("toc.follow")){let s=S(t.pipe(_e(1),m(()=>{})),t.pipe(_e(250),m(()=>"smooth")));i.pipe(v(({prev:p})=>p.length>0),We(o.pipe(be(se))),ee(s)).subscribe(([[{prev:p}],c])=>{let[l]=p[p.length-1];if(l.offsetHeight){let f=cr(l);if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:h}=ce(f);f.scrollTo({top:u-h/2,behavior:c})}}})}return G("navigation.tracking")&&t.pipe(U(a),Z("offset"),_e(250),Ce(1),U(n.pipe(Ce(1))),st({delay:250}),ee(i)).subscribe(([,{prev:s}])=>{let p=xe(),c=s[s.length-1];if(c&&c.length){let[l]=c,{hash:f}=new URL(l.href);p.hash!==f&&(p.hash=f,history.replaceState({},"",`${p}`))}else p.hash="",history.replaceState({},"",`${p}`)}),ls(e,{viewport$:t,header$:r}).pipe(E(s=>i.next(s)),L(()=>i.complete()),m(s=>R({ref:e},s)))})}function ms(e,{viewport$:t,main$:r,target$:o}){let n=t.pipe(m(({offset:{y:a}})=>a),Ye(2,1),m(([a,s])=>a>s&&s>0),K()),i=r.pipe(m(({active:a})=>a));return z([i,n]).pipe(m(([a,s])=>!(a&&s)),K(),U(o.pipe(Ce(1))),ne(!0),st({delay:250}),m(a=>({hidden:a})))}function gi(e,{viewport$:t,header$:r,main$:o,target$:n}){let i=new g,a=i.pipe(X(),ne(!0));return i.subscribe({next({hidden:s}){e.hidden=s,s?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(U(a),Z("height")).subscribe(({height:s})=>{e.style.top=`${s+16}px`}),d(e,"click").subscribe(s=>{s.preventDefault(),window.scrollTo({top:0})}),ms(e,{viewport$:t,main$:o,target$:n}).pipe(E(s=>i.next(s)),L(()=>i.complete()),m(s=>R({ref:e},s)))}function xi({document$:e,viewport$:t}){e.pipe(b(()=>$(".md-ellipsis")),oe(r=>tt(r).pipe(U(e.pipe(Ce(1))),v(o=>o),m(()=>r),Te(1))),v(r=>r.offsetWidth{let o=r.innerText,n=r.closest("a")||r;return n.title=o,lt(n,{viewport$:t}).pipe(U(e.pipe(Ce(1))),L(()=>n.removeAttribute("title")))})).subscribe(),e.pipe(b(()=>$(".md-status")),oe(r=>lt(r,{viewport$:t}))).subscribe()}function yi({document$:e,tablet$:t}){e.pipe(b(()=>$(".md-toggle--indeterminate")),E(r=>{r.indeterminate=!0,r.checked=!1}),oe(r=>d(r,"change").pipe(Dr(()=>r.classList.contains("md-toggle--indeterminate")),m(()=>r))),ee(t)).subscribe(([r,o])=>{r.classList.remove("md-toggle--indeterminate"),o&&(r.checked=!1)})}function fs(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function Ei({document$:e}){e.pipe(b(()=>$("[data-md-scrollfix]")),E(t=>t.removeAttribute("data-md-scrollfix")),v(fs),oe(t=>d(t,"touchstart").pipe(m(()=>t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function wi({viewport$:e,tablet$:t}){z([Ve("search"),t]).pipe(m(([r,o])=>r&&!o),b(r=>I(r).pipe(Ge(r?400:100))),ee(e)).subscribe(([r,{offset:{y:o}}])=>{if(r)document.body.setAttribute("data-md-scrolllock",""),document.body.style.top=`-${o}px`;else{let n=-1*parseInt(document.body.style.top,10);document.body.removeAttribute("data-md-scrolllock"),document.body.style.top="",n&&window.scrollTo(0,n)}})}Object.entries||(Object.entries=function(e){let t=[];for(let r of Object.keys(e))t.push([r,e[r]]);return t});Object.values||(Object.values=function(e){let t=[];for(let r of Object.keys(e))t.push(e[r]);return t});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(e,t){typeof e=="object"?(this.scrollLeft=e.left,this.scrollTop=e.top):(this.scrollLeft=e,this.scrollTop=t)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...e){let t=this.parentNode;if(t){e.length===0&&t.removeChild(this);for(let r=e.length-1;r>=0;r--){let o=e[r];typeof o=="string"?o=document.createTextNode(o):o.parentNode&&o.parentNode.removeChild(o),r?t.insertBefore(this.previousSibling,o):t.replaceChild(o,this)}}}));function us(){return location.protocol==="file:"?wt(`${new URL("search/search_index.js",Xr.base)}`).pipe(m(()=>__index),B(1)):Ne(new URL("search/search_index.json",Xr.base))}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var ot=Yo(),jt=nn(),Ot=cn(jt),Zr=on(),Oe=bn(),hr=$t("(min-width: 960px)"),Si=$t("(min-width: 1220px)"),Oi=pn(),Xr=ye(),Mi=document.forms.namedItem("search")?us():Ke,eo=new g;Bn({alert$:eo});var to=new g;G("navigation.instant")&&Zn({location$:jt,viewport$:Oe,progress$:to}).subscribe(ot);var Ti;((Ti=Xr.version)==null?void 0:Ti.provider)==="mike"&&ii({document$:ot});S(jt,Ot).pipe(Ge(125)).subscribe(()=>{Je("drawer",!1),Je("search",!1)});Zr.pipe(v(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=fe("link[rel=prev]");typeof t!="undefined"&&pt(t);break;case"n":case".":let r=fe("link[rel=next]");typeof r!="undefined"&&pt(r);break;case"Enter":let o=Re();o instanceof HTMLLabelElement&&o.click()}});xi({viewport$:Oe,document$:ot});yi({document$:ot,tablet$:hr});Ei({document$:ot});wi({viewport$:Oe,tablet$:hr});var rt=Nn(Se("header"),{viewport$:Oe}),Ft=ot.pipe(m(()=>Se("main")),b(e=>Qn(e,{viewport$:Oe,header$:rt})),B(1)),ds=S(...ae("consent").map(e=>xn(e,{target$:Ot})),...ae("dialog").map(e=>Dn(e,{alert$:eo})),...ae("header").map(e=>zn(e,{viewport$:Oe,header$:rt,main$:Ft})),...ae("palette").map(e=>Kn(e)),...ae("progress").map(e=>Yn(e,{progress$:to})),...ae("search").map(e=>li(e,{index$:Mi,keyboard$:Zr})),...ae("source").map(e=>hi(e))),hs=C(()=>S(...ae("announce").map(e=>gn(e)),...ae("content").map(e=>Un(e,{viewport$:Oe,target$:Ot,print$:Oi})),...ae("content").map(e=>G("search.highlight")?mi(e,{index$:Mi,location$:jt}):M),...ae("header-title").map(e=>qn(e,{viewport$:Oe,header$:rt})),...ae("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?Nr(Si,()=>Jr(e,{viewport$:Oe,header$:rt,main$:Ft})):Nr(hr,()=>Jr(e,{viewport$:Oe,header$:rt,main$:Ft}))),...ae("tabs").map(e=>bi(e,{viewport$:Oe,header$:rt})),...ae("toc").map(e=>vi(e,{viewport$:Oe,header$:rt,main$:Ft,target$:Ot})),...ae("top").map(e=>gi(e,{viewport$:Oe,header$:rt,main$:Ft,target$:Ot})))),Li=ot.pipe(b(()=>hs),Pe(ds),B(1));Li.subscribe();window.document$=ot;window.location$=jt;window.target$=Ot;window.keyboard$=Zr;window.viewport$=Oe;window.tablet$=hr;window.screen$=Si;window.print$=Oi;window.alert$=eo;window.progress$=to;window.component$=Li;})(); +//# sourceMappingURL=bundle.081f42fc.min.js.map + diff --git a/0.2.3/assets/javascripts/bundle.081f42fc.min.js.map b/0.2.3/assets/javascripts/bundle.081f42fc.min.js.map new file mode 100644 index 000000000..e055db5ac --- /dev/null +++ b/0.2.3/assets/javascripts/bundle.081f42fc.min.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/clipboard/dist/clipboard.js", "node_modules/escape-html/index.js", "src/templates/assets/javascripts/bundle.ts", "node_modules/rxjs/node_modules/tslib/tslib.es6.js", "node_modules/rxjs/src/internal/util/isFunction.ts", "node_modules/rxjs/src/internal/util/createErrorClass.ts", "node_modules/rxjs/src/internal/util/UnsubscriptionError.ts", "node_modules/rxjs/src/internal/util/arrRemove.ts", "node_modules/rxjs/src/internal/Subscription.ts", "node_modules/rxjs/src/internal/config.ts", "node_modules/rxjs/src/internal/scheduler/timeoutProvider.ts", "node_modules/rxjs/src/internal/util/reportUnhandledError.ts", "node_modules/rxjs/src/internal/util/noop.ts", "node_modules/rxjs/src/internal/NotificationFactories.ts", "node_modules/rxjs/src/internal/util/errorContext.ts", "node_modules/rxjs/src/internal/Subscriber.ts", "node_modules/rxjs/src/internal/symbol/observable.ts", "node_modules/rxjs/src/internal/util/identity.ts", "node_modules/rxjs/src/internal/util/pipe.ts", "node_modules/rxjs/src/internal/Observable.ts", "node_modules/rxjs/src/internal/util/lift.ts", "node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts", "node_modules/rxjs/src/internal/scheduler/animationFrameProvider.ts", "node_modules/rxjs/src/internal/util/ObjectUnsubscribedError.ts", "node_modules/rxjs/src/internal/Subject.ts", "node_modules/rxjs/src/internal/BehaviorSubject.ts", "node_modules/rxjs/src/internal/scheduler/dateTimestampProvider.ts", "node_modules/rxjs/src/internal/ReplaySubject.ts", "node_modules/rxjs/src/internal/scheduler/Action.ts", "node_modules/rxjs/src/internal/scheduler/intervalProvider.ts", "node_modules/rxjs/src/internal/scheduler/AsyncAction.ts", "node_modules/rxjs/src/internal/Scheduler.ts", "node_modules/rxjs/src/internal/scheduler/AsyncScheduler.ts", "node_modules/rxjs/src/internal/scheduler/async.ts", "node_modules/rxjs/src/internal/scheduler/QueueAction.ts", "node_modules/rxjs/src/internal/scheduler/QueueScheduler.ts", "node_modules/rxjs/src/internal/scheduler/queue.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameAction.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameScheduler.ts", "node_modules/rxjs/src/internal/scheduler/animationFrame.ts", "node_modules/rxjs/src/internal/observable/empty.ts", "node_modules/rxjs/src/internal/util/isScheduler.ts", "node_modules/rxjs/src/internal/util/args.ts", "node_modules/rxjs/src/internal/util/isArrayLike.ts", "node_modules/rxjs/src/internal/util/isPromise.ts", "node_modules/rxjs/src/internal/util/isInteropObservable.ts", "node_modules/rxjs/src/internal/util/isAsyncIterable.ts", "node_modules/rxjs/src/internal/util/throwUnobservableError.ts", "node_modules/rxjs/src/internal/symbol/iterator.ts", "node_modules/rxjs/src/internal/util/isIterable.ts", "node_modules/rxjs/src/internal/util/isReadableStreamLike.ts", "node_modules/rxjs/src/internal/observable/innerFrom.ts", "node_modules/rxjs/src/internal/util/executeSchedule.ts", "node_modules/rxjs/src/internal/operators/observeOn.ts", "node_modules/rxjs/src/internal/operators/subscribeOn.ts", "node_modules/rxjs/src/internal/scheduled/scheduleObservable.ts", "node_modules/rxjs/src/internal/scheduled/schedulePromise.ts", "node_modules/rxjs/src/internal/scheduled/scheduleArray.ts", "node_modules/rxjs/src/internal/scheduled/scheduleIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleAsyncIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleReadableStreamLike.ts", "node_modules/rxjs/src/internal/scheduled/scheduled.ts", "node_modules/rxjs/src/internal/observable/from.ts", "node_modules/rxjs/src/internal/observable/of.ts", "node_modules/rxjs/src/internal/observable/throwError.ts", "node_modules/rxjs/src/internal/util/EmptyError.ts", "node_modules/rxjs/src/internal/util/isDate.ts", "node_modules/rxjs/src/internal/operators/map.ts", "node_modules/rxjs/src/internal/util/mapOneOrManyArgs.ts", "node_modules/rxjs/src/internal/util/argsArgArrayOrObject.ts", "node_modules/rxjs/src/internal/util/createObject.ts", "node_modules/rxjs/src/internal/observable/combineLatest.ts", "node_modules/rxjs/src/internal/operators/mergeInternals.ts", "node_modules/rxjs/src/internal/operators/mergeMap.ts", "node_modules/rxjs/src/internal/operators/mergeAll.ts", "node_modules/rxjs/src/internal/operators/concatAll.ts", "node_modules/rxjs/src/internal/observable/concat.ts", "node_modules/rxjs/src/internal/observable/defer.ts", "node_modules/rxjs/src/internal/observable/fromEvent.ts", "node_modules/rxjs/src/internal/observable/fromEventPattern.ts", "node_modules/rxjs/src/internal/observable/timer.ts", "node_modules/rxjs/src/internal/observable/merge.ts", "node_modules/rxjs/src/internal/observable/never.ts", "node_modules/rxjs/src/internal/util/argsOrArgArray.ts", "node_modules/rxjs/src/internal/operators/filter.ts", "node_modules/rxjs/src/internal/observable/zip.ts", "node_modules/rxjs/src/internal/operators/audit.ts", "node_modules/rxjs/src/internal/operators/auditTime.ts", "node_modules/rxjs/src/internal/operators/bufferCount.ts", "node_modules/rxjs/src/internal/operators/catchError.ts", "node_modules/rxjs/src/internal/operators/scanInternals.ts", "node_modules/rxjs/src/internal/operators/combineLatest.ts", "node_modules/rxjs/src/internal/operators/combineLatestWith.ts", "node_modules/rxjs/src/internal/operators/debounce.ts", "node_modules/rxjs/src/internal/operators/debounceTime.ts", "node_modules/rxjs/src/internal/operators/defaultIfEmpty.ts", "node_modules/rxjs/src/internal/operators/take.ts", "node_modules/rxjs/src/internal/operators/ignoreElements.ts", "node_modules/rxjs/src/internal/operators/mapTo.ts", "node_modules/rxjs/src/internal/operators/delayWhen.ts", "node_modules/rxjs/src/internal/operators/delay.ts", "node_modules/rxjs/src/internal/operators/distinctUntilChanged.ts", "node_modules/rxjs/src/internal/operators/distinctUntilKeyChanged.ts", "node_modules/rxjs/src/internal/operators/throwIfEmpty.ts", "node_modules/rxjs/src/internal/operators/endWith.ts", "node_modules/rxjs/src/internal/operators/finalize.ts", "node_modules/rxjs/src/internal/operators/first.ts", "node_modules/rxjs/src/internal/operators/takeLast.ts", "node_modules/rxjs/src/internal/operators/merge.ts", "node_modules/rxjs/src/internal/operators/mergeWith.ts", "node_modules/rxjs/src/internal/operators/repeat.ts", "node_modules/rxjs/src/internal/operators/scan.ts", "node_modules/rxjs/src/internal/operators/share.ts", "node_modules/rxjs/src/internal/operators/shareReplay.ts", "node_modules/rxjs/src/internal/operators/skip.ts", "node_modules/rxjs/src/internal/operators/skipUntil.ts", "node_modules/rxjs/src/internal/operators/startWith.ts", "node_modules/rxjs/src/internal/operators/switchMap.ts", "node_modules/rxjs/src/internal/operators/takeUntil.ts", "node_modules/rxjs/src/internal/operators/takeWhile.ts", "node_modules/rxjs/src/internal/operators/tap.ts", "node_modules/rxjs/src/internal/operators/throttle.ts", "node_modules/rxjs/src/internal/operators/throttleTime.ts", "node_modules/rxjs/src/internal/operators/withLatestFrom.ts", "node_modules/rxjs/src/internal/operators/zip.ts", "node_modules/rxjs/src/internal/operators/zipWith.ts", "src/templates/assets/javascripts/browser/document/index.ts", "src/templates/assets/javascripts/browser/element/_/index.ts", "src/templates/assets/javascripts/browser/element/focus/index.ts", "src/templates/assets/javascripts/browser/element/hover/index.ts", "src/templates/assets/javascripts/utilities/h/index.ts", "src/templates/assets/javascripts/utilities/round/index.ts", "src/templates/assets/javascripts/browser/script/index.ts", "src/templates/assets/javascripts/browser/element/size/_/index.ts", "src/templates/assets/javascripts/browser/element/size/content/index.ts", "src/templates/assets/javascripts/browser/element/offset/_/index.ts", "src/templates/assets/javascripts/browser/element/offset/content/index.ts", "src/templates/assets/javascripts/browser/element/visibility/index.ts", "src/templates/assets/javascripts/browser/toggle/index.ts", "src/templates/assets/javascripts/browser/keyboard/index.ts", "src/templates/assets/javascripts/browser/location/_/index.ts", "src/templates/assets/javascripts/browser/location/hash/index.ts", "src/templates/assets/javascripts/browser/media/index.ts", "src/templates/assets/javascripts/browser/request/index.ts", "src/templates/assets/javascripts/browser/viewport/offset/index.ts", "src/templates/assets/javascripts/browser/viewport/size/index.ts", "src/templates/assets/javascripts/browser/viewport/_/index.ts", "src/templates/assets/javascripts/browser/viewport/at/index.ts", "src/templates/assets/javascripts/browser/worker/index.ts", "src/templates/assets/javascripts/_/index.ts", "src/templates/assets/javascripts/components/_/index.ts", "src/templates/assets/javascripts/components/announce/index.ts", "src/templates/assets/javascripts/components/consent/index.ts", "src/templates/assets/javascripts/templates/tooltip/index.tsx", "src/templates/assets/javascripts/templates/annotation/index.tsx", "src/templates/assets/javascripts/templates/clipboard/index.tsx", "src/templates/assets/javascripts/templates/search/index.tsx", "src/templates/assets/javascripts/templates/source/index.tsx", "src/templates/assets/javascripts/templates/tabbed/index.tsx", "src/templates/assets/javascripts/templates/table/index.tsx", "src/templates/assets/javascripts/templates/version/index.tsx", "src/templates/assets/javascripts/components/tooltip2/index.ts", "src/templates/assets/javascripts/components/content/annotation/_/index.ts", "src/templates/assets/javascripts/components/content/annotation/list/index.ts", "src/templates/assets/javascripts/components/content/annotation/block/index.ts", "src/templates/assets/javascripts/components/content/code/_/index.ts", "src/templates/assets/javascripts/components/content/details/index.ts", "src/templates/assets/javascripts/components/content/mermaid/index.css", "src/templates/assets/javascripts/components/content/mermaid/index.ts", "src/templates/assets/javascripts/components/content/table/index.ts", "src/templates/assets/javascripts/components/content/tabs/index.ts", "src/templates/assets/javascripts/components/content/_/index.ts", "src/templates/assets/javascripts/components/dialog/index.ts", "src/templates/assets/javascripts/components/tooltip/index.ts", "src/templates/assets/javascripts/components/header/_/index.ts", "src/templates/assets/javascripts/components/header/title/index.ts", "src/templates/assets/javascripts/components/main/index.ts", "src/templates/assets/javascripts/components/palette/index.ts", "src/templates/assets/javascripts/components/progress/index.ts", "src/templates/assets/javascripts/integrations/clipboard/index.ts", "src/templates/assets/javascripts/integrations/sitemap/index.ts", "src/templates/assets/javascripts/integrations/instant/index.ts", "src/templates/assets/javascripts/integrations/search/highlighter/index.ts", "src/templates/assets/javascripts/integrations/search/worker/message/index.ts", "src/templates/assets/javascripts/integrations/search/worker/_/index.ts", "src/templates/assets/javascripts/integrations/version/index.ts", "src/templates/assets/javascripts/components/search/query/index.ts", "src/templates/assets/javascripts/components/search/result/index.ts", "src/templates/assets/javascripts/components/search/share/index.ts", "src/templates/assets/javascripts/components/search/suggest/index.ts", "src/templates/assets/javascripts/components/search/_/index.ts", "src/templates/assets/javascripts/components/search/highlight/index.ts", "src/templates/assets/javascripts/components/sidebar/index.ts", "src/templates/assets/javascripts/components/source/facts/github/index.ts", "src/templates/assets/javascripts/components/source/facts/gitlab/index.ts", "src/templates/assets/javascripts/components/source/facts/_/index.ts", "src/templates/assets/javascripts/components/source/_/index.ts", "src/templates/assets/javascripts/components/tabs/index.ts", "src/templates/assets/javascripts/components/toc/index.ts", "src/templates/assets/javascripts/components/top/index.ts", "src/templates/assets/javascripts/patches/ellipsis/index.ts", "src/templates/assets/javascripts/patches/indeterminate/index.ts", "src/templates/assets/javascripts/patches/scrollfix/index.ts", "src/templates/assets/javascripts/patches/scrolllock/index.ts", "src/templates/assets/javascripts/polyfills/index.ts"], + "sourcesContent": ["(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory() :\n typeof define === 'function' && define.amd ? define(factory) :\n (factory());\n}(this, (function () { 'use strict';\n\n /**\n * Applies the :focus-visible polyfill at the given scope.\n * A scope in this case is either the top-level Document or a Shadow Root.\n *\n * @param {(Document|ShadowRoot)} scope\n * @see https://github.com/WICG/focus-visible\n */\n function applyFocusVisiblePolyfill(scope) {\n var hadKeyboardEvent = true;\n var hadFocusVisibleRecently = false;\n var hadFocusVisibleRecentlyTimeout = null;\n\n var inputTypesAllowlist = {\n text: true,\n search: true,\n url: true,\n tel: true,\n email: true,\n password: true,\n number: true,\n date: true,\n month: true,\n week: true,\n time: true,\n datetime: true,\n 'datetime-local': true\n };\n\n /**\n * Helper function for legacy browsers and iframes which sometimes focus\n * elements like document, body, and non-interactive SVG.\n * @param {Element} el\n */\n function isValidFocusTarget(el) {\n if (\n el &&\n el !== document &&\n el.nodeName !== 'HTML' &&\n el.nodeName !== 'BODY' &&\n 'classList' in el &&\n 'contains' in el.classList\n ) {\n return true;\n }\n return false;\n }\n\n /**\n * Computes whether the given element should automatically trigger the\n * `focus-visible` class being added, i.e. whether it should always match\n * `:focus-visible` when focused.\n * @param {Element} el\n * @return {boolean}\n */\n function focusTriggersKeyboardModality(el) {\n var type = el.type;\n var tagName = el.tagName;\n\n if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {\n return true;\n }\n\n if (tagName === 'TEXTAREA' && !el.readOnly) {\n return true;\n }\n\n if (el.isContentEditable) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Add the `focus-visible` class to the given element if it was not added by\n * the author.\n * @param {Element} el\n */\n function addFocusVisibleClass(el) {\n if (el.classList.contains('focus-visible')) {\n return;\n }\n el.classList.add('focus-visible');\n el.setAttribute('data-focus-visible-added', '');\n }\n\n /**\n * Remove the `focus-visible` class from the given element if it was not\n * originally added by the author.\n * @param {Element} el\n */\n function removeFocusVisibleClass(el) {\n if (!el.hasAttribute('data-focus-visible-added')) {\n return;\n }\n el.classList.remove('focus-visible');\n el.removeAttribute('data-focus-visible-added');\n }\n\n /**\n * If the most recent user interaction was via the keyboard;\n * and the key press did not include a meta, alt/option, or control key;\n * then the modality is keyboard. Otherwise, the modality is not keyboard.\n * Apply `focus-visible` to any current active element and keep track\n * of our keyboard modality state with `hadKeyboardEvent`.\n * @param {KeyboardEvent} e\n */\n function onKeyDown(e) {\n if (e.metaKey || e.altKey || e.ctrlKey) {\n return;\n }\n\n if (isValidFocusTarget(scope.activeElement)) {\n addFocusVisibleClass(scope.activeElement);\n }\n\n hadKeyboardEvent = true;\n }\n\n /**\n * If at any point a user clicks with a pointing device, ensure that we change\n * the modality away from keyboard.\n * This avoids the situation where a user presses a key on an already focused\n * element, and then clicks on a different element, focusing it with a\n * pointing device, while we still think we're in keyboard modality.\n * @param {Event} e\n */\n function onPointerDown(e) {\n hadKeyboardEvent = false;\n }\n\n /**\n * On `focus`, add the `focus-visible` class to the target if:\n * - the target received focus as a result of keyboard navigation, or\n * - the event target is an element that will likely require interaction\n * via the keyboard (e.g. a text box)\n * @param {Event} e\n */\n function onFocus(e) {\n // Prevent IE from focusing the document or HTML element.\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {\n addFocusVisibleClass(e.target);\n }\n }\n\n /**\n * On `blur`, remove the `focus-visible` class from the target.\n * @param {Event} e\n */\n function onBlur(e) {\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (\n e.target.classList.contains('focus-visible') ||\n e.target.hasAttribute('data-focus-visible-added')\n ) {\n // To detect a tab/window switch, we look for a blur event followed\n // rapidly by a visibility change.\n // If we don't see a visibility change within 100ms, it's probably a\n // regular focus change.\n hadFocusVisibleRecently = true;\n window.clearTimeout(hadFocusVisibleRecentlyTimeout);\n hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {\n hadFocusVisibleRecently = false;\n }, 100);\n removeFocusVisibleClass(e.target);\n }\n }\n\n /**\n * If the user changes tabs, keep track of whether or not the previously\n * focused element had .focus-visible.\n * @param {Event} e\n */\n function onVisibilityChange(e) {\n if (document.visibilityState === 'hidden') {\n // If the tab becomes active again, the browser will handle calling focus\n // on the element (Safari actually calls it twice).\n // If this tab change caused a blur on an element with focus-visible,\n // re-apply the class when the user switches back to the tab.\n if (hadFocusVisibleRecently) {\n hadKeyboardEvent = true;\n }\n addInitialPointerMoveListeners();\n }\n }\n\n /**\n * Add a group of listeners to detect usage of any pointing devices.\n * These listeners will be added when the polyfill first loads, and anytime\n * the window is blurred, so that they are active when the window regains\n * focus.\n */\n function addInitialPointerMoveListeners() {\n document.addEventListener('mousemove', onInitialPointerMove);\n document.addEventListener('mousedown', onInitialPointerMove);\n document.addEventListener('mouseup', onInitialPointerMove);\n document.addEventListener('pointermove', onInitialPointerMove);\n document.addEventListener('pointerdown', onInitialPointerMove);\n document.addEventListener('pointerup', onInitialPointerMove);\n document.addEventListener('touchmove', onInitialPointerMove);\n document.addEventListener('touchstart', onInitialPointerMove);\n document.addEventListener('touchend', onInitialPointerMove);\n }\n\n function removeInitialPointerMoveListeners() {\n document.removeEventListener('mousemove', onInitialPointerMove);\n document.removeEventListener('mousedown', onInitialPointerMove);\n document.removeEventListener('mouseup', onInitialPointerMove);\n document.removeEventListener('pointermove', onInitialPointerMove);\n document.removeEventListener('pointerdown', onInitialPointerMove);\n document.removeEventListener('pointerup', onInitialPointerMove);\n document.removeEventListener('touchmove', onInitialPointerMove);\n document.removeEventListener('touchstart', onInitialPointerMove);\n document.removeEventListener('touchend', onInitialPointerMove);\n }\n\n /**\n * When the polfyill first loads, assume the user is in keyboard modality.\n * If any event is received from a pointing device (e.g. mouse, pointer,\n * touch), turn off keyboard modality.\n * This accounts for situations where focus enters the page from the URL bar.\n * @param {Event} e\n */\n function onInitialPointerMove(e) {\n // Work around a Safari quirk that fires a mousemove on whenever the\n // window blurs, even if you're tabbing out of the page. \u00AF\\_(\u30C4)_/\u00AF\n if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {\n return;\n }\n\n hadKeyboardEvent = false;\n removeInitialPointerMoveListeners();\n }\n\n // For some kinds of state, we are interested in changes at the global scope\n // only. For example, global pointer input, global key presses and global\n // visibility change should affect the state at every scope:\n document.addEventListener('keydown', onKeyDown, true);\n document.addEventListener('mousedown', onPointerDown, true);\n document.addEventListener('pointerdown', onPointerDown, true);\n document.addEventListener('touchstart', onPointerDown, true);\n document.addEventListener('visibilitychange', onVisibilityChange, true);\n\n addInitialPointerMoveListeners();\n\n // For focus and blur, we specifically care about state changes in the local\n // scope. This is because focus / blur events that originate from within a\n // shadow root are not re-dispatched from the host element if it was already\n // the active element in its own scope:\n scope.addEventListener('focus', onFocus, true);\n scope.addEventListener('blur', onBlur, true);\n\n // We detect that a node is a ShadowRoot by ensuring that it is a\n // DocumentFragment and also has a host property. This check covers native\n // implementation and polyfill implementation transparently. If we only cared\n // about the native implementation, we could just check if the scope was\n // an instance of a ShadowRoot.\n if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {\n // Since a ShadowRoot is a special kind of DocumentFragment, it does not\n // have a root element to add a class to. So, we add this attribute to the\n // host element instead:\n scope.host.setAttribute('data-js-focus-visible', '');\n } else if (scope.nodeType === Node.DOCUMENT_NODE) {\n document.documentElement.classList.add('js-focus-visible');\n document.documentElement.setAttribute('data-js-focus-visible', '');\n }\n }\n\n // It is important to wrap all references to global window and document in\n // these checks to support server-side rendering use cases\n // @see https://github.com/WICG/focus-visible/issues/199\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n // Make the polyfill helper globally available. This can be used as a signal\n // to interested libraries that wish to coordinate with the polyfill for e.g.,\n // applying the polyfill to a shadow root:\n window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;\n\n // Notify interested libraries of the polyfill's presence, in case the\n // polyfill was loaded lazily:\n var event;\n\n try {\n event = new CustomEvent('focus-visible-polyfill-ready');\n } catch (error) {\n // IE11 does not support using CustomEvent as a constructor directly:\n event = document.createEvent('CustomEvent');\n event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});\n }\n\n window.dispatchEvent(event);\n }\n\n if (typeof document !== 'undefined') {\n // Apply the polyfill to the global document, so that no JavaScript\n // coordination is required to use the polyfill in the top-level document:\n applyFocusVisiblePolyfill(document);\n }\n\n})));\n", "/*!\n * clipboard.js v2.0.11\n * https://clipboardjs.com/\n *\n * Licensed MIT \u00A9 Zeno Rocha\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClipboardJS\"] = factory();\n\telse\n\t\troot[\"ClipboardJS\"] = factory();\n})(this, function() {\nreturn /******/ (function() { // webpackBootstrap\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ 686:\n/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n\n// EXPORTS\n__webpack_require__.d(__webpack_exports__, {\n \"default\": function() { return /* binding */ clipboard; }\n});\n\n// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js\nvar tiny_emitter = __webpack_require__(279);\nvar tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);\n// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js\nvar listen = __webpack_require__(370);\nvar listen_default = /*#__PURE__*/__webpack_require__.n(listen);\n// EXTERNAL MODULE: ./node_modules/select/src/select.js\nvar src_select = __webpack_require__(817);\nvar select_default = /*#__PURE__*/__webpack_require__.n(src_select);\n;// CONCATENATED MODULE: ./src/common/command.js\n/**\n * Executes a given operation type.\n * @param {String} type\n * @return {Boolean}\n */\nfunction command(type) {\n try {\n return document.execCommand(type);\n } catch (err) {\n return false;\n }\n}\n;// CONCATENATED MODULE: ./src/actions/cut.js\n\n\n/**\n * Cut action wrapper.\n * @param {String|HTMLElement} target\n * @return {String}\n */\n\nvar ClipboardActionCut = function ClipboardActionCut(target) {\n var selectedText = select_default()(target);\n command('cut');\n return selectedText;\n};\n\n/* harmony default export */ var actions_cut = (ClipboardActionCut);\n;// CONCATENATED MODULE: ./src/common/create-fake-element.js\n/**\n * Creates a fake textarea element with a value.\n * @param {String} value\n * @return {HTMLElement}\n */\nfunction createFakeElement(value) {\n var isRTL = document.documentElement.getAttribute('dir') === 'rtl';\n var fakeElement = document.createElement('textarea'); // Prevent zooming on iOS\n\n fakeElement.style.fontSize = '12pt'; // Reset box model\n\n fakeElement.style.border = '0';\n fakeElement.style.padding = '0';\n fakeElement.style.margin = '0'; // Move element out of screen horizontally\n\n fakeElement.style.position = 'absolute';\n fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically\n\n var yPosition = window.pageYOffset || document.documentElement.scrollTop;\n fakeElement.style.top = \"\".concat(yPosition, \"px\");\n fakeElement.setAttribute('readonly', '');\n fakeElement.value = value;\n return fakeElement;\n}\n;// CONCATENATED MODULE: ./src/actions/copy.js\n\n\n\n/**\n * Create fake copy action wrapper using a fake element.\n * @param {String} target\n * @param {Object} options\n * @return {String}\n */\n\nvar fakeCopyAction = function fakeCopyAction(value, options) {\n var fakeElement = createFakeElement(value);\n options.container.appendChild(fakeElement);\n var selectedText = select_default()(fakeElement);\n command('copy');\n fakeElement.remove();\n return selectedText;\n};\n/**\n * Copy action wrapper.\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @return {String}\n */\n\n\nvar ClipboardActionCopy = function ClipboardActionCopy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n var selectedText = '';\n\n if (typeof target === 'string') {\n selectedText = fakeCopyAction(target, options);\n } else if (target instanceof HTMLInputElement && !['text', 'search', 'url', 'tel', 'password'].includes(target === null || target === void 0 ? void 0 : target.type)) {\n // If input type doesn't support `setSelectionRange`. Simulate it. https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange\n selectedText = fakeCopyAction(target.value, options);\n } else {\n selectedText = select_default()(target);\n command('copy');\n }\n\n return selectedText;\n};\n\n/* harmony default export */ var actions_copy = (ClipboardActionCopy);\n;// CONCATENATED MODULE: ./src/actions/default.js\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\n\n\n/**\n * Inner function which performs selection from either `text` or `target`\n * properties and then executes copy or cut operations.\n * @param {Object} options\n */\n\nvar ClipboardActionDefault = function ClipboardActionDefault() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n // Defines base properties passed from constructor.\n var _options$action = options.action,\n action = _options$action === void 0 ? 'copy' : _options$action,\n container = options.container,\n target = options.target,\n text = options.text; // Sets the `action` to be performed which can be either 'copy' or 'cut'.\n\n if (action !== 'copy' && action !== 'cut') {\n throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"');\n } // Sets the `target` property using an element that will be have its content copied.\n\n\n if (target !== undefined) {\n if (target && _typeof(target) === 'object' && target.nodeType === 1) {\n if (action === 'copy' && target.hasAttribute('disabled')) {\n throw new Error('Invalid \"target\" attribute. Please use \"readonly\" instead of \"disabled\" attribute');\n }\n\n if (action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {\n throw new Error('Invalid \"target\" attribute. You can\\'t cut text from elements with \"readonly\" or \"disabled\" attributes');\n }\n } else {\n throw new Error('Invalid \"target\" value, use a valid Element');\n }\n } // Define selection strategy based on `text` property.\n\n\n if (text) {\n return actions_copy(text, {\n container: container\n });\n } // Defines which selection strategy based on `target` property.\n\n\n if (target) {\n return action === 'cut' ? actions_cut(target) : actions_copy(target, {\n container: container\n });\n }\n};\n\n/* harmony default export */ var actions_default = (ClipboardActionDefault);\n;// CONCATENATED MODULE: ./src/clipboard.js\nfunction clipboard_typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { clipboard_typeof = function _typeof(obj) { return typeof obj; }; } else { clipboard_typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return clipboard_typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (clipboard_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n\n\n\n\n\n/**\n * Helper function to retrieve attribute value.\n * @param {String} suffix\n * @param {Element} element\n */\n\nfunction getAttributeValue(suffix, element) {\n var attribute = \"data-clipboard-\".concat(suffix);\n\n if (!element.hasAttribute(attribute)) {\n return;\n }\n\n return element.getAttribute(attribute);\n}\n/**\n * Base class which takes one or more elements, adds event listeners to them,\n * and instantiates a new `ClipboardAction` on each click.\n */\n\n\nvar Clipboard = /*#__PURE__*/function (_Emitter) {\n _inherits(Clipboard, _Emitter);\n\n var _super = _createSuper(Clipboard);\n\n /**\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n * @param {Object} options\n */\n function Clipboard(trigger, options) {\n var _this;\n\n _classCallCheck(this, Clipboard);\n\n _this = _super.call(this);\n\n _this.resolveOptions(options);\n\n _this.listenClick(trigger);\n\n return _this;\n }\n /**\n * Defines if attributes would be resolved using internal setter functions\n * or custom functions that were passed in the constructor.\n * @param {Object} options\n */\n\n\n _createClass(Clipboard, [{\n key: \"resolveOptions\",\n value: function resolveOptions() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n this.action = typeof options.action === 'function' ? options.action : this.defaultAction;\n this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;\n this.text = typeof options.text === 'function' ? options.text : this.defaultText;\n this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;\n }\n /**\n * Adds a click event listener to the passed trigger.\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n */\n\n }, {\n key: \"listenClick\",\n value: function listenClick(trigger) {\n var _this2 = this;\n\n this.listener = listen_default()(trigger, 'click', function (e) {\n return _this2.onClick(e);\n });\n }\n /**\n * Defines a new `ClipboardAction` on each click event.\n * @param {Event} e\n */\n\n }, {\n key: \"onClick\",\n value: function onClick(e) {\n var trigger = e.delegateTarget || e.currentTarget;\n var action = this.action(trigger) || 'copy';\n var text = actions_default({\n action: action,\n container: this.container,\n target: this.target(trigger),\n text: this.text(trigger)\n }); // Fires an event based on the copy operation result.\n\n this.emit(text ? 'success' : 'error', {\n action: action,\n text: text,\n trigger: trigger,\n clearSelection: function clearSelection() {\n if (trigger) {\n trigger.focus();\n }\n\n window.getSelection().removeAllRanges();\n }\n });\n }\n /**\n * Default `action` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultAction\",\n value: function defaultAction(trigger) {\n return getAttributeValue('action', trigger);\n }\n /**\n * Default `target` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultTarget\",\n value: function defaultTarget(trigger) {\n var selector = getAttributeValue('target', trigger);\n\n if (selector) {\n return document.querySelector(selector);\n }\n }\n /**\n * Allow fire programmatically a copy action\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @returns Text copied.\n */\n\n }, {\n key: \"defaultText\",\n\n /**\n * Default `text` lookup function.\n * @param {Element} trigger\n */\n value: function defaultText(trigger) {\n return getAttributeValue('text', trigger);\n }\n /**\n * Destroy lifecycle.\n */\n\n }, {\n key: \"destroy\",\n value: function destroy() {\n this.listener.destroy();\n }\n }], [{\n key: \"copy\",\n value: function copy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n return actions_copy(target, options);\n }\n /**\n * Allow fire programmatically a cut action\n * @param {String|HTMLElement} target\n * @returns Text cutted.\n */\n\n }, {\n key: \"cut\",\n value: function cut(target) {\n return actions_cut(target);\n }\n /**\n * Returns the support of the given action, or all actions if no action is\n * given.\n * @param {String} [action]\n */\n\n }, {\n key: \"isSupported\",\n value: function isSupported() {\n var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];\n var actions = typeof action === 'string' ? [action] : action;\n var support = !!document.queryCommandSupported;\n actions.forEach(function (action) {\n support = support && !!document.queryCommandSupported(action);\n });\n return support;\n }\n }]);\n\n return Clipboard;\n}((tiny_emitter_default()));\n\n/* harmony default export */ var clipboard = (Clipboard);\n\n/***/ }),\n\n/***/ 828:\n/***/ (function(module) {\n\nvar DOCUMENT_NODE_TYPE = 9;\n\n/**\n * A polyfill for Element.matches()\n */\nif (typeof Element !== 'undefined' && !Element.prototype.matches) {\n var proto = Element.prototype;\n\n proto.matches = proto.matchesSelector ||\n proto.mozMatchesSelector ||\n proto.msMatchesSelector ||\n proto.oMatchesSelector ||\n proto.webkitMatchesSelector;\n}\n\n/**\n * Finds the closest parent that matches a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @return {Function}\n */\nfunction closest (element, selector) {\n while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {\n if (typeof element.matches === 'function' &&\n element.matches(selector)) {\n return element;\n }\n element = element.parentNode;\n }\n}\n\nmodule.exports = closest;\n\n\n/***/ }),\n\n/***/ 438:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar closest = __webpack_require__(828);\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction _delegate(element, selector, type, callback, useCapture) {\n var listenerFn = listener.apply(this, arguments);\n\n element.addEventListener(type, listenerFn, useCapture);\n\n return {\n destroy: function() {\n element.removeEventListener(type, listenerFn, useCapture);\n }\n }\n}\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element|String|Array} [elements]\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction delegate(elements, selector, type, callback, useCapture) {\n // Handle the regular Element usage\n if (typeof elements.addEventListener === 'function') {\n return _delegate.apply(null, arguments);\n }\n\n // Handle Element-less usage, it defaults to global delegation\n if (typeof type === 'function') {\n // Use `document` as the first parameter, then apply arguments\n // This is a short way to .unshift `arguments` without running into deoptimizations\n return _delegate.bind(null, document).apply(null, arguments);\n }\n\n // Handle Selector-based usage\n if (typeof elements === 'string') {\n elements = document.querySelectorAll(elements);\n }\n\n // Handle Array-like based usage\n return Array.prototype.map.call(elements, function (element) {\n return _delegate(element, selector, type, callback, useCapture);\n });\n}\n\n/**\n * Finds closest match and invokes callback.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Function}\n */\nfunction listener(element, selector, type, callback) {\n return function(e) {\n e.delegateTarget = closest(e.target, selector);\n\n if (e.delegateTarget) {\n callback.call(element, e);\n }\n }\n}\n\nmodule.exports = delegate;\n\n\n/***/ }),\n\n/***/ 879:\n/***/ (function(__unused_webpack_module, exports) {\n\n/**\n * Check if argument is a HTML element.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.node = function(value) {\n return value !== undefined\n && value instanceof HTMLElement\n && value.nodeType === 1;\n};\n\n/**\n * Check if argument is a list of HTML elements.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.nodeList = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return value !== undefined\n && (type === '[object NodeList]' || type === '[object HTMLCollection]')\n && ('length' in value)\n && (value.length === 0 || exports.node(value[0]));\n};\n\n/**\n * Check if argument is a string.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.string = function(value) {\n return typeof value === 'string'\n || value instanceof String;\n};\n\n/**\n * Check if argument is a function.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.fn = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return type === '[object Function]';\n};\n\n\n/***/ }),\n\n/***/ 370:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar is = __webpack_require__(879);\nvar delegate = __webpack_require__(438);\n\n/**\n * Validates all params and calls the right\n * listener function based on its target type.\n *\n * @param {String|HTMLElement|HTMLCollection|NodeList} target\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listen(target, type, callback) {\n if (!target && !type && !callback) {\n throw new Error('Missing required arguments');\n }\n\n if (!is.string(type)) {\n throw new TypeError('Second argument must be a String');\n }\n\n if (!is.fn(callback)) {\n throw new TypeError('Third argument must be a Function');\n }\n\n if (is.node(target)) {\n return listenNode(target, type, callback);\n }\n else if (is.nodeList(target)) {\n return listenNodeList(target, type, callback);\n }\n else if (is.string(target)) {\n return listenSelector(target, type, callback);\n }\n else {\n throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');\n }\n}\n\n/**\n * Adds an event listener to a HTML element\n * and returns a remove listener function.\n *\n * @param {HTMLElement} node\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNode(node, type, callback) {\n node.addEventListener(type, callback);\n\n return {\n destroy: function() {\n node.removeEventListener(type, callback);\n }\n }\n}\n\n/**\n * Add an event listener to a list of HTML elements\n * and returns a remove listener function.\n *\n * @param {NodeList|HTMLCollection} nodeList\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNodeList(nodeList, type, callback) {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.addEventListener(type, callback);\n });\n\n return {\n destroy: function() {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.removeEventListener(type, callback);\n });\n }\n }\n}\n\n/**\n * Add an event listener to a selector\n * and returns a remove listener function.\n *\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenSelector(selector, type, callback) {\n return delegate(document.body, selector, type, callback);\n}\n\nmodule.exports = listen;\n\n\n/***/ }),\n\n/***/ 817:\n/***/ (function(module) {\n\nfunction select(element) {\n var selectedText;\n\n if (element.nodeName === 'SELECT') {\n element.focus();\n\n selectedText = element.value;\n }\n else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {\n var isReadOnly = element.hasAttribute('readonly');\n\n if (!isReadOnly) {\n element.setAttribute('readonly', '');\n }\n\n element.select();\n element.setSelectionRange(0, element.value.length);\n\n if (!isReadOnly) {\n element.removeAttribute('readonly');\n }\n\n selectedText = element.value;\n }\n else {\n if (element.hasAttribute('contenteditable')) {\n element.focus();\n }\n\n var selection = window.getSelection();\n var range = document.createRange();\n\n range.selectNodeContents(element);\n selection.removeAllRanges();\n selection.addRange(range);\n\n selectedText = selection.toString();\n }\n\n return selectedText;\n}\n\nmodule.exports = select;\n\n\n/***/ }),\n\n/***/ 279:\n/***/ (function(module) {\n\nfunction E () {\n // Keep this empty so it's easier to inherit from\n // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)\n}\n\nE.prototype = {\n on: function (name, callback, ctx) {\n var e = this.e || (this.e = {});\n\n (e[name] || (e[name] = [])).push({\n fn: callback,\n ctx: ctx\n });\n\n return this;\n },\n\n once: function (name, callback, ctx) {\n var self = this;\n function listener () {\n self.off(name, listener);\n callback.apply(ctx, arguments);\n };\n\n listener._ = callback\n return this.on(name, listener, ctx);\n },\n\n emit: function (name) {\n var data = [].slice.call(arguments, 1);\n var evtArr = ((this.e || (this.e = {}))[name] || []).slice();\n var i = 0;\n var len = evtArr.length;\n\n for (i; i < len; i++) {\n evtArr[i].fn.apply(evtArr[i].ctx, data);\n }\n\n return this;\n },\n\n off: function (name, callback) {\n var e = this.e || (this.e = {});\n var evts = e[name];\n var liveEvents = [];\n\n if (evts && callback) {\n for (var i = 0, len = evts.length; i < len; i++) {\n if (evts[i].fn !== callback && evts[i].fn._ !== callback)\n liveEvents.push(evts[i]);\n }\n }\n\n // Remove event from queue to prevent memory leak\n // Suggested by https://github.com/lazd\n // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910\n\n (liveEvents.length)\n ? e[name] = liveEvents\n : delete e[name];\n\n return this;\n }\n};\n\nmodule.exports = E;\nmodule.exports.TinyEmitter = E;\n\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(__webpack_module_cache__[moduleId]) {\n/******/ \t\t\treturn __webpack_module_cache__[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t/* webpack/runtime/compat get default export */\n/******/ \t!function() {\n/******/ \t\t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t\t__webpack_require__.n = function(module) {\n/******/ \t\t\tvar getter = module && module.__esModule ?\n/******/ \t\t\t\tfunction() { return module['default']; } :\n/******/ \t\t\t\tfunction() { return module; };\n/******/ \t\t\t__webpack_require__.d(getter, { a: getter });\n/******/ \t\t\treturn getter;\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/define property getters */\n/******/ \t!function() {\n/******/ \t\t// define getter functions for harmony exports\n/******/ \t\t__webpack_require__.d = function(exports, definition) {\n/******/ \t\t\tfor(var key in definition) {\n/******/ \t\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n/******/ \t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n/******/ \t\t\t\t}\n/******/ \t\t\t}\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/hasOwnProperty shorthand */\n/******/ \t!function() {\n/******/ \t\t__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }\n/******/ \t}();\n/******/ \t\n/************************************************************************/\n/******/ \t// module exports must be returned from runtime so entry inlining is disabled\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(686);\n/******/ })()\n.default;\n});", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n var str = '' + string;\n var match = matchHtmlRegExp.exec(str);\n\n if (!match) {\n return str;\n }\n\n var escape;\n var html = '';\n var index = 0;\n var lastIndex = 0;\n\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34: // \"\n escape = '"';\n break;\n case 38: // &\n escape = '&';\n break;\n case 39: // '\n escape = ''';\n break;\n case 60: // <\n escape = '<';\n break;\n case 62: // >\n escape = '>';\n break;\n default:\n continue;\n }\n\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n\n lastIndex = index + 1;\n html += escape;\n }\n\n return lastIndex !== index\n ? html + str.substring(lastIndex, index)\n : html;\n}\n", "/*\n * Copyright (c) 2016-2024 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport \"focus-visible\"\n\nimport {\n EMPTY,\n NEVER,\n Observable,\n Subject,\n defer,\n delay,\n filter,\n map,\n merge,\n mergeWith,\n shareReplay,\n switchMap\n} from \"rxjs\"\n\nimport { configuration, feature } from \"./_\"\nimport {\n at,\n getActiveElement,\n getOptionalElement,\n requestJSON,\n setLocation,\n setToggle,\n watchDocument,\n watchKeyboard,\n watchLocation,\n watchLocationTarget,\n watchMedia,\n watchPrint,\n watchScript,\n watchViewport\n} from \"./browser\"\nimport {\n getComponentElement,\n getComponentElements,\n mountAnnounce,\n mountBackToTop,\n mountConsent,\n mountContent,\n mountDialog,\n mountHeader,\n mountHeaderTitle,\n mountPalette,\n mountProgress,\n mountSearch,\n mountSearchHiglight,\n mountSidebar,\n mountSource,\n mountTableOfContents,\n mountTabs,\n watchHeader,\n watchMain\n} from \"./components\"\nimport {\n SearchIndex,\n setupClipboardJS,\n setupInstantNavigation,\n setupVersionSelector\n} from \"./integrations\"\nimport {\n patchEllipsis,\n patchIndeterminate,\n patchScrollfix,\n patchScrolllock\n} from \"./patches\"\nimport \"./polyfills\"\n\n/* ----------------------------------------------------------------------------\n * Functions - @todo refactor\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch search index\n *\n * @returns Search index observable\n */\nfunction fetchSearchIndex(): Observable {\n if (location.protocol === \"file:\") {\n return watchScript(\n `${new URL(\"search/search_index.js\", config.base)}`\n )\n .pipe(\n // @ts-ignore - @todo fix typings\n map(() => __index),\n shareReplay(1)\n )\n } else {\n return requestJSON(\n new URL(\"search/search_index.json\", config.base)\n )\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Application\n * ------------------------------------------------------------------------- */\n\n/* Yay, JavaScript is available */\ndocument.documentElement.classList.remove(\"no-js\")\ndocument.documentElement.classList.add(\"js\")\n\n/* Set up navigation observables and subjects */\nconst document$ = watchDocument()\nconst location$ = watchLocation()\nconst target$ = watchLocationTarget(location$)\nconst keyboard$ = watchKeyboard()\n\n/* Set up media observables */\nconst viewport$ = watchViewport()\nconst tablet$ = watchMedia(\"(min-width: 960px)\")\nconst screen$ = watchMedia(\"(min-width: 1220px)\")\nconst print$ = watchPrint()\n\n/* Retrieve search index, if search is enabled */\nconst config = configuration()\nconst index$ = document.forms.namedItem(\"search\")\n ? fetchSearchIndex()\n : NEVER\n\n/* Set up Clipboard.js integration */\nconst alert$ = new Subject()\nsetupClipboardJS({ alert$ })\n\n/* Set up progress indicator */\nconst progress$ = new Subject()\n\n/* Set up instant navigation, if enabled */\nif (feature(\"navigation.instant\"))\n setupInstantNavigation({ location$, viewport$, progress$ })\n .subscribe(document$)\n\n/* Set up version selector */\nif (config.version?.provider === \"mike\")\n setupVersionSelector({ document$ })\n\n/* Always close drawer and search on navigation */\nmerge(location$, target$)\n .pipe(\n delay(125)\n )\n .subscribe(() => {\n setToggle(\"drawer\", false)\n setToggle(\"search\", false)\n })\n\n/* Set up global keyboard handlers */\nkeyboard$\n .pipe(\n filter(({ mode }) => mode === \"global\")\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Go to previous page */\n case \"p\":\n case \",\":\n const prev = getOptionalElement(\"link[rel=prev]\")\n if (typeof prev !== \"undefined\")\n setLocation(prev)\n break\n\n /* Go to next page */\n case \"n\":\n case \".\":\n const next = getOptionalElement(\"link[rel=next]\")\n if (typeof next !== \"undefined\")\n setLocation(next)\n break\n\n /* Expand navigation, see https://bit.ly/3ZjG5io */\n case \"Enter\":\n const active = getActiveElement()\n if (active instanceof HTMLLabelElement)\n active.click()\n }\n })\n\n/* Set up patches */\npatchEllipsis({ viewport$, document$ })\npatchIndeterminate({ document$, tablet$ })\npatchScrollfix({ document$ })\npatchScrolllock({ viewport$, tablet$ })\n\n/* Set up header and main area observable */\nconst header$ = watchHeader(getComponentElement(\"header\"), { viewport$ })\nconst main$ = document$\n .pipe(\n map(() => getComponentElement(\"main\")),\n switchMap(el => watchMain(el, { viewport$, header$ })),\n shareReplay(1)\n )\n\n/* Set up control component observables */\nconst control$ = merge(\n\n /* Consent */\n ...getComponentElements(\"consent\")\n .map(el => mountConsent(el, { target$ })),\n\n /* Dialog */\n ...getComponentElements(\"dialog\")\n .map(el => mountDialog(el, { alert$ })),\n\n /* Header */\n ...getComponentElements(\"header\")\n .map(el => mountHeader(el, { viewport$, header$, main$ })),\n\n /* Color palette */\n ...getComponentElements(\"palette\")\n .map(el => mountPalette(el)),\n\n /* Progress bar */\n ...getComponentElements(\"progress\")\n .map(el => mountProgress(el, { progress$ })),\n\n /* Search */\n ...getComponentElements(\"search\")\n .map(el => mountSearch(el, { index$, keyboard$ })),\n\n /* Repository information */\n ...getComponentElements(\"source\")\n .map(el => mountSource(el))\n)\n\n/* Set up content component observables */\nconst content$ = defer(() => merge(\n\n /* Announcement bar */\n ...getComponentElements(\"announce\")\n .map(el => mountAnnounce(el)),\n\n /* Content */\n ...getComponentElements(\"content\")\n .map(el => mountContent(el, { viewport$, target$, print$ })),\n\n /* Search highlighting */\n ...getComponentElements(\"content\")\n .map(el => feature(\"search.highlight\")\n ? mountSearchHiglight(el, { index$, location$ })\n : EMPTY\n ),\n\n /* Header title */\n ...getComponentElements(\"header-title\")\n .map(el => mountHeaderTitle(el, { viewport$, header$ })),\n\n /* Sidebar */\n ...getComponentElements(\"sidebar\")\n .map(el => el.getAttribute(\"data-md-type\") === \"navigation\"\n ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))\n : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))\n ),\n\n /* Navigation tabs */\n ...getComponentElements(\"tabs\")\n .map(el => mountTabs(el, { viewport$, header$ })),\n\n /* Table of contents */\n ...getComponentElements(\"toc\")\n .map(el => mountTableOfContents(el, {\n viewport$, header$, main$, target$\n })),\n\n /* Back-to-top button */\n ...getComponentElements(\"top\")\n .map(el => mountBackToTop(el, { viewport$, header$, main$, target$ }))\n))\n\n/* Set up component observables */\nconst component$ = document$\n .pipe(\n switchMap(() => content$),\n mergeWith(control$),\n shareReplay(1)\n )\n\n/* Subscribe to all components */\ncomponent$.subscribe()\n\n/* ----------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------- */\n\nwindow.document$ = document$ /* Document observable */\nwindow.location$ = location$ /* Location subject */\nwindow.target$ = target$ /* Location target observable */\nwindow.keyboard$ = keyboard$ /* Keyboard observable */\nwindow.viewport$ = viewport$ /* Viewport observable */\nwindow.tablet$ = tablet$ /* Media tablet observable */\nwindow.screen$ = screen$ /* Media screen observable */\nwindow.print$ = print$ /* Media print observable */\nwindow.alert$ = alert$ /* Alert subject */\nwindow.progress$ = progress$ /* Progress indicator subject */\nwindow.component$ = component$ /* Component observable */\n", "/*! *****************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise */\r\n\r\nvar extendStatics = function(d, b) {\r\n extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n return extendStatics(d, b);\r\n};\r\n\r\nexport function __extends(d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nexport var __assign = function() {\r\n __assign = Object.assign || function __assign(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n }\r\n return __assign.apply(this, arguments);\r\n}\r\n\r\nexport function __rest(s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n}\r\n\r\nexport function __decorate(decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nexport function __param(paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nexport function __metadata(metadataKey, metadataValue) {\r\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n}\r\n\r\nexport function __awaiter(thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n}\r\n\r\nexport function __generator(thisArg, body) {\r\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\r\n return g = { next: verb(0), \"throw\": verb(1), \"return\": verb(2) }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n function verb(n) { return function (v) { return step([n, v]); }; }\r\n function step(op) {\r\n if (f) throw new TypeError(\"Generator is already executing.\");\r\n while (_) try {\r\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n if (y = 0, t) op = [op[0] & 2, t.value];\r\n switch (op[0]) {\r\n case 0: case 1: t = op; break;\r\n case 4: _.label++; return { value: op[1], done: false };\r\n case 5: _.label++; y = op[1]; op = [0]; continue;\r\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n default:\r\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n if (t[2]) _.ops.pop();\r\n _.trys.pop(); continue;\r\n }\r\n op = body.call(thisArg, _);\r\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n }\r\n}\r\n\r\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });\r\n}) : (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n o[k2] = m[k];\r\n});\r\n\r\nexport function __exportStar(m, o) {\r\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\r\n}\r\n\r\nexport function __values(o) {\r\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n if (m) return m.call(o);\r\n if (o && typeof o.length === \"number\") return {\r\n next: function () {\r\n if (o && i >= o.length) o = void 0;\r\n return { value: o && o[i++], done: !o };\r\n }\r\n };\r\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n}\r\n\r\nexport function __read(o, n) {\r\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n if (!m) return o;\r\n var i = m.call(o), r, ar = [], e;\r\n try {\r\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n }\r\n catch (error) { e = { error: error }; }\r\n finally {\r\n try {\r\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n }\r\n finally { if (e) throw e.error; }\r\n }\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spread() {\r\n for (var ar = [], i = 0; i < arguments.length; i++)\r\n ar = ar.concat(__read(arguments[i]));\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spreadArrays() {\r\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n r[k] = a[j];\r\n return r;\r\n}\r\n\r\nexport function __spreadArray(to, from, pack) {\r\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\r\n if (ar || !(i in from)) {\r\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\r\n ar[i] = from[i];\r\n }\r\n }\r\n return to.concat(ar || Array.prototype.slice.call(from));\r\n}\r\n\r\nexport function __await(v) {\r\n return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n}\r\n\r\nexport function __asyncGenerator(thisArg, _arguments, generator) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n return i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }\r\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n function fulfill(value) { resume(\"next\", value); }\r\n function reject(value) { resume(\"throw\", value); }\r\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n}\r\n\r\nexport function __asyncDelegator(o) {\r\n var i, p;\r\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === \"return\" } : f ? f(v) : v; } : f; }\r\n}\r\n\r\nexport function __asyncValues(o) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var m = o[Symbol.asyncIterator], i;\r\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n}\r\n\r\nexport function __makeTemplateObject(cooked, raw) {\r\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n return cooked;\r\n};\r\n\r\nvar __setModuleDefault = Object.create ? (function(o, v) {\r\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\r\n}) : function(o, v) {\r\n o[\"default\"] = v;\r\n};\r\n\r\nexport function __importStar(mod) {\r\n if (mod && mod.__esModule) return mod;\r\n var result = {};\r\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\r\n __setModuleDefault(result, mod);\r\n return result;\r\n}\r\n\r\nexport function __importDefault(mod) {\r\n return (mod && mod.__esModule) ? mod : { default: mod };\r\n}\r\n\r\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n}\r\n\r\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n}\r\n", "/**\n * Returns true if the object is a function.\n * @param value The value to check\n */\nexport function isFunction(value: any): value is (...args: any[]) => any {\n return typeof value === 'function';\n}\n", "/**\n * Used to create Error subclasses until the community moves away from ES5.\n *\n * This is because compiling from TypeScript down to ES5 has issues with subclassing Errors\n * as well as other built-in types: https://github.com/Microsoft/TypeScript/issues/12123\n *\n * @param createImpl A factory function to create the actual constructor implementation. The returned\n * function should be a named function that calls `_super` internally.\n */\nexport function createErrorClass(createImpl: (_super: any) => any): T {\n const _super = (instance: any) => {\n Error.call(instance);\n instance.stack = new Error().stack;\n };\n\n const ctorFunc = createImpl(_super);\n ctorFunc.prototype = Object.create(Error.prototype);\n ctorFunc.prototype.constructor = ctorFunc;\n return ctorFunc;\n}\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface UnsubscriptionError extends Error {\n readonly errors: any[];\n}\n\nexport interface UnsubscriptionErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (errors: any[]): UnsubscriptionError;\n}\n\n/**\n * An error thrown when one or more errors have occurred during the\n * `unsubscribe` of a {@link Subscription}.\n */\nexport const UnsubscriptionError: UnsubscriptionErrorCtor = createErrorClass(\n (_super) =>\n function UnsubscriptionErrorImpl(this: any, errors: (Error | string)[]) {\n _super(this);\n this.message = errors\n ? `${errors.length} errors occurred during unsubscription:\n${errors.map((err, i) => `${i + 1}) ${err.toString()}`).join('\\n ')}`\n : '';\n this.name = 'UnsubscriptionError';\n this.errors = errors;\n }\n);\n", "/**\n * Removes an item from an array, mutating it.\n * @param arr The array to remove the item from\n * @param item The item to remove\n */\nexport function arrRemove(arr: T[] | undefined | null, item: T) {\n if (arr) {\n const index = arr.indexOf(item);\n 0 <= index && arr.splice(index, 1);\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { UnsubscriptionError } from './util/UnsubscriptionError';\nimport { SubscriptionLike, TeardownLogic, Unsubscribable } from './types';\nimport { arrRemove } from './util/arrRemove';\n\n/**\n * Represents a disposable resource, such as the execution of an Observable. A\n * Subscription has one important method, `unsubscribe`, that takes no argument\n * and just disposes the resource held by the subscription.\n *\n * Additionally, subscriptions may be grouped together through the `add()`\n * method, which will attach a child Subscription to the current Subscription.\n * When a Subscription is unsubscribed, all its children (and its grandchildren)\n * will be unsubscribed as well.\n *\n * @class Subscription\n */\nexport class Subscription implements SubscriptionLike {\n /** @nocollapse */\n public static EMPTY = (() => {\n const empty = new Subscription();\n empty.closed = true;\n return empty;\n })();\n\n /**\n * A flag to indicate whether this Subscription has already been unsubscribed.\n */\n public closed = false;\n\n private _parentage: Subscription[] | Subscription | null = null;\n\n /**\n * The list of registered finalizers to execute upon unsubscription. Adding and removing from this\n * list occurs in the {@link #add} and {@link #remove} methods.\n */\n private _finalizers: Exclude[] | null = null;\n\n /**\n * @param initialTeardown A function executed first as part of the finalization\n * process that is kicked off when {@link #unsubscribe} is called.\n */\n constructor(private initialTeardown?: () => void) {}\n\n /**\n * Disposes the resources held by the subscription. May, for instance, cancel\n * an ongoing Observable execution or cancel any other type of work that\n * started when the Subscription was created.\n * @return {void}\n */\n unsubscribe(): void {\n let errors: any[] | undefined;\n\n if (!this.closed) {\n this.closed = true;\n\n // Remove this from it's parents.\n const { _parentage } = this;\n if (_parentage) {\n this._parentage = null;\n if (Array.isArray(_parentage)) {\n for (const parent of _parentage) {\n parent.remove(this);\n }\n } else {\n _parentage.remove(this);\n }\n }\n\n const { initialTeardown: initialFinalizer } = this;\n if (isFunction(initialFinalizer)) {\n try {\n initialFinalizer();\n } catch (e) {\n errors = e instanceof UnsubscriptionError ? e.errors : [e];\n }\n }\n\n const { _finalizers } = this;\n if (_finalizers) {\n this._finalizers = null;\n for (const finalizer of _finalizers) {\n try {\n execFinalizer(finalizer);\n } catch (err) {\n errors = errors ?? [];\n if (err instanceof UnsubscriptionError) {\n errors = [...errors, ...err.errors];\n } else {\n errors.push(err);\n }\n }\n }\n }\n\n if (errors) {\n throw new UnsubscriptionError(errors);\n }\n }\n }\n\n /**\n * Adds a finalizer to this subscription, so that finalization will be unsubscribed/called\n * when this subscription is unsubscribed. If this subscription is already {@link #closed},\n * because it has already been unsubscribed, then whatever finalizer is passed to it\n * will automatically be executed (unless the finalizer itself is also a closed subscription).\n *\n * Closed Subscriptions cannot be added as finalizers to any subscription. Adding a closed\n * subscription to a any subscription will result in no operation. (A noop).\n *\n * Adding a subscription to itself, or adding `null` or `undefined` will not perform any\n * operation at all. (A noop).\n *\n * `Subscription` instances that are added to this instance will automatically remove themselves\n * if they are unsubscribed. Functions and {@link Unsubscribable} objects that you wish to remove\n * will need to be removed manually with {@link #remove}\n *\n * @param teardown The finalization logic to add to this subscription.\n */\n add(teardown: TeardownLogic): void {\n // Only add the finalizer if it's not undefined\n // and don't add a subscription to itself.\n if (teardown && teardown !== this) {\n if (this.closed) {\n // If this subscription is already closed,\n // execute whatever finalizer is handed to it automatically.\n execFinalizer(teardown);\n } else {\n if (teardown instanceof Subscription) {\n // We don't add closed subscriptions, and we don't add the same subscription\n // twice. Subscription unsubscribe is idempotent.\n if (teardown.closed || teardown._hasParent(this)) {\n return;\n }\n teardown._addParent(this);\n }\n (this._finalizers = this._finalizers ?? []).push(teardown);\n }\n }\n }\n\n /**\n * Checks to see if a this subscription already has a particular parent.\n * This will signal that this subscription has already been added to the parent in question.\n * @param parent the parent to check for\n */\n private _hasParent(parent: Subscription) {\n const { _parentage } = this;\n return _parentage === parent || (Array.isArray(_parentage) && _parentage.includes(parent));\n }\n\n /**\n * Adds a parent to this subscription so it can be removed from the parent if it\n * unsubscribes on it's own.\n *\n * NOTE: THIS ASSUMES THAT {@link _hasParent} HAS ALREADY BEEN CHECKED.\n * @param parent The parent subscription to add\n */\n private _addParent(parent: Subscription) {\n const { _parentage } = this;\n this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent;\n }\n\n /**\n * Called on a child when it is removed via {@link #remove}.\n * @param parent The parent to remove\n */\n private _removeParent(parent: Subscription) {\n const { _parentage } = this;\n if (_parentage === parent) {\n this._parentage = null;\n } else if (Array.isArray(_parentage)) {\n arrRemove(_parentage, parent);\n }\n }\n\n /**\n * Removes a finalizer from this subscription that was previously added with the {@link #add} method.\n *\n * Note that `Subscription` instances, when unsubscribed, will automatically remove themselves\n * from every other `Subscription` they have been added to. This means that using the `remove` method\n * is not a common thing and should be used thoughtfully.\n *\n * If you add the same finalizer instance of a function or an unsubscribable object to a `Subscription` instance\n * more than once, you will need to call `remove` the same number of times to remove all instances.\n *\n * All finalizer instances are removed to free up memory upon unsubscription.\n *\n * @param teardown The finalizer to remove from this subscription\n */\n remove(teardown: Exclude): void {\n const { _finalizers } = this;\n _finalizers && arrRemove(_finalizers, teardown);\n\n if (teardown instanceof Subscription) {\n teardown._removeParent(this);\n }\n }\n}\n\nexport const EMPTY_SUBSCRIPTION = Subscription.EMPTY;\n\nexport function isSubscription(value: any): value is Subscription {\n return (\n value instanceof Subscription ||\n (value && 'closed' in value && isFunction(value.remove) && isFunction(value.add) && isFunction(value.unsubscribe))\n );\n}\n\nfunction execFinalizer(finalizer: Unsubscribable | (() => void)) {\n if (isFunction(finalizer)) {\n finalizer();\n } else {\n finalizer.unsubscribe();\n }\n}\n", "import { Subscriber } from './Subscriber';\nimport { ObservableNotification } from './types';\n\n/**\n * The {@link GlobalConfig} object for RxJS. It is used to configure things\n * like how to react on unhandled errors.\n */\nexport const config: GlobalConfig = {\n onUnhandledError: null,\n onStoppedNotification: null,\n Promise: undefined,\n useDeprecatedSynchronousErrorHandling: false,\n useDeprecatedNextContext: false,\n};\n\n/**\n * The global configuration object for RxJS, used to configure things\n * like how to react on unhandled errors. Accessible via {@link config}\n * object.\n */\nexport interface GlobalConfig {\n /**\n * A registration point for unhandled errors from RxJS. These are errors that\n * cannot were not handled by consuming code in the usual subscription path. For\n * example, if you have this configured, and you subscribe to an observable without\n * providing an error handler, errors from that subscription will end up here. This\n * will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onUnhandledError: ((err: any) => void) | null;\n\n /**\n * A registration point for notifications that cannot be sent to subscribers because they\n * have completed, errored or have been explicitly unsubscribed. By default, next, complete\n * and error notifications sent to stopped subscribers are noops. However, sometimes callers\n * might want a different behavior. For example, with sources that attempt to report errors\n * to stopped subscribers, a caller can configure RxJS to throw an unhandled error instead.\n * This will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onStoppedNotification: ((notification: ObservableNotification, subscriber: Subscriber) => void) | null;\n\n /**\n * The promise constructor used by default for {@link Observable#toPromise toPromise} and {@link Observable#forEach forEach}\n * methods.\n *\n * @deprecated As of version 8, RxJS will no longer support this sort of injection of a\n * Promise constructor. If you need a Promise implementation other than native promises,\n * please polyfill/patch Promise as you see appropriate. Will be removed in v8.\n */\n Promise?: PromiseConstructorLike;\n\n /**\n * If true, turns on synchronous error rethrowing, which is a deprecated behavior\n * in v6 and higher. This behavior enables bad patterns like wrapping a subscribe\n * call in a try/catch block. It also enables producer interference, a nasty bug\n * where a multicast can be broken for all observers by a downstream consumer with\n * an unhandled error. DO NOT USE THIS FLAG UNLESS IT'S NEEDED TO BUY TIME\n * FOR MIGRATION REASONS.\n *\n * @deprecated As of version 8, RxJS will no longer support synchronous throwing\n * of unhandled errors. All errors will be thrown on a separate call stack to prevent bad\n * behaviors described above. Will be removed in v8.\n */\n useDeprecatedSynchronousErrorHandling: boolean;\n\n /**\n * If true, enables an as-of-yet undocumented feature from v5: The ability to access\n * `unsubscribe()` via `this` context in `next` functions created in observers passed\n * to `subscribe`.\n *\n * This is being removed because the performance was severely problematic, and it could also cause\n * issues when types other than POJOs are passed to subscribe as subscribers, as they will likely have\n * their `this` context overwritten.\n *\n * @deprecated As of version 8, RxJS will no longer support altering the\n * context of next functions provided as part of an observer to Subscribe. Instead,\n * you will have access to a subscription or a signal or token that will allow you to do things like\n * unsubscribe and test closed status. Will be removed in v8.\n */\n useDeprecatedNextContext: boolean;\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetTimeoutFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearTimeoutFunction = (handle: TimerHandle) => void;\n\ninterface TimeoutProvider {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n delegate:\n | {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n }\n | undefined;\n}\n\nexport const timeoutProvider: TimeoutProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setTimeout(handler: () => void, timeout?: number, ...args) {\n const { delegate } = timeoutProvider;\n if (delegate?.setTimeout) {\n return delegate.setTimeout(handler, timeout, ...args);\n }\n return setTimeout(handler, timeout, ...args);\n },\n clearTimeout(handle) {\n const { delegate } = timeoutProvider;\n return (delegate?.clearTimeout || clearTimeout)(handle as any);\n },\n delegate: undefined,\n};\n", "import { config } from '../config';\nimport { timeoutProvider } from '../scheduler/timeoutProvider';\n\n/**\n * Handles an error on another job either with the user-configured {@link onUnhandledError},\n * or by throwing it on that new job so it can be picked up by `window.onerror`, `process.on('error')`, etc.\n *\n * This should be called whenever there is an error that is out-of-band with the subscription\n * or when an error hits a terminal boundary of the subscription and no error handler was provided.\n *\n * @param err the error to report\n */\nexport function reportUnhandledError(err: any) {\n timeoutProvider.setTimeout(() => {\n const { onUnhandledError } = config;\n if (onUnhandledError) {\n // Execute the user-configured error handler.\n onUnhandledError(err);\n } else {\n // Throw so it is picked up by the runtime's uncaught error mechanism.\n throw err;\n }\n });\n}\n", "/* tslint:disable:no-empty */\nexport function noop() { }\n", "import { CompleteNotification, NextNotification, ErrorNotification } from './types';\n\n/**\n * A completion object optimized for memory use and created to be the\n * same \"shape\" as other notifications in v8.\n * @internal\n */\nexport const COMPLETE_NOTIFICATION = (() => createNotification('C', undefined, undefined) as CompleteNotification)();\n\n/**\n * Internal use only. Creates an optimized error notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function errorNotification(error: any): ErrorNotification {\n return createNotification('E', undefined, error) as any;\n}\n\n/**\n * Internal use only. Creates an optimized next notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function nextNotification(value: T) {\n return createNotification('N', value, undefined) as NextNotification;\n}\n\n/**\n * Ensures that all notifications created internally have the same \"shape\" in v8.\n *\n * TODO: This is only exported to support a crazy legacy test in `groupBy`.\n * @internal\n */\nexport function createNotification(kind: 'N' | 'E' | 'C', value: any, error: any) {\n return {\n kind,\n value,\n error,\n };\n}\n", "import { config } from '../config';\n\nlet context: { errorThrown: boolean; error: any } | null = null;\n\n/**\n * Handles dealing with errors for super-gross mode. Creates a context, in which\n * any synchronously thrown errors will be passed to {@link captureError}. Which\n * will record the error such that it will be rethrown after the call back is complete.\n * TODO: Remove in v8\n * @param cb An immediately executed function.\n */\nexport function errorContext(cb: () => void) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n const isRoot = !context;\n if (isRoot) {\n context = { errorThrown: false, error: null };\n }\n cb();\n if (isRoot) {\n const { errorThrown, error } = context!;\n context = null;\n if (errorThrown) {\n throw error;\n }\n }\n } else {\n // This is the general non-deprecated path for everyone that\n // isn't crazy enough to use super-gross mode (useDeprecatedSynchronousErrorHandling)\n cb();\n }\n}\n\n/**\n * Captures errors only in super-gross mode.\n * @param err the error to capture\n */\nexport function captureError(err: any) {\n if (config.useDeprecatedSynchronousErrorHandling && context) {\n context.errorThrown = true;\n context.error = err;\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { Observer, ObservableNotification } from './types';\nimport { isSubscription, Subscription } from './Subscription';\nimport { config } from './config';\nimport { reportUnhandledError } from './util/reportUnhandledError';\nimport { noop } from './util/noop';\nimport { nextNotification, errorNotification, COMPLETE_NOTIFICATION } from './NotificationFactories';\nimport { timeoutProvider } from './scheduler/timeoutProvider';\nimport { captureError } from './util/errorContext';\n\n/**\n * Implements the {@link Observer} interface and extends the\n * {@link Subscription} class. While the {@link Observer} is the public API for\n * consuming the values of an {@link Observable}, all Observers get converted to\n * a Subscriber, in order to provide Subscription-like capabilities such as\n * `unsubscribe`. Subscriber is a common type in RxJS, and crucial for\n * implementing operators, but it is rarely used as a public API.\n *\n * @class Subscriber\n */\nexport class Subscriber extends Subscription implements Observer {\n /**\n * A static factory for a Subscriber, given a (potentially partial) definition\n * of an Observer.\n * @param next The `next` callback of an Observer.\n * @param error The `error` callback of an\n * Observer.\n * @param complete The `complete` callback of an\n * Observer.\n * @return A Subscriber wrapping the (partially defined)\n * Observer represented by the given arguments.\n * @nocollapse\n * @deprecated Do not use. Will be removed in v8. There is no replacement for this\n * method, and there is no reason to be creating instances of `Subscriber` directly.\n * If you have a specific use case, please file an issue.\n */\n static create(next?: (x?: T) => void, error?: (e?: any) => void, complete?: () => void): Subscriber {\n return new SafeSubscriber(next, error, complete);\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected isStopped: boolean = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected destination: Subscriber | Observer; // this `any` is the escape hatch to erase extra type param (e.g. R)\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * There is no reason to directly create an instance of Subscriber. This type is exported for typings reasons.\n */\n constructor(destination?: Subscriber | Observer) {\n super();\n if (destination) {\n this.destination = destination;\n // Automatically chain subscriptions together here.\n // if destination is a Subscription, then it is a Subscriber.\n if (isSubscription(destination)) {\n destination.add(this);\n }\n } else {\n this.destination = EMPTY_OBSERVER;\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `next` from\n * the Observable, with a value. The Observable may call this method 0 or more\n * times.\n * @param {T} [value] The `next` value.\n * @return {void}\n */\n next(value?: T): void {\n if (this.isStopped) {\n handleStoppedNotification(nextNotification(value), this);\n } else {\n this._next(value!);\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `error` from\n * the Observable, with an attached `Error`. Notifies the Observer that\n * the Observable has experienced an error condition.\n * @param {any} [err] The `error` exception.\n * @return {void}\n */\n error(err?: any): void {\n if (this.isStopped) {\n handleStoppedNotification(errorNotification(err), this);\n } else {\n this.isStopped = true;\n this._error(err);\n }\n }\n\n /**\n * The {@link Observer} callback to receive a valueless notification of type\n * `complete` from the Observable. Notifies the Observer that the Observable\n * has finished sending push-based notifications.\n * @return {void}\n */\n complete(): void {\n if (this.isStopped) {\n handleStoppedNotification(COMPLETE_NOTIFICATION, this);\n } else {\n this.isStopped = true;\n this._complete();\n }\n }\n\n unsubscribe(): void {\n if (!this.closed) {\n this.isStopped = true;\n super.unsubscribe();\n this.destination = null!;\n }\n }\n\n protected _next(value: T): void {\n this.destination.next(value);\n }\n\n protected _error(err: any): void {\n try {\n this.destination.error(err);\n } finally {\n this.unsubscribe();\n }\n }\n\n protected _complete(): void {\n try {\n this.destination.complete();\n } finally {\n this.unsubscribe();\n }\n }\n}\n\n/**\n * This bind is captured here because we want to be able to have\n * compatibility with monoid libraries that tend to use a method named\n * `bind`. In particular, a library called Monio requires this.\n */\nconst _bind = Function.prototype.bind;\n\nfunction bind any>(fn: Fn, thisArg: any): Fn {\n return _bind.call(fn, thisArg);\n}\n\n/**\n * Internal optimization only, DO NOT EXPOSE.\n * @internal\n */\nclass ConsumerObserver implements Observer {\n constructor(private partialObserver: Partial>) {}\n\n next(value: T): void {\n const { partialObserver } = this;\n if (partialObserver.next) {\n try {\n partialObserver.next(value);\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n\n error(err: any): void {\n const { partialObserver } = this;\n if (partialObserver.error) {\n try {\n partialObserver.error(err);\n } catch (error) {\n handleUnhandledError(error);\n }\n } else {\n handleUnhandledError(err);\n }\n }\n\n complete(): void {\n const { partialObserver } = this;\n if (partialObserver.complete) {\n try {\n partialObserver.complete();\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n}\n\nexport class SafeSubscriber extends Subscriber {\n constructor(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((e?: any) => void) | null,\n complete?: (() => void) | null\n ) {\n super();\n\n let partialObserver: Partial>;\n if (isFunction(observerOrNext) || !observerOrNext) {\n // The first argument is a function, not an observer. The next\n // two arguments *could* be observers, or they could be empty.\n partialObserver = {\n next: (observerOrNext ?? undefined) as (((value: T) => void) | undefined),\n error: error ?? undefined,\n complete: complete ?? undefined,\n };\n } else {\n // The first argument is a partial observer.\n let context: any;\n if (this && config.useDeprecatedNextContext) {\n // This is a deprecated path that made `this.unsubscribe()` available in\n // next handler functions passed to subscribe. This only exists behind a flag\n // now, as it is *very* slow.\n context = Object.create(observerOrNext);\n context.unsubscribe = () => this.unsubscribe();\n partialObserver = {\n next: observerOrNext.next && bind(observerOrNext.next, context),\n error: observerOrNext.error && bind(observerOrNext.error, context),\n complete: observerOrNext.complete && bind(observerOrNext.complete, context),\n };\n } else {\n // The \"normal\" path. Just use the partial observer directly.\n partialObserver = observerOrNext;\n }\n }\n\n // Wrap the partial observer to ensure it's a full observer, and\n // make sure proper error handling is accounted for.\n this.destination = new ConsumerObserver(partialObserver);\n }\n}\n\nfunction handleUnhandledError(error: any) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n captureError(error);\n } else {\n // Ideal path, we report this as an unhandled error,\n // which is thrown on a new call stack.\n reportUnhandledError(error);\n }\n}\n\n/**\n * An error handler used when no error handler was supplied\n * to the SafeSubscriber -- meaning no error handler was supplied\n * do the `subscribe` call on our observable.\n * @param err The error to handle\n */\nfunction defaultErrorHandler(err: any) {\n throw err;\n}\n\n/**\n * A handler for notifications that cannot be sent to a stopped subscriber.\n * @param notification The notification being sent\n * @param subscriber The stopped subscriber\n */\nfunction handleStoppedNotification(notification: ObservableNotification, subscriber: Subscriber) {\n const { onStoppedNotification } = config;\n onStoppedNotification && timeoutProvider.setTimeout(() => onStoppedNotification(notification, subscriber));\n}\n\n/**\n * The observer used as a stub for subscriptions where the user did not\n * pass any arguments to `subscribe`. Comes with the default error handling\n * behavior.\n */\nexport const EMPTY_OBSERVER: Readonly> & { closed: true } = {\n closed: true,\n next: noop,\n error: defaultErrorHandler,\n complete: noop,\n};\n", "/**\n * Symbol.observable or a string \"@@observable\". Used for interop\n *\n * @deprecated We will no longer be exporting this symbol in upcoming versions of RxJS.\n * Instead polyfill and use Symbol.observable directly *or* use https://www.npmjs.com/package/symbol-observable\n */\nexport const observable: string | symbol = (() => (typeof Symbol === 'function' && Symbol.observable) || '@@observable')();\n", "/**\n * This function takes one parameter and just returns it. Simply put,\n * this is like `(x: T): T => x`.\n *\n * ## Examples\n *\n * This is useful in some cases when using things like `mergeMap`\n *\n * ```ts\n * import { interval, take, map, range, mergeMap, identity } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(5));\n *\n * const result$ = source$.pipe(\n * map(i => range(i)),\n * mergeMap(identity) // same as mergeMap(x => x)\n * );\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * Or when you want to selectively apply an operator\n *\n * ```ts\n * import { interval, take, identity } from 'rxjs';\n *\n * const shouldLimit = () => Math.random() < 0.5;\n *\n * const source$ = interval(1000);\n *\n * const result$ = source$.pipe(shouldLimit() ? take(5) : identity);\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * @param x Any value that is returned by this function\n * @returns The value passed as the first parameter to this function\n */\nexport function identity(x: T): T {\n return x;\n}\n", "import { identity } from './identity';\nimport { UnaryFunction } from '../types';\n\nexport function pipe(): typeof identity;\nexport function pipe(fn1: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction, fn3: UnaryFunction): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction,\n ...fns: UnaryFunction[]\n): UnaryFunction;\n\n/**\n * pipe() can be called on one or more functions, each of which can take one argument (\"UnaryFunction\")\n * and uses it to return a value.\n * It returns a function that takes one argument, passes it to the first UnaryFunction, and then\n * passes the result to the next one, passes that result to the next one, and so on. \n */\nexport function pipe(...fns: Array>): UnaryFunction {\n return pipeFromArray(fns);\n}\n\n/** @internal */\nexport function pipeFromArray(fns: Array>): UnaryFunction {\n if (fns.length === 0) {\n return identity as UnaryFunction;\n }\n\n if (fns.length === 1) {\n return fns[0];\n }\n\n return function piped(input: T): R {\n return fns.reduce((prev: any, fn: UnaryFunction) => fn(prev), input as any);\n };\n}\n", "import { Operator } from './Operator';\nimport { SafeSubscriber, Subscriber } from './Subscriber';\nimport { isSubscription, Subscription } from './Subscription';\nimport { TeardownLogic, OperatorFunction, Subscribable, Observer } from './types';\nimport { observable as Symbol_observable } from './symbol/observable';\nimport { pipeFromArray } from './util/pipe';\nimport { config } from './config';\nimport { isFunction } from './util/isFunction';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A representation of any set of values over any amount of time. This is the most basic building block\n * of RxJS.\n *\n * @class Observable\n */\nexport class Observable implements Subscribable {\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n source: Observable | undefined;\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n operator: Operator | undefined;\n\n /**\n * @constructor\n * @param {Function} subscribe the function that is called when the Observable is\n * initially subscribed to. This function is given a Subscriber, to which new values\n * can be `next`ed, or an `error` method can be called to raise an error, or\n * `complete` can be called to notify of a successful completion.\n */\n constructor(subscribe?: (this: Observable, subscriber: Subscriber) => TeardownLogic) {\n if (subscribe) {\n this._subscribe = subscribe;\n }\n }\n\n // HACK: Since TypeScript inherits static properties too, we have to\n // fight against TypeScript here so Subject can have a different static create signature\n /**\n * Creates a new Observable by calling the Observable constructor\n * @owner Observable\n * @method create\n * @param {Function} subscribe? the subscriber function to be passed to the Observable constructor\n * @return {Observable} a new observable\n * @nocollapse\n * @deprecated Use `new Observable()` instead. Will be removed in v8.\n */\n static create: (...args: any[]) => any = (subscribe?: (subscriber: Subscriber) => TeardownLogic) => {\n return new Observable(subscribe);\n };\n\n /**\n * Creates a new Observable, with this Observable instance as the source, and the passed\n * operator defined as the new observable's operator.\n * @method lift\n * @param operator the operator defining the operation to take on the observable\n * @return a new observable with the Operator applied\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * If you have implemented an operator using `lift`, it is recommended that you create an\n * operator by simply returning `new Observable()` directly. See \"Creating new operators from\n * scratch\" section here: https://rxjs.dev/guide/operators\n */\n lift(operator?: Operator): Observable {\n const observable = new Observable();\n observable.source = this;\n observable.operator = operator;\n return observable;\n }\n\n subscribe(observerOrNext?: Partial> | ((value: T) => void)): Subscription;\n /** @deprecated Instead of passing separate callback arguments, use an observer argument. Signatures taking separate callback arguments will be removed in v8. Details: https://rxjs.dev/deprecations/subscribe-arguments */\n subscribe(next?: ((value: T) => void) | null, error?: ((error: any) => void) | null, complete?: (() => void) | null): Subscription;\n /**\n * Invokes an execution of an Observable and registers Observer handlers for notifications it will emit.\n *\n * Use it when you have all these Observables, but still nothing is happening.\n *\n * `subscribe` is not a regular operator, but a method that calls Observable's internal `subscribe` function. It\n * might be for example a function that you passed to Observable's constructor, but most of the time it is\n * a library implementation, which defines what will be emitted by an Observable, and when it be will emitted. This means\n * that calling `subscribe` is actually the moment when Observable starts its work, not when it is created, as it is often\n * the thought.\n *\n * Apart from starting the execution of an Observable, this method allows you to listen for values\n * that an Observable emits, as well as for when it completes or errors. You can achieve this in two\n * of the following ways.\n *\n * The first way is creating an object that implements {@link Observer} interface. It should have methods\n * defined by that interface, but note that it should be just a regular JavaScript object, which you can create\n * yourself in any way you want (ES6 class, classic function constructor, object literal etc.). In particular, do\n * not attempt to use any RxJS implementation details to create Observers - you don't need them. Remember also\n * that your object does not have to implement all methods. If you find yourself creating a method that doesn't\n * do anything, you can simply omit it. Note however, if the `error` method is not provided and an error happens,\n * it will be thrown asynchronously. Errors thrown asynchronously cannot be caught using `try`/`catch`. Instead,\n * use the {@link onUnhandledError} configuration option or use a runtime handler (like `window.onerror` or\n * `process.on('error)`) to be notified of unhandled errors. Because of this, it's recommended that you provide\n * an `error` method to avoid missing thrown errors.\n *\n * The second way is to give up on Observer object altogether and simply provide callback functions in place of its methods.\n * This means you can provide three functions as arguments to `subscribe`, where the first function is equivalent\n * of a `next` method, the second of an `error` method and the third of a `complete` method. Just as in case of an Observer,\n * if you do not need to listen for something, you can omit a function by passing `undefined` or `null`,\n * since `subscribe` recognizes these functions by where they were placed in function call. When it comes\n * to the `error` function, as with an Observer, if not provided, errors emitted by an Observable will be thrown asynchronously.\n *\n * You can, however, subscribe with no parameters at all. This may be the case where you're not interested in terminal events\n * and you also handled emissions internally by using operators (e.g. using `tap`).\n *\n * Whichever style of calling `subscribe` you use, in both cases it returns a Subscription object.\n * This object allows you to call `unsubscribe` on it, which in turn will stop the work that an Observable does and will clean\n * up all resources that an Observable used. Note that cancelling a subscription will not call `complete` callback\n * provided to `subscribe` function, which is reserved for a regular completion signal that comes from an Observable.\n *\n * Remember that callbacks provided to `subscribe` are not guaranteed to be called asynchronously.\n * It is an Observable itself that decides when these functions will be called. For example {@link of}\n * by default emits all its values synchronously. Always check documentation for how given Observable\n * will behave when subscribed and if its default behavior can be modified with a `scheduler`.\n *\n * #### Examples\n *\n * Subscribe with an {@link guide/observer Observer}\n *\n * ```ts\n * import { of } from 'rxjs';\n *\n * const sumObserver = {\n * sum: 0,\n * next(value) {\n * console.log('Adding: ' + value);\n * this.sum = this.sum + value;\n * },\n * error() {\n * // We actually could just remove this method,\n * // since we do not really care about errors right now.\n * },\n * complete() {\n * console.log('Sum equals: ' + this.sum);\n * }\n * };\n *\n * of(1, 2, 3) // Synchronously emits 1, 2, 3 and then completes.\n * .subscribe(sumObserver);\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Subscribe with functions ({@link deprecations/subscribe-arguments deprecated})\n *\n * ```ts\n * import { of } from 'rxjs'\n *\n * let sum = 0;\n *\n * of(1, 2, 3).subscribe(\n * value => {\n * console.log('Adding: ' + value);\n * sum = sum + value;\n * },\n * undefined,\n * () => console.log('Sum equals: ' + sum)\n * );\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Cancel a subscription\n *\n * ```ts\n * import { interval } from 'rxjs';\n *\n * const subscription = interval(1000).subscribe({\n * next(num) {\n * console.log(num)\n * },\n * complete() {\n * // Will not be called, even when cancelling subscription.\n * console.log('completed!');\n * }\n * });\n *\n * setTimeout(() => {\n * subscription.unsubscribe();\n * console.log('unsubscribed!');\n * }, 2500);\n *\n * // Logs:\n * // 0 after 1s\n * // 1 after 2s\n * // 'unsubscribed!' after 2.5s\n * ```\n *\n * @param {Observer|Function} observerOrNext (optional) Either an observer with methods to be called,\n * or the first of three possible handlers, which is the handler for each value emitted from the subscribed\n * Observable.\n * @param {Function} error (optional) A handler for a terminal event resulting from an error. If no error handler is provided,\n * the error will be thrown asynchronously as unhandled.\n * @param {Function} complete (optional) A handler for a terminal event resulting from successful completion.\n * @return {Subscription} a subscription reference to the registered handlers\n * @method subscribe\n */\n subscribe(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((error: any) => void) | null,\n complete?: (() => void) | null\n ): Subscription {\n const subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);\n\n errorContext(() => {\n const { operator, source } = this;\n subscriber.add(\n operator\n ? // We're dealing with a subscription in the\n // operator chain to one of our lifted operators.\n operator.call(subscriber, source)\n : source\n ? // If `source` has a value, but `operator` does not, something that\n // had intimate knowledge of our API, like our `Subject`, must have\n // set it. We're going to just call `_subscribe` directly.\n this._subscribe(subscriber)\n : // In all other cases, we're likely wrapping a user-provided initializer\n // function, so we need to catch errors and handle them appropriately.\n this._trySubscribe(subscriber)\n );\n });\n\n return subscriber;\n }\n\n /** @internal */\n protected _trySubscribe(sink: Subscriber): TeardownLogic {\n try {\n return this._subscribe(sink);\n } catch (err) {\n // We don't need to return anything in this case,\n // because it's just going to try to `add()` to a subscription\n // above.\n sink.error(err);\n }\n }\n\n /**\n * Used as a NON-CANCELLABLE means of subscribing to an observable, for use with\n * APIs that expect promises, like `async/await`. You cannot unsubscribe from this.\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * #### Example\n *\n * ```ts\n * import { interval, take } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(4));\n *\n * async function getTotal() {\n * let total = 0;\n *\n * await source$.forEach(value => {\n * total += value;\n * console.log('observable -> ' + value);\n * });\n *\n * return total;\n * }\n *\n * getTotal().then(\n * total => console.log('Total: ' + total)\n * );\n *\n * // Expected:\n * // 'observable -> 0'\n * // 'observable -> 1'\n * // 'observable -> 2'\n * // 'observable -> 3'\n * // 'Total: 6'\n * ```\n *\n * @param next a handler for each value emitted by the observable\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n */\n forEach(next: (value: T) => void): Promise;\n\n /**\n * @param next a handler for each value emitted by the observable\n * @param promiseCtor a constructor function used to instantiate the Promise\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n * @deprecated Passing a Promise constructor will no longer be available\n * in upcoming versions of RxJS. This is because it adds weight to the library, for very\n * little benefit. If you need this functionality, it is recommended that you either\n * polyfill Promise, or you create an adapter to convert the returned native promise\n * to whatever promise implementation you wanted. Will be removed in v8.\n */\n forEach(next: (value: T) => void, promiseCtor: PromiseConstructorLike): Promise;\n\n forEach(next: (value: T) => void, promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n const subscriber = new SafeSubscriber({\n next: (value) => {\n try {\n next(value);\n } catch (err) {\n reject(err);\n subscriber.unsubscribe();\n }\n },\n error: reject,\n complete: resolve,\n });\n this.subscribe(subscriber);\n }) as Promise;\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): TeardownLogic {\n return this.source?.subscribe(subscriber);\n }\n\n /**\n * An interop point defined by the es7-observable spec https://github.com/zenparsing/es-observable\n * @method Symbol.observable\n * @return {Observable} this instance of the observable\n */\n [Symbol_observable]() {\n return this;\n }\n\n /* tslint:disable:max-line-length */\n pipe(): Observable;\n pipe(op1: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction, op3: OperatorFunction): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction,\n ...operations: OperatorFunction[]\n ): Observable;\n /* tslint:enable:max-line-length */\n\n /**\n * Used to stitch together functional operators into a chain.\n * @method pipe\n * @return {Observable} the Observable result of all of the operators having\n * been called in the order they were passed in.\n *\n * ## Example\n *\n * ```ts\n * import { interval, filter, map, scan } from 'rxjs';\n *\n * interval(1000)\n * .pipe(\n * filter(x => x % 2 === 0),\n * map(x => x + x),\n * scan((acc, x) => acc + x)\n * )\n * .subscribe(x => console.log(x));\n * ```\n */\n pipe(...operations: OperatorFunction[]): Observable {\n return pipeFromArray(operations)(this);\n }\n\n /* tslint:disable:max-line-length */\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: typeof Promise): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: PromiseConstructorLike): Promise;\n /* tslint:enable:max-line-length */\n\n /**\n * Subscribe to this Observable and get a Promise resolving on\n * `complete` with the last emission (if any).\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * @method toPromise\n * @param [promiseCtor] a constructor function used to instantiate\n * the Promise\n * @return A Promise that resolves with the last value emit, or\n * rejects on an error. If there were no emissions, Promise\n * resolves with undefined.\n * @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise\n */\n toPromise(promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n let value: T | undefined;\n this.subscribe(\n (x: T) => (value = x),\n (err: any) => reject(err),\n () => resolve(value)\n );\n }) as Promise;\n }\n}\n\n/**\n * Decides between a passed promise constructor from consuming code,\n * A default configured promise constructor, and the native promise\n * constructor and returns it. If nothing can be found, it will throw\n * an error.\n * @param promiseCtor The optional promise constructor to passed by consuming code\n */\nfunction getPromiseCtor(promiseCtor: PromiseConstructorLike | undefined) {\n return promiseCtor ?? config.Promise ?? Promise;\n}\n\nfunction isObserver(value: any): value is Observer {\n return value && isFunction(value.next) && isFunction(value.error) && isFunction(value.complete);\n}\n\nfunction isSubscriber(value: any): value is Subscriber {\n return (value && value instanceof Subscriber) || (isObserver(value) && isSubscription(value));\n}\n", "import { Observable } from '../Observable';\nimport { Subscriber } from '../Subscriber';\nimport { OperatorFunction } from '../types';\nimport { isFunction } from './isFunction';\n\n/**\n * Used to determine if an object is an Observable with a lift function.\n */\nexport function hasLift(source: any): source is { lift: InstanceType['lift'] } {\n return isFunction(source?.lift);\n}\n\n/**\n * Creates an `OperatorFunction`. Used to define operators throughout the library in a concise way.\n * @param init The logic to connect the liftedSource to the subscriber at the moment of subscription.\n */\nexport function operate(\n init: (liftedSource: Observable, subscriber: Subscriber) => (() => void) | void\n): OperatorFunction {\n return (source: Observable) => {\n if (hasLift(source)) {\n return source.lift(function (this: Subscriber, liftedSource: Observable) {\n try {\n return init(liftedSource, this);\n } catch (err) {\n this.error(err);\n }\n });\n }\n throw new TypeError('Unable to lift unknown Observable type');\n };\n}\n", "import { Subscriber } from '../Subscriber';\n\n/**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional teardown logic here. This will only be called on teardown if the\n * subscriber itself is not already closed. This is called after all other teardown logic is executed.\n */\nexport function createOperatorSubscriber(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n onFinalize?: () => void\n): Subscriber {\n return new OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize);\n}\n\n/**\n * A generic helper for allowing operators to be created with a Subscriber and\n * use closures to capture necessary state from the operator function itself.\n */\nexport class OperatorSubscriber extends Subscriber {\n /**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional finalization logic here. This will only be called on finalization if the\n * subscriber itself is not already closed. This is called after all other finalization logic is executed.\n * @param shouldUnsubscribe An optional check to see if an unsubscribe call should truly unsubscribe.\n * NOTE: This currently **ONLY** exists to support the strange behavior of {@link groupBy}, where unsubscription\n * to the resulting observable does not actually disconnect from the source if there are active subscriptions\n * to any grouped observable. (DO NOT EXPOSE OR USE EXTERNALLY!!!)\n */\n constructor(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n private onFinalize?: () => void,\n private shouldUnsubscribe?: () => boolean\n ) {\n // It's important - for performance reasons - that all of this class's\n // members are initialized and that they are always initialized in the same\n // order. This will ensure that all OperatorSubscriber instances have the\n // same hidden class in V8. This, in turn, will help keep the number of\n // hidden classes involved in property accesses within the base class as\n // low as possible. If the number of hidden classes involved exceeds four,\n // the property accesses will become megamorphic and performance penalties\n // will be incurred - i.e. inline caches won't be used.\n //\n // The reasons for ensuring all instances have the same hidden class are\n // further discussed in this blog post from Benedikt Meurer:\n // https://benediktmeurer.de/2018/03/23/impact-of-polymorphism-on-component-based-frameworks-like-react/\n super(destination);\n this._next = onNext\n ? function (this: OperatorSubscriber, value: T) {\n try {\n onNext(value);\n } catch (err) {\n destination.error(err);\n }\n }\n : super._next;\n this._error = onError\n ? function (this: OperatorSubscriber, err: any) {\n try {\n onError(err);\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._error;\n this._complete = onComplete\n ? function (this: OperatorSubscriber) {\n try {\n onComplete();\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._complete;\n }\n\n unsubscribe() {\n if (!this.shouldUnsubscribe || this.shouldUnsubscribe()) {\n const { closed } = this;\n super.unsubscribe();\n // Execute additional teardown if we have any and we didn't already do so.\n !closed && this.onFinalize?.();\n }\n }\n}\n", "import { Subscription } from '../Subscription';\n\ninterface AnimationFrameProvider {\n schedule(callback: FrameRequestCallback): Subscription;\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n delegate:\n | {\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n }\n | undefined;\n}\n\nexport const animationFrameProvider: AnimationFrameProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n schedule(callback) {\n let request = requestAnimationFrame;\n let cancel: typeof cancelAnimationFrame | undefined = cancelAnimationFrame;\n const { delegate } = animationFrameProvider;\n if (delegate) {\n request = delegate.requestAnimationFrame;\n cancel = delegate.cancelAnimationFrame;\n }\n const handle = request((timestamp) => {\n // Clear the cancel function. The request has been fulfilled, so\n // attempting to cancel the request upon unsubscription would be\n // pointless.\n cancel = undefined;\n callback(timestamp);\n });\n return new Subscription(() => cancel?.(handle));\n },\n requestAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.requestAnimationFrame || requestAnimationFrame)(...args);\n },\n cancelAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.cancelAnimationFrame || cancelAnimationFrame)(...args);\n },\n delegate: undefined,\n};\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface ObjectUnsubscribedError extends Error {}\n\nexport interface ObjectUnsubscribedErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (): ObjectUnsubscribedError;\n}\n\n/**\n * An error thrown when an action is invalid because the object has been\n * unsubscribed.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n *\n * @class ObjectUnsubscribedError\n */\nexport const ObjectUnsubscribedError: ObjectUnsubscribedErrorCtor = createErrorClass(\n (_super) =>\n function ObjectUnsubscribedErrorImpl(this: any) {\n _super(this);\n this.name = 'ObjectUnsubscribedError';\n this.message = 'object unsubscribed';\n }\n);\n", "import { Operator } from './Operator';\nimport { Observable } from './Observable';\nimport { Subscriber } from './Subscriber';\nimport { Subscription, EMPTY_SUBSCRIPTION } from './Subscription';\nimport { Observer, SubscriptionLike, TeardownLogic } from './types';\nimport { ObjectUnsubscribedError } from './util/ObjectUnsubscribedError';\nimport { arrRemove } from './util/arrRemove';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A Subject is a special type of Observable that allows values to be\n * multicasted to many Observers. Subjects are like EventEmitters.\n *\n * Every Subject is an Observable and an Observer. You can subscribe to a\n * Subject, and you can call next to feed values as well as error and complete.\n */\nexport class Subject extends Observable implements SubscriptionLike {\n closed = false;\n\n private currentObservers: Observer[] | null = null;\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n observers: Observer[] = [];\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n isStopped = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n hasError = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n thrownError: any = null;\n\n /**\n * Creates a \"subject\" by basically gluing an observer to an observable.\n *\n * @nocollapse\n * @deprecated Recommended you do not use. Will be removed at some point in the future. Plans for replacement still under discussion.\n */\n static create: (...args: any[]) => any = (destination: Observer, source: Observable): AnonymousSubject => {\n return new AnonymousSubject(destination, source);\n };\n\n constructor() {\n // NOTE: This must be here to obscure Observable's constructor.\n super();\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n lift(operator: Operator): Observable {\n const subject = new AnonymousSubject(this, this);\n subject.operator = operator as any;\n return subject as any;\n }\n\n /** @internal */\n protected _throwIfClosed() {\n if (this.closed) {\n throw new ObjectUnsubscribedError();\n }\n }\n\n next(value: T) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n if (!this.currentObservers) {\n this.currentObservers = Array.from(this.observers);\n }\n for (const observer of this.currentObservers) {\n observer.next(value);\n }\n }\n });\n }\n\n error(err: any) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.hasError = this.isStopped = true;\n this.thrownError = err;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.error(err);\n }\n }\n });\n }\n\n complete() {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.isStopped = true;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.complete();\n }\n }\n });\n }\n\n unsubscribe() {\n this.isStopped = this.closed = true;\n this.observers = this.currentObservers = null!;\n }\n\n get observed() {\n return this.observers?.length > 0;\n }\n\n /** @internal */\n protected _trySubscribe(subscriber: Subscriber): TeardownLogic {\n this._throwIfClosed();\n return super._trySubscribe(subscriber);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._checkFinalizedStatuses(subscriber);\n return this._innerSubscribe(subscriber);\n }\n\n /** @internal */\n protected _innerSubscribe(subscriber: Subscriber) {\n const { hasError, isStopped, observers } = this;\n if (hasError || isStopped) {\n return EMPTY_SUBSCRIPTION;\n }\n this.currentObservers = null;\n observers.push(subscriber);\n return new Subscription(() => {\n this.currentObservers = null;\n arrRemove(observers, subscriber);\n });\n }\n\n /** @internal */\n protected _checkFinalizedStatuses(subscriber: Subscriber) {\n const { hasError, thrownError, isStopped } = this;\n if (hasError) {\n subscriber.error(thrownError);\n } else if (isStopped) {\n subscriber.complete();\n }\n }\n\n /**\n * Creates a new Observable with this Subject as the source. You can do this\n * to create custom Observer-side logic of the Subject and conceal it from\n * code that uses the Observable.\n * @return {Observable} Observable that the Subject casts to\n */\n asObservable(): Observable {\n const observable: any = new Observable();\n observable.source = this;\n return observable;\n }\n}\n\n/**\n * @class AnonymousSubject\n */\nexport class AnonymousSubject extends Subject {\n constructor(\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n public destination?: Observer,\n source?: Observable\n ) {\n super();\n this.source = source;\n }\n\n next(value: T) {\n this.destination?.next?.(value);\n }\n\n error(err: any) {\n this.destination?.error?.(err);\n }\n\n complete() {\n this.destination?.complete?.();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n return this.source?.subscribe(subscriber) ?? EMPTY_SUBSCRIPTION;\n }\n}\n", "import { Subject } from './Subject';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\n\n/**\n * A variant of Subject that requires an initial value and emits its current\n * value whenever it is subscribed to.\n *\n * @class BehaviorSubject\n */\nexport class BehaviorSubject extends Subject {\n constructor(private _value: T) {\n super();\n }\n\n get value(): T {\n return this.getValue();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n const subscription = super._subscribe(subscriber);\n !subscription.closed && subscriber.next(this._value);\n return subscription;\n }\n\n getValue(): T {\n const { hasError, thrownError, _value } = this;\n if (hasError) {\n throw thrownError;\n }\n this._throwIfClosed();\n return _value;\n }\n\n next(value: T): void {\n super.next((this._value = value));\n }\n}\n", "import { TimestampProvider } from '../types';\n\ninterface DateTimestampProvider extends TimestampProvider {\n delegate: TimestampProvider | undefined;\n}\n\nexport const dateTimestampProvider: DateTimestampProvider = {\n now() {\n // Use the variable rather than `this` so that the function can be called\n // without being bound to the provider.\n return (dateTimestampProvider.delegate || Date).now();\n },\n delegate: undefined,\n};\n", "import { Subject } from './Subject';\nimport { TimestampProvider } from './types';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * A variant of {@link Subject} that \"replays\" old values to new subscribers by emitting them when they first subscribe.\n *\n * `ReplaySubject` has an internal buffer that will store a specified number of values that it has observed. Like `Subject`,\n * `ReplaySubject` \"observes\" values by having them passed to its `next` method. When it observes a value, it will store that\n * value for a time determined by the configuration of the `ReplaySubject`, as passed to its constructor.\n *\n * When a new subscriber subscribes to the `ReplaySubject` instance, it will synchronously emit all values in its buffer in\n * a First-In-First-Out (FIFO) manner. The `ReplaySubject` will also complete, if it has observed completion; and it will\n * error if it has observed an error.\n *\n * There are two main configuration items to be concerned with:\n *\n * 1. `bufferSize` - This will determine how many items are stored in the buffer, defaults to infinite.\n * 2. `windowTime` - The amount of time to hold a value in the buffer before removing it from the buffer.\n *\n * Both configurations may exist simultaneously. So if you would like to buffer a maximum of 3 values, as long as the values\n * are less than 2 seconds old, you could do so with a `new ReplaySubject(3, 2000)`.\n *\n * ### Differences with BehaviorSubject\n *\n * `BehaviorSubject` is similar to `new ReplaySubject(1)`, with a couple of exceptions:\n *\n * 1. `BehaviorSubject` comes \"primed\" with a single value upon construction.\n * 2. `ReplaySubject` will replay values, even after observing an error, where `BehaviorSubject` will not.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n * @see {@link shareReplay}\n */\nexport class ReplaySubject extends Subject {\n private _buffer: (T | number)[] = [];\n private _infiniteTimeWindow = true;\n\n /**\n * @param bufferSize The size of the buffer to replay on subscription\n * @param windowTime The amount of time the buffered items will stay buffered\n * @param timestampProvider An object with a `now()` method that provides the current timestamp. This is used to\n * calculate the amount of time something has been buffered.\n */\n constructor(\n private _bufferSize = Infinity,\n private _windowTime = Infinity,\n private _timestampProvider: TimestampProvider = dateTimestampProvider\n ) {\n super();\n this._infiniteTimeWindow = _windowTime === Infinity;\n this._bufferSize = Math.max(1, _bufferSize);\n this._windowTime = Math.max(1, _windowTime);\n }\n\n next(value: T): void {\n const { isStopped, _buffer, _infiniteTimeWindow, _timestampProvider, _windowTime } = this;\n if (!isStopped) {\n _buffer.push(value);\n !_infiniteTimeWindow && _buffer.push(_timestampProvider.now() + _windowTime);\n }\n this._trimBuffer();\n super.next(value);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._trimBuffer();\n\n const subscription = this._innerSubscribe(subscriber);\n\n const { _infiniteTimeWindow, _buffer } = this;\n // We use a copy here, so reentrant code does not mutate our array while we're\n // emitting it to a new subscriber.\n const copy = _buffer.slice();\n for (let i = 0; i < copy.length && !subscriber.closed; i += _infiniteTimeWindow ? 1 : 2) {\n subscriber.next(copy[i] as T);\n }\n\n this._checkFinalizedStatuses(subscriber);\n\n return subscription;\n }\n\n private _trimBuffer() {\n const { _bufferSize, _timestampProvider, _buffer, _infiniteTimeWindow } = this;\n // If we don't have an infinite buffer size, and we're over the length,\n // use splice to truncate the old buffer values off. Note that we have to\n // double the size for instances where we're not using an infinite time window\n // because we're storing the values and the timestamps in the same array.\n const adjustedBufferSize = (_infiniteTimeWindow ? 1 : 2) * _bufferSize;\n _bufferSize < Infinity && adjustedBufferSize < _buffer.length && _buffer.splice(0, _buffer.length - adjustedBufferSize);\n\n // Now, if we're not in an infinite time window, remove all values where the time is\n // older than what is allowed.\n if (!_infiniteTimeWindow) {\n const now = _timestampProvider.now();\n let last = 0;\n // Search the array for the first timestamp that isn't expired and\n // truncate the buffer up to that point.\n for (let i = 1; i < _buffer.length && (_buffer[i] as number) <= now; i += 2) {\n last = i;\n }\n last && _buffer.splice(0, last + 1);\n }\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Subscription } from '../Subscription';\nimport { SchedulerAction } from '../types';\n\n/**\n * A unit of work to be executed in a `scheduler`. An action is typically\n * created from within a {@link SchedulerLike} and an RxJS user does not need to concern\n * themselves about creating and manipulating an Action.\n *\n * ```ts\n * class Action extends Subscription {\n * new (scheduler: Scheduler, work: (state?: T) => void);\n * schedule(state?: T, delay: number = 0): Subscription;\n * }\n * ```\n *\n * @class Action\n */\nexport class Action extends Subscription {\n constructor(scheduler: Scheduler, work: (this: SchedulerAction, state?: T) => void) {\n super();\n }\n /**\n * Schedules this action on its parent {@link SchedulerLike} for execution. May be passed\n * some context object, `state`. May happen at some point in the future,\n * according to the `delay` parameter, if specified.\n * @param {T} [state] Some contextual data that the `work` function uses when\n * called by the Scheduler.\n * @param {number} [delay] Time to wait before executing the work, where the\n * time unit is implicit and defined by the Scheduler.\n * @return {void}\n */\n public schedule(state?: T, delay: number = 0): Subscription {\n return this;\n }\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetIntervalFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearIntervalFunction = (handle: TimerHandle) => void;\n\ninterface IntervalProvider {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n delegate:\n | {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n }\n | undefined;\n}\n\nexport const intervalProvider: IntervalProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setInterval(handler: () => void, timeout?: number, ...args) {\n const { delegate } = intervalProvider;\n if (delegate?.setInterval) {\n return delegate.setInterval(handler, timeout, ...args);\n }\n return setInterval(handler, timeout, ...args);\n },\n clearInterval(handle) {\n const { delegate } = intervalProvider;\n return (delegate?.clearInterval || clearInterval)(handle as any);\n },\n delegate: undefined,\n};\n", "import { Action } from './Action';\nimport { SchedulerAction } from '../types';\nimport { Subscription } from '../Subscription';\nimport { AsyncScheduler } from './AsyncScheduler';\nimport { intervalProvider } from './intervalProvider';\nimport { arrRemove } from '../util/arrRemove';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncAction extends Action {\n public id: TimerHandle | undefined;\n public state?: T;\n // @ts-ignore: Property has no initializer and is not definitely assigned\n public delay: number;\n protected pending: boolean = false;\n\n constructor(protected scheduler: AsyncScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (this.closed) {\n return this;\n }\n\n // Always replace the current state with the new state.\n this.state = state;\n\n const id = this.id;\n const scheduler = this.scheduler;\n\n //\n // Important implementation note:\n //\n // Actions only execute once by default, unless rescheduled from within the\n // scheduled callback. This allows us to implement single and repeat\n // actions via the same code path, without adding API surface area, as well\n // as mimic traditional recursion but across asynchronous boundaries.\n //\n // However, JS runtimes and timers distinguish between intervals achieved by\n // serial `setTimeout` calls vs. a single `setInterval` call. An interval of\n // serial `setTimeout` calls can be individually delayed, which delays\n // scheduling the next `setTimeout`, and so on. `setInterval` attempts to\n // guarantee the interval callback will be invoked more precisely to the\n // interval period, regardless of load.\n //\n // Therefore, we use `setInterval` to schedule single and repeat actions.\n // If the action reschedules itself with the same delay, the interval is not\n // canceled. If the action doesn't reschedule, or reschedules with a\n // different delay, the interval will be canceled after scheduled callback\n // execution.\n //\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, delay);\n }\n\n // Set the pending flag indicating that this action has been scheduled, or\n // has recursively rescheduled itself.\n this.pending = true;\n\n this.delay = delay;\n // If this action has already an async Id, don't request a new one.\n this.id = this.id ?? this.requestAsyncId(scheduler, this.id, delay);\n\n return this;\n }\n\n protected requestAsyncId(scheduler: AsyncScheduler, _id?: TimerHandle, delay: number = 0): TimerHandle {\n return intervalProvider.setInterval(scheduler.flush.bind(scheduler, this), delay);\n }\n\n protected recycleAsyncId(_scheduler: AsyncScheduler, id?: TimerHandle, delay: number | null = 0): TimerHandle | undefined {\n // If this action is rescheduled with the same delay time, don't clear the interval id.\n if (delay != null && this.delay === delay && this.pending === false) {\n return id;\n }\n // Otherwise, if the action's delay time is different from the current delay,\n // or the action has been rescheduled before it's executed, clear the interval id\n if (id != null) {\n intervalProvider.clearInterval(id);\n }\n\n return undefined;\n }\n\n /**\n * Immediately executes this action and the `work` it contains.\n * @return {any}\n */\n public execute(state: T, delay: number): any {\n if (this.closed) {\n return new Error('executing a cancelled action');\n }\n\n this.pending = false;\n const error = this._execute(state, delay);\n if (error) {\n return error;\n } else if (this.pending === false && this.id != null) {\n // Dequeue if the action didn't reschedule itself. Don't call\n // unsubscribe(), because the action could reschedule later.\n // For example:\n // ```\n // scheduler.schedule(function doWork(counter) {\n // /* ... I'm a busy worker bee ... */\n // var originalAction = this;\n // /* wait 100ms before rescheduling the action */\n // setTimeout(function () {\n // originalAction.schedule(counter + 1);\n // }, 100);\n // }, 1000);\n // ```\n this.id = this.recycleAsyncId(this.scheduler, this.id, null);\n }\n }\n\n protected _execute(state: T, _delay: number): any {\n let errored: boolean = false;\n let errorValue: any;\n try {\n this.work(state);\n } catch (e) {\n errored = true;\n // HACK: Since code elsewhere is relying on the \"truthiness\" of the\n // return here, we can't have it return \"\" or 0 or false.\n // TODO: Clean this up when we refactor schedulers mid-version-8 or so.\n errorValue = e ? e : new Error('Scheduled action threw falsy error');\n }\n if (errored) {\n this.unsubscribe();\n return errorValue;\n }\n }\n\n unsubscribe() {\n if (!this.closed) {\n const { id, scheduler } = this;\n const { actions } = scheduler;\n\n this.work = this.state = this.scheduler = null!;\n this.pending = false;\n\n arrRemove(actions, this);\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, null);\n }\n\n this.delay = null!;\n super.unsubscribe();\n }\n }\n}\n", "import { Action } from './scheduler/Action';\nimport { Subscription } from './Subscription';\nimport { SchedulerLike, SchedulerAction } from './types';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * An execution context and a data structure to order tasks and schedule their\n * execution. Provides a notion of (potentially virtual) time, through the\n * `now()` getter method.\n *\n * Each unit of work in a Scheduler is called an `Action`.\n *\n * ```ts\n * class Scheduler {\n * now(): number;\n * schedule(work, delay?, state?): Subscription;\n * }\n * ```\n *\n * @class Scheduler\n * @deprecated Scheduler is an internal implementation detail of RxJS, and\n * should not be used directly. Rather, create your own class and implement\n * {@link SchedulerLike}. Will be made internal in v8.\n */\nexport class Scheduler implements SchedulerLike {\n public static now: () => number = dateTimestampProvider.now;\n\n constructor(private schedulerActionCtor: typeof Action, now: () => number = Scheduler.now) {\n this.now = now;\n }\n\n /**\n * A getter method that returns a number representing the current time\n * (at the time this function was called) according to the scheduler's own\n * internal clock.\n * @return {number} A number that represents the current time. May or may not\n * have a relation to wall-clock time. May or may not refer to a time unit\n * (e.g. milliseconds).\n */\n public now: () => number;\n\n /**\n * Schedules a function, `work`, for execution. May happen at some point in\n * the future, according to the `delay` parameter, if specified. May be passed\n * some context object, `state`, which will be passed to the `work` function.\n *\n * The given arguments will be processed an stored as an Action object in a\n * queue of actions.\n *\n * @param {function(state: ?T): ?Subscription} work A function representing a\n * task, or some unit of work to be executed by the Scheduler.\n * @param {number} [delay] Time to wait before executing the work, where the\n * time unit is implicit and defined by the Scheduler itself.\n * @param {T} [state] Some contextual data that the `work` function uses when\n * called by the Scheduler.\n * @return {Subscription} A subscription in order to be able to unsubscribe\n * the scheduled work.\n */\n public schedule(work: (this: SchedulerAction, state?: T) => void, delay: number = 0, state?: T): Subscription {\n return new this.schedulerActionCtor(this, work).schedule(state, delay);\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Action } from './Action';\nimport { AsyncAction } from './AsyncAction';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncScheduler extends Scheduler {\n public actions: Array> = [];\n /**\n * A flag to indicate whether the Scheduler is currently executing a batch of\n * queued actions.\n * @type {boolean}\n * @internal\n */\n public _active: boolean = false;\n /**\n * An internal ID used to track the latest asynchronous task such as those\n * coming from `setTimeout`, `setInterval`, `requestAnimationFrame`, and\n * others.\n * @type {any}\n * @internal\n */\n public _scheduled: TimerHandle | undefined;\n\n constructor(SchedulerAction: typeof Action, now: () => number = Scheduler.now) {\n super(SchedulerAction, now);\n }\n\n public flush(action: AsyncAction): void {\n const { actions } = this;\n\n if (this._active) {\n actions.push(action);\n return;\n }\n\n let error: any;\n this._active = true;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions.shift()!)); // exhaust the scheduler queue\n\n this._active = false;\n\n if (error) {\n while ((action = actions.shift()!)) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\n/**\n *\n * Async Scheduler\n *\n * Schedule task as if you used setTimeout(task, duration)\n *\n * `async` scheduler schedules tasks asynchronously, by putting them on the JavaScript\n * event loop queue. It is best used to delay tasks in time or to schedule tasks repeating\n * in intervals.\n *\n * If you just want to \"defer\" task, that is to perform it right after currently\n * executing synchronous code ends (commonly achieved by `setTimeout(deferredTask, 0)`),\n * better choice will be the {@link asapScheduler} scheduler.\n *\n * ## Examples\n * Use async scheduler to delay task\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * const task = () => console.log('it works!');\n *\n * asyncScheduler.schedule(task, 2000);\n *\n * // After 2 seconds logs:\n * // \"it works!\"\n * ```\n *\n * Use async scheduler to repeat task in intervals\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * function task(state) {\n * console.log(state);\n * this.schedule(state + 1, 1000); // `this` references currently executing Action,\n * // which we reschedule with new state and delay\n * }\n *\n * asyncScheduler.schedule(task, 3000, 0);\n *\n * // Logs:\n * // 0 after 3s\n * // 1 after 4s\n * // 2 after 5s\n * // 3 after 6s\n * ```\n */\n\nexport const asyncScheduler = new AsyncScheduler(AsyncAction);\n\n/**\n * @deprecated Renamed to {@link asyncScheduler}. Will be removed in v8.\n */\nexport const async = asyncScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { Subscription } from '../Subscription';\nimport { QueueScheduler } from './QueueScheduler';\nimport { SchedulerAction } from '../types';\nimport { TimerHandle } from './timerHandle';\n\nexport class QueueAction extends AsyncAction {\n constructor(protected scheduler: QueueScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (delay > 0) {\n return super.schedule(state, delay);\n }\n this.delay = delay;\n this.state = state;\n this.scheduler.flush(this);\n return this;\n }\n\n public execute(state: T, delay: number): any {\n return delay > 0 || this.closed ? super.execute(state, delay) : this._execute(state, delay);\n }\n\n protected requestAsyncId(scheduler: QueueScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n\n if ((delay != null && delay > 0) || (delay == null && this.delay > 0)) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n\n // Otherwise flush the scheduler starting with this action.\n scheduler.flush(this);\n\n // HACK: In the past, this was returning `void`. However, `void` isn't a valid\n // `TimerHandle`, and generally the return value here isn't really used. So the\n // compromise is to return `0` which is both \"falsy\" and a valid `TimerHandle`,\n // as opposed to refactoring every other instanceo of `requestAsyncId`.\n return 0;\n }\n}\n", "import { AsyncScheduler } from './AsyncScheduler';\n\nexport class QueueScheduler extends AsyncScheduler {\n}\n", "import { QueueAction } from './QueueAction';\nimport { QueueScheduler } from './QueueScheduler';\n\n/**\n *\n * Queue Scheduler\n *\n * Put every next task on a queue, instead of executing it immediately\n *\n * `queue` scheduler, when used with delay, behaves the same as {@link asyncScheduler} scheduler.\n *\n * When used without delay, it schedules given task synchronously - executes it right when\n * it is scheduled. However when called recursively, that is when inside the scheduled task,\n * another task is scheduled with queue scheduler, instead of executing immediately as well,\n * that task will be put on a queue and wait for current one to finish.\n *\n * This means that when you execute task with `queue` scheduler, you are sure it will end\n * before any other task scheduled with that scheduler will start.\n *\n * ## Examples\n * Schedule recursively first, then do something\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(() => {\n * queueScheduler.schedule(() => console.log('second')); // will not happen now, but will be put on a queue\n *\n * console.log('first');\n * });\n *\n * // Logs:\n * // \"first\"\n * // \"second\"\n * ```\n *\n * Reschedule itself recursively\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(function(state) {\n * if (state !== 0) {\n * console.log('before', state);\n * this.schedule(state - 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * console.log('after', state);\n * }\n * }, 0, 3);\n *\n * // In scheduler that runs recursively, you would expect:\n * // \"before\", 3\n * // \"before\", 2\n * // \"before\", 1\n * // \"after\", 1\n * // \"after\", 2\n * // \"after\", 3\n *\n * // But with queue it logs:\n * // \"before\", 3\n * // \"after\", 3\n * // \"before\", 2\n * // \"after\", 2\n * // \"before\", 1\n * // \"after\", 1\n * ```\n */\n\nexport const queueScheduler = new QueueScheduler(QueueAction);\n\n/**\n * @deprecated Renamed to {@link queueScheduler}. Will be removed in v8.\n */\nexport const queue = queueScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\nimport { SchedulerAction } from '../types';\nimport { animationFrameProvider } from './animationFrameProvider';\nimport { TimerHandle } from './timerHandle';\n\nexport class AnimationFrameAction extends AsyncAction {\n constructor(protected scheduler: AnimationFrameScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n protected requestAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay is greater than 0, request as an async action.\n if (delay !== null && delay > 0) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n // Push the action to the end of the scheduler queue.\n scheduler.actions.push(this);\n // If an animation frame has already been requested, don't request another\n // one. If an animation frame hasn't been requested yet, request one. Return\n // the current animation frame request id.\n return scheduler._scheduled || (scheduler._scheduled = animationFrameProvider.requestAnimationFrame(() => scheduler.flush(undefined)));\n }\n\n protected recycleAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle | undefined {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n if (delay != null ? delay > 0 : this.delay > 0) {\n return super.recycleAsyncId(scheduler, id, delay);\n }\n // If the scheduler queue has no remaining actions with the same async id,\n // cancel the requested animation frame and set the scheduled flag to\n // undefined so the next AnimationFrameAction will request its own.\n const { actions } = scheduler;\n if (id != null && actions[actions.length - 1]?.id !== id) {\n animationFrameProvider.cancelAnimationFrame(id as number);\n scheduler._scheduled = undefined;\n }\n // Return undefined so the action knows to request a new async id if it's rescheduled.\n return undefined;\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\nexport class AnimationFrameScheduler extends AsyncScheduler {\n public flush(action?: AsyncAction): void {\n this._active = true;\n // The async id that effects a call to flush is stored in _scheduled.\n // Before executing an action, it's necessary to check the action's async\n // id to determine whether it's supposed to be executed in the current\n // flush.\n // Previous implementations of this method used a count to determine this,\n // but that was unsound, as actions that are unsubscribed - i.e. cancelled -\n // are removed from the actions array and that can shift actions that are\n // scheduled to be executed in a subsequent flush into positions at which\n // they are executed within the current flush.\n const flushId = this._scheduled;\n this._scheduled = undefined;\n\n const { actions } = this;\n let error: any;\n action = action || actions.shift()!;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions[0]) && action.id === flushId && actions.shift());\n\n this._active = false;\n\n if (error) {\n while ((action = actions[0]) && action.id === flushId && actions.shift()) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AnimationFrameAction } from './AnimationFrameAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\n\n/**\n *\n * Animation Frame Scheduler\n *\n * Perform task when `window.requestAnimationFrame` would fire\n *\n * When `animationFrame` scheduler is used with delay, it will fall back to {@link asyncScheduler} scheduler\n * behaviour.\n *\n * Without delay, `animationFrame` scheduler can be used to create smooth browser animations.\n * It makes sure scheduled task will happen just before next browser content repaint,\n * thus performing animations as efficiently as possible.\n *\n * ## Example\n * Schedule div height animation\n * ```ts\n * // html:
\n * import { animationFrameScheduler } from 'rxjs';\n *\n * const div = document.querySelector('div');\n *\n * animationFrameScheduler.schedule(function(height) {\n * div.style.height = height + \"px\";\n *\n * this.schedule(height + 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * }, 0, 0);\n *\n * // You will see a div element growing in height\n * ```\n */\n\nexport const animationFrameScheduler = new AnimationFrameScheduler(AnimationFrameAction);\n\n/**\n * @deprecated Renamed to {@link animationFrameScheduler}. Will be removed in v8.\n */\nexport const animationFrame = animationFrameScheduler;\n", "import { Observable } from '../Observable';\nimport { SchedulerLike } from '../types';\n\n/**\n * A simple Observable that emits no items to the Observer and immediately\n * emits a complete notification.\n *\n * Just emits 'complete', and nothing else.\n *\n * ![](empty.png)\n *\n * A simple Observable that only emits the complete notification. It can be used\n * for composing with other Observables, such as in a {@link mergeMap}.\n *\n * ## Examples\n *\n * Log complete notification\n *\n * ```ts\n * import { EMPTY } from 'rxjs';\n *\n * EMPTY.subscribe({\n * next: () => console.log('Next'),\n * complete: () => console.log('Complete!')\n * });\n *\n * // Outputs\n * // Complete!\n * ```\n *\n * Emit the number 7, then complete\n *\n * ```ts\n * import { EMPTY, startWith } from 'rxjs';\n *\n * const result = EMPTY.pipe(startWith(7));\n * result.subscribe(x => console.log(x));\n *\n * // Outputs\n * // 7\n * ```\n *\n * Map and flatten only odd numbers to the sequence `'a'`, `'b'`, `'c'`\n *\n * ```ts\n * import { interval, mergeMap, of, EMPTY } from 'rxjs';\n *\n * const interval$ = interval(1000);\n * const result = interval$.pipe(\n * mergeMap(x => x % 2 === 1 ? of('a', 'b', 'c') : EMPTY),\n * );\n * result.subscribe(x => console.log(x));\n *\n * // Results in the following to the console:\n * // x is equal to the count on the interval, e.g. (0, 1, 2, 3, ...)\n * // x will occur every 1000ms\n * // if x % 2 is equal to 1, print a, b, c (each on its own)\n * // if x % 2 is not equal to 1, nothing will be output\n * ```\n *\n * @see {@link Observable}\n * @see {@link NEVER}\n * @see {@link of}\n * @see {@link throwError}\n */\nexport const EMPTY = new Observable((subscriber) => subscriber.complete());\n\n/**\n * @param scheduler A {@link SchedulerLike} to use for scheduling\n * the emission of the complete notification.\n * @deprecated Replaced with the {@link EMPTY} constant or {@link scheduled} (e.g. `scheduled([], scheduler)`). Will be removed in v8.\n */\nexport function empty(scheduler?: SchedulerLike) {\n return scheduler ? emptyScheduled(scheduler) : EMPTY;\n}\n\nfunction emptyScheduled(scheduler: SchedulerLike) {\n return new Observable((subscriber) => scheduler.schedule(() => subscriber.complete()));\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport function isScheduler(value: any): value is SchedulerLike {\n return value && isFunction(value.schedule);\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\nimport { isScheduler } from './isScheduler';\n\nfunction last(arr: T[]): T | undefined {\n return arr[arr.length - 1];\n}\n\nexport function popResultSelector(args: any[]): ((...args: unknown[]) => unknown) | undefined {\n return isFunction(last(args)) ? args.pop() : undefined;\n}\n\nexport function popScheduler(args: any[]): SchedulerLike | undefined {\n return isScheduler(last(args)) ? args.pop() : undefined;\n}\n\nexport function popNumber(args: any[], defaultValue: number): number {\n return typeof last(args) === 'number' ? args.pop()! : defaultValue;\n}\n", "export const isArrayLike = ((x: any): x is ArrayLike => x && typeof x.length === 'number' && typeof x !== 'function');", "import { isFunction } from \"./isFunction\";\n\n/**\n * Tests to see if the object is \"thennable\".\n * @param value the object to test\n */\nexport function isPromise(value: any): value is PromiseLike {\n return isFunction(value?.then);\n}\n", "import { InteropObservable } from '../types';\nimport { observable as Symbol_observable } from '../symbol/observable';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being Observable (but not necessary an Rx Observable) */\nexport function isInteropObservable(input: any): input is InteropObservable {\n return isFunction(input[Symbol_observable]);\n}\n", "import { isFunction } from './isFunction';\n\nexport function isAsyncIterable(obj: any): obj is AsyncIterable {\n return Symbol.asyncIterator && isFunction(obj?.[Symbol.asyncIterator]);\n}\n", "/**\n * Creates the TypeError to throw if an invalid object is passed to `from` or `scheduled`.\n * @param input The object that was passed.\n */\nexport function createInvalidObservableTypeError(input: any) {\n // TODO: We should create error codes that can be looked up, so this can be less verbose.\n return new TypeError(\n `You provided ${\n input !== null && typeof input === 'object' ? 'an invalid object' : `'${input}'`\n } where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.`\n );\n}\n", "export function getSymbolIterator(): symbol {\n if (typeof Symbol !== 'function' || !Symbol.iterator) {\n return '@@iterator' as any;\n }\n\n return Symbol.iterator;\n}\n\nexport const iterator = getSymbolIterator();\n", "import { iterator as Symbol_iterator } from '../symbol/iterator';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being an Iterable */\nexport function isIterable(input: any): input is Iterable {\n return isFunction(input?.[Symbol_iterator]);\n}\n", "import { ReadableStreamLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport async function* readableStreamLikeToAsyncGenerator(readableStream: ReadableStreamLike): AsyncGenerator {\n const reader = readableStream.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n return;\n }\n yield value!;\n }\n } finally {\n reader.releaseLock();\n }\n}\n\nexport function isReadableStreamLike(obj: any): obj is ReadableStreamLike {\n // We don't want to use instanceof checks because they would return\n // false for instances from another Realm, like an
\ No newline at end of file diff --git a/0.2.3/operations/apply/index.html b/0.2.3/operations/apply/index.html new file mode 100644 index 000000000..78438554b --- /dev/null +++ b/0.2.3/operations/apply/index.html @@ -0,0 +1,70 @@ + Apply - Chainsaw

Apply

The apply operation defines resources that should be applied to a Kubernetes cluster. If the resource does not exist yet it will be created, otherwise, it will be configured to match the provided configuration.

Configuration

The full structure of the Apply is documented here.

Features

Supported features
Bindings support ✅
Outputs support ✅
Templating support ✅
Operation checks support ✅

Examples

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - apply:
+        # use a specific file
+        file: my-configmap.yaml
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - apply:
+        # use glob pattern
+        file: "configs/*.yaml"
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - apply:
+        # use an URL
+        file: https://raw.githubusercontent.com/kyverno/chainsaw/main/testdata/step/configmap.yaml
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - apply:
+        # specify resource inline
+        resource:
+          apiVersion: v1
+          kind: ConfigMap
+          metadata:
+            name: chainsaw-quick-start
+          data:
+            foo: bar
+

Operation check

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - apply:
+        file: my-configmap.yaml
+        expect:
+        - match:
+            # this check applies only if the match
+            # statement below evaluates to `true`
+            apiVersion: v1
+            kind: ConfigMap
+          check:
+            # an error is expected, this will:
+            # - succeed if the operation failed
+            # - fail if the operation succeeded
+            ($error != null): true
+
\ No newline at end of file diff --git a/0.2.3/operations/assert/index.html b/0.2.3/operations/assert/index.html new file mode 100644 index 000000000..248400dcf --- /dev/null +++ b/0.2.3/operations/assert/index.html @@ -0,0 +1,50 @@ + Assert - Chainsaw

Assert

The assert operation allows you to specify conditions that should hold true for a successful test.

For example, after applying resources, you might want to ensure that a particular pod is running or a service is accessible.

Configuration

The full structure of the Assert is documented here.

Features

Supported features
Bindings support ✅
Outputs support ❌
Templating support ❌ | ✅
Operation checks support ❌

Templating

When working with assert and error operations, the content is already an assertion tree and therefore mostly represents a logical operation. An exception to this rule is for fields participating in the resource selection process.

For this reason, only elements used for looking up the resources from the cluster will be considered for templating. That is, apiVersion, kind, name, namespace and labels.

Examples

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - assert:
+        # use a specific file
+        file: ../resources/deployment-assert.yaml
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - assert:
+        # use glob pattern
+        file: "../assertions/*.yaml"
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - assert:
+        # use an URL
+        file: https://raw.githubusercontent.com/kyverno/chainsaw/main/testdata/resource/valid.yaml
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - assert:
+        # specify resource inline
+        resource:
+          apiVersion: v1
+          kind: Deployment
+          metadata:
+            name: foo
+          spec:
+            (replicas > 3): true
+
\ No newline at end of file diff --git a/0.2.3/operations/command/index.html b/0.2.3/operations/command/index.html new file mode 100644 index 000000000..1fda128f8 --- /dev/null +++ b/0.2.3/operations/command/index.html @@ -0,0 +1,28 @@ + Command - Chainsaw

Command

The command operation provides a mean to execute a specific command during the test step.

Warning

Command arguments are not going through shell expansion.

It's crucial to consider potential differences in behavior, as Chainsaw may interpret them differently compared to regular shell environments.

Configuration

The full structure of the Command is documented here.

Features

Supported features
Bindings support ✅
Outputs support ✅
Templating support ❌
Operation checks support ✅

KUBECONFIG

  • Unless --no-cluster is specified, Chainsaw always executes commands in the context of a temporary KUBECONFIG, built from the configured target cluster.
  • This specific KUBECONFIG has a single cluster, auth info and context configured (all named chainsaw).

Examples

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - command:
+        entrypoint: echo
+        args:
+        - hello chainsaw
+

Operation check

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - command:
+        entrypoint: echo
+        args:
+        - hello chainsaw
+        check:
+          # an error is expected, this will:
+          # - succeed if the operation failed
+          # - fail if the operation succeeded
+          ($error != null): true
+
\ No newline at end of file diff --git a/0.2.3/operations/create/index.html b/0.2.3/operations/create/index.html new file mode 100644 index 000000000..3345f4459 --- /dev/null +++ b/0.2.3/operations/create/index.html @@ -0,0 +1,70 @@ + Create - Chainsaw

Create

The create operation defines resources that should be created in a Kubernetes cluster.

If the resource to be created already exists in the cluster, the step will fail.

Configuration

The full structure of the Create is documented here.

Features

Supported features
Bindings support ✅
Outputs support ✅
Templating support ✅
Operation checks support ✅

Examples

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - create:
+        # use a specific file
+        file: my-configmap.yaml
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - create:
+        # use glob pattern
+        file: "configs/*.yaml"
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - create:
+        # use an URL
+        file: https://raw.githubusercontent.com/kyverno/chainsaw/main/testdata/resource/valid.yaml
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - create:
+        # specify resource inline
+        resource:
+          apiVersion: v1
+          kind: ConfigMap
+          metadata:
+            name: chainsaw-quick-start
+          data:
+            foo: bar
+

Operation check

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - create:
+        file: my-configmap.yaml
+        expect:
+        - match:
+            # this check applies only if the match
+            # statement below evaluates to `true`
+            apiVersion: v1
+            kind: ConfigMap
+          check:
+            # an error is expected, this will:
+            # - succeed if the operation failed
+            # - fail if the operation succeeded
+            ($error != null): true
+
\ No newline at end of file diff --git a/0.2.3/operations/delete/index.html b/0.2.3/operations/delete/index.html new file mode 100644 index 000000000..58cc960ba --- /dev/null +++ b/0.2.3/operations/delete/index.html @@ -0,0 +1,41 @@ + Delete - Chainsaw

Delete

The delete operation defines resources that should be deleted from a Kubernetes cluster.

Warning

The propagation policy is forced to Background because some types default to Orphan (this is the case for unmanaged jobs for example) and we don't want to let dangling pods run in the cluster after cleanup.

Configuration

The full structure of the Delete is documented here.

Features

Supported features
Bindings support ✅
Outputs support ❌
Templating support ✅
Operation checks support ✅

Examples

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - delete:
+        ref:
+          apiVersion: v1
+          kind: Pod
+          namespace: default
+          name: my-test-pod
+

Operation check

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - delete:
+        ref:
+          apiVersion: v1
+          kind: Pod
+          namespace: default
+          name: my-test-pod
+        expect:
+        - match:
+            # this check applies only if the match
+            # statement below evaluates to `true`
+            apiVersion: v1
+            kind: Pod
+            metadata:
+              namespace: default
+              name: my-test-pod
+          check:
+            # an error is expected, this will:
+            # - succeed if the operation failed
+            # - fail if the operation succeeded
+            ($error != null): true
+
\ No newline at end of file diff --git a/0.2.3/operations/error/index.html b/0.2.3/operations/error/index.html new file mode 100644 index 000000000..0b35cbeca --- /dev/null +++ b/0.2.3/operations/error/index.html @@ -0,0 +1,50 @@ + Error - Chainsaw

Error

The error operation lets you define a set of expected errors for a test step. If any of these errors occur during the test, they are treated as expected outcomes. However, if an error that's not on this list occurs, it will be treated as a test failure.

Configuration

The full structure of the Error is documented here.

Features

Supported features
Bindings support ✅
Outputs support ❌
Templating support ❌ | ✅
Operation checks support ❌

Templating

When working with assert and error operations, the content is already an assertion tree and therefore mostly represents a logical operation. An exception to this rule is for fields participating in the resource selection process.

For this reason, only elements used for looking up the resources from the cluster will be considered for templating. That is, apiVersion, kind, name, namespace and labels.

Examples

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - error:
+        # use a specific file
+        file: ../resources/deployment-error.yaml
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - error:
+        # use glob pattern
+        file: "../errors/*.yaml"
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - error:
+        # use an URL
+        file: https://raw.githubusercontent.com/user/repo/branch/path/to/deployment-error.yaml
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - error:
+        # specify resource inline
+        resource:
+          apiVersion: v1
+          kind: Deployment
+          metadata:
+            name: foo
+          spec:
+            (replicas > 3): true
+
\ No newline at end of file diff --git a/0.2.3/operations/helpers/describe/index.html b/0.2.3/operations/helpers/describe/index.html new file mode 100644 index 000000000..f81eb244a --- /dev/null +++ b/0.2.3/operations/helpers/describe/index.html @@ -0,0 +1,66 @@ + Describe - Chainsaw

Describe

Show details of a specific resource or group of resources.

Deprecated syntax

You can specify the resource directly instead of using apiVersion and kind.

This is a deprecated syntax though and will be removed in a future version.

Configuration

The full structure of the Describe resource is documented here.

Features

Supported features
Bindings support ❌
Outputs support ❌
Templating support ❌
Operation checks support ❌

Clustered resources

When used with a clustered resource, the namespace is ignored and is not added to the corresponding kubectl command.

Test namespace

When used with a namespaced resource, Chainsaw will default the scope to the ephemeral test namespace.

All namespaces

When used with a namespaced resource, it is possible to consider all namespaces in the cluster by setting namespace: '*'.

Examples

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try: ...
+    catch:
+    - describe:
+        # describe all pods in the test namespace
+        apiVersion: v1
+        kind: Pod
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try: ...
+    catch:
+    - describe:
+        apiVersion: v1
+        kind: Pod
+        # describe pods that have a name starting with the provided `my-pod`
+        name: my-pod
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try: ...
+    catch:
+    - describe:
+        apiVersion: v1
+        kind: Pod
+        # describe pods in the namespace `foo`
+        namespace: foo
+

Label selector

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try: ...
+    catch:
+    - describe:
+        apiVersion: v1
+        kind: Pod
+        # describe pods using a label selector query
+        selector: app=my-app
+

Show events

Tip

By default, showEventsis true.

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try: ...
+    catch:
+    - describe:
+        apiVersion: v1
+        kind: Pod
+        showEvents: false
+
\ No newline at end of file diff --git a/0.2.3/operations/helpers/events/index.html b/0.2.3/operations/helpers/events/index.html new file mode 100644 index 000000000..43b5abe38 --- /dev/null +++ b/0.2.3/operations/helpers/events/index.html @@ -0,0 +1,69 @@ + Events - Chainsaw

Events

Display one or many events.

Configuration

The full structure of the Events resource is documented here.

Features

Supported features
Bindings support ❌
Outputs support ❌
Templating support ❌
Operation checks support ❌

Test namespace

When used with a namespaced resource, Chainsaw will default the scope to the ephemeral test namespace.

All namespaces

When used with a namespaced resource, it is possible to consider all namespaces in the cluster by setting namespace: '*'.

Examples

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try: ...
+    catch:
+    # get all events in the test namespace
+    - events: {}
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try: ...
+    catch:
+    # get events in a specific namespace
+    - events:
+        namespace: foo
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try: ...
+    catch:
+    # get event by name
+    - events:
+        name: my-event
+

Label selector

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try: ...
+    catch:
+    - events:
+        # get events using a label selector query
+        selector: app=my-app
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try: ...
+    catch:
+    - events:
+        # get events using a label selector query
+        selector: app=my-app
+        namespace: foo
+

Format

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try: ...
+    catch:
+    - events:
+        format: json
+
\ No newline at end of file diff --git a/0.2.3/operations/helpers/get/index.html b/0.2.3/operations/helpers/get/index.html new file mode 100644 index 000000000..a5288e4bd --- /dev/null +++ b/0.2.3/operations/helpers/get/index.html @@ -0,0 +1,66 @@ + Get - Chainsaw

Get

Display one or many resources.

Deprecated syntax

You can specify the resource directly instead of using apiVersion and kind.

This is a deprecated syntax though and will be removed in a future version.

Configuration

The full structure of the Get resource is documented here.

Features

Supported features
Bindings support ❌
Outputs support ❌
Templating support ❌
Operation checks support ❌

Clustered resources

When used with a clustered resource, the namespace is ignored and is not added to the corresponding kubectl command.

Test namespace

When used with a namespaced resource, Chainsaw will default the scope to the ephemeral test namespace.

All namespaces

When used with a namespaced resource, it is possible to consider all namespaces in the cluster by setting namespace: '*'.

Examples

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try: ...
+    catch:
+    # get all pods in the test namespace
+    - get:
+        apiVersion: v1
+        kind: Pod
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try: ...
+    catch:
+    - get:
+        apiVersion: v1
+        kind: Pod
+        # get pods that have a name starting with the provided `my-pod`
+        name: my-pod
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try: ...
+    catch:
+    - get:
+        apiVersion: v1
+        kind: Pod
+        # get pods in the namespace `foo`
+        namespace: foo
+

Label selector

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try: ...
+    catch:
+    - get:
+        apiVersion: v1
+        kind: Pod
+        # get pods using a label selector query
+        selector: app=my-app
+

Format

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try: ...
+    catch:
+    - get:
+        apiVersion: v1
+        kind: Pod
+        format: json
+
\ No newline at end of file diff --git a/0.2.3/operations/helpers/index.html b/0.2.3/operations/helpers/index.html new file mode 100644 index 000000000..ff8adca91 --- /dev/null +++ b/0.2.3/operations/helpers/index.html @@ -0,0 +1 @@ + Kubectl helpers - Chainsaw

Kubectl helpers

Kubectl helpers are declarative versions of kubectl imperative commands.

Implementation

Helpers are implemented as syntactic sugars.

They are translated into their corresponding kubectl commands and executed as such.

KUBECONFIG

  • Chainsaw always executes commands in the context of a temporary KUBECONFIG, built from the configured target cluster.
  • This specific KUBECONFIG has a single cluster, auth info and context configured (all named chainsaw).

Helpers

\ No newline at end of file diff --git a/0.2.3/operations/helpers/logs/index.html b/0.2.3/operations/helpers/logs/index.html new file mode 100644 index 000000000..8a0b0696a --- /dev/null +++ b/0.2.3/operations/helpers/logs/index.html @@ -0,0 +1,66 @@ + Pods logs - Chainsaw

Pods logs

Print the logs for a container in a pod or specified resource.

Configuration

The full structure of the PodLogs resource is documented here.

Features

Supported features
Bindings support ❌
Outputs support ❌
Templating support ❌
Operation checks support ❌

Test namespace

Chainsaw will default the scope to the ephemeral test namespace.

All namespaces

It is possible to consider all namespaces in the cluster by setting namespace: '*'.

Examples

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try: ...
+    catch:
+    # all pods in the test namespace
+    - podLogs: {}
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try: ...
+    catch:
+    - podLogs:
+        # pods that have a name starting with the provided `my-pod`
+        name: my-pod
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try: ...
+    catch:
+    - podLogs:
+        # pods in the namespace `foo`
+        namespace: foo
+

Label selector

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try: ...
+    catch:
+    - podLogs:
+        # match pods using a label selector query
+        selector: app=my-app
+

Tail

Tip

By default, tail will be 10 when a label selector is used, and all if a pod name is specified.

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try: ...
+    catch:
+    - podLogs:
+        tail: 30
+

Container

Tip

By default logs from all containers will be fetched.

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try: ...
+    catch:
+    - podLogs:
+        container: nginx
+
\ No newline at end of file diff --git a/0.2.3/operations/helpers/wait/index.html b/0.2.3/operations/helpers/wait/index.html new file mode 100644 index 000000000..c4c5ca565 --- /dev/null +++ b/0.2.3/operations/helpers/wait/index.html @@ -0,0 +1,95 @@ + Wait - Chainsaw

Wait

Wait for a specific condition on one or many resources.

Deprecated syntax

You can specify the resource directly instead of using apiVersion and kind.

This is a deprecated syntax though and will be removed in a future version.

Configuration

The full structure of the Wait resource is documented here.

Features

Supported features
Bindings support ❌
Outputs support ❌
Templating support ❌
Operation checks support ❌

Clustered resources

When used with a clustered resource, the namespace is ignored and is not added to the corresponding kubectl command.

All resources

If you don't specify a name or a selector, the wait operation will consider all resources.

Test namespace

When used with a namespaced resource, Chainsaw will default the scope to the ephemeral test namespace.

All namespaces

When used with a namespaced resource, it is possible to consider all namespaces in the cluster by setting namespace: '*'.

Examples

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    # wait all pods are ready in the test namespace
+    - wait:
+        apiVersion: v1
+        kind: Pod
+        timeout: 1m
+        for:
+          condition:
+            name: Ready
+            value: 'true'
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - wait:
+        apiVersion: v1
+        kind: Pod
+        # wait a specific pod is ready in the test namespace
+        name: my-pod
+        timeout: 1m
+        for:
+          condition:
+            name: Ready
+            value: 'true'
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - wait:
+        apiVersion: v1
+        kind: Pod
+        # wait all pods are ready in the namespace `foo`
+        namespace: foo
+        timeout: 1m
+        for:
+          condition:
+            name: Ready
+            value: 'true'
+

Label selector

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - wait:
+        apiVersion: v1
+        kind: Pod
+        # match pods using a label selector query
+        selector: app=foo
+        timeout: 1m
+        for:
+          condition:
+            name: Ready
+            value: 'true'
+

Deletion

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - wait:
+        apiVersion: v1
+        kind: Pod
+        timeout: 1m
+        for:
+          # wait for deletion
+          deletion: {}
+

Format

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - wait:
+        apiVersion: v1
+        kind: Pod
+        format: json
+
\ No newline at end of file diff --git a/0.2.3/operations/index.html b/0.2.3/operations/index.html new file mode 100644 index 000000000..206a4e7dd --- /dev/null +++ b/0.2.3/operations/index.html @@ -0,0 +1,30 @@ + Operations - Chainsaw

Operations

Chainsaw supports the following operations:

Helpers

Chainsaw also supports kubectl helpers.

Properties

Action unicity

Every operation must consist of a single action.

While it is syntactically possible to create an operation with multiple actions, Chainsaw will verify and reject tests if operations containing multiple actions are found.

The reasoning behind this intentional choice is that it becomes harder to understand in which order actions will be executed when an operation consists of multiple actions. For this reason, operations consisting of multiple actions are not allowed.

Common fields

Continue on error

The continueOnError field determines whether a test step should continue executing or not if the operation fails (in any case the test will be marked as failed).

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+      # in case of error the test will be marked as failed
+      # but the step will not stop execution and will
+      # continue executing the following operations
+    - continueOnError: true
+      apply:
+        resource:
+          apiVersion: v1
+          kind: ConfigMap
+          metadata:
+            name: quick-start
+          data:
+            foo: bar
+

Description

All operations support a description field that can be used document your tests.

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - description: Waits a couple of seconds
+      sleep:
+        duration: 3s
+
\ No newline at end of file diff --git a/0.2.3/operations/patch/index.html b/0.2.3/operations/patch/index.html new file mode 100644 index 000000000..d9cce6c82 --- /dev/null +++ b/0.2.3/operations/patch/index.html @@ -0,0 +1,70 @@ + Patch - Chainsaw

Patch

The patch operation defines resources that should be modified in a Kubernetes cluster.

If the resource to be modified does not exist in the cluster, the step will fail.

Configuration

The full structure of the Patch is documented here.

Features

Supported features
Bindings support ✅
Outputs support ✅
Templating support ✅
Operation checks support ✅

Examples

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - patch:
+        # use a specific file
+        file: my-configmap.yaml
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - patch:
+        # use glob pattern
+        file: "configs/*.yaml"
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - patch:
+        # use an URL
+        file: https://raw.githubusercontent.com/kyverno/chainsaw/main/testdata/resource/valid.yaml
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - patch:
+        # specify resource inline
+        resource:
+          apiVersion: v1
+          kind: ConfigMap
+          metadata:
+            name: chainsaw-quick-start
+          data:
+            foo: bar
+

Operation check

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - patch:
+        file: my-configmap.yaml
+        expect:
+        - match:
+            # this check applies only if the match
+            # statement below evaluates to `true`
+            apiVersion: v1
+            kind: ConfigMap
+          check:
+            # an error is expected, this will:
+            # - succeed if the operation failed
+            # - fail if the operation succeeded
+            ($error != null): true
+
\ No newline at end of file diff --git a/0.2.3/operations/script/index.html b/0.2.3/operations/script/index.html new file mode 100644 index 000000000..5f54d6fe3 --- /dev/null +++ b/0.2.3/operations/script/index.html @@ -0,0 +1,26 @@ + Script - Chainsaw

Script

The script operation provides a means to run a script during the test step.

Configuration

The full structure of the Script is documented here.

Features

Supported features
Bindings support ✅
Outputs support ✅
Templating support ❌
Operation checks support ✅

KUBECONFIG

  • Unless --no-cluster is specified, Chainsaw always executes commands in the context of a temporary KUBECONFIG, built from the configured target cluster.
  • This specific KUBECONFIG has a single cluster, auth info and context configured (all named chainsaw).

Examples

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - script:
+        content: |
+          echo "hello chainsaw"
+

Operation check

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - script:
+        content: |
+          echo "hello chainsaw"
+        check:
+          # an error is expected, this will:
+          # - succeed if the operation failed
+          # - fail if the operation succeeded
+          ($error != null): true
+
\ No newline at end of file diff --git a/0.2.3/operations/sleep/index.html b/0.2.3/operations/sleep/index.html new file mode 100644 index 000000000..c735ae6d4 --- /dev/null +++ b/0.2.3/operations/sleep/index.html @@ -0,0 +1,10 @@ + Sleep - Chainsaw

Sleep

The sleep operation provides a means to sleep for a configured duration.

Configuration

The full structure of the Sleep is documented here.

Features

Supported features
Bindings support ❌
Outputs support ❌
Templating support ❌
Operation checks support ❌

Examples

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - sleep:
+        duration: 30s
+
\ No newline at end of file diff --git a/0.2.3/operations/todo/non-resource-assert._md b/0.2.3/operations/todo/non-resource-assert._md new file mode 100644 index 000000000..28d3ba025 --- /dev/null +++ b/0.2.3/operations/todo/non-resource-assert._md @@ -0,0 +1,28 @@ +# Non-resource assertions + +Under certain circumstances, it makes sense to evaluate assertions that do not depend on resources. +For example, when asserting the number of nodes in a cluster is equal to a known value. + +## Usage examples + +Below is an example of using `assert` in a `Test` resource. + +!!! example "Using a file" + + ```yaml + apiVersion: chainsaw.kyverno.io/v1alpha1 + kind: Test + metadata: + name: non-resource-assertion + spec: + steps: + - try: + - assert: + resource: + (x_k8s_list($client, 'v1', 'Node')): + (length(items)): 1 + - error: + resource: + (x_k8s_list($client, 'v1', 'Node')): + (length(items)): 2 + ``` diff --git a/0.2.3/operations/todo/templating._md b/0.2.3/operations/todo/templating._md new file mode 100644 index 000000000..204b3daf7 --- /dev/null +++ b/0.2.3/operations/todo/templating._md @@ -0,0 +1,134 @@ +# Resource templating + +!!! warning "Experimental status" + This is an **experimental feature**, and implementation could change slightly in the next versions. + +!!! info + Templating was disabled by default in `v0.1.*` but is now enabled by default since `v0.2.1`. + +Chainsaw can apply transformations to the resources before they are processed by the operation. + +This is useful when a resource needs some runtime configuration. + +Templating must be enabled at the configuration, test, step, or operation level for the templating process to kick in. +Alternatively, templating can be enabled using the `--template` flag when invoking chainsaw from the command line. + +!!! note + Unlike assertion trees, templating can only be specified in leave nodes of the YAML tree. + +## Supported operations + +Resource templating is supported in the following operations: + +- [Apply](./apply.md) +- [Assert](./assert.md) +- [Create](./create.md) +- [Delete](./delete.md) +- [Error](./error.md) +- [Patch](./patch.md) +- [Update](./update.md) + +### Assert and Error + +When templating `assert` or `error` operations, the content is already an assertion tree. + +For this reason, only the elements used for looking up the resources to be processed by the operation will be considered for templating. +That is, only `apiVersion`, `kind`, `name`, `namespace` and `labels` are considered for templating. +Other fields are not, they are part of the assertion tree only. + +!!! example "assert and error example" + + ```yaml + apiVersion: chainsaw.kyverno.io/v1alpha1 + kind: Test + metadata: + name: template + spec: + template: true + steps: + - assert: + resource: + # apiVersion, kind, name, namespace and labels are considered for templating + apiVersion: v1 + kind: ConfigMap + metadata: + name: ($namespace) + # other fields are not (they are part of the assertion tree) + data: + foo: ($namespace) + - error: + resource: + # apiVersion, kind, name, namespace and labels are considered for templating + apiVersion: v1 + kind: ConfigMap + metadata: + name: ($namespace) + # other fields are not (they are part of the assertion tree) + data: + bar: ($namespace) + ``` + +### Apply, Create and Patch + +When templating `apply`, `create` or `patch` operations, the whole content is considered for templating. + +!!! example "apply and create example" + + ```yaml + apiVersion: chainsaw.kyverno.io/v1alpha1 + kind: Test + metadata: + name: template + spec: + template: true + steps: + - apply: + resource: + # the whole content is considered for templating + apiVersion: v1 + kind: ConfigMap + metadata: + name: ($namespace) + data: + foo: ($namespace) + - create: + resource: + # the whole content is considered for templating + apiVersion: v1 + kind: ConfigMap + metadata: + name: ($namespace) + data: + foo: ($namespace) + - patch: + resource: + # the whole content is considered for templating + apiVersion: v1 + kind: ConfigMap + metadata: + name: ($namespace) + data: + foo: ($namespace) + ``` + +### Delete + +When templating `delete` operations, the whole content is considered for templating. + +!!! example "apply and create example" + + ```yaml + apiVersion: chainsaw.kyverno.io/v1alpha1 + kind: Test + metadata: + name: template + spec: + template: true + steps: + - delete: + ref: + # the whole content is considered for templating + apiVersion: v1 + kind: ConfigMap + name: ($namespace) + ``` \ No newline at end of file diff --git a/0.2.3/operations/update/index.html b/0.2.3/operations/update/index.html new file mode 100644 index 000000000..101b0472b --- /dev/null +++ b/0.2.3/operations/update/index.html @@ -0,0 +1,70 @@ + Update - Chainsaw

Update

The update operation defines resources that should be updated in a Kubernetes cluster.

If the resource to be updated doesn't exist in the cluster, the step will fail.

Configuration

The full structure of the Update is documented here.

Features

Supported features
Bindings support ✅
Outputs support ✅
Templating support ✅
Operation checks support ✅

Examples

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - update:
+        # use a specific file
+        file: my-configmap.yaml
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example-multi
+spec:
+  steps:
+  - try:
+    - update:
+        # use glob pattern
+        file: "configs/*.yaml"
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - update:
+        # use an URL
+        file: https://raw.githubusercontent.com/kyverno/chainsaw/main/testdata/resource/valid.yaml
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - update:
+        # specify resource inline
+        resource:
+          apiVersion: v1
+          kind: ConfigMap
+          metadata:
+            name: chainsaw-quick-start
+          data:
+            foo: bar
+

Operation check

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - update:
+        file: my-configmap.yaml
+        expect:
+        - match:
+            # this check applies only if the match
+            # statement below evaluates to `true`
+            apiVersion: v1
+            kind: ConfigMap
+          check:
+            # an error is expected, this will:
+            # - succeed if the operation failed
+            # - fail if the operation succeeded
+            ($error != null): true
+
\ No newline at end of file diff --git a/0.2.3/overrides/home.html b/0.2.3/overrides/home.html new file mode 100644 index 000000000..78c0e54fb --- /dev/null +++ b/0.2.3/overrides/home.html @@ -0,0 +1,483 @@ +{% extends "main.html" %} +{% block tabs %} +{{ super() }} + +
+
+
+
+
+
+

Kyverno Chainsaw

+

Kyverno end to end testing tool!

+
    +
  • Used internally to continuously test Kyverno
  • +
  • Entirely low code and declarative
  • +
  • Provides an advanced and flexible assertion model
  • +
  • Run it locally or in your CI/CD pipelines
  • +
  • ... and much more!
  • +
+ + Get started + + + Learn more + +
+
+ +
+
+ +
+
+
+
+
+
+
+

+ Everything you would expect + +

+
+
+
    +
  • +
    + {% include ".icons/fontawesome/solid/gear.svg" %} +
    +
    +

    Easy to install

    +

    + Install locally using a package manager like brew or nix, or simply download the + binary from one of our + releases. + If you want to run using a Docker image, we have that too. +

    +
    +
  • +
  • +
    + {% include ".icons/fontawesome/solid/pen.svg" %} +
    +
    +

    Easy to use

    +

    + Write tests in minutes, not hours. All it takes is a YAML file to define the steps + of a + test. Chainsaw will do the rest, no need to learn a programing language or write a + single line of code! +

    +
    +
  • +
  • +
    + {% include ".icons/fontawesome/solid/flag-checkered.svg" %} +
    +
    +

    Comprehensive reporting

    +

    + Understand and diagnose failures easily, thanks to a comprehensive output showing + precisely what failed and why. + Generate JUnit compatible reports to integrate with other test reporting tools. +

    +
    +
  • +
  • +
    + {% include ".icons/fontawesome/solid/paint-roller.svg" %} +
    +
    +

    Resource templating

    +

    + Kubernetes is all about resouces and tests need to work with resources. + Chainsaw has built-in support for bindings, operation outputs and resource + templating to + describe complex test scenarios. +

    +
    +
  • +
  • +
    + {% include ".icons/fontawesome/solid/circle-plus.svg" %} +
    +
    +

    Positive testing

    +

    + Create, update, delete resources and assert your controller reconciles the desired + and observed states in the expected way. +

    +
    +
  • +
  • +
    + {% include ".icons/fontawesome/solid/circle-minus.svg" %} +
    +
    +

    Negative testing

    +

    + Try submitting invalid resources, invalid changes, or other disallowed actions and + make sure they are rejected. +

    +
    +
  • +
  • +
    + {% include ".icons/fontawesome/solid/arrows-to-dot.svg" %} +
    +
    +

    Stay focused

    +

    + Focus on the software you are building, write test scenarios using YAML and let + Chainsaw + tell you what passes or not. Integrate in your CI pipeline to prevent regressions + and + release with better confidence. +

    +
    +
  • +
  • +
    + {% include ".icons/fontawesome/solid/boxes-stacked.svg" %} +
    +
    +

    Multi cluster

    +

    + Native support for tests involving multiple clusters, either static or dynamically + created ones, make Chainsaw an excellent tool for testing highly complex + environments + and architectures. +

    +
    +
  • +
+
+ +
+
+
+
+
+
+
+

+ Widely adopted + +

+
+
+
+

+
+ + Running all end to end tests for both Kyverno and the policies catalog. + +
+
+

+
+ + Chainsaw replaced and improved upon our bash test framework for testing the RbacDefinition + CRD. + +
+
+

+
+ + Chainsaw cut down on hacks, improved code-reuse and enhanced test debugging. + +
+
+

+
+ + Chainsaw enabled easier e2e testing and CI debugging after replacing kuttl. + +
+
+

+
+ + Chainsaw helped a lot for declarative assertion of Redis Cluster state against various e2e + test. + +
+
+

+
+ + Chainsaw replaced Kuttl, and helped us get rid of many unreadable bash scripts. + +
+
+

+
+ + Chainsaw replaced Kuttl, helped us improve logging and remove native kubectl calls. + +
+
+

+
+ + Chainsaw cranked up the tempo, making our e2e tests dance to a rhythm of reliability and + efficiency. + +
+
+

+
+ + Chainsaw replaced Kuttl and made our e2e tests much more readable and easier to debug. + +
+
+ +
+
+
+
+
+
+
+

+ Discover Chainsaw in video + +

+

+ + {% include ".icons/fontawesome/regular/lightbulb.svg" %} + + Watch this excellent review to discover the unique power of Chainsaw +

+
+
+ +
+
+
+
+{% endblock %} +{% block main %}{% endblock %} +{% block content %}{% endblock %} \ No newline at end of file diff --git a/0.2.3/overrides/main.html b/0.2.3/overrides/main.html new file mode 100644 index 000000000..e070e95c3 --- /dev/null +++ b/0.2.3/overrides/main.html @@ -0,0 +1,30 @@ +{% extends "base.html" %} + +{% block outdated %} + You're not viewing the latest version. + + Click here to go to latest. + +{% endblock %} + +{% block extrahead %} +{% set title = config.site_name %} +{% if page and page.title and not page.is_homepage %} +{% set title = config.site_name ~ " - " ~ page.title | striptags %} +{% endif %} +{% set image = config.site_url ~ '/static/card.png' %} + + + + + + + + + + + + + + +{% endblock %} diff --git a/0.2.3/quick-start/assertion-trees/index.html b/0.2.3/quick-start/assertion-trees/index.html new file mode 100644 index 000000000..ed694ea70 --- /dev/null +++ b/0.2.3/quick-start/assertion-trees/index.html @@ -0,0 +1,131 @@ + Use assertions - Chainsaw

Use assertions

Chainsaw allows declaring complex assertions with a simple and no-code approach, allowing assertions based on comparisons beyond simple equality, working with arrays, and other scenarios that could not be achieved before.

Tip

Under the hood, Chainsaw uses kyverno-json assertion trees. Refer to the assertion trees documentation for more details on the supported syntax.

Basic assertion

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - assert:
+        resource:
+          apiVersion: apps/v1
+          kind: Deployment
+          metadata:
+            name: coredns
+            namespace: kube-system
+          spec:
+            replicas: 2
+

When asking Chainsaw to execute the assertion above, it will look for a deployment named coredns in the kube-system namespace and will compare the existing resource with the (partial) resource definition contained in the assertion.

In this specific case, if the field spec.replicas is set to 2 in the existing resource, the assertion will be considered valid. If it is not equal to 2 the assertion will be considered failed.

This is the most basic assertion Chainsaw can evaluate.

Slightly less basic assertion

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - assert:
+        resource:
+          apiVersion: apps/v1
+          kind: Deployment
+          metadata:
+            labels:
+              k8s-app: kube-dns
+            namespace: kube-system
+          spec:
+            replicas: 2
+

This time we are not providing a resource name.

Chainsaw will look up all deployments with the k8s-app: kube-dns label in the kube-system namespace. The assertion will be considered valid if at least one deployment matches the (partial) resource definition contained in the assertion. If none match, the assertion will be considered failed.

Apart from the resource lookup process being a little bit more interesting, this kind of assertion is essentially the same as the previous one. Chainsaw is basically making a decision by comparing an actual and expected resource.

Beyond simple equality

The assertion below will check that the number of replicas for a deployment is greater than 1 AND less than 4.

Chainsaw doesn't need to know the exact expected number of replicas. The (replicas > 1 && replicas < 4) expression will be evaluated until the result is true or the operation timeout expires (making the assertion fail).

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - assert:
+        resource:
+          apiVersion: apps/v1
+          kind: Deployment
+          metadata:
+            name: coredns
+            namespace: kube-system
+          spec:
+            (replicas > `1` && replicas < `4`): true
+

Tip

To indicate that a key or value in the YAML document is an expression, simply place the element between parentheses:

  • this is an expression -> interpreted as a string
  • (this is an expression) -> interpreted as a JMESPath expression

Working with arrays

Chainsaw query language makes it easy to assert on arrays. You can filter and transform arrays to select what you want to assert.

Filtering

In the example below we are creating a resource, then we assert that a condition with type == 'Ready' exists and has a field matching status: 'True':

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - apply:
+        resource:
+          apiVersion: tempo.grafana.com/v1alpha1
+          kind: TempoStack
+          metadata:
+            name: simplest
+          spec:
+            storage:
+              secret:
+                name: minio
+                type: s3
+            ...
+    - assert:
+        resource:
+          apiVersion: tempo.grafana.com/v1alpha1
+          kind: TempoStack
+          metadata:
+            name: simplest
+          status:
+            # filter conditions array to keep elements where `type == 'Ready'`
+            # and assert there's a single element matching the filter
+            # and that this element status is `True`
+            (conditions[?type == 'Ready']):
+            - status: 'True'
+

Iterating

Being able to filter arrays allows selecting the elements to be processed.

On top of that, Chainsaw allows iterating over array elements to validate each item separately.

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - assert:
+        resource:
+          apiVersion: apps/v1
+          kind: Deployment
+          metadata:
+            labels:
+              k8s-app: kube-dns
+            namespace: kube-system
+          spec:
+            template:
+              spec:
+                # the `~` modifier tells Chainsaw to iterate over the array elements
+                ~.(containers):
+                  securityContext: {}
+

This assertion uses the ~ modifier and Chainsaw will evaluate descendants once per element in the array.

Comprehensive reporting

Chainsaw offers detailed resource diffs upon assertion failures.

In the example below, the assertion failure message (metadata.annotations.foo: Invalid value: "null": Expected value: "bar") is augmented with a resource diff.

It provides a clear view of discrepancies between expected and actual resources and gives more context around the specific failure (we can easily identify the owner of the offending pod for example).

| 09:55:50 | deployment | step-1   | ASSERT    | RUN   | v1/Pod @ chainsaw-rare-liger/*
+| 09:56:20 | deployment | step-1   | ASSERT    | ERROR | v1/Pod @ chainsaw-rare-liger/*
+    === ERROR
+    ---------------------------------------------------
+    v1/Pod/chainsaw-rare-liger/example-5477b4ff8c-tnhd9
+    ---------------------------------------------------
+    * metadata.annotations.foo: Invalid value: "null": Expected value: "bar"
+
+    --- expected
+    +++ actual
+    @@ -1,10 +1,16 @@
+      apiVersion: v1
+      kind: Pod
+      metadata:
+    -  annotations:
+    -    foo: bar
+        labels:
+          app: nginx
+    +  name: example-5477b4ff8c-tnhd9
+        namespace: chainsaw-rare-liger
+    +  ownerReferences:
+    +  - apiVersion: apps/v1
+    +    blockOwnerDeletion: true
+    +    controller: true
+    +    kind: ReplicaSet
+    +    name: example-5477b4ff8c
+    +    uid: 118abe16-ec42-4894-83db-64479c4aac6f
+      spec: {}
+| 09:56:20 | deployment | step-1   | TRY       | DONE  |
+

Next step

To continue our exploration of the main Chainsaw features, let's look at bindings and resource templating next.

\ No newline at end of file diff --git a/0.2.3/quick-start/bindings/index.html b/0.2.3/quick-start/bindings/index.html new file mode 100644 index 000000000..35e99e4e4 --- /dev/null +++ b/0.2.3/quick-start/bindings/index.html @@ -0,0 +1,41 @@ + Use bindings - Chainsaw

Use bindings

You can think of bindings as a side context where you can store and retrieve data based on keys.

This is particularly useful when some data is only known at runtime. For example, to pass data from one operation to another, to implement resource templating, to fetch data from an external system, etc.

Chainsaw offers some built-in bindings you can directly use in your tests but you can also create your own bindings if needed.

Inheritance

Bindings can be configured at the test, step or operation level.

All bindings configured at a given level are automatically inherited in child levels.

JMESPath

Chainsaw uses the JMESPath language, and bindings are implemented using lexical scoping.

Immutability

Bindings are immutable. This means two bindings can have the same name without overwriting each other.

When a binding is registered it potentially hides other bindings with the same name.

When this binding goes out of scope, previously registered bindings with the same name become visible again.

Built-in bindings

The $namespace binding is a good example of a built-in binding provided by Chainsaw. It contains the name of the ephemeral namespace used to execute a test (by default Chainsaw will create an ephemeral namespace for each test).

In the operation below, we are assigning the value of the $namespace binding to an environment variable, and echo it in a script:

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - script:
+        env:
+          # assign the value of the `$namespace` binding
+          # to the environment variable `FOO`
+        - name: FOO
+          value: ($namespace)
+        content: echo $FOO
+

Custom bindings

On top of built-in bindings, you can also create your own ones, combine bindings together, call JMESPath functions using bindings as arguments, etc.

In the test below we create custom bindings at different levels in the test, combine them by calling the join function, assign the result to an environment variable, and echo it in a script:

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  # bindings can be declared at the test level
+  bindings:
+  - name: chainsaw
+    value: chainsaw
+  steps:
+    # bindings can also be declared at the step level
+  - bindings:
+    - name: hello
+      value: hello
+    try:
+    - script:
+        # bindings can also be declared at the operation level
+        bindings:
+        - name: awesome
+          value: awesome
+        env:
+          # combined bindings together using the `join` functions and
+          # assign the result to the GREETINGS environment variable
+        - name: GREETINGS
+          value: (join(' ', [$hello, $chainsaw, 'is', $awesome]))
+        content: echo $GREETINGS
+

Next step

Let's see how bindings can be useful with resource templating.

\ No newline at end of file diff --git a/0.2.3/quick-start/cleanup/index.html b/0.2.3/quick-start/cleanup/index.html new file mode 100644 index 000000000..a44cb152d --- /dev/null +++ b/0.2.3/quick-start/cleanup/index.html @@ -0,0 +1,55 @@ + Control your cleanup - Chainsaw

Control your cleanup

Unless configured differently, by default Chainsaw will automatically remove the resources it created after a test finishes.

Cleanup happens in reverse order of creation (created last, cleaned up first). This is important, especially when the controller being tested makes use of finalizers.

Overriding cleanup timeout

Note that Chainsaw performs a blocking deletion, that is, it will wait until the resource is not present anymore in the cluster before proceeding with the next resource cleanup.

Timeout

A global cleanup timeout can be defined at the configuration level or using command line flags.

It can also be overridden on a per-test or per-step basis but not at the operation level.

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  timeouts:
+    # cleanup timeout at the test level
+    cleanup: 30s
+  steps:
+  - timeouts:
+      # cleanup timeout at the step level
+      cleanup: 2m
+    try: ...
+

Automatic cleanup

After a test, every resource created by Chainsaw will be automatically deleted. This applies to create and apply operations.

In the logs below we can see Chainsaw deletes the previously created resource:

    | 15:21:29 | quick-start | @setup   | CREATE    | OK    | v1/Namespace @ chainsaw-cute-cod
+    | 15:21:29 | quick-start | step-1   | TRY       | RUN   |
+    | 15:21:29 | quick-start | step-1   | APPLY     | RUN   | v1/ConfigMap @ chainsaw-cute-cod/chainsaw-quick-start
+    | 15:21:29 | quick-start | step-1   | CREATE    | OK    | v1/ConfigMap @ chainsaw-cute-cod/chainsaw-quick-start
+    | 15:21:29 | quick-start | step-1   | APPLY     | DONE  | v1/ConfigMap @ chainsaw-cute-cod/chainsaw-quick-start
+    | 15:21:29 | quick-start | step-1   | ASSERT    | RUN   | v1/ConfigMap @ chainsaw-cute-cod/chainsaw-quick-start
+    | 15:21:29 | quick-start | step-1   | ASSERT    | DONE  | v1/ConfigMap @ chainsaw-cute-cod/chainsaw-quick-start
+    | 15:21:29 | quick-start | step-1   | TRY       | DONE  |
+    === step cleanup process start ===
+    | 15:21:29 | quick-start | step-1   | CLEANUP   | RUN   |
+    | 15:21:29 | quick-start | step-1   | DELETE    | RUN   | v1/ConfigMap @ chainsaw-cute-cod/chainsaw-quick-start
+    | 15:21:29 | quick-start | step-1   | DELETE    | OK    | v1/ConfigMap @ chainsaw-cute-cod/chainsaw-quick-start
+    | 15:21:29 | quick-start | step-1   | DELETE    | DONE  | v1/ConfigMap @ chainsaw-cute-cod/chainsaw-quick-start
+    | 15:21:29 | quick-start | step-1   | CLEANUP   | DONE  |
+    === step cleanup process end ===
+    === test cleanup process start ===
+    | 15:21:29 | quick-start | @cleanup | DELETE    | RUN   | v1/Namespace @ chainsaw-cute-cod
+    | 15:21:29 | quick-start | @cleanup | DELETE    | OK    | v1/Namespace @ chainsaw-cute-cod
+    | 15:21:34 | quick-start | @cleanup | DELETE    | DONE  | v1/Namespace @ chainsaw-cute-cod
+    === test cleanup process end ===
+

Manual cleanup

Under certain circumstances, automatic cleanup is not enough and we want to execute custom operations.

Chainsaw allows registering cleanup operations that will be run after automatic cleanup. Custom cleanup operations live at the test step level:

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+    # this step will create a local cluster
+  - try:
+    - script:
+        timeout: 1m
+        content: |
+          kind create cluster --name dynamic --kubeconfig ./dynamic
+    # at cleanup time, we want to delete the local cluster we created
+    # and remove the associated kubeconfig
+    cleanup:
+    - script:
+        content: |
+          kind delete cluster --name dynamic
+    - script:
+        content: |
+          rm -f ./dynamic
+

Next step

At this point, we covered the main Chainsaw features.

Look at the next steps section to find out what to do next.

\ No newline at end of file diff --git a/0.2.3/quick-start/completion/index.html b/0.2.3/quick-start/completion/index.html new file mode 100644 index 000000000..9a50eecaa --- /dev/null +++ b/0.2.3/quick-start/completion/index.html @@ -0,0 +1 @@ + Shell completion - Chainsaw

Shell completion

Once installed, use chainsaw completion command to generate and register the autocompletion script for the specified shell.

Supported shells are:

\ No newline at end of file diff --git a/0.2.3/quick-start/first-test/index.html b/0.2.3/quick-start/first-test/index.html new file mode 100644 index 000000000..4002d1011 --- /dev/null +++ b/0.2.3/quick-start/first-test/index.html @@ -0,0 +1,33 @@ + Create a test - Chainsaw

Create a test

To create a Chainsaw test all you need to do is to create one (or more) YAML file(s).

The recommended approach is to create one folder per test, with a chainsaw-test.yaml file containing one (or more) test definition(s). The test definition can reference other files in the same folder or anywhere else on the file system as needed.

Tip

While chainsaw supports other syntaxes, we strongly recommend the explicit approach.

What is a test?

To put it simply, a test can be represented as an ordered sequence of test steps.

In turn, a test step can be represented as an ordered sequence of operations.

When one of the operations fails the test is considered failed.

If all operations succeed the test is considered successful.

Let's write our first test

For this quick start, we will create a (very simple) Test with one step and two operations:

  1. Create a ConfigMap from a manifest
  2. Verify the ConfigMap was created and contains the expected data

Follow the instructions below to create the folder and files defining our first test.

Create a test folder

# create test folder
+mkdir chainsaw-quick-start
+
+# enter test folder
+cd chainsaw-quick-start
+

Create a ConfigMap manifest

# create a ConfigMap
+cat > configmap.yaml << EOF
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: chainsaw-quick-start
+data:
+  foo: bar
+EOF
+

Create a test manifest

By default, Chainsaw will look for a file named chainsaw-test.yaml in every folder.

# create test file
+cat > chainsaw-test.yaml << EOF
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: quick-start
+spec:
+  steps:
+  - try:
+    # first operation: create the config map
+    - apply:
+        # file is relative to the test folder
+        file: configmap.yaml
+    # second operation: verify the config map exists and contains the expected data
+    - assert:
+        # file is relative to the test folder
+        file: configmap.yaml
+EOF
+

Next step

Now we have created our first test, you can continue to the next section to execute it.

\ No newline at end of file diff --git a/0.2.3/quick-start/index.html b/0.2.3/quick-start/index.html new file mode 100644 index 000000000..fca91c55d --- /dev/null +++ b/0.2.3/quick-start/index.html @@ -0,0 +1 @@ + Getting started - Chainsaw

Getting started

Chainsaw is a tool primarily developed to run end-to-end tests in Kubernetes clusters.

It is meant to test Kubernetes operators work as expected by running a sequence of steps and asserting various conditions.

Why we made it?

While developing Kyverno we need to run end-to-end tests to make sure our admission controller works as expected.

A typical Kyverno end-to-end test

Kyverno can validate, mutate and generate resources based on policies installed in a cluster and a typical test is:

  1. Create a policy
  2. Create a resource
  3. Check that Kyverno acted as expected
  4. Cleanup and move to the next test

How to use it?

Chainsaw is built with CI tools in mind - you only really need to download and execute it in your build script.

However, installing it on your local machine is entirely possible.

\ No newline at end of file diff --git a/0.2.3/quick-start/install/index.html b/0.2.3/quick-start/install/index.html new file mode 100644 index 000000000..298569034 --- /dev/null +++ b/0.2.3/quick-start/install/index.html @@ -0,0 +1,23 @@ + Installation - Chainsaw

Installation

You can install the pre-compiled binary (in several ways), compile from sources, or run with Docker.

We also provide a GitHub action to easily install Chainsaw in your workflows.

Install the pre-compiled binary

Homebrew tap

add tap:

brew tap kyverno/chainsaw https://github.com/kyverno/chainsaw
+

install chainsaw:

brew install kyverno/chainsaw/chainsaw
+

Don't forget to specify the tap name

Homebrew core already has a tool named chainsaw.

Be sure that you specify the tap name when installing to install the right tool.

Manually

Download the pre-compiled binaries for your system from the releases page and copy them to the desired location.

Install using go install

You can install with go install with:

go install github.com/kyverno/chainsaw@latest
+

Run with Docker

Chainsaw is also available as a Docker image which you can pull and run:

docker pull ghcr.io/kyverno/chainsaw:<version>
+

Warning

Since Chainsaw relies on files for its operation (like test definitions), you will need to bind mount the necessary directories when running it via Docker.

docker run --rm                             \
+    -v ./testdata/e2e/:/chainsaw/           \
+    -v ${HOME}/.kube/:/etc/kubeconfig/      \
+    -e KUBECONFIG=/etc/kubeconfig/config    \
+    --network=host                          \
+    ghcr.io/kyverno/chainsaw:<version>      \
+    test /chainsaw --config /chainsaw/config.yaml
+

Compile from sources

clone:

git clone https://github.com/kyverno/chainsaw.git
+

build the binaries:

cd chainsaw
+go mod tidy
+make build
+

verify it works:

./chainsaw version
+

Install using Nix Package

To install kyverno-chainsaw, refer to the documentation.

On NixOS

nix-env -iA nixos.kyverno-chainsaw
+

On Non-NixOS

nix-env -iA nixpkgs.kyverno-chainsaw
+

Warning

Using nix-env permanently modifies a local profile of installed packages. This must be updated and maintained by the user in the same way as with a traditional package manager, foregoing many of the benefits that make Nix uniquely powerful. Using nix-shell or a NixOS configuration is recommended instead.

Using NixOS Configuration

Add the following Nix code to your NixOS Configuration, usually located in /etc/nixos/configuration.nix :

environment.systemPackages = [
+  pkgs.kyverno-chainsaw
+];
+

Using nix-shell

A nix-shell will temporarily modify your $PATH environment variable. This can be used to try a piece of software before deciding to permanently install it. Use the following command to install kyverno-chainsaw :

nix-shell -p kyverno-chainsaw
+

GitHub action

A GitHub action is available to install Chainsaw in your workflows. See the GitHub action dedicated documentation.

\ No newline at end of file diff --git a/0.2.3/quick-start/next-steps/index.html b/0.2.3/quick-start/next-steps/index.html new file mode 100644 index 000000000..372278860 --- /dev/null +++ b/0.2.3/quick-start/next-steps/index.html @@ -0,0 +1 @@ + Next steps - Chainsaw

Next steps

We covered the main features of Chainsaw in this Getting started sections.

While this should help you understand Chainsaw better, there are a lot of other things Chainsaw can do for you.

Tip

If there's anything you would like to be improved, please reach out, we will be happy to discuss and improve as much as we can.

To continue exploring the capabilities of Chainsaw:

\ No newline at end of file diff --git a/0.2.3/quick-start/operation-outputs/index.html b/0.2.3/quick-start/operation-outputs/index.html new file mode 100644 index 000000000..58b1d046d --- /dev/null +++ b/0.2.3/quick-start/operation-outputs/index.html @@ -0,0 +1,40 @@ + Use operation outputs - Chainsaw

Use operation outputs

Operation outputs can be useful for communicating and reusing computation results across operations.

Lifetime of outputs

Once an output has been added to the bindings context, this binding will be available to all following operations in the same step.

Currently, outputs do not cross the step boundaries.

Matching

An output supports an optional match field. The match is used to conditionally create a binding.

In the case of applying a file, for example, the file may contain multiple resources. The match can be used to select the resource to use for creating the binding.

Load an existing resource

The example below invokes a kubectl command to get a configmap from the cluster in json format.

The json output is then parsed and added to the $cm binding and the next operation performs an assertion on it by reading the binding instead of querying the cluster.

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - script:
+        content: kubectl get cm quick-start -n $NAMESPACE -o json
+        outputs:
+          # parse stdout json output and bind the result to `$cm`
+        - name: cm
+          value: (json_parse($stdout))
+    - assert:
+        resource:
+          ($cm):
+            metadata:
+              (uid != null): true
+

Match a resource

The example below applies resources from a file.

When the resource being applied is a configmap, we bind the resource to an output to print its UID in the next operation.

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - apply:
+        file: ./resources.yaml
+        outputs:
+          # match the configmap resource and bind it to `$cm`
+        - match:
+            apiVersion: v1
+            kind: ConfigMap
+          name: cm
+          value: (@)
+    - script:
+        env:
+        - name: UID
+          value: ($cm.metadata.uid)
+        content: echo $UID
+

Next step

In the next section, we will look at the three main elements of a test step, the try, catch and finally blocks.

\ No newline at end of file diff --git a/0.2.3/quick-start/resource-templating/index.html b/0.2.3/quick-start/resource-templating/index.html new file mode 100644 index 000000000..fc00a873f --- /dev/null +++ b/0.2.3/quick-start/resource-templating/index.html @@ -0,0 +1,29 @@ + Use resource templating - Chainsaw

Use resource templating

Chainsaw simplifies dynamic resource configuration with native resource templating support.

Sometimes things we need to create resources or assertions are only known at runtime.

In the past, users have created all sorts of hacks using tools like envsubst for dynamic substitution of env-variables. Those workarounds usually lack flexibility and introduce new problems like hiding the real resources from Chainsaw, preventing it from cleaning resources properly.

Tip

Resource templating is heavily based on bindings and uses JMESPath language.

Leverage bindings

In the template below, we are using the $namespace binding at two different places, effectively injecting the ephemeral namespace name in the name and the data.foo fields:

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - assert:
+      resource:
+        apiVersion: v1
+        kind: ConfigMap
+        metadata:
+          name: ($namespace)
+        data:
+          foo: ($namespace)
+

Leverage JMESPath

In the template below, we are using the JMESPath join function to create a unique resource name:

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - apply:
+      resource:
+        apiVersion: v1
+        kind: ConfigMap
+        metadata:
+          name: (join('-', [$namespace, 'cm']))
+        data:
+          foo: bar
+

Next step

Combining bindings and templates with operation outputs allows even more flexibility to pass arbitrary data from one operation to another.

\ No newline at end of file diff --git a/0.2.3/quick-start/resources/index.html b/0.2.3/quick-start/resources/index.html new file mode 100644 index 000000000..e0a3d65f0 --- /dev/null +++ b/0.2.3/quick-start/resources/index.html @@ -0,0 +1 @@ + Additional resources - Chainsaw
\ No newline at end of file diff --git a/0.2.3/quick-start/run-tests/index.html b/0.2.3/quick-start/run-tests/index.html new file mode 100644 index 000000000..8030f2ee6 --- /dev/null +++ b/0.2.3/quick-start/run-tests/index.html @@ -0,0 +1,54 @@ + Run tests - Chainsaw

Run tests

After installing chainsaw and writing tests, the next natural step is to run Chainsaw to execute the tests.

Create a local cluster

To use Chainsaw you will need a Kubernetes cluster, Chainsaw won't create one for you.

Not a cluster management tool

We consider this is not the responsibility of Chainsaw to manage clusters. There are plenty of solutions to create and manage local clusters that will do that better than Chainsaw.

The command below will create a local cluster using kind. Use the tool of your choice or directly jump to the next section if you already have a KUBECONFIG configured and pointing to a valid cluster.

# create cluster
+kind create cluster --image "kindest/node:v1.29.4"
+

Run Chainsaw

Now you can run the chainsaw test command.

> chainsaw test
+
+Version: (devel)
+Loading default configuration...
+- Using test file: chainsaw-test.yaml
+- TestDirs [.]
+- SkipDelete false
+- FailFast false
+- ReportFormat ''
+- ReportName ''
+- Namespace ''
+- FullName false
+- IncludeTestRegex ''
+- ExcludeTestRegex ''
+- ApplyTimeout 5s
+- AssertTimeout 30s
+- CleanupTimeout 30s
+- DeleteTimeout 15s
+- ErrorTimeout 30s
+- ExecTimeout 5s
+Loading tests...
+- quick-start (.)
+Running tests...
+=== RUN   chainsaw
+=== PAUSE chainsaw
+=== CONT  chainsaw
+=== RUN   chainsaw/quick-start
+=== PAUSE chainsaw/quick-start
+=== CONT  chainsaw/quick-start
+    | 10:44:26 | quick-start | @setup   | CREATE    | OK    | v1/Namespace @ chainsaw-immense-jay
+    | 10:44:26 | quick-start | step-1   | TRY       | RUN   |
+    | 10:44:26 | quick-start | step-1   | APPLY     | RUN   | v1/ConfigMap @ chainsaw-immense-jay/chainsaw-quick-start
+    | 10:44:26 | quick-start | step-1   | CREATE    | OK    | v1/ConfigMap @ chainsaw-immense-jay/chainsaw-quick-start
+    | 10:44:26 | quick-start | step-1   | APPLY     | DONE  | v1/ConfigMap @ chainsaw-immense-jay/chainsaw-quick-start
+    | 10:44:26 | quick-start | step-1   | ASSERT    | RUN   | v1/ConfigMap @ chainsaw-immense-jay/chainsaw-quick-start
+    | 10:44:26 | quick-start | step-1   | ASSERT    | DONE  | v1/ConfigMap @ chainsaw-immense-jay/chainsaw-quick-start
+    | 10:44:26 | quick-start | step-1   | TRY       | DONE  |
+    | 10:44:26 | quick-start | @cleanup | DELETE    | RUN   | v1/ConfigMap @ chainsaw-immense-jay/chainsaw-quick-start
+    | 10:44:26 | quick-start | @cleanup | DELETE    | OK    | v1/ConfigMap @ chainsaw-immense-jay/chainsaw-quick-start
+    | 10:44:26 | quick-start | @cleanup | DELETE    | DONE  | v1/ConfigMap @ chainsaw-immense-jay/chainsaw-quick-start
+    | 10:44:26 | quick-start | @cleanup | DELETE    | RUN   | v1/Namespace @ chainsaw-immense-jay
+    | 10:44:26 | quick-start | @cleanup | DELETE    | OK    | v1/Namespace @ chainsaw-immense-jay
+    | 10:44:31 | quick-start | @cleanup | DELETE    | DONE  | v1/Namespace @ chainsaw-immense-jay
+--- PASS: chainsaw (0.00s)
+    --- PASS: chainsaw/quick-start (5.25s)
+PASS
+Tests Summary...
+- Passed  tests 1
+- Failed  tests 0
+- Skipped tests 0
+Done.
+

Tip

Chainsaw expects a path to the test folder and will discover tests by analyzing files recursively. When no path is provided Chainsaw will use the current path by default (.).

Next step

The test above demonstrates the most basic usage of Chainsaw. In the next sections, we will look at the main features that make Chainsaw a very unique tool.

\ No newline at end of file diff --git a/0.2.3/quick-start/timeouts/index.html b/0.2.3/quick-start/timeouts/index.html new file mode 100644 index 000000000..96b946505 --- /dev/null +++ b/0.2.3/quick-start/timeouts/index.html @@ -0,0 +1,108 @@ + Control your timeouts - Chainsaw

Control your timeouts

Timeouts in Chainsaw are specified per type of operation. This is handy because the timeout varies greatly depending on the nature of an operation.

For example, applying a manifest in a cluster is expected to be reasonably fast, while validating a resource can be a long operation.

Inheritance

Timeouts can be configured globally and at the test, step or individual operation level.

All timeouts configured at a given level are automatically inherited in child levels. When looking up a timeout, the most specific one takes precedence over the others.

Info

To learn more about timeouts and how to configure global values, see the timeouts configuration page.

At the test level

When a timeout is configured at the test level it will apply to all operations and steps in the test, unless overridden at a more specific level.

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  # timeouts configured at the test level will apply to all operations and steps
+  # unless overriden at the step level and/or individual operation level
+  timeouts:
+    apply: 5s
+    assert: 1m
+    # ...
+  steps:
+  - try:
+    - apply:
+        resource:
+          apiVersion: tempo.grafana.com/v1alpha1
+          kind: TempoStack
+          metadata:
+            name: simplest
+          spec:
+            storage:
+              secret:
+                name: minio
+                type: s3
+            # ...
+    - assert:
+        resource:
+          apiVersion: tempo.grafana.com/v1alpha1
+          kind: TempoStack
+          metadata:
+            name: simplest
+          status:
+            (conditions[?type == 'Ready']):
+            - status: 'True'
+

At the step level

When a timeout is configured at the step level it will apply to all operations in the step, unless overridden at a more specific level.

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+    # timeouts configured at the step level will apply to all operations
+    # in the step unless overriden at the individual operation level
+  - timeouts:
+      apply: 5s
+      # ...
+    try:
+    - apply:
+        resource:
+          apiVersion: tempo.grafana.com/v1alpha1
+          kind: TempoStack
+          metadata:
+            name: simplest
+          spec:
+            storage:
+              secret:
+                name: minio
+                type: s3
+            # ...
+    # timeouts configured at the step level will apply to all operations
+    # in the step unless overriden at the individual operation level
+  - timeouts:
+      assert: 1m
+      # ...
+    try:
+    - assert:
+        resource:
+          apiVersion: tempo.grafana.com/v1alpha1
+          kind: TempoStack
+          metadata:
+            name: simplest
+          status:
+            (conditions[?type == 'Ready']):
+            - status: 'True'
+

At the operation level

When a timeout is configured at the operation level, it takes precedence over all timeouts configured at upper levels.

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - apply:
+        # timeout configured at the operation level takes precedence
+        # over timeouts configured at upper levels
+        timeout: 5s
+        resource:
+          apiVersion: tempo.grafana.com/v1alpha1
+          kind: TempoStack
+          metadata:
+            name: simplest
+          spec:
+            storage:
+              secret:
+                name: minio
+                type: s3
+            # ...
+    - assert:
+        # timeout configured at the operation level takes precedence
+        # over timeouts configured at upper levels
+        timeout: 1m
+        resource:
+          apiVersion: tempo.grafana.com/v1alpha1
+          kind: TempoStack
+          metadata:
+            name: simplest
+          status:
+            (conditions[?type == 'Ready']):
+            - status: 'True'
+

Next step

In the next section, we will see how Chainsaw manages cleanup.

\ No newline at end of file diff --git a/0.2.3/quick-start/try-catch/index.html b/0.2.3/quick-start/try-catch/index.html new file mode 100644 index 000000000..d41041916 --- /dev/null +++ b/0.2.3/quick-start/try-catch/index.html @@ -0,0 +1,41 @@ + Use try, catch and finally - Chainsaw

Use try, catch and finally

A test step is made of 3 main blocks used to determine the actions Chainsaw will perform when executing the step, depending on operations outcome.

  • The try block (required)
  • The catch block (optional)
  • The finally block (optional)

Operations defined in the try block are executed first, then:

  • If an operation fails to execute, Chainsaw won't execute the remaining operations and will execute all operations defined in the catch block instead (if any).
  • If all operations succeed, Chainsaw will NOT execute operations defined in the catch block (if any).
  • Regardless of the step outcome (success or failure), Chainsaw will execute all operations defined in the finally block (if any).

Note

Note that all operations coming from the catch or finally blocks are executed. If one operation fails, Chainsaw will mark the test as failed and continue executing with the next operation.

Cleanup

At the end of a test, Chainsaw automatically cleans up the resources created during the test (cleanup is done in the opposite order of creation).

All operations from the catch and finally blocks are executed before the cleanup process kicks in. This order allows analyzing the resources that potentially caused the step failure before they are deleted.

Catch

Operations in a catch block are executed only when the step is considered failed.

This is particularly useful to collect additional information to help understand what caused the failure.

In the example below, the test contains a catch block to collect events in the cluster when an operation fails in the step.

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+  - try:
+    - apply:
+        # ...
+    - assert:
+        # ...
+    # collect events in the `catch` block
+    # will be executed only if an operation failed
+    catch:
+    - events: {}
+

Finally

Operations in a finally block will always execute regardless of the success or failure of the test step.

This is particularly useful to perform manual cleanup.

In the example below we create a local cluster in a script operation. The cluster deletion script is added to the finally block, guaranteeing the cluster will be deleted regardless of the test outcome.

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+    # create a local cluster
+  - try:
+    - script:
+        timeout: 1m
+        content: |
+          kind create cluster --name dynamic --kubeconfig ./dynamic
+    - apply:
+        # ...
+    - assert:
+        # ...
+    # add cluster deletion script in the `finally` block
+    # to guarantee the cluster will be deleted after the test
+    finally:
+    - script:
+        content: |
+          kind delete cluster --name dynamic
+    - script:
+        content: |
+          rm -f ./dynamic
+

Next step

Every operation in a test must be executed in a timely fashion. In the next section, we will see how you can control your timeouts.

\ No newline at end of file diff --git a/0.2.3/reference/apis/chainsaw.v1alpha1/index.html b/0.2.3/reference/apis/chainsaw.v1alpha1/index.html new file mode 100644 index 000000000..b140fc6b9 --- /dev/null +++ b/0.2.3/reference/apis/chainsaw.v1alpha1/index.html @@ -0,0 +1 @@ + chainsaw (v1alpha1) - Chainsaw

v1alpha1

Package v1alpha1 contains API Schema definitions for the v1alpha1 API group.

Resource Types

Configuration

Configuration is the resource that contains the configuration used to run tests.

Field Type Required Inline Description
apiVersion string ✅ chainsaw.kyverno.io/v1alpha1
kind string ✅ Configuration
metadata meta/v1.ObjectMeta

Standard object's metadata.

spec ConfigurationSpec ✅

Configuration spec.

Test

Test is the resource that contains a test definition.

Field Type Required Inline Description
apiVersion string ✅ chainsaw.kyverno.io/v1alpha1
kind string ✅ Test
metadata meta/v1.ObjectMeta

Standard object's metadata.

spec TestSpec ✅

Test spec.

Apply

Appears in:

Apply represents a set of configurations or resources that should be applied during testing.

Field Type Required Inline Description
timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

bindings []Binding

Bindings defines additional binding key/values.

outputs []Output

Outputs defines output bindings.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

FileRefOrResource FileRefOrResource ✅ ✅

FileRefOrResource provides a reference to the resources to be applied.

template bool

Template determines whether resources should be considered for templating.

dryRun bool

DryRun determines whether the file should be applied in dry run mode.

expect []Expectation

Expect defines a list of matched checks to validate the operation outcome.

Assert

Appears in:

Assert represents a test condition that is expected to hold true during the testing process.

Field Type Required Inline Description
timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

bindings []Binding

Bindings defines additional binding key/values.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

FileRefOrCheck FileRefOrCheck ✅ ✅

FileRefOrAssert provides a reference to the assertion.

template bool

Template determines whether resources should be considered for templating.

Binding

Appears in:

Binding represents a key/value set as a binding in an executing test.

Field Type Required Inline Description
name string ✅

Name the name of the binding.

value policy/v1alpha1.Any ✅

Value value of the binding.

Catch

Appears in:

Catch defines actions to be executed on failure.

Field Type Required Inline Description
description string

Description contains a description of the operation.

podLogs PodLogs

PodLogs determines the pod logs collector to execute.

events Events

Events determines the events collector to execute.

describe Describe

Describe determines the resource describe collector to execute.

wait Wait

Wait determines the resource wait collector to execute.

get Get

Get determines the resource get collector to execute.

delete Delete

Delete represents a deletion operation.

command Command

Command defines a command to run.

script Script

Script defines a script to run.

sleep Sleep

Sleep defines zzzz.

Cluster

Appears in:

Cluster defines cluster config and context.

Field Type Required Inline Description
kubeconfig string ✅

Kubeconfig is the path to the referenced file.

context string

Context is the name of the context to use.

Command

Appears in:

Command describes a command to run as a part of a test step.

Field Type Required Inline Description
timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

bindings []Binding

Bindings defines additional binding key/values.

outputs []Output

Outputs defines output bindings.

env []Binding

Env defines additional environment variables.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

entrypoint string ✅

Entrypoint is the command entry point to run.

args []string

Args is the command arguments.

skipLogOutput bool

SkipLogOutput removes the output from the command. Useful for sensitive logs or to reduce noise.

check policy/v1alpha1.Any

Check is an assertion tree to validate the operation outcome.

Condition

Appears in:

Condition represents parameters for waiting on a specific condition of a resource.

Field Type Required Inline Description
name string ✅

Name defines the specific condition to wait for, e.g., "Available", "Ready".

value string

Value defines the specific condition status to wait for, e.g., "True", "False".

ConfigurationSpec

Appears in:

ConfigurationSpec contains the configuration used to run tests.

Field Type Required Inline Description
timeouts Timeouts

Global timeouts configuration. Applies to all tests/test steps if not overridden.

skipDelete bool

If set, do not delete the resources after running the tests (implies SkipClusterDelete).

template bool

Template determines whether resources should be considered for templating.

failFast bool

FailFast determines whether the test should stop upon encountering the first failure.

parallel int

The maximum number of tests to run at once.

deletionPropagationPolicy meta/v1.DeletionPropagation

DeletionPropagationPolicy decides if a deletion will propagate to the dependents of the object, and how the garbage collector will handle the propagation.

reportFormat ReportFormatType

ReportFormat determines test report format (JSON

reportPath string

ReportPath defines the path.

reportName string

ReportName defines the name of report to create. It defaults to "chainsaw-report".

namespace string

Namespace defines the namespace to use for tests. If not specified, every test will execute in a random ephemeral namespace unless the namespace is overridden in a the test spec.

namespaceTemplate policy/v1alpha1.Any

NamespaceTemplate defines a template to create the test namespace.

fullName bool

FullName makes use of the full test case folder path instead of the folder name.

excludeTestRegex string

ExcludeTestRegex is used to exclude tests based on a regular expression.

includeTestRegex string

IncludeTestRegex is used to include tests based on a regular expression.

repeatCount int

RepeatCount indicates how many times the tests should be executed.

testFile string

TestFile is the name of the file containing the test to run. If no extension is provided, chainsaw will try with .yaml first and .yml if needed.

forceTerminationGracePeriod meta/v1.Duration

ForceTerminationGracePeriod forces the termination grace period on pods, statefulsets, daemonsets and deployments.

delayBeforeCleanup meta/v1.Duration

DelayBeforeCleanup adds a delay between the time a test ends and the time cleanup starts.

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

catch []Catch

Catch defines what the tests steps will execute when an error happens. This will be combined with catch handlers defined at the test and step levels.

Create

Appears in:

Create represents a set of resources that should be created. If a resource already exists in the cluster it will fail.

Field Type Required Inline Description
timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

bindings []Binding

Bindings defines additional binding key/values.

outputs []Output

Outputs defines output bindings.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

FileRefOrResource FileRefOrResource ✅ ✅

FileRefOrResource provides a reference to the file containing the resources to be created.

template bool

Template determines whether resources should be considered for templating.

dryRun bool

DryRun determines whether the file should be applied in dry run mode.

expect []Expectation

Expect defines a list of matched checks to validate the operation outcome.

Delete

Appears in:

Delete is a reference to an object that should be deleted

Field Type Required Inline Description
timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

bindings []Binding

Bindings defines additional binding key/values.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

template bool

Template determines whether resources should be considered for templating.

ref ObjectReference ✅

ObjectReference determines objects to be deleted.

expect []Expectation

Expect defines a list of matched checks to validate the operation outcome.

deletionPropagationPolicy meta/v1.DeletionPropagation

DeletionPropagationPolicy decides if a deletion will propagate to the dependents of the object, and how the garbage collector will handle the propagation. Overrides the deletion propagation policy set in the Configuration, the Test and the TestStep.

Deletion

Appears in:

Deletion represents parameters for waiting on a resource's deletion.

Describe

Appears in:

Describe defines how to describe resources.

Field Type Required Inline Description
timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

ResourceReference ResourceReference ✅ ✅

ResourceReference referenced resource type.

ObjectLabelsSelector ObjectLabelsSelector ✅ ✅

ObjectLabelsSelector determines the selection process of referenced objects.

showEvents bool

Show Events indicates whether to include related events.

Error

Appears in:

Error represents an anticipated error condition that may arise during testing. Instead of treating such an error as a test failure, it acknowledges it as expected.

Field Type Required Inline Description
timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

bindings []Binding

Bindings defines additional binding key/values.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

FileRefOrCheck FileRefOrCheck ✅ ✅

FileRefOrAssert provides a reference to the expected error.

template bool

Template determines whether resources should be considered for templating.

Events

Appears in:

Events defines how to collect events.

Field Type Required Inline Description
timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

ObjectLabelsSelector ObjectLabelsSelector ✅ ✅

ObjectLabelsSelector determines the selection process of referenced objects.

format Format

Format determines the output format (json or yaml).

Expectation

Appears in:

Expectation represents a check to be applied on the result of an operation with a match filter to determine if the verification should be considered.

Field Type Required Inline Description
match policy/v1alpha1.Any

Match defines the matching statement.

check policy/v1alpha1.Any ✅

Check defines the verification statement.

FileRef

Appears in:

FileRef represents a file reference.

Field Type Required Inline Description
file string ✅

File is the path to the referenced file. This can be a direct path to a file or an expression that matches multiple files, such as "manifest/*.yaml" for all YAML files within the "manifest" directory.

FileRefOrCheck

Appears in:

FileRefOrCheck represents a file reference or resource.

Field Type Required Inline Description
FileRef FileRef ✅

FileRef provides a reference to the file containing the resources to be applied.

resource policy/v1alpha1.Any

Check provides a check used in assertions.

FileRefOrResource

Appears in:

FileRefOrResource represents a file reference or resource.

Field Type Required Inline Description
FileRef FileRef ✅

FileRef provides a reference to the file containing the resources to be applied.

resource meta/v1/unstructured.Unstructured

Resource provides a resource to be applied.

Finally

Appears in:

Finally defines actions to be executed at the end of a test.

Field Type Required Inline Description
description string

Description contains a description of the operation.

podLogs PodLogs

PodLogs determines the pod logs collector to execute.

events Events

Events determines the events collector to execute.

describe Describe

Describe determines the resource describe collector to execute.

wait Wait

Wait determines the resource wait collector to execute.

get Get

Get determines the resource get collector to execute.

delete Delete

Delete represents a deletion operation.

command Command

Command defines a command to run.

script Script

Script defines a script to run.

sleep Sleep

Sleep defines zzzz.

For

Appears in:

For specifies the condition to wait for.

Field Type Required Inline Description
deletion Deletion

Deletion specifies parameters for waiting on a resource's deletion.

condition Condition

Condition specifies the condition to wait for.

jsonPath JsonPath

JsonPath specifies the json path condition to wait for.

Format

(Alias of string)

Appears in:

Format determines the output format (json or yaml).

Get

Appears in:

Get defines how to get resources.

Field Type Required Inline Description
timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

ResourceReference ResourceReference ✅ ✅

ResourceReference referenced resource type.

ObjectLabelsSelector ObjectLabelsSelector ✅ ✅

ObjectLabelsSelector determines the selection process of referenced objects.

format Format

Format determines the output format (json or yaml).

JsonPath

Appears in:

JsonPath represents parameters for waiting on a json path of a resource.

Field Type Required Inline Description
path string ✅

Path defines the json path to wait for, e.g. '{.status.phase}'.

value string ✅

Value defines the expected value to wait for, e.g., "Running".

ObjectLabelsSelector

Appears in:

ObjectLabelsSelector represents a strategy to select objects. For a single object name and namespace are used to identify the object. For multiple objects use selector.

Field Type Required Inline Description
namespace string

Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/

name string

Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

selector string

Selector defines labels selector.

ObjectReference

Appears in:

ObjectReference represents one or more objects with a specific apiVersion and kind. For a single object name and namespace are used to identify the object. For multiple objects use labels.

Field Type Required Inline Description
ObjectType ObjectType ✅ ✅

ObjectType determines the type of referenced objects.

ObjectSelector ObjectSelector ✅ ✅

ObjectSelector determines the selection process of referenced objects.

ObjectSelector

Appears in:

ObjectSelector represents a strategy to select objects. For a single object name and namespace are used to identify the object. For multiple objects use labels.

Field Type Required Inline Description
namespace string

Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/

name string

Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

labels map[string]string

Label selector to match objects to delete

ObjectType

Appears in:

ObjectType represents a specific apiVersion and kind.

Field Type Required Inline Description
apiVersion string ✅

API version of the referent.

kind string ✅

Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds

Operation

Appears in:

Operation defines a single operation, only one action is permitted for a given operation.

Field Type Required Inline Description
OperationBase OperationBase ✅

OperationBase defines common elements to all operations.

apply Apply

Apply represents resources that should be applied for this test step. This can include things like configuration settings or any other resources that need to be available during the test.

assert Assert

Assert represents an assertion to be made. It checks whether the conditions specified in the assertion hold true.

command Command

Command defines a command to run.

create Create

Create represents a creation operation.

delete Delete

Delete represents a deletion operation.

error Error

Error represents the expected errors for this test step. If any of these errors occur, the test will consider them as expected; otherwise, they will be treated as test failures.

patch Patch

Patch represents a patch operation.

script Script

Script defines a script to run.

sleep Sleep

Sleep defines zzzz.

update Update

Update represents an update operation.

wait Wait

Wait determines the resource wait collector to execute.

OperationBase

Appears in:

OperationBase defines common elements to all operations.

Field Type Required Inline Description
description string

Description contains a description of the operation.

continueOnError bool

ContinueOnError determines whether a test should continue or not in case the operation was not successful. Even if the test continues executing, it will still be reported as failed.

Output

Appears in:

Output represents an output binding with a match to determine if the binding must be considered or not.

Field Type Required Inline Description
Binding Binding ✅ ✅

Binding determines the binding to create when the match succeeds.

match policy/v1alpha1.Any

Match defines the matching statement.

Patch

Appears in:

Patch represents a set of resources that should be patched. If a resource doesn't exist yet in the cluster it will fail.

Field Type Required Inline Description
timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

bindings []Binding

Bindings defines additional binding key/values.

outputs []Output

Outputs defines output bindings.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

FileRefOrResource FileRefOrResource ✅ ✅

FileRefOrResource provides a reference to the file containing the resources to be patched.

template bool

Template determines whether resources should be considered for templating.

dryRun bool

DryRun determines whether the file should be applied in dry run mode.

expect []Expectation

Expect defines a list of matched checks to validate the operation outcome.

PodLogs

Appears in:

PodLogs defines how to collect pod logs.

Field Type Required Inline Description
timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

ObjectLabelsSelector ObjectLabelsSelector ✅ ✅

ObjectLabelsSelector determines the selection process of referenced objects.

container string

Container in pod to get logs from else --all-containers is used.

tail int

Tail is the number of last lines to collect from pods. If omitted or zero, then the default is 10 if you use a selector, or -1 (all) if you use a pod name. This matches default behavior of kubectl logs.

ReportFormatType

(Alias of string)

Appears in:

ResourceReference

Appears in:

ResourceReference represents a resource (API), it can be represented with a resource or a kind. Optionally an apiVersion can be specified.

Field Type Required Inline Description
apiVersion string

API version of the referent.

kind string

Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds

resource string

Resource name of the referent.

Script

Appears in:

Script describes a script to run as a part of a test step.

Field Type Required Inline Description
timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

bindings []Binding

Bindings defines additional binding key/values.

outputs []Output

Outputs defines output bindings.

env []Binding

Env defines additional environment variables.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

content string

Content defines a shell script (run with "sh -c ...").

skipLogOutput bool

SkipLogOutput removes the output from the command. Useful for sensitive logs or to reduce noise.

check policy/v1alpha1.Any

Check is an assertion tree to validate the operation outcome.

Sleep

Appears in:

Sleep represents a duration while nothing happens.

Field Type Required Inline Description
duration meta/v1.Duration ✅

Duration is the delay used for sleeping.

TestSpec

Appears in:

TestSpec contains the test spec.

Field Type Required Inline Description
description string

Description contains a description of the test.

timeouts Timeouts

Timeouts for the test. Overrides the global timeouts set in the Configuration on a per operation basis.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

skip bool

Skip determines whether the test should skipped.

concurrent bool

Concurrent determines whether the test should run concurrently with other tests.

skipDelete bool

SkipDelete determines whether the resources created by the test should be deleted after the test is executed.

template bool

Template determines whether resources should be considered for templating.

namespace string

Namespace determines whether the test should run in a random ephemeral namespace or not.

namespaceTemplate policy/v1alpha1.Any

NamespaceTemplate defines a template to create the test namespace.

bindings []Binding

Bindings defines additional binding key/values.

steps []TestStep ✅

Steps defining the test.

catch []Catch

Catch defines what the steps will execute when an error happens. This will be combined with catch handlers defined at the step level.

forceTerminationGracePeriod meta/v1.Duration

ForceTerminationGracePeriod forces the termination grace period on pods, statefulsets, daemonsets and deployments.

delayBeforeCleanup meta/v1.Duration

DelayBeforeCleanup adds a delay between the time a test ends and the time cleanup starts.

deletionPropagationPolicy meta/v1.DeletionPropagation

DeletionPropagationPolicy decides if a deletion will propagate to the dependents of the object, and how the garbage collector will handle the propagation. Overrides the deletion propagation policy set in the Configuration.

TestStep

Appears in:

TestStep contains the test step definition used in a test spec.

Field Type Required Inline Description
name string

Name of the step.

TestStepSpec TestStepSpec ✅ ✅

TestStepSpec of the step.

TestStepSpec

Appears in:

TestStepSpec defines the desired state and behavior for each test step.

Field Type Required Inline Description
description string

Description contains a description of the test step.

timeouts Timeouts

Timeouts for the test step. Overrides the global timeouts set in the Configuration and the timeouts eventually set in the Test.

deletionPropagationPolicy meta/v1.DeletionPropagation

DeletionPropagationPolicy decides if a deletion will propagate to the dependents of the object, and how the garbage collector will handle the propagation. Overrides the deletion propagation policy set in both the Configuration and the Test.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

skipDelete bool

SkipDelete determines whether the resources created by the step should be deleted after the test step is executed.

template bool

Template determines whether resources should be considered for templating.

bindings []Binding

Bindings defines additional binding key/values.

try []Operation ✅

Try defines what the step will try to execute.

catch []Catch

Catch defines what the step will execute when an error happens.

finally []Finally

Finally defines what the step will execute after the step is terminated.

cleanup []Finally

Cleanup defines what will be executed after the test is terminated.

Timeouts

Appears in:

Timeouts contains timeouts per operation.

Field Type Required Inline Description
apply meta/v1.Duration ✅

Apply defines the timeout for the apply operation

assert meta/v1.Duration ✅

Assert defines the timeout for the assert operation

cleanup meta/v1.Duration ✅

Cleanup defines the timeout for the cleanup operation

delete meta/v1.Duration ✅

Delete defines the timeout for the delete operation

error meta/v1.Duration ✅

Error defines the timeout for the error operation

exec meta/v1.Duration ✅

Exec defines the timeout for exec operations

Update

Appears in:

Update represents a set of resources that should be updated. If a resource does not exist in the cluster it will fail.

Field Type Required Inline Description
timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

bindings []Binding

Bindings defines additional binding key/values.

outputs []Output

Outputs defines output bindings.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

FileRefOrResource FileRefOrResource ✅ ✅

FileRefOrResource provides a reference to the file containing the resources to be created.

template bool

Template determines whether resources should be considered for templating.

dryRun bool

DryRun determines whether the file should be applied in dry run mode.

expect []Expectation

Expect defines a list of matched checks to validate the operation outcome.

Wait

Appears in:

Wait specifies how to perform wait operations on resources.

Field Type Required Inline Description
timeout meta/v1.Duration

Timeout for the operation. Specifies how long to wait for the condition to be met before timing out.

cluster string

Cluster defines the target cluster where the wait operation will be performed (default cluster will be used if not specified).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

ResourceReference ResourceReference ✅ ✅

ResourceReference referenced resource type.

ObjectLabelsSelector ObjectLabelsSelector ✅ ✅

ObjectLabelsSelector determines the selection process of referenced objects.

for For ✅

For specifies the condition to wait for.

format Format

Format determines the output format (json or yaml).

\ No newline at end of file diff --git a/0.2.3/reference/apis/chainsaw.v1alpha2/index.html b/0.2.3/reference/apis/chainsaw.v1alpha2/index.html new file mode 100644 index 000000000..c09f8df4f --- /dev/null +++ b/0.2.3/reference/apis/chainsaw.v1alpha2/index.html @@ -0,0 +1 @@ + chainsaw (v1alpha2) - Chainsaw

v1alpha2

Package v1alpha2 contains API Schema definitions for the v1alpha2 API group.

Resource Types

Configuration

Configuration is the resource that contains the configuration used to run tests.

Field Type Required Inline Description
apiVersion string ✅ chainsaw.kyverno.io/v1alpha2
kind string ✅ Configuration
metadata meta/v1.ObjectMeta

Standard object's metadata.

spec ConfigurationSpec ✅

Configuration spec.

Cleanup

Appears in:

Cleanup options contain the configuration used for cleaning up resources.

Field Type Required Inline Description
skipDelete bool

If set, do not delete the resources after running a test.

delayBeforeCleanup meta/v1.Duration

DelayBeforeCleanup adds a delay between the time a test ends and the time cleanup starts.

ConfigurationSpec

Appears in:

ConfigurationSpec contains the configuration used to run tests.

Field Type Required Inline Description
cleanup Cleanup

Cleanup contains cleanup configuration.

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

deletion DeletionOptions

Deletion contains the global deletion configuration.

discovery Discovery

Discovery contains tests discovery configuration.

error ErrorOptions

Error contains the global error configuration.

execution Execution

Execution contains tests execution configuration.

namespace Namespace

Namespace contains properties for the namespace to use for tests.

report Report

Report contains properties for the report.

templating Templating

Templating contains the templating config.

timeouts Timeouts

Global timeouts configuration. Applies to all tests/test steps if not overridden.

DeletionOptions

Appears in:

DeletionOptions contains the configuration used for deleting resources.

Field Type Required Inline Description
propagation meta/v1.DeletionPropagation

Propagation decides if a deletion will propagate to the dependents of the object, and how the garbage collector will handle the propagation.

Discovery

Appears in:

Discovery options contain the discovery configuration used when discovering tests in folders.

Field Type Required Inline Description
excludeTestRegex string

ExcludeTestRegex is used to exclude tests based on a regular expression.

includeTestRegex string

IncludeTestRegex is used to include tests based on a regular expression.

testFile string

TestFile is the name of the file containing the test to run. If no extension is provided, chainsaw will try with .yaml first and .yml if needed.

fullName bool

FullName makes use of the full test case folder path instead of the folder name.

ErrorOptions

Appears in:

ErrorOptions contains the global error configuration.

Field Type Required Inline Description
catch []Catch

Catch defines what the tests steps will execute when an error happens. This will be combined with catch handlers defined at the test and step levels.

Execution

Appears in:

Execution options determine how tests are run.

Field Type Required Inline Description
failFast bool

FailFast determines whether the test should stop upon encountering the first failure.

parallel int

The maximum number of tests to run at once.

repeatCount int

RepeatCount indicates how many times the tests should be executed.

forceTerminationGracePeriod meta/v1.Duration

ForceTerminationGracePeriod forces the termination grace period on pods, statefulsets, daemonsets and deployments.

Namespace

Appears in:

Namespace options contain the configuration used to allocate a namespace for each test.

Field Type Required Inline Description
name string

Name defines the namespace to use for tests. If not specified, every test will execute in a random ephemeral namespace unless the namespace is overridden in a the test spec.

template policy/v1alpha1.Any

Template defines a template to create the test namespace.

Report

Appears in:

Report options contain the configuration used for reporting.

Field Type Required Inline Description
format ReportFormatType

ReportFormat determines test report format (JSON

path string

ReportPath defines the path.

name string

ReportName defines the name of report to create. It defaults to "chainsaw-report".

ReportFormatType

(Alias of string)

Appears in:

Templating

Appears in:

Templating options contain the templating configuration.

Field Type Required Inline Description
enabled bool

Enabled determines whether resources should be considered for templating.

\ No newline at end of file diff --git a/0.2.3/reference/builtins/index.html b/0.2.3/reference/builtins/index.html new file mode 100644 index 000000000..d1a868277 --- /dev/null +++ b/0.2.3/reference/builtins/index.html @@ -0,0 +1 @@ + Built-in bindings - Chainsaw

Built-in bindings

Chainsaw provides built-in bindings listed below.

Common

Name Purpose Type
$values Values provided when invoking chainsaw with --values flag any
$namespace Name of the current test namespace string
$client Kubernetes client chainsaw is connected to (if not running with --no-cluster) object
$config Kubernetes client config chainsaw is connected to (if not running with --no-cluster) object

In tests

Name Purpose Type
$test.id Current test id int

Note

  • $test.id starts at 1 for the first test

In steps

Name Purpose Type
$step.id Current step id int

Note

  • $step.id starts at 1 for the first step

In operations

Name Purpose Type
$operation.id Current operation id int
$operation.resourceId Current resource id int

Note

  • $operation.id starts at 1 for the first operation
  • $operation.resourceId maps to the resource id (starting at 1) in case the operation loads a file that contains multiple resources (the same operation is repeated once per resource)

In checks and outputs

Name Purpose Type
@ The state of the resource (if any) at the end of the operation any
$error The error message (if any) at the end of the operation string
$stdout The content of the standard console output (if any) at the end of the operation string
$stderr The content of the standard console error output (if any) at the end of the operation string

Note

  • $stdout and $stderr are only available in script and command operations
\ No newline at end of file diff --git a/0.2.3/reference/commands/chainsaw/index.html b/0.2.3/reference/commands/chainsaw/index.html new file mode 100644 index 000000000..ffd447ed0 --- /dev/null +++ b/0.2.3/reference/commands/chainsaw/index.html @@ -0,0 +1,3 @@ + chainsaw - Chainsaw

chainsaw

chainsaw

Stronger tool for e2e testing

chainsaw [flags]
+

Options

  -h, --help   help for chainsaw
+

SEE ALSO

\ No newline at end of file diff --git a/0.2.3/reference/commands/chainsaw_assert/index.html b/0.2.3/reference/commands/chainsaw_assert/index.html new file mode 100644 index 000000000..8399d37bf --- /dev/null +++ b/0.2.3/reference/commands/chainsaw_assert/index.html @@ -0,0 +1,28 @@ + chainsaw assert - Chainsaw

chainsaw assert

chainsaw assert

Evaluate assertion

chainsaw assert [flags] [FILE]
+

Options

      --clustered                           Defines if the resource is clustered (only applies when resource is loaded from a file)
+  -f, --file string                         Path to the file to assert or '-' to read from stdin
+  -h, --help                                help for assert
+      --kube-as string                      Username to impersonate for the operation
+      --kube-as-group stringArray           Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
+      --kube-as-uid string                  UID to impersonate for the operation
+      --kube-certificate-authority string   Path to a cert file for the certificate authority
+      --kube-client-certificate string      Path to a client certificate file for TLS
+      --kube-client-key string              Path to a client key file for TLS
+      --kube-cluster string                 The name of the kubeconfig cluster to use
+      --kube-context string                 The name of the kubeconfig context to use
+      --kube-disable-compression            If true, opt-out of response compression for all requests to the server
+      --kube-insecure-skip-tls-verify       If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
+  -n, --kube-namespace string               If present, the namespace scope for this CLI request
+      --kube-password string                Password for basic authentication to the API server
+      --kube-proxy-url string               If provided, this URL will be used to connect via proxy
+      --kube-request-timeout string         The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
+      --kube-server string                  The address and port of the Kubernetes API server
+      --kube-tls-server-name string         If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used.
+      --kube-token string                   Bearer token for authentication to the API server
+      --kube-user string                    The name of the kubeconfig user to use
+      --kube-username string                Username for basic authentication to the API server
+      --namespace string                    Namespace to use (default "default")
+      --no-color                            Removes output colors
+  -r, --resource string                     Path to the file containing the resource
+      --timeout duration                    The assert timeout to use (default 30s)
+

SEE ALSO

  • chainsaw - Stronger tool for e2e testing
\ No newline at end of file diff --git a/0.2.3/reference/commands/chainsaw_build/index.html b/0.2.3/reference/commands/chainsaw_build/index.html new file mode 100644 index 000000000..4cfb47abf --- /dev/null +++ b/0.2.3/reference/commands/chainsaw_build/index.html @@ -0,0 +1,3 @@ + chainsaw build - Chainsaw

chainsaw build

chainsaw build

Build commands

chainsaw build [flags]
+

Options

  -h, --help   help for build
+

SEE ALSO

\ No newline at end of file diff --git a/0.2.3/reference/commands/chainsaw_build_docs/index.html b/0.2.3/reference/commands/chainsaw_build_docs/index.html new file mode 100644 index 000000000..0924bbdec --- /dev/null +++ b/0.2.3/reference/commands/chainsaw_build_docs/index.html @@ -0,0 +1,7 @@ + chainsaw build docs - Chainsaw

chainsaw build docs

chainsaw build docs

Build tests documentation

chainsaw build docs [flags]
+

Options

      --catalog string         Path to the built test catalog file
+  -h, --help                   help for docs
+      --readme-file string     Name of the built docs file (default "README.md")
+      --test-dir stringArray   Directories containing test cases to run
+      --test-file string       Name of the test file (default "chainsaw-test")
+

SEE ALSO

\ No newline at end of file diff --git a/0.2.3/reference/commands/chainsaw_completion/index.html b/0.2.3/reference/commands/chainsaw_completion/index.html new file mode 100644 index 000000000..f3b8b91ac --- /dev/null +++ b/0.2.3/reference/commands/chainsaw_completion/index.html @@ -0,0 +1,2 @@ + chainsaw completion - Chainsaw

chainsaw completion

chainsaw completion

Generate the autocompletion script for the specified shell

Synopsis

Generate the autocompletion script for chainsaw for the specified shell. See each sub-command's help for details on how to use the generated script.

Options

  -h, --help   help for completion
+

SEE ALSO

\ No newline at end of file diff --git a/0.2.3/reference/commands/chainsaw_completion_bash/index.html b/0.2.3/reference/commands/chainsaw_completion_bash/index.html new file mode 100644 index 000000000..ab9f0e2d3 --- /dev/null +++ b/0.2.3/reference/commands/chainsaw_completion_bash/index.html @@ -0,0 +1,7 @@ + chainsaw completion bash - Chainsaw

chainsaw completion bash

chainsaw completion bash

Generate the autocompletion script for bash

Synopsis

Generate the autocompletion script for the bash shell.

This script depends on the 'bash-completion' package. If it is not installed already, you can install it via your OS's package manager.

To load completions in your current shell session:

source <(chainsaw completion bash)
+

To load completions for every new session, execute once:

Linux:

chainsaw completion bash > /etc/bash_completion.d/chainsaw
+

macOS:

chainsaw completion bash > $(brew --prefix)/etc/bash_completion.d/chainsaw
+

You will need to start a new shell for this setup to take effect.

chainsaw completion bash
+

Options

  -h, --help              help for bash
+      --no-descriptions   disable completion descriptions
+

SEE ALSO

\ No newline at end of file diff --git a/0.2.3/reference/commands/chainsaw_completion_fish/index.html b/0.2.3/reference/commands/chainsaw_completion_fish/index.html new file mode 100644 index 000000000..3d5d2a88f --- /dev/null +++ b/0.2.3/reference/commands/chainsaw_completion_fish/index.html @@ -0,0 +1,6 @@ + chainsaw completion fish - Chainsaw

chainsaw completion fish

chainsaw completion fish

Generate the autocompletion script for fish

Synopsis

Generate the autocompletion script for the fish shell.

To load completions in your current shell session:

chainsaw completion fish | source
+

To load completions for every new session, execute once:

chainsaw completion fish > ~/.config/fish/completions/chainsaw.fish
+

You will need to start a new shell for this setup to take effect.

chainsaw completion fish [flags]
+

Options

  -h, --help              help for fish
+      --no-descriptions   disable completion descriptions
+

SEE ALSO

\ No newline at end of file diff --git a/0.2.3/reference/commands/chainsaw_completion_powershell/index.html b/0.2.3/reference/commands/chainsaw_completion_powershell/index.html new file mode 100644 index 000000000..048a196f1 --- /dev/null +++ b/0.2.3/reference/commands/chainsaw_completion_powershell/index.html @@ -0,0 +1,5 @@ + chainsaw completion powershell - Chainsaw

chainsaw completion powershell

chainsaw completion powershell

Generate the autocompletion script for powershell

Synopsis

Generate the autocompletion script for powershell.

To load completions in your current shell session:

chainsaw completion powershell | Out-String | Invoke-Expression
+

To load completions for every new session, add the output of the above command to your powershell profile.

chainsaw completion powershell [flags]
+

Options

  -h, --help              help for powershell
+      --no-descriptions   disable completion descriptions
+

SEE ALSO

\ No newline at end of file diff --git a/0.2.3/reference/commands/chainsaw_completion_zsh/index.html b/0.2.3/reference/commands/chainsaw_completion_zsh/index.html new file mode 100644 index 000000000..5ca337cc4 --- /dev/null +++ b/0.2.3/reference/commands/chainsaw_completion_zsh/index.html @@ -0,0 +1,8 @@ + chainsaw completion zsh - Chainsaw

chainsaw completion zsh

chainsaw completion zsh

Generate the autocompletion script for zsh

Synopsis

Generate the autocompletion script for the zsh shell.

If shell completion is not already enabled in your environment you will need to enable it. You can execute the following once:

echo "autoload -U compinit; compinit" >> ~/.zshrc
+

To load completions in your current shell session:

source <(chainsaw completion zsh)
+

To load completions for every new session, execute once:

Linux:

chainsaw completion zsh > "${fpath[1]}/_chainsaw"
+

macOS:

chainsaw completion zsh > $(brew --prefix)/share/zsh/site-functions/_chainsaw
+

You will need to start a new shell for this setup to take effect.

chainsaw completion zsh [flags]
+

Options

  -h, --help              help for zsh
+      --no-descriptions   disable completion descriptions
+

SEE ALSO

\ No newline at end of file diff --git a/0.2.3/reference/commands/chainsaw_create/index.html b/0.2.3/reference/commands/chainsaw_create/index.html new file mode 100644 index 000000000..daa3561b9 --- /dev/null +++ b/0.2.3/reference/commands/chainsaw_create/index.html @@ -0,0 +1,3 @@ + chainsaw create - Chainsaw

chainsaw create

chainsaw create

Create Chainsaw resources

chainsaw create [flags]
+

Options

  -h, --help   help for create
+

SEE ALSO

\ No newline at end of file diff --git a/0.2.3/reference/commands/chainsaw_create_test/index.html b/0.2.3/reference/commands/chainsaw_create_test/index.html new file mode 100644 index 000000000..7b4e33070 --- /dev/null +++ b/0.2.3/reference/commands/chainsaw_create_test/index.html @@ -0,0 +1,6 @@ + chainsaw create test - Chainsaw

chainsaw create test

chainsaw create test

Create a Chainsaw test

chainsaw create test [flags]
+

Options

      --description   If set, adds description when applicable (default true)
+      --force         If set, existing test will be deleted if needed
+  -h, --help          help for test
+      --save          If set, created test will be saved
+

SEE ALSO

\ No newline at end of file diff --git a/0.2.3/reference/commands/chainsaw_docs/index.html b/0.2.3/reference/commands/chainsaw_docs/index.html new file mode 100644 index 000000000..da3df7c8d --- /dev/null +++ b/0.2.3/reference/commands/chainsaw_docs/index.html @@ -0,0 +1,6 @@ + chainsaw docs - Chainsaw

chainsaw docs

chainsaw docs

Generate reference documentation

chainsaw docs [flags]
+

Options

      --autogenTag      Determines if the generated docs should contain a timestamp (default true)
+  -h, --help            help for docs
+  -o, --output string   Output path (default ".")
+      --website         Website version
+

SEE ALSO

  • chainsaw - Stronger tool for e2e testing
\ No newline at end of file diff --git a/0.2.3/reference/commands/chainsaw_export/index.html b/0.2.3/reference/commands/chainsaw_export/index.html new file mode 100644 index 000000000..7f46df363 --- /dev/null +++ b/0.2.3/reference/commands/chainsaw_export/index.html @@ -0,0 +1,3 @@ + chainsaw export - Chainsaw

chainsaw export

chainsaw export

Export commands

chainsaw export [flags]
+

Options

  -h, --help   help for export
+

SEE ALSO

\ No newline at end of file diff --git a/0.2.3/reference/commands/chainsaw_export_schemas/index.html b/0.2.3/reference/commands/chainsaw_export_schemas/index.html new file mode 100644 index 000000000..be7241ced --- /dev/null +++ b/0.2.3/reference/commands/chainsaw_export_schemas/index.html @@ -0,0 +1,3 @@ + chainsaw export schemas - Chainsaw

chainsaw export schemas

chainsaw export schemas

Export JSON schemas

chainsaw export schemas [flags]
+

Options

  -h, --help   help for schemas
+

SEE ALSO

\ No newline at end of file diff --git a/0.2.3/reference/commands/chainsaw_lint/index.html b/0.2.3/reference/commands/chainsaw_lint/index.html new file mode 100644 index 000000000..02539e44b --- /dev/null +++ b/0.2.3/reference/commands/chainsaw_lint/index.html @@ -0,0 +1,4 @@ + chainsaw lint - Chainsaw

chainsaw lint

chainsaw lint

Lint a file or read from standard input

Synopsis

Use chainsaw lint to lint a specific file or read from standard input for either test or configuration.

chainsaw lint [test|configuration] [flags]
+

Options

  -f, --file string   Specify the file to lint or '-' for standard input
+  -h, --help          help for lint
+

SEE ALSO

  • chainsaw - Stronger tool for e2e testing
\ No newline at end of file diff --git a/0.2.3/reference/commands/chainsaw_migrate/index.html b/0.2.3/reference/commands/chainsaw_migrate/index.html new file mode 100644 index 000000000..21098c3a2 --- /dev/null +++ b/0.2.3/reference/commands/chainsaw_migrate/index.html @@ -0,0 +1,3 @@ + chainsaw migrate - Chainsaw

chainsaw migrate

chainsaw migrate

Migrate resources to Chainsaw

chainsaw migrate [flags]
+

Options

  -h, --help   help for migrate
+

SEE ALSO

\ No newline at end of file diff --git a/0.2.3/reference/commands/chainsaw_migrate_kuttl/index.html b/0.2.3/reference/commands/chainsaw_migrate_kuttl/index.html new file mode 100644 index 000000000..47671a30f --- /dev/null +++ b/0.2.3/reference/commands/chainsaw_migrate_kuttl/index.html @@ -0,0 +1,3 @@ + chainsaw migrate kuttl - Chainsaw
\ No newline at end of file diff --git a/0.2.3/reference/commands/chainsaw_migrate_kuttl_config/index.html b/0.2.3/reference/commands/chainsaw_migrate_kuttl_config/index.html new file mode 100644 index 000000000..d7d410368 --- /dev/null +++ b/0.2.3/reference/commands/chainsaw_migrate_kuttl_config/index.html @@ -0,0 +1,5 @@ + chainsaw migrate kuttl config - Chainsaw

chainsaw migrate kuttl config

chainsaw migrate kuttl config

Migrate KUTTL config to Chainsaw

chainsaw migrate kuttl config [flags]
+

Options

      --cleanup   If set, delete converted files
+  -h, --help      help for config
+      --save      If set, converted files will be saved
+

SEE ALSO

\ No newline at end of file diff --git a/0.2.3/reference/commands/chainsaw_migrate_kuttl_tests/index.html b/0.2.3/reference/commands/chainsaw_migrate_kuttl_tests/index.html new file mode 100644 index 000000000..9f1fed2d5 --- /dev/null +++ b/0.2.3/reference/commands/chainsaw_migrate_kuttl_tests/index.html @@ -0,0 +1,5 @@ + chainsaw migrate kuttl tests - Chainsaw

chainsaw migrate kuttl tests

chainsaw migrate kuttl tests

Migrate KUTTL tests to Chainsaw

chainsaw migrate kuttl tests [flags]
+

Options

      --cleanup   If set, delete converted files
+  -h, --help      help for tests
+      --save      If set, converted files will be saved
+

SEE ALSO

\ No newline at end of file diff --git a/0.2.3/reference/commands/chainsaw_test/index.html b/0.2.3/reference/commands/chainsaw_test/index.html new file mode 100644 index 000000000..513f02dce --- /dev/null +++ b/0.2.3/reference/commands/chainsaw_test/index.html @@ -0,0 +1,52 @@ + chainsaw test - Chainsaw

chainsaw test

chainsaw test

Run tests

chainsaw test [flags]... [test directories]...
+

Options

      --apply-timeout duration                    The apply timeout to use as default for configuration (default 5s)
+      --assert-timeout duration                   The assert timeout to use as default for configuration (default 30s)
+      --cleanup-delay duration                    Adds a delay between the time a test ends and the time cleanup starts
+      --cleanup-timeout duration                  The cleanup timeout to use as default for configuration (default 30s)
+      --cluster strings                           Register cluster (format <cluster name>=<kubeconfig path>:[context name])
+      --config string                             Chainsaw configuration file
+      --delete-timeout duration                   The delete timeout to use as default for configuration (default 15s)
+      --deletion-propagation-policy string        The deletion propagation policy (Foreground|Background|Orphan) (default "Background")
+      --error-timeout duration                    The error timeout to use as default for configuration (default 30s)
+      --exclude-test-regex string                 Regular expression to exclude tests
+      --exec-timeout duration                     The exec timeout to use as default for configuration (default 5s)
+      --fail-fast                                 Stop the test upon encountering the first failure
+      --force-termination-grace-period duration   If specified, overrides termination grace periods in applicable resources
+      --full-name                                 Use full test case folder path instead of folder name
+  -h, --help                                      help for test
+      --include-test-regex string                 Regular expression to include tests
+      --kube-as string                            Username to impersonate for the operation
+      --kube-as-group stringArray                 Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
+      --kube-as-uid string                        UID to impersonate for the operation
+      --kube-certificate-authority string         Path to a cert file for the certificate authority
+      --kube-client-certificate string            Path to a client certificate file for TLS
+      --kube-client-key string                    Path to a client key file for TLS
+      --kube-cluster string                       The name of the kubeconfig cluster to use
+      --kube-context string                       The name of the kubeconfig context to use
+      --kube-disable-compression                  If true, opt-out of response compression for all requests to the server
+      --kube-insecure-skip-tls-verify             If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
+  -n, --kube-namespace string                     If present, the namespace scope for this CLI request
+      --kube-password string                      Password for basic authentication to the API server
+      --kube-proxy-url string                     If provided, this URL will be used to connect via proxy
+      --kube-request-timeout string               The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
+      --kube-server string                        The address and port of the Kubernetes API server
+      --kube-tls-server-name string               If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used.
+      --kube-token string                         Bearer token for authentication to the API server
+      --kube-user string                          The name of the kubeconfig user to use
+      --kube-username string                      Username for basic authentication to the API server
+      --namespace string                          Namespace to use for tests
+      --no-cluster                                Runs without cluster
+      --no-color                                  Removes output colors
+      --parallel int                              The maximum number of tests to run at once
+      --pause-on-failure                          Pause test execution failure (implies no concurrency)
+      --repeat-count int                          Number of times to repeat each test (default 1)
+      --report-format string                      Test report format (JSON|XML|nil)
+      --report-name string                        The name of the report to create (default "chainsaw-report")
+      --report-path string                        The path of the report to create
+      --selector strings                          Selector (label query) to filter on
+      --skip-delete                               If set, do not delete the resources after running the tests
+      --template                                  If set, resources will be considered for templating (default true)
+      --test-dir strings                          Directories containing test cases to run
+      --test-file string                          Name of the test file (default "chainsaw-test")
+      --values strings                            Values passed to the tests
+

SEE ALSO

  • chainsaw - Stronger tool for e2e testing
\ No newline at end of file diff --git a/0.2.3/reference/commands/chainsaw_version/index.html b/0.2.3/reference/commands/chainsaw_version/index.html new file mode 100644 index 000000000..89bde69c6 --- /dev/null +++ b/0.2.3/reference/commands/chainsaw_version/index.html @@ -0,0 +1,3 @@ + chainsaw version - Chainsaw

chainsaw version

chainsaw version

Print the version informations

chainsaw version [flags]
+

Options

  -h, --help   help for version
+

SEE ALSO

  • chainsaw - Stronger tool for e2e testing
\ No newline at end of file diff --git a/0.2.3/reference/index.html b/0.2.3/reference/index.html new file mode 100644 index 000000000..b6d5b60a9 --- /dev/null +++ b/0.2.3/reference/index.html @@ -0,0 +1 @@ + Reference documentation - Chainsaw

Reference documentation

Info

Select an item in the navigation menu to browse a specific page.

\ No newline at end of file diff --git a/0.2.3/reference/jp/functions/index.html b/0.2.3/reference/jp/functions/index.html new file mode 100644 index 000000000..23e1b9c9e --- /dev/null +++ b/0.2.3/reference/jp/functions/index.html @@ -0,0 +1 @@ + Functions - Chainsaw

Functions

Experimental functions

Experimental functions are denoted by the x_ prefix.

These are functions that are subject to signature change in a future version.

built-in functions

Name Signature
abs abs(number)
avg avg(array[number])
ceil ceil(number)
contains contains(array|string, any)
ends_with ends_with(string, string)
find_first find_first(string, string, number, number)
find_last find_last(string, string, number, number)
floor floor(number)
from_items from_items(array[array])
group_by group_by(array, expref)
items items(object)
join join(string, array[string])
keys keys(object)
length length(string|array|object)
lower lower(string)
map map(expref, array)
max max(array[number]|array[string])
max_by max_by(array, expref)
merge merge(object)
min min(array[number]|array[string])
min_by min_by(array, expref)
not_null not_null(any)
pad_left pad_left(string, number, string)
pad_right pad_right(string, number, string)
replace replace(string, string, string, number)
reverse reverse(array|string)
sort sort(array[string]|array[number])
sort_by sort_by(array, expref)
split split(string, string, number)
starts_with starts_with(string, string)
sum sum(array[number])
to_array to_array(any)
to_number to_number(any)
to_string to_string(any)
trim trim(string, string)
trim_left trim_left(string, string)
trim_right trim_right(string, string)
type type(any)
upper upper(string)
values values(object)
zip zip(array, array)

kyverno-json functions

Name Signature
at at(array, any)
concat concat(string, string)
json_parse json_parse(string)
wildcard wildcard(string, string)

kyverno functions

Name Signature
compare compare(string, string)
equal_fold equal_fold(string, string)
replace replace(string, string, string, number)
replace_all replace_all(string, string, string)
to_upper to_upper(string)
to_lower to_lower(string)
trim trim(string, string)
trim_prefix trim_prefix(string, string)
split split(string, string)
regex_replace_all regex_replace_all(string, string|number, string|number)
regex_replace_all_literal regex_replace_all_literal(string, string|number, string|number)
regex_match regex_match(string, string|number)
pattern_match pattern_match(string, string|number)
label_match label_match(object, object)
to_boolean to_boolean(string)
add add(any, any)
sum sum(array)
subtract subtract(any, any)
multiply multiply(any, any)
divide divide(any, any)
modulo modulo(any, any)
round round(number, number)
base64_decode base64_decode(string)
base64_encode base64_encode(string)
time_since time_since(string, string, string)
time_now time_now()
time_now_utc time_now_utc()
path_canonicalize path_canonicalize(string)
truncate truncate(string, number)
semver_compare semver_compare(string, string)
parse_json parse_json(string)
parse_yaml parse_yaml(string)
lookup lookup(object|array, string|number)
items items(object|array, string, string)
object_from_lists object_from_lists(array, array)
random random(string)
x509_decode x509_decode(string)
time_to_cron time_to_cron(string)
time_add time_add(string, string)
time_parse time_parse(string, string)
time_utc time_utc(string)
time_diff time_diff(string, string)
time_before time_before(string, string)
time_after time_after(string, string)
time_between time_between(string, string, string)
time_truncate time_truncate(string, string)

chainsaw functions

Name Signature
env env(string)
x_k8s_get x_k8s_get(any, string, string, string, string)
x_k8s_list x_k8s_list(any, string, string, string)
x_k8s_exists x_k8s_exists(any, string, string, string, string)
x_k8s_resource_exists x_k8s_resource_exists(any, string, string)
x_k8s_server_version x_k8s_server_version(any)
\ No newline at end of file diff --git a/0.2.3/reference/json-schemas/index.html b/0.2.3/reference/json-schemas/index.html new file mode 100644 index 000000000..67a076a6e --- /dev/null +++ b/0.2.3/reference/json-schemas/index.html @@ -0,0 +1,32 @@ + JSON schemas - Chainsaw

JSON schemas

JSON schemas for Chainsaw are available:

They can be used to enable validation and autocompletion in your IDE.

VS code

In VS code, simply add a comment on top of your YAML resources.

Test

# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: basic
+spec:
+  steps:
+  - try:
+    - apply:
+        file: configmap.yaml
+    - assert:
+        file: configmap-assert.yaml
+

Configuration

# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/configuration-chainsaw-v1alpha2.json
+apiVersion: chainsaw.kyverno.io/v1alpha2
+kind: Configuration
+metadata:
+  name: example
+spec:
+  timeouts:
+    apply: 45s
+    assert: 20s
+    cleanup: 45s
+    delete: 25s
+    error: 10s
+    exec: 45s
+  cleanup:
+    skipDelete: false
+  execution:
+    failFast: true
+    parallel: 4
+

Exporting schemas

Chainsaw can also export JSON schemas locally if you don't want to reference them from GitHub:

chainsaw export schemas <local path>
+

See chainsaw export schemas command documentation for more details.

\ No newline at end of file diff --git a/0.2.3/search/search_index.json b/0.2.3/search/search_index.json new file mode 100644 index 000000000..9ef5a4bbe --- /dev/null +++ b/0.2.3/search/search_index.json @@ -0,0 +1 @@ +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"cicd/gh-action/","title":"GitHub action","text":"

A GitHub action is available to easily install Chainsaw in your workflows.

The GitHub action is available at kyverno/action-install-chainsaw or in the marketplace.

"},{"location":"cicd/gh-action/#usage","title":"Usage","text":"

This action currently supports GitHub-provided Linux, macOS and Windows runners (self-hosted runners may not work).

Add the following entry to your Github workflow YAML file:

uses: kyverno/action-install-chainsaw@v0.1.0\nwith:\n  release: v0.1.0 # optional\n

Example using a pinned version:

jobs:\n  example:\n    runs-on: ubuntu-latest\n\n    permissions: {}\n\n    name: Install Chainsaw\n    steps:\n      - name: Install Chainsaw\n        uses: kyverno/action-install-chainsaw@v0.1.0\n        with:\n          release: v0.0.9\n      - name: Check install\n        run: chainsaw version\n

Example using the default version:

jobs:\n  example:\n    runs-on: ubuntu-latest\n\n    permissions: {}\n\n    name: Install Chainsaw\n    steps:\n      - name: Install Chainsaw\n        uses: kyverno/action-install-chainsaw@v0.1.0\n      - name: Check install\n        run: chainsaw version\n

Example using cosign verification:

jobs:\n  example:\n    runs-on: ubuntu-latest\n\n    permissions: {}\n\n    name: Install Chainsaw\n    steps:\n      - name: Install Cosign\n        uses: sigstore/cosign-installer@v3.1.1\n      - name: Install Chainsaw\n        uses: kyverno/action-install-chainsaw@v0.1.0\n        with:\n          verify: true\n      - name: Check install\n        run: chainsaw version\n

If you want to install Chainsaw from its main version by using go install under the hood, you can set release as main. Once you did that, Chainsaw will be installed via go install which means that please ensure that go is installed.

Example of installing Chainsaw via go install:

jobs:\n  example:\n    runs-on: ubuntu-latest\n\n    permissions: {}\n\n    name: Install Chainsaw via go install\n    steps:\n      - name: Install go\n        uses: actions/setup-go@v4\n        with:\n          go-version: '1.21'\n      - name: Install Chainsaw\n        uses: kyverno/action-install-chainsaw@v0.1.0\n        with:\n          release: main\n      - name: Check install\n        run: chainsaw version\n
"},{"location":"cicd/gh-action/#optional-inputs","title":"Optional Inputs","text":"

The following optional inputs:

Input Description release chainsaw version to use instead of the default. install-dir directory to place the chainsaw binary into instead of the default ($HOME/.chainsaw). use-sudo set to true if install-dir location requires sudo privs. Defaults to false. verify set to true to enable cosign verification of the downloaded archive."},{"location":"community/","title":"Community","text":"

Chainsaw has a growing community and we would definitely love to see you join and contribute.

Everyone is welcome to make suggestions, report bugs, open feature requests, contribute code or docs, participate in discussions, write blogs or anything that can benefit the project.

Chainsaw is built and maintained under the Kyverno umbrella but decisions are Community driven Everyone's voice matters

"},{"location":"community/#slack-channel","title":"Slack channel","text":"

Join our slack channel #kyverno-chainsaw to meet with users, contributors and maintainers.

"},{"location":"community/#community-meetings","title":"Community Meetings","text":"

To attend our community meetings, join the Chainsaw group. You will then be sent a meeting invite and will have access to the agenda and meeting notes. Any member may suggest topics for discussion.

This is a public, weekly for Kyverno-Chainsaw maintainers to make announcements and provide project updates, and request input and feedback. This forum allows community members to raise agenda items of any sort, including but not limited to any PRs or issues on which they are working.

Weekly every Thursday at 2:00 PM UTC

  • Chainsaw group
  • Zoom Meeting
  • Agenda and meeting notes
"},{"location":"community/#roadmap","title":"RoadMap","text":"

For detailed information on our planned features and upcoming updates, please view our Roadmap.

"},{"location":"community/#contributing","title":"Contributing","text":"

Please read the contributing guide for details around:

  1. Code of Conduct
  2. Code Culture
  3. Details on how to contribute
"},{"location":"community/#adopters","title":"Adopters","text":"

If you are using Chainsaw and want to share it publicly we always appreciate a bit of support. Pull requests to the ADOPTERS LIST will put a smile on our faces

"},{"location":"configuration/","title":"Configuring Chainsaw","text":"

This documentation focuses on providing a breakdown of the Chainsaw configuration structure and how to use it.

Chainsaw can be configured in two different and complementary ways:

  • Using a configuration file
  • Overriding configuration with command-line flags
"},{"location":"configuration/#specific-configuration-options","title":"Specific configuration options","text":"

Please pay attention to the configuration options below, they may or may not be relevant in your case but can be useful in certain cases:

  • Timeouts
  • Discovery options
  • Execution options
  • Namespace options
  • Templating options
  • Cleanup options
  • Deletion options
  • Error options
  • Reporting options
  • Multi-cluster options
  • Pause options
  • No cluster options
  • Label selectors
  • External values
"},{"location":"configuration/file/","title":"Configuration file","text":"

Chainsaw prioritizes its configuration in the following order:

  1. User-specified configuration

    If you explicitly provide a configuration file using a command-line flag

  2. Default configuration file

    If no configuration is specified, Chainsaw will look for a default file named .chainsaw.yaml in the current working directory

  3. Internal default configuration

    In the absence of both of the above, Chainsaw will use a default configuration file embedded in the Chainsaw binary

"},{"location":"configuration/file/#example","title":"Example","text":"
apiVersion: chainsaw.kyverno.io/v1alpha2\nkind: Configuration\nmetadata:\n  name: example\nspec:\n  timeouts:\n    apply: 45s\n    assert: 20s\n    cleanup: 45s\n    delete: 25s\n    error: 10s\n    exec: 45s\n  cleanup:\n    skipDelete: false\n  execution:\n    failFast: true\n    parallel: 4\n  # ...\n
"},{"location":"configuration/file/#how-to-specify-a-configuration","title":"How to specify a configuration","text":"

To use a custom configuration file:

chainsaw test --config path/to/your/config.yaml\n
"},{"location":"configuration/file/#default-configuration","title":"Default configuration","text":"

The default configuration below is used by Chainsaw when no configuration file was provided and the default file .chainsaw.yaml does not exist.

apiVersion: chainsaw.kyverno.io/v1alpha2\nkind: Configuration\nmetadata:\n  name: default\nspec: {}\n
"},{"location":"configuration/file/#reference-documentation","title":"Reference documentation","text":"

See Configuration API reference for more details.

"},{"location":"configuration/flags/","title":"Command line flags","text":"

After a configuration file is loaded, you can override specific settings using command-line flags.

Precedence

Command-line flags always take precedence over the configuration coming from a configuration file.

"},{"location":"configuration/flags/#example","title":"Example","text":"
chainsaw test                         \\\n  path/to/test/dir                    \\\n  --config path/to/your/config.yaml   \\\n  --assert-timeout 45s                \\\n  --skip-delete false                 \\\n  --fail-fast true                    \\\n  --parallel 4                        \\\n  ...\n

In this example, Chainsaw will load a configuration file but the timeout configuration and other settings will be overridden by the values set in the flags, regardless of the value in the loaded configuration file.

"},{"location":"configuration/flags/#reference-documentation","title":"Reference documentation","text":"

See chainsaw test command reference for the list of all available flags.

"},{"location":"configuration/options/cleanup/","title":"Cleanup options","text":"

Cleanup options contain the configuration used by Chainsaw for cleaning up resources.

"},{"location":"configuration/options/cleanup/#supported-elements","title":"Supported elements","text":"Element Default Description skipDelete false If set, do not delete the resources after running a test. delayBeforeCleanup DelayBeforeCleanup adds a delay between the time a test ends and the time cleanup starts."},{"location":"configuration/options/cleanup/#delay-before-cleanup","title":"Delay before cleanup","text":"

At the end of each test, Chainsaw will delete the resources it created during the test.

When testing operators, it can be useful to wait a little bit before starting the cleanup process to make sure the operator/controller has the necessary time to update its internal state.

"},{"location":"configuration/options/cleanup/#configuration","title":"Configuration","text":""},{"location":"configuration/options/cleanup/#with-file","title":"With file","text":"
apiVersion: chainsaw.kyverno.io/v1alpha2\nkind: Configuration\nmetadata:\n  name: example\nspec:\n  cleanup:\n    skipDelete: true\n    delayBeforeCleanup: 5s\n
"},{"location":"configuration/options/cleanup/#with-flags","title":"With flags","text":"
chainsaw test                   \\\n  --skip-delete                 \\\n  --delay-before-cleanup 5s\n
"},{"location":"configuration/options/clusters/","title":"Multi-cluster options","text":"

Multi-cluster options contain the configuration of additional clusters.

"},{"location":"configuration/options/clusters/#supported-elements","title":"Supported elements","text":"

Every cluster is registered by name and supports the following elements:

Element Default Description kubeconfig string Kubeconfig is the path to the referenced file. context string Context is the name of the context to use."},{"location":"configuration/options/clusters/#configuration","title":"Configuration","text":""},{"location":"configuration/options/clusters/#with-file","title":"With file","text":"
apiVersion: chainsaw.kyverno.io/v1alpha2\nkind: Configuration\nmetadata:\n  name: custom-config\nspec:\n  clusters:\n    # this cluster will use the default (current) context\n    # configured in the kubeconfig file\n    cluster-1:\n      kubeconfig: /path/to/kubeconfig-1\n    # this cluster will use the context named `context-2`\n    # in the kubeconfig file\n    cluster-2:\n      kubeconfig: /path/to/kubeconfig-2\n      context: context-2\n
"},{"location":"configuration/options/clusters/#with-flags","title":"With flags","text":"

Note

The --cluster flag can appear multiple times and is expected to come in the following format:

--cluster cluster-name=/path/to/kubeconfig[:context-name].

chainsaw test                                               \\\n    --cluster cluster-1=/path/to/kubeconfig-1               \\\n    --cluster cluster-2=/path/to/kubeconfig-2:context-2\n
"},{"location":"configuration/options/deletion/","title":"Deletion options","text":"

Deletion options determine the configuration used by Chainsaw for deleting resources.

"},{"location":"configuration/options/deletion/#supported-elements","title":"Supported elements","text":"Element Default Description propagation Background Propagation decides if a deletion will propagate to the dependents of the object, and how the garbage collector will handle the propagation."},{"location":"configuration/options/deletion/#propagation","title":"Propagation","text":"

This element will affect Kubernetes cascading deletion. Supported values are Orphan, Background and Foreground.

Tip

Setting Orphan is probably never a good idea because it would leak resources in the test cluster. Chainsaw uses Background as its default value which is a reasonable choice.

Note that Foreground can be useful to fail when the dependent resources fail to delete.

"},{"location":"configuration/options/deletion/#configuration","title":"Configuration","text":""},{"location":"configuration/options/deletion/#with-file","title":"With file","text":"
apiVersion: chainsaw.kyverno.io/v1alpha2\nkind: Configuration\nmetadata:\n  name: example\nspec:\n  deletion:\n    propagation: Foreground\n
"},{"location":"configuration/options/deletion/#with-flags","title":"With flags","text":"
chainsaw test --deletion-propagation-policy Foreground\n
"},{"location":"configuration/options/discovery/","title":"Discovery options","text":"

Discovery options contain the discovery configuration used by Chainsaw when discovering tests in specified folders.

"},{"location":"configuration/options/discovery/#supported-elements","title":"Supported elements","text":"Element Default Description testFile chainsaw-test TestFile is the name of the file containing the test to run. If no extension is provided, chainsaw will try with .yaml first and .yml if needed. fullName false FullName makes use of the full test case folder path instead of the folder name. includeTestRegex IncludeTestRegex is used to include tests based on a regular expression. excludeTestRegex ExcludeTestRegex is used to exclude tests based on a regular expression."},{"location":"configuration/options/discovery/#configuration","title":"Configuration","text":""},{"location":"configuration/options/discovery/#with-file","title":"With file","text":"
apiVersion: chainsaw.kyverno.io/v1alpha2\nkind: Configuration\nmetadata:\n  name: example\nspec:\n  discovery:\n    testFile: chainsaw-test\n    fullName: true\n    includeTestRegex: chainsaw/.*\n    excludeTestRegex: chainsaw/exclude-.*\n
"},{"location":"configuration/options/discovery/#with-flags","title":"With flags","text":"
chainsaw test                                   \\\n  --test-file chainsaw-test                     \\\n  --full-name                                   \\\n  --include-test-regex 'chainsaw/.*'            \\\n  --exclude-test-regex 'chainsaw/exclude-.*'\n
"},{"location":"configuration/options/error/","title":"Error options","text":"

Error options contain the global error configuration used by Chainsaw.

"},{"location":"configuration/options/error/#supported-elements","title":"Supported elements","text":"Field Default Description catch Catch defines what the tests steps will execute when an error happens. This will be combined with catch handlers defined at the test and step levels."},{"location":"configuration/options/error/#configuration","title":"Configuration","text":""},{"location":"configuration/options/error/#with-file","title":"With file","text":"
apiVersion: chainsaw.kyverno.io/v1alpha2\nkind: Configuration\nmetadata:\n  name: example\nspec:\n  error:\n    catch:\n    - events: {}\n    - describe:\n        resource: crds\n
"},{"location":"configuration/options/error/#with-flags","title":"With flags","text":"

Note

Error options can't be configured with flags.

"},{"location":"configuration/options/execution/","title":"Execution options","text":"

Execution options determine how tests are run by Chainsaw.

"},{"location":"configuration/options/execution/#supported-elements","title":"Supported elements","text":"Element Default Description failFast false FailFast determines whether the test should stop upon encountering the first failure. parallel auto The maximum number of tests to run at once. repeatCount 1 RepeatCount indicates how many times the tests should be executed. forceTerminationGracePeriod ForceTerminationGracePeriod forces the termination grace period on pods, statefulsets, daemonsets and deployments."},{"location":"configuration/options/execution/#termination-grace-period","title":"Termination grace period","text":"

Some Kubernetes resources can take time before being terminated. For example, deleting a pod can take time if the underlying container doesn't quit quickly enough.

Chainsaw can override the grace period for the following resource kinds:

  • Pod
  • Deployment
  • StatefulSet
  • DaemonSet
  • Job
  • CronJob
"},{"location":"configuration/options/execution/#configuration","title":"Configuration","text":""},{"location":"configuration/options/execution/#with-file","title":"With file","text":"
apiVersion: chainsaw.kyverno.io/v1alpha2\nkind: Configuration\nmetadata:\n  name: example\nspec:\n  execution:\n    failFast: true\n    parallel: 8\n    repeatCount: 2\n    forceTerminationGracePeriod: 5s\n
"},{"location":"configuration/options/execution/#with-flags","title":"With flags","text":"
chainsaw test                                   \\\n  --fail-fast                                   \\\n  --parallel 8                                  \\\n  --repeat-count 2                              \\\n  --force-termination-grace-period 5s\n
"},{"location":"configuration/options/label-selectors/","title":"Label selectors","text":"

Chainsaw can filter the tests to run using label selectors.

"},{"location":"configuration/options/label-selectors/#configuration","title":"Configuration","text":""},{"location":"configuration/options/label-selectors/#with-file","title":"With file","text":"

Note

Label selectors can't be configured with a configuration file.

"},{"location":"configuration/options/label-selectors/#with-flags","title":"With flags","text":"
chainsaw test --selector foo=bar\n
"},{"location":"configuration/options/namespace/","title":"Namespace options","text":"

Namespace options contain the configuration used by Chainsaw to allocate a namespace for each test.

"},{"location":"configuration/options/namespace/#supported-elements","title":"Supported elements","text":"Element Default Description name Name defines the namespace to use for tests. If not specified, every test will execute in a random ephemeral namespace unless the namespace is overridden in a the test spec. template Template defines a template to create the test namespace."},{"location":"configuration/options/namespace/#configuration","title":"Configuration","text":""},{"location":"configuration/options/namespace/#with-file","title":"With file","text":"
apiVersion: chainsaw.kyverno.io/v1alpha2\nkind: Configuration\nmetadata:\n  name: example\nspec:\n  namespace:\n    name: foo\n    template:\n      metadata:\n        annotations:\n          from-config-file: hello\n
"},{"location":"configuration/options/namespace/#with-flags","title":"With flags","text":"

Note

The template element can't be configured with flags.

chainsaw test --namespace foo\n
"},{"location":"configuration/options/no-cluster/","title":"No cluster options","text":"

Chainsaw can be run without any connection to a Kubernetes cluster.

In this case, Chainsaw will not try to create an ephemeral namespace and all operations requiring a Kubernetes cluster will fail.

"},{"location":"configuration/options/no-cluster/#configuration","title":"Configuration","text":""},{"location":"configuration/options/no-cluster/#with-file","title":"With file","text":"

Note

No cluster options can't be configured with a configuration file.

"},{"location":"configuration/options/no-cluster/#with-flags","title":"With flags","text":"
chainsaw test --no-cluster\n
"},{"location":"configuration/options/pause/","title":"Pause options","text":"

Chainsaw can be configured to pause and wait for user input when a failure happens. This is useful when Chainsaw is run locally to allow debugging and troubleshooting failures.

"},{"location":"configuration/options/pause/#configuration","title":"Configuration","text":""},{"location":"configuration/options/pause/#with-file","title":"With file","text":"

Note

Pause options can't be configured with a configuration file.

"},{"location":"configuration/options/pause/#with-flags","title":"With flags","text":"
chainsaw test --pause-on-failure\n
"},{"location":"configuration/options/report/","title":"Reporting options","text":"

Reporting options contain the configuration used by Chainsaw for reporting.

"},{"location":"configuration/options/report/#supported-elements","title":"Supported elements","text":"Element Default Description format JSON ReportFormat determines test report format (JSON path ReportPath defines the path. name chainsaw-report ReportName defines the name of report to create. It defaults to \"chainsaw-report\"."},{"location":"configuration/options/report/#configuration","title":"Configuration","text":""},{"location":"configuration/options/report/#with-file","title":"With file","text":"
apiVersion: chainsaw.kyverno.io/v1alpha2\nkind: Configuration\nmetadata:\n  name: example\nspec:\n  report:\n    format: JSON\n    name: chainsaw-report\n    path: /home/chainsaw\n
"},{"location":"configuration/options/report/#with-flags","title":"With flags","text":"

Note

The report path can be specified as either a relative or an absolute path.

chainsaw test                             \\\n  --report-format JSON                    \\\n  --report-name chainsaw-report           \\\n  --report-path /path/to/save/report\n
"},{"location":"configuration/options/templating/","title":"Templating options","text":"

Templating options contain the templating configuration.

"},{"location":"configuration/options/templating/#supported-elements","title":"Supported elements","text":"Element Default Description enabled true Enabled determines whether resources should be considered for templating.

Tip

Templating was disabled by default in v0.1.* but is now enabled by default since v0.2.1.

"},{"location":"configuration/options/templating/#configuration","title":"Configuration","text":""},{"location":"configuration/options/templating/#with-file","title":"With file","text":"
apiVersion: chainsaw.kyverno.io/v1alpha2\nkind: Configuration\nmetadata:\n  name: example\nspec:\n  templating:\n    enabled: false\n
"},{"location":"configuration/options/templating/#with-flags","title":"With flags","text":"
chainsaw test --template=false\n
"},{"location":"configuration/options/timeouts/","title":"Timeouts","text":"

Timeouts in Chainsaw are specified per type of operation. This is required because the timeout varies greatly depending on the nature of an operation.

For example, applying a manifest in a cluster is expected to execute reasonably fast, while validating a resource can be a much longer operation.

"},{"location":"configuration/options/timeouts/#supported-timeouts","title":"Supported timeouts","text":"Element Default Description apply 5s Used when Chainsaw applies manifests in a cluster assert 30s Used when Chainsaw validates resources in a cluster cleanup 30s Used when Chainsaw removes resources created for a test delete 15s Used when Chainsaw deletes resources from a cluster error 30s Used when Chainsaw validates resources in a cluster exec 5s Used when Chainsaw executes arbitrary commands or scripts"},{"location":"configuration/options/timeouts/#configuration","title":"Configuration","text":""},{"location":"configuration/options/timeouts/#with-file","title":"With file","text":"
apiVersion: chainsaw.kyverno.io/v1alpha2\nkind: Configuration\nmetadata:\n  name: example\nspec:\n  timeouts:\n    apply: 45s\n    assert: 20s\n    cleanup: 45s\n    delete: 25s\n    error: 10s\n    exec: 45s\n
"},{"location":"configuration/options/timeouts/#with-flags","title":"With flags","text":"
chainsaw test               \\\n  --apply-timeout 45s       \\\n  --assert-timeout 45s      \\\n  --cleanup-timeout 45s     \\\n  --delete-timeout 45s      \\\n  --error-timeout 45s       \\\n  --exec-timeout 45s\n
"},{"location":"configuration/options/values/","title":"External values","text":"

Chainsaw can pass arbitrary values when running tests using the --values flag. Values will be available to tests under the $values binding.

"},{"location":"configuration/options/values/#configuration","title":"Configuration","text":""},{"location":"configuration/options/values/#with-file","title":"With file","text":"

Note

Values can't be configured with a configuration file.

"},{"location":"configuration/options/values/#with-flags","title":"With flags","text":"
chainsaw test --values ./values.yaml\n
"},{"location":"examples/","title":"Examples","text":"

Info

Select an item in the navigation menu to browse a specific page.

"},{"location":"examples/concurrency/","title":"Concurrency control","text":"

By default, Chainsaw will run tests in parallel.

The number of concurrent tests can be configured globally using a configuration file or with the --parallel flag.

Alternatively, the concurrent nature of a test can specified at the test level:

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  # concurrency can be specified per test (`true` or `false`)\n  # default value is `true`\n  concurrent: false\n  # ...\n

All non-concurrent tests are executed first, followed by the concurrent tests running in parallel.

"},{"location":"examples/crds/","title":"Work with CRDs","text":"

New CRDs are not immediately available for use in the Kubernetes API until the Kubernetes API has acknowledged them.

If a CRD is being defined inside of a test step, be sure to wait for it to appear.

The test below applies a CRD and waits for it to become available:

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - apply:\n        resource:\n          apiVersion: apiextensions.k8s.io/v1\n          kind: CustomResourceDefinition\n          metadata:\n            name: issues.example.com\n          spec:\n            group: example.com\n            names:\n              kind: Issue\n              listKind: IssueList\n              plural: issues\n              singular: issue\n            scope: Namespaced\n            versions: ...\n    - assert:\n        resource:\n          apiVersion: apiextensions.k8s.io/v1\n          kind: CustomResourceDefinition\n          metadata:\n            name: issues.example.com\n          status:\n            acceptedNames:\n              kind: Issue\n              listKind: IssueList\n              plural: issues\n              singular: issue\n            storedVersions:\n            - v1alpha1\n

The CRD can be used in subsequent steps.

"},{"location":"examples/events/","title":"Work with events","text":"

Kubernetes events are regular Kubernetes objects and can be asserted on just like any other object:

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - assert:\n        resource:\n          apiVersion: v1\n          kind: Event\n          reason: Started\n          source:\n            component: kubelet\n          involvedObject:\n            apiVersion: v1\n            kind: Pod\n            name: my-pod\n
"},{"location":"examples/inline/","title":"Inline resources","text":"

When an operation needs to reference a resource, it can do so using a file path or directly specify the resource inline using the resource field.

The test below is equivalent to our first test:

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - apply:\n        resource:\n          apiVersion: v1\n          kind: ConfigMap\n          metadata:\n            name: quick-start\n          data:\n            foo: bar\n    - assert:\n        resource:\n          apiVersion: v1\n          kind: ConfigMap\n          metadata:\n            name: quick-start\n          data:\n            foo: bar\n
"},{"location":"examples/kube-version/","title":"Check Kubernetes version","text":"

The test below fetches the Kubernetes cluster version using the x_k8s_server_version function. It then uses the minor version retrieved to adapt an assertion based on the value in the $minorversion binding.

Tip

You can implement a ternary operator in JMESPath using an expression like this:

<condition> && <value-if-true> || <value-if-false>

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  bindings:\n  - name: version\n    value: (x_k8s_server_version($config))\n  - name: minorversion\n    value: (to_number($version.minor))\n  steps:\n  - try:\n    - apply:\n        resource:\n          apiVersion: v1\n          kind: Pod\n          metadata:\n            name: pod01\n          spec:\n            containers:\n            - name: busybox\n              image: busybox:1.35\n    # ...\n    - assert:\n        resource:\n          apiVersion: v1\n          kind: Pod\n          metadata:\n            annotations:\n              # If the minor version of the Kubernetes cluster against\n              # which this is tested is less than 29, the annotation is\n              # expected to have the group 'system:masters' in it.\n              # Otherwise, due to a change in kubeadm, the group should\n              # be 'kubeadm:cluster-admins'.\n              kyverno.io/created-by: (($minorversion < `29` && '{\"groups\":[\"system:masters\",\"system:authenticated\"],\"username\":\"kubernetes-admin\"}') || '{\"groups\":[\"kubeadm:cluster-admins\",\"system:authenticated\"],\"username\":\"kubernetes-admin\"}')\n            name: pod01\n
"},{"location":"examples/label-selectors/","title":"Work with label selectors","text":"

Chainsaw can filter the tests to run using label selectors.

You can pass label selectors using the --selector flag when invoking the chainsaw test command.

Given the test below:

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: basic\n  labels:\n    foo: bar\nspec:\n  # ...\n

Invoking Chainsaw with the command below will take the test above into account:

chainsaw test --selector foo=bar\n
"},{"location":"examples/multi-cluster/","title":"Multi-cluster setup","text":"

Chainsaw supports registering and using multiple clusters in tests.

We can also register clusters dynamically and combine this with cluster selection to achieve scenarios where clusters are dynamically allocated in a test step, used in the following steps, and cleaned up at the end.

The following test demonstrates such a scenario by creating a local kind cluster in the first, using it in the second step, and configuring a cleanup script to delete the cluster when the test terminates:

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    # create a local cluster\n    - script:\n        timeout: 1m\n        content: |\n          kind create cluster --name dynamic --kubeconfig ./dynamic\n    # register `cleanup` operations to delete the cluster\n    # at the end of the test\n    cleanup:\n    - script:\n        content: |\n          kind delete cluster --name dynamic\n    - script:\n        content: |\n          rm -f ./dynamic\n    # register the `dynamic` cluster in this step\n  - clusters:\n      dynamic:\n        kubeconfig: ./dynamic\n    # and use the `dynamic` cluster for all operations in the step\n    cluster: dynamic\n    try:\n    - apply:\n        resource:\n          apiVersion: v1\n          kind: ConfigMap\n          metadata:\n            name: quick-start\n            namespace: default\n          data:\n            foo: bar\n    - assert:\n        resource:\n          apiVersion: v1\n          kind: ConfigMap\n          metadata:\n            name: quick-start\n            namespace: default\n          data:\n            foo: bar\n

Running the test above will produce the following output:

    | 10:44:53 | example | @setup   | CREATE    | OK    | v1/Namespace @ chainsaw-useful-seahorse\n    | 10:44:53 | example | step-1   | TRY       | RUN   |\n    | 10:44:53 | example | step-1   | SCRIPT    | RUN   |\n        === COMMAND\n        /bin/sh -c kind create cluster --name dynamic --kubeconfig ./dynamic\n    | 10:45:10 | example | step-1   | SCRIPT    | LOG   |\n        === STDERR\n        Creating cluster \"dynamic\" ...\n         \u2022 Ensuring node image (kindest/node:v1.27.3) \ud83d\uddbc  ...\n         \u2713 Ensuring node image (kindest/node:v1.27.3) \ud83d\uddbc\n         \u2022 Preparing nodes \ud83d\udce6   ...\n         \u2713 Preparing nodes \ud83d\udce6 \n         \u2022 Writing configuration \ud83d\udcdc  ...\n         \u2713 Writing configuration \ud83d\udcdc\n         \u2022 Starting control-plane \ud83d\udd79\ufe0f  ...\n         \u2713 Starting control-plane \ud83d\udd79\ufe0f\n         \u2022 Installing CNI \ud83d\udd0c  ...\n         \u2713 Installing CNI \ud83d\udd0c\n         \u2022 Installing StorageClass \ud83d\udcbe  ...\n         \u2713 Installing StorageClass \ud83d\udcbe\n        Set kubectl context to \"kind-dynamic\"\n        You can now use your cluster with:\n\n        kubectl cluster-info --context kind-dynamic --kubeconfig ./dynamic\n\n        Thanks for using kind! \ud83d\ude0a\n    | 10:45:10 | example | step-1   | SCRIPT    | DONE  |\n    | 10:45:10 | example | step-1   | TRY       | DONE  |\n    | 10:45:10 | example | step-2   | TRY       | RUN   |\n    | 10:45:10 | example | step-2   | APPLY     | RUN   | v1/ConfigMap @ default/quick-start\n    | 10:45:10 | example | step-2   | CREATE    | OK    | v1/ConfigMap @ default/quick-start\n    | 10:45:10 | example | step-2   | APPLY     | DONE  | v1/ConfigMap @ default/quick-start\n    | 10:45:10 | example | step-2   | ASSERT    | RUN   | v1/ConfigMap @ default/quick-start\n    | 10:45:10 | example | step-2   | ASSERT    | DONE  | v1/ConfigMap @ default/quick-start\n    | 10:45:10 | example | step-2   | TRY       | DONE  |\n    | 10:45:10 | example | step-2   | CLEANUP   | RUN   |\n    | 10:45:10 | example | step-2   | DELETE    | RUN   | v1/ConfigMap @ default/quick-start\n    | 10:45:10 | example | step-2   | DELETE    | OK    | v1/ConfigMap @ default/quick-start\n    | 10:45:10 | example | step-2   | DELETE    | DONE  | v1/ConfigMap @ default/quick-start\n    | 10:45:10 | example | step-2   | CLEANUP   | DONE  |\n    | 10:45:10 | example | step-1   | CLEANUP   | RUN   |\n    | 10:45:10 | example | step-1   | SCRIPT    | RUN   |\n        === COMMAND\n        /bin/sh -c kind delete cluster --name dynamic\n    | 10:45:10 | example | step-1   | SCRIPT    | LOG   |\n        === STDERR\n        Deleting cluster \"dynamic\" ...\n        Deleted nodes: [\"dynamic-control-plane\"]\n    | 10:45:10 | example | step-1   | SCRIPT    | DONE  |\n    | 10:45:10 | example | step-1   | SCRIPT    | RUN   |\n        === COMMAND\n        /bin/sh -c rm -f ./dynamic\n    | 10:45:10 | example | step-1   | SCRIPT    | DONE  |\n    | 10:45:10 | example | step-1   | CLEANUP   | DONE  |\n    | 10:45:10 | example | @cleanup | DELETE    | RUN   | v1/Namespace @ chainsaw-useful-seahorse\n    | 10:45:11 | example | @cleanup | DELETE    | OK    | v1/Namespace @ chainsaw-useful-seahorse\n    | 10:45:16 | example | @cleanup | DELETE    | DONE  | v1/Namespace @ chainsaw-useful-seahorse\n
"},{"location":"examples/negative-testing/","title":"Negative testing","text":"

Negative testing is the process of testing cases that are supposed to fail. That is, a test expects errors to happen and if the expected errors don't occur the test must fail.

Chainsaw supports negative testing by letting you decide what should be considered an error or not.

Tip

By default, Chainsaw will consider an operation failed if there was an error executing it (non-zero exit code in scripts and commands, error returned by the API server when calling into Kubernetes, etc...).

"},{"location":"examples/negative-testing/#script-case","title":"Script case","text":"

The test below expects an error and validates the returned error message:

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - script:\n        content: kubectl get foo\n        check:\n          ($error != null): true\n          ($stderr): |-\n            error: the server doesn't have a resource type \"foo\"\n

If for whatever reason, the kubectl get foo doesn't return an error, or the message received in standard error output is not error: the server doesn't have a resource type \"foo\", Chainsaw will consider the operation failed.

If it returns an error and the expected error message, Chainsaw will consider the operation successful.

"},{"location":"examples/negative-testing/#working-with-resources","title":"Working with resources","text":"

The test below tries to apply resources in a cluster but expects the operation to fail:

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - apply:\n        file: resources.yaml\n        expect:\n          # check that applying the resource failed\n        - check:\n            ($error != null): true\n

If applying the resource succeeded, Chainsaw will consider the operation failed.

On the other hand, if applying the resource fails, Chainsaw will consider the operation to be successful.

"},{"location":"examples/negative-testing/#resource-matching","title":"Resource matching","text":"

In the previous example, if the resources.yaml contains multiple resources, but only some of them may be expected to fail.

Chainsaw allows matching resources when evaluating checks:

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - apply:\n        file: resources.yaml\n        expect:\n          # the check below only applies if the resource being checked\n          # matches the condition defined in the `match` field\n        - match:\n            apiVersion: v1\n            kind: ConfigMap\n            metadata:\n              name: quick-start\n          check:\n            ($error != null): true\n

Using the match field, we can easily target failures related to specific resources.

"},{"location":"examples/non-resource-assertions/","title":"Non-resource assertions","text":"

Under certain circumstances, it makes sense to evaluate assertions that do not depend on resources. For example, when asserting the number of nodes in a cluster is equal to a known value.

The test below uses the x_k8s_list function to query the list of nodes in the cluster. It uses the results to compare the number of nodes found with a known number (1 in this case).

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - assert:\n        resource:\n          (x_k8s_list($client, 'v1', 'Node')):\n            (length(items)): 1\n
"},{"location":"examples/test-output/","title":"Test command output","text":"

Chainsaw can be used to easily check terminal output from CLIs and other commands. This is useful in that convoluted bash scripts involving chaining together tools like grep can be avoided or at least minimized to only complex use cases. Output to both stdout and stderr can be checked for a given string or precise contents.

"},{"location":"examples/test-output/#checking-output-contains","title":"Checking Output Contains","text":"

One basic use case for content checking is that the output simply contains a given string or piece of content. For example, you might want to run automated tests on a CLI binary you build to ensure that a given command produces output that contains some content you specify somewhere in the output. Let's use the following output from the kubectl version command to show these examples.

kubectl version\n\nClient Version: v1.28.2\nKustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3\nServer Version: v1.27.4+k3s1\n

Below is an example that ensures the string '1.28' is found somewhere in that output. So long as the content is present anywhere, the test will succeed. To perform this check, the contains() JMESPath filter is used.

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: test\nspec:\n  steps:\n  - name: Check kubectl\n    try:\n    - script:\n        content: kubectl version\n        check:\n          # This check ensures that the string '1.28' is found\n          # in stdout or else fails\n          (contains($stdout, '1.28')): true\n

Checks for content containing a given value can be negated as well. For example, checking to ensure the output does NOT contain the string '1.25'.

- script:\n    content: kubectl version\n    check:\n      # This check ensures that the string '1.25' is NOT found\n      # in stdout or else fails\n      (contains($stdout, '1.25')): false\n
"},{"location":"examples/test-output/#checking-output-is-exactly","title":"Checking Output Is Exactly","text":"

In addition to checking that CLI/command output contains some contents, you may need to ensure that the contents are exactly as intended. The Chainsaw test below accomplishes this by comparing the entire contents of stdout with those specified in the block scalar. If so much as one character, space, or line break is off, the test will fail. This is useful in that not only can content be checked but the formatting of that content can be ensured it matches a given declaration.

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: test\nspec:\n  steps:\n  - name: Check kubectl\n    try:\n    - script:\n        content: kubectl version\n        check:\n          # This check ensures the contents of stdout are exactly as shown.\n          # Any deviations will cause a failure.\n          ($stdout): |-\n            Client Version: v1.28.2\n            Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3\n            Server Version: v1.27.4+k3s1\n
"},{"location":"examples/test-output/#checking-output-in-errors","title":"Checking Output In Errors","text":"

In addition to testing that commands succeed and with output in a given shape, it's equally valuable and necessary to perform negative tests; that tests fail and with contents that are as expected. Similarly, those checks can be for output which has some contents as well as output which appears exactly as desired. For example, you may wish to check that running the kubectl foo command not only fails as expected but that the output shown to users contains a certain word or sentence.

kubectl foo\n\nerror: unknown command \"foo\" for \"kubectl\"\n\nDid you mean this?\n        top\n

Below you can see an example where the command kubectl foo is expected to fail but that the error message returned contains some output, in this case the string 'top'.

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: test\nspec:\n  steps:\n  - name: Check bad kubectl command\n    try:\n    - script:\n        content: kubectl foo\n        check:\n          # This checks that the result of the content was an error.\n          ($error != null): true\n          # This check below ensures that the string 'top' is found in stderr or else fails\n          (contains($stderr, 'top')): true\n

Likewise, this failure output can be checked that it is precise. Note that in the example below, due to the use of a tab character in the output of kubectl foo, the value of the ($stderr) field is given as a string to preserve these non-printing characters.

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: test\nspec:\n  steps:\n  - name: Check kubectl\n    try:\n    - script:\n        content: kubectl foo\n        check:\n          # This checks that the result of the content was an error.\n          ($error != null): true\n          # This checks that the output is exactly as intended.\n          ($stderr): \"error: unknown command \\\"foo\\\" for \\\"kubectl\\\"\\n\\nDid you mean this?\\n\\ttop\"\n
"},{"location":"examples/values/","title":"Pass data to tests","text":"

Chainsaw can pass arbitrary values when running tests using the --values flag. Values will be available to tests under the $values binding.

This is useful when a test needs to be configured externally.

"},{"location":"examples/values/#reference-external-data","title":"Reference external data","text":"

The test below expects the $value.foo to be provided when chainsaw is invoked.

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - assert:\n        resource:\n          ($values.foo): bar\n
"},{"location":"examples/values/#invoking-chainsaw","title":"Invoking Chainsaw","text":""},{"location":"examples/values/#read-values-from-a-file","title":"Read values from a file","text":"
chainsaw test --values ./values.yaml\n
"},{"location":"examples/values/#read-from-stdin","title":"Read from stdin","text":"
echo \"foo: bar\" | chainsaw test --values -\n
"},{"location":"examples/values/#use-heredoc","title":"Use heredoc","text":"
chainsaw test --values - <<EOF\nfoo: bar\nEOF\n
"},{"location":"general/bindings/","title":"Bindings","text":"

You can think of bindings as a side context where you can store and retrieve data by name.

This is particularly useful when some data is only known at runtime. For example, to pass data from one operation to another, to implement resource templating, to fetch data from an external system, or anything that needs to be computed at runtime.

"},{"location":"general/bindings/#syntax","title":"Syntax","text":"

The test below illustrates bindings declaration at different levels:

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  # bindings can be declared at the test level\n  bindings:\n  - name: chainsaw\n    value: chainsaw\n  steps:\n    # bindings can also be declared at the step level\n  - bindings:\n    - name: hello\n      value: hello\n    try:\n    - script:\n        # bindings can also be declared at the operation level\n        bindings:\n        - name: awesome\n          value: awesome\n        env:\n          # combined bindings together using the `join` functions and\n          # assign the result to the GREETINGS environment variable\n        - name: GREETINGS\n          value: (join(' ', [$hello, $chainsaw, 'is', $awesome]))\n        content: echo $GREETINGS\n
"},{"location":"general/bindings/#reference","title":"Reference","text":"

Browse the reference documentation to see the syntax details and where bindings can be declared.

"},{"location":"general/bindings/#inheritance","title":"Inheritance","text":"

Bindings can be configured at the test, step or operation level.

All bindings configured at a given level are automatically inherited at lower levels.

"},{"location":"general/bindings/#immutability","title":"Immutability","text":"

Bindings are immutable. This means two bindings can have the same name without overwriting each other.

When a binding is registered it potentially hides other bindings with the same name.

When this binding goes out of scope, previously registered bindings with the same name become visible again.

"},{"location":"general/bindings/#templating","title":"Templating","text":"

Both name and value of a binding can use templating.

"},{"location":"general/bindings/#built-in-bindings","title":"Built-in bindings","text":"

Chainsaw offers some built-in bindings you can directly use in your tests, steps and operations.

Browse the built-in bindings list to find available bindings.

"},{"location":"general/checks/","title":"Operation checks","text":"

Considering an operation's success or failure is not always as simple as checking an error code.

  • Sometimes an operation can fail but the failure is what you expected, hence the operation should be reported as successful.
  • Sometimes an operation can succeed but the result is not what you expected, in this case, the operation should be reported as a failure.

To support those kinds of use cases, some operations support additional checks to evaluate the operation result against an assertion tree.

"},{"location":"general/checks/#input-model","title":"Input model","text":"

Different operations have a different model passed through the assertion tree.

Please consult the Built-in bindings reference documentation to learn what is available depending on the operation.

"},{"location":"general/checks/#expect-vs-check","title":"Expect vs Check","text":"

While a simple check is enough to determine the result of a single operation, we needed a more advanced construct to cover apply, create, delete, patch and update operations. Those operations can operate on files containing multiple resources and every resource can lead to a different result and expectation.

"},{"location":"general/checks/#check","title":"Check","text":"

The example below uses a simple check. The operation is expected to fail (($error != null): true):

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - script:\n        content: |\n          exit 1\n        check:\n          # an error is expected, this will:\n          # - succeed if the operation failed\n          # - fail if the operation succeeded\n          ($error != null): true\n
"},{"location":"general/checks/#expect","title":"Expect","text":"

To support more granular checks we use the expect field that contains an array of Expectations.

Every expectation is made of an optional match combined with a check statement.

This way it is possible to control the scope of a check:

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - create:\n        file: resources.yaml\n        expect:\n        - match:\n            # this check applies only if the match\n            # statement below evaluates to `true`\n            apiVersion: v1\n            kind: ConfigMap\n          check:\n            # an error is expected, this will:\n            # - succeed if the operation failed\n            # - fail if the operation succeeded\n            ($error != null): true\n

In the test above, only config maps are expected to fail. If the resources.yaml file contains other type of resources they are supposed to be created without error (if an error happens for a non config map resource, the operation will be considered a failure).

"},{"location":"general/inheritance/","title":"Inheritance","text":"

Chainsaw has a concept of levels and most of the configuration elements and dynamic elements are inherited from one layer to the next in one way or another.

"},{"location":"general/inheritance/#levels","title":"Levels","text":"
flowchart TD\n    Configuration -. Configuration elements are inherited in tests .-> Test\n    Test -. Test elements are inherited in test steps .-> Step\n    Step -. Step elements are inherited in step operations .-> Operation
"},{"location":"general/inheritance/#configuration","title":"Configuration","text":"

The first layer comes from the Chainsaw configuration. You can think about this layer as the global scope and a way to configure how Chainsaw will behave globally.

Under certain circumstances, lower layers will be allowed to consume and/or override elements from upper layers.

"},{"location":"general/inheritance/#test","title":"Test","text":"

At the test level, you can override or create new elements. They will only be visible to the test, steps and operations that are part of it.

In any case, tests are strongly isolated and have no way to communicate with or depend on other tests.

"},{"location":"general/inheritance/#step","title":"Step","text":"

Again, at the step level, you can override or create new elements and they will only be visible to the step and operations that are part of it.

"},{"location":"general/inheritance/#operation","title":"Operation","text":"

At the operation level, you can override or create new elements and use them in the operation itself.

"},{"location":"general/inheritance/#immutability","title":"Immutability","text":"

Even if elements are inherited, they are immutable.

Some elements can be overridden but never overwritten.

"},{"location":"general/inheritance/#outputs","title":"Outputs","text":"

Inheritance always flows from one level to the next and never propagates back to the upper levels.

There's no exception to this rule, but the only case where one operation can communicate with other ones is when using outputs.

"},{"location":"general/namespace/","title":"Test namespace","text":"

By default, Chainsaw will create an ephemeral namespace with a random name for each test, unless a specific namespace name is provided at the global or test level.

"},{"location":"general/namespace/#selection","title":"Selection","text":""},{"location":"general/namespace/#global","title":"Global","text":"

One way to control the namespace used to run tests is to specify the name in the Chainsaw configuration Namespace options.

If a namespace name is specified at the configuration level Chainsaw will use it to run the tests (unless an individual test overrides the namespace name).

"},{"location":"general/namespace/#per-test","title":"Per test","text":"

If the test name is specified in a test spec, Chainsaw will use it to run the test regardless of whether a namespace name was configured at the global level.

"},{"location":"general/namespace/#random","title":"Random","text":"

If no namespace name was specified at the global or test level, Chainsaw will create a random one for the lifetime of the test.

"},{"location":"general/namespace/#cleanup","title":"Cleanup","text":"

As with any other resource, Chainsaw will clean up the namespace only if the namespace was created by Chainsaw.

If the namespace already exists when the test starts, Chainsaw will use it to run the test but won't delete it after the test terminates.

"},{"location":"general/namespace/#template","title":"Template","text":"

A namespace template can be provided at the global or test level.

This is useful if you want to make something specific with the namespace Chainsaw creates (add labels, add annotations, etc...).

Tip

A namespace template specified at the test level takes precedence over the namespace template specified at the global level.

"},{"location":"general/namespace/#namespace-injection","title":"Namespace injection","text":"

Because the name of the namespace is only known at runtime, depending on the resource being manipulated, Chainsaw will eventually inject the namespace name, except if:

  • the resource already has a namespace specified
  • the resource is a clustered resource
"},{"location":"general/namespace/#example","title":"Example","text":"

The resource below is a namespaced one and has no namespace specified. Chainsaw will automatically inject the namespace name in it:

apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: chainsaw-quick-start\n  # there is no namespace configured and the resource\n  # is a namespaced one.\n  # Chainsaw will automatically inject the test namespace\ndata:\n  foo: bar\n
"},{"location":"general/outputs/","title":"Outputs","text":"

Operation outputs can be useful for communicating and reusing computation results across operations.

Chainsaw evaluates outputs after an operation has finished executing. The results of output evaluations are registered in the bindings and are made available for the following operations.

"},{"location":"general/outputs/#syntax","title":"Syntax","text":""},{"location":"general/outputs/#basic","title":"Basic","text":"

The test below illustrates output usage:

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  bindings:\n  - name: chainsaw\n    value: chainsaw\n  steps:\n  - bindings:\n    - name: hello\n      value: hello\n    try:\n    - script:\n        bindings:\n        - name: awesome\n          value: awesome\n        env:\n        - name: GREETINGS\n          value: (join(' ', [$hello, $chainsaw, 'is', $awesome]))\n        # output is used to register a new `$OUTPUT` binding\n        outputs:\n        - name: OUTPUT\n          value: ($stdout)\n        content: echo $GREETINGS\n    - script:\n        # output from the previous operation is used\n        # to configure an evironment variable\n        env:\n        - name: INPUT\n          value: ($OUTPUT)\n        content: echo $INPUT\n
"},{"location":"general/outputs/#matching","title":"Matching","text":"

An output supports an optional match field. The match statement is used to conditionally assign the output binding.

The test below illustrates output with matching:

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  bindings:\n  - name: chainsaw\n    value: chainsaw\n  steps:\n  - bindings:\n    - name: hello\n      value: hello\n    try:\n    - script:\n        bindings:\n        - name: awesome\n          value: awesome\n        env:\n        - name: GREETINGS\n          value: (join(' ', [$hello, $chainsaw, 'is', $awesome]))\n        # output is used to register a new `$OUTPUT` binding\n        outputs:\n          # by default, the `$OUTPUT` binding is assigned\n          # the content of the standard output\n        - name: OUTPUT\n          value: ($stdout)\n          # if the match statement evaluates to true,\n          # the `$OUTPUT` binding will be set to\n          # 'YES! chainsaw is awesome'\n        - match:\n            ($OUTPUT): hello chainsaw is awesome\n          name: OUTPUT\n          value: YES! chainsaw is awesome\n        content: echo $GREETINGS\n    - script:\n        # output from the previous operation is used\n        # to configure an evironment variable\n        env:\n        - name: INPUT\n          value: ($OUTPUT)\n        content: echo $INPUT\n
"},{"location":"general/outputs/#reference","title":"Reference","text":"

Browse the reference documentation to see the syntax details and where outputs can be declared.

"},{"location":"general/outputs/#templating","title":"Templating","text":"

Both name and value of an output can use templating.

"},{"location":"general/references/","title":"References","text":"

Chainsaw tests often need to reference resources. Including references in tests can be done in multiple ways.

"},{"location":"general/references/#inline","title":"Inline","text":"

One way to declare a resource is to do it directly inside the test definition:

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - apply:\n        # specify resource inline\n        resource:\n          apiVersion: v1\n          kind: ConfigMap\n          metadata:\n            name: quick-start\n          data:\n            foo: bar\n

This doesn't encourage file reuse but can be handy, especially when the resource definition is short or when the execution environment doesn't support file system access.

"},{"location":"general/references/#file-reference","title":"File reference","text":"

Another option is to use the file field. The file can be a specific file, or multiple files declared using a glob pattern:

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - apply:\n        # use a specific file\n        file: my-configmap.yaml\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - apply:\n        # use glob pattern\n        file: \"configs/*.yaml\"\n
"},{"location":"general/references/#url-reference","title":"URL reference","text":"

A third option is to use a URL. Chainsaw uses https://github.com/hashicorp/go-getter, it will download the content from the remote service and load it in the operation resources:

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - apply:\n        # use an URL\n        file: https://raw.githubusercontent.com/kyverno/chainsaw/main/testdata/step/configmap.yaml\n
"},{"location":"general/references/#cardinality","title":"Cardinality","text":"

When using file-based references, it is important to note that the referenced file(s) can declare multiple resources. Internally, Chainsaw will duplicate the operation once per resource.

This is important to keep this in mind, especially when working with bindings and outputs. Bindings and outputs will be evaluated for every operation instance.

"},{"location":"general/templating/","title":"Templating","text":"

Chainsaw simplifies dynamic configuration with native templating support.

In the past, users have created all sorts of hacks using tools like envsubst for dynamic substitution of env-variables. Those workarounds usually lack flexibility and introduce new problems like hiding the real resources from Chainsaw, preventing it from cleaning resources properly.

Templating in Chainsaw solves exactly this kind of problem.

"},{"location":"general/templating/#syntax","title":"Syntax","text":"

Tip

Resource templating is heavily based on bindings and uses JMESPath language.

"},{"location":"general/templating/#bindings","title":"Bindings","text":"

In the template below, we are using the $namespace binding at two different places, effectively injecting the ephemeral namespace name in the name and the data.foo fields:

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - assert:\n      resource:\n        apiVersion: v1\n        kind: ConfigMap\n        metadata:\n          name: ($namespace)\n        data:\n          foo: ($namespace)\n
"},{"location":"general/templating/#jmespath","title":"JMESPath","text":"

In the template below, we are using the JMESPath join function to create a unique resource name:

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - apply:\n      resource:\n        apiVersion: v1\n        kind: ConfigMap\n        metadata:\n          name: (join('-', [$namespace, 'cm']))\n        data:\n          foo: bar\n
"},{"location":"guides/kuttl-migration/","title":"Migration from KUTTL","text":""},{"location":"guides/kuttl-migration/#overview","title":"Overview","text":"

The chainsaw migrate kuttl tests and chainsaw migrate kuttl config commands are designed for the migration of KUTTL tests to Chainsaw.

  • chainsaw migrate kuttl config

    migrates a KUTTL TestSuite to the corresponding Chainsaw Configuration

  • chainsaw migrate kuttl tests

    migrates KUTTL tests to the corresponding Chainsaw Tests

Reference documentation

You can view the full command documentation here.

"},{"location":"guides/kuttl-migration/#examples","title":"Examples","text":""},{"location":"guides/kuttl-migration/#migrate-tests","title":"Migrate tests","text":"

The command below will migrate KUTTL tests to Chainsaw and overwrite original files with converted ones.

chainsaw migrate kuttl tests path/to/kuttl/tests --save --cleanup\n

This will generate a chainsaw-test.yaml for every KUTTL test discovered.

"},{"location":"guides/kuttl-migration/#migrate-configuration","title":"Migrate configuration","text":"

The command below will migrate a KUTTL test suite file to the corresponding Chainsaw Configuration.

chainsaw migrate kuttl config path/to/kuttl/testsuite --save --cleanup\n

This will generate a .chainsaw.yaml configuration file.

"},{"location":"guides/lint/","title":"Lint tests","text":""},{"location":"guides/lint/#overview","title":"Overview","text":"

Chainsaw comes with a lint command to detect ill-formated tests.

Reference documentation

You can view the full command documentation here.

"},{"location":"guides/lint/#usage","title":"Usage","text":"

To build the docs of a test, Chainsaw provides the chainsaw lint test -f path/to/chainsaw-test.yaml command.

chainsaw lint test -f - <<EOF\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: assertion-tree\nspec:\n  steps:\n  - try:\n    - assert:\n        file: assert.yaml\nEOF\n
Processing input...\nThe document is valid\n
"},{"location":"guides/test-docs/","title":"Building test docs","text":""},{"location":"guides/test-docs/#overview","title":"Overview","text":"

Chainsaw makes it simple to build the documentation of your tests.

As test suites grow, it becomes important to document what a test does and how it is supposed to work.

Going through the implementation of a test to understand its purpose is not an efficient strategy.

Reference documentation

You can view the full command documentation here.

"},{"location":"guides/test-docs/#usage","title":"Usage","text":"

To build the docs of a test, Chainsaw provides the chainsaw build docs command.

chainsaw build docs --test-dir path/to/chainsaw/tests\n

This will automatically discover tests and document steps and operations in try, catch and finally statements.

"},{"location":"guides/test-docs/#the-description-field","title":"The description field","text":"

Additionally, you can set the description field in:

  • TestSpec
  • TestStepSpec
  • Operation
  • Catch
  • Finally

Chainsaw will output them nicely in the built docs.

"},{"location":"guides/test-docs/#example","title":"Example","text":"

See below for an example test and the corresponding built docs.

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: basic\nspec:\n  description: This is a very simple test that creates a configmap and checks the content is as expected.\n  steps:\n  - description: This steps applies the configmap in the cluster and checks the configmap content.\n    try:\n    - description: Create the configmap.\n      apply:\n        file: configmap.yaml\n    - description: Check the configmap content.\n      assert:\n        file: configmap-assert.yaml\n
"},{"location":"guides/test-docs/#test-basic","title":"Test: basic","text":"

This is a very simple test that creates a configmap and checks the content is as expected.

"},{"location":"guides/test-docs/#steps","title":"Steps","text":"# Name Try Catch Finally 1 step-1 2 0 0"},{"location":"guides/test-docs/#step-step-1","title":"Step: step-1","text":"

This step applies the configmap in the cluster and checks the configmap content.

"},{"location":"guides/test-docs/#try","title":"Try","text":"# Operation Description 1 apply Create the configmap. 2 assert Check the configmap content."},{"location":"operations/","title":"Operations","text":"

Chainsaw supports the following operations:

  • Apply
  • Assert
  • Command
  • Create
  • Delete
  • Error
  • Patch
  • Script
  • Sleep
  • Update
"},{"location":"operations/#helpers","title":"Helpers","text":"

Chainsaw also supports kubectl helpers.

"},{"location":"operations/#properties","title":"Properties","text":""},{"location":"operations/#action-unicity","title":"Action unicity","text":"

Every operation must consist of a single action.

While it is syntactically possible to create an operation with multiple actions, Chainsaw will verify and reject tests if operations containing multiple actions are found.

The reasoning behind this intentional choice is that it becomes harder to understand in which order actions will be executed when an operation consists of multiple actions. For this reason, operations consisting of multiple actions are not allowed.

"},{"location":"operations/#common-fields","title":"Common fields","text":""},{"location":"operations/#continue-on-error","title":"Continue on error","text":"

The continueOnError field determines whether a test step should continue executing or not if the operation fails (in any case the test will be marked as failed).

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n      # in case of error the test will be marked as failed\n      # but the step will not stop execution and will\n      # continue executing the following operations\n    - continueOnError: true\n      apply:\n        resource:\n          apiVersion: v1\n          kind: ConfigMap\n          metadata:\n            name: quick-start\n          data:\n            foo: bar\n
"},{"location":"operations/#description","title":"Description","text":"

All operations support a description field that can be used document your tests.

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - description: Waits a couple of seconds\n      sleep:\n        duration: 3s\n
"},{"location":"operations/apply/","title":"Apply","text":"

The apply operation defines resources that should be applied to a Kubernetes cluster. If the resource does not exist yet it will be created, otherwise, it will be configured to match the provided configuration.

"},{"location":"operations/apply/#configuration","title":"Configuration","text":"

The full structure of the Apply is documented here.

"},{"location":"operations/apply/#features","title":"Features","text":"Supported features Bindings support Outputs support Templating support Operation checks support"},{"location":"operations/apply/#examples","title":"Examples","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - apply:\n        # use a specific file\n        file: my-configmap.yaml\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - apply:\n        # use glob pattern\n        file: \"configs/*.yaml\"\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - apply:\n        # use an URL\n        file: https://raw.githubusercontent.com/kyverno/chainsaw/main/testdata/step/configmap.yaml\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - apply:\n        # specify resource inline\n        resource:\n          apiVersion: v1\n          kind: ConfigMap\n          metadata:\n            name: chainsaw-quick-start\n          data:\n            foo: bar\n
"},{"location":"operations/apply/#operation-check","title":"Operation check","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - apply:\n        file: my-configmap.yaml\n        expect:\n        - match:\n            # this check applies only if the match\n            # statement below evaluates to `true`\n            apiVersion: v1\n            kind: ConfigMap\n          check:\n            # an error is expected, this will:\n            # - succeed if the operation failed\n            # - fail if the operation succeeded\n            ($error != null): true\n
"},{"location":"operations/assert/","title":"Assert","text":"

The assert operation allows you to specify conditions that should hold true for a successful test.

For example, after applying resources, you might want to ensure that a particular pod is running or a service is accessible.

"},{"location":"operations/assert/#configuration","title":"Configuration","text":"

The full structure of the Assert is documented here.

"},{"location":"operations/assert/#features","title":"Features","text":"Supported features Bindings support Outputs support Templating support | Operation checks support"},{"location":"operations/assert/#templating","title":"Templating","text":"

When working with assert and error operations, the content is already an assertion tree and therefore mostly represents a logical operation. An exception to this rule is for fields participating in the resource selection process.

For this reason, only elements used for looking up the resources from the cluster will be considered for templating. That is, apiVersion, kind, name, namespace and labels.

"},{"location":"operations/assert/#examples","title":"Examples","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - assert:\n        # use a specific file\n        file: ../resources/deployment-assert.yaml\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - assert:\n        # use glob pattern\n        file: \"../assertions/*.yaml\"\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - assert:\n        # use an URL\n        file: https://raw.githubusercontent.com/kyverno/chainsaw/main/testdata/resource/valid.yaml\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - assert:\n        # specify resource inline\n        resource:\n          apiVersion: v1\n          kind: Deployment\n          metadata:\n            name: foo\n          spec:\n            (replicas > 3): true\n
"},{"location":"operations/command/","title":"Command","text":"

The command operation provides a mean to execute a specific command during the test step.

Warning

Command arguments are not going through shell expansion.

It's crucial to consider potential differences in behavior, as Chainsaw may interpret them differently compared to regular shell environments.

"},{"location":"operations/command/#configuration","title":"Configuration","text":"

The full structure of the Command is documented here.

"},{"location":"operations/command/#features","title":"Features","text":"Supported features Bindings support Outputs support Templating support Operation checks support"},{"location":"operations/command/#kubeconfig","title":"KUBECONFIG","text":"
  • Unless --no-cluster is specified, Chainsaw always executes commands in the context of a temporary KUBECONFIG, built from the configured target cluster.
  • This specific KUBECONFIG has a single cluster, auth info and context configured (all named chainsaw).
"},{"location":"operations/command/#examples","title":"Examples","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - command:\n        entrypoint: echo\n        args:\n        - hello chainsaw\n
"},{"location":"operations/command/#operation-check","title":"Operation check","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - command:\n        entrypoint: echo\n        args:\n        - hello chainsaw\n        check:\n          # an error is expected, this will:\n          # - succeed if the operation failed\n          # - fail if the operation succeeded\n          ($error != null): true\n
"},{"location":"operations/create/","title":"Create","text":"

The create operation defines resources that should be created in a Kubernetes cluster.

If the resource to be created already exists in the cluster, the step will fail.

"},{"location":"operations/create/#configuration","title":"Configuration","text":"

The full structure of the Create is documented here.

"},{"location":"operations/create/#features","title":"Features","text":"Supported features Bindings support Outputs support Templating support Operation checks support"},{"location":"operations/create/#examples","title":"Examples","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - create:\n        # use a specific file\n        file: my-configmap.yaml\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - create:\n        # use glob pattern\n        file: \"configs/*.yaml\"\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - create:\n        # use an URL\n        file: https://raw.githubusercontent.com/kyverno/chainsaw/main/testdata/resource/valid.yaml\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - create:\n        # specify resource inline\n        resource:\n          apiVersion: v1\n          kind: ConfigMap\n          metadata:\n            name: chainsaw-quick-start\n          data:\n            foo: bar\n
"},{"location":"operations/create/#operation-check","title":"Operation check","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - create:\n        file: my-configmap.yaml\n        expect:\n        - match:\n            # this check applies only if the match\n            # statement below evaluates to `true`\n            apiVersion: v1\n            kind: ConfigMap\n          check:\n            # an error is expected, this will:\n            # - succeed if the operation failed\n            # - fail if the operation succeeded\n            ($error != null): true\n
"},{"location":"operations/delete/","title":"Delete","text":"

The delete operation defines resources that should be deleted from a Kubernetes cluster.

Warning

The propagation policy is forced to Background because some types default to Orphan (this is the case for unmanaged jobs for example) and we don't want to let dangling pods run in the cluster after cleanup.

"},{"location":"operations/delete/#configuration","title":"Configuration","text":"

The full structure of the Delete is documented here.

"},{"location":"operations/delete/#features","title":"Features","text":"Supported features Bindings support Outputs support Templating support Operation checks support"},{"location":"operations/delete/#examples","title":"Examples","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - delete:\n        ref:\n          apiVersion: v1\n          kind: Pod\n          namespace: default\n          name: my-test-pod\n
"},{"location":"operations/delete/#operation-check","title":"Operation check","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - delete:\n        ref:\n          apiVersion: v1\n          kind: Pod\n          namespace: default\n          name: my-test-pod\n        expect:\n        - match:\n            # this check applies only if the match\n            # statement below evaluates to `true`\n            apiVersion: v1\n            kind: Pod\n            metadata:\n              namespace: default\n              name: my-test-pod\n          check:\n            # an error is expected, this will:\n            # - succeed if the operation failed\n            # - fail if the operation succeeded\n            ($error != null): true\n
"},{"location":"operations/error/","title":"Error","text":"

The error operation lets you define a set of expected errors for a test step. If any of these errors occur during the test, they are treated as expected outcomes. However, if an error that's not on this list occurs, it will be treated as a test failure.

"},{"location":"operations/error/#configuration","title":"Configuration","text":"

The full structure of the Error is documented here.

"},{"location":"operations/error/#features","title":"Features","text":"Supported features Bindings support Outputs support Templating support | Operation checks support"},{"location":"operations/error/#templating","title":"Templating","text":"

When working with assert and error operations, the content is already an assertion tree and therefore mostly represents a logical operation. An exception to this rule is for fields participating in the resource selection process.

For this reason, only elements used for looking up the resources from the cluster will be considered for templating. That is, apiVersion, kind, name, namespace and labels.

"},{"location":"operations/error/#examples","title":"Examples","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - error:\n        # use a specific file\n        file: ../resources/deployment-error.yaml\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - error:\n        # use glob pattern\n        file: \"../errors/*.yaml\"\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - error:\n        # use an URL\n        file: https://raw.githubusercontent.com/user/repo/branch/path/to/deployment-error.yaml\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - error:\n        # specify resource inline\n        resource:\n          apiVersion: v1\n          kind: Deployment\n          metadata:\n            name: foo\n          spec:\n            (replicas > 3): true\n
"},{"location":"operations/patch/","title":"Patch","text":"

The patch operation defines resources that should be modified in a Kubernetes cluster.

If the resource to be modified does not exist in the cluster, the step will fail.

"},{"location":"operations/patch/#configuration","title":"Configuration","text":"

The full structure of the Patch is documented here.

"},{"location":"operations/patch/#features","title":"Features","text":"Supported features Bindings support Outputs support Templating support Operation checks support"},{"location":"operations/patch/#examples","title":"Examples","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - patch:\n        # use a specific file\n        file: my-configmap.yaml\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - patch:\n        # use glob pattern\n        file: \"configs/*.yaml\"\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - patch:\n        # use an URL\n        file: https://raw.githubusercontent.com/kyverno/chainsaw/main/testdata/resource/valid.yaml\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - patch:\n        # specify resource inline\n        resource:\n          apiVersion: v1\n          kind: ConfigMap\n          metadata:\n            name: chainsaw-quick-start\n          data:\n            foo: bar\n
"},{"location":"operations/patch/#operation-check","title":"Operation check","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - patch:\n        file: my-configmap.yaml\n        expect:\n        - match:\n            # this check applies only if the match\n            # statement below evaluates to `true`\n            apiVersion: v1\n            kind: ConfigMap\n          check:\n            # an error is expected, this will:\n            # - succeed if the operation failed\n            # - fail if the operation succeeded\n            ($error != null): true\n
"},{"location":"operations/script/","title":"Script","text":"

The script operation provides a means to run a script during the test step.

"},{"location":"operations/script/#configuration","title":"Configuration","text":"

The full structure of the Script is documented here.

"},{"location":"operations/script/#features","title":"Features","text":"Supported features Bindings support Outputs support Templating support Operation checks support"},{"location":"operations/script/#kubeconfig","title":"KUBECONFIG","text":"
  • Unless --no-cluster is specified, Chainsaw always executes commands in the context of a temporary KUBECONFIG, built from the configured target cluster.
  • This specific KUBECONFIG has a single cluster, auth info and context configured (all named chainsaw).
"},{"location":"operations/script/#examples","title":"Examples","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - script:\n        content: |\n          echo \"hello chainsaw\"\n
"},{"location":"operations/script/#operation-check","title":"Operation check","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - script:\n        content: |\n          echo \"hello chainsaw\"\n        check:\n          # an error is expected, this will:\n          # - succeed if the operation failed\n          # - fail if the operation succeeded\n          ($error != null): true\n
"},{"location":"operations/sleep/","title":"Sleep","text":"

The sleep operation provides a means to sleep for a configured duration.

"},{"location":"operations/sleep/#configuration","title":"Configuration","text":"

The full structure of the Sleep is documented here.

"},{"location":"operations/sleep/#features","title":"Features","text":"Supported features Bindings support Outputs support Templating support Operation checks support"},{"location":"operations/sleep/#examples","title":"Examples","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - sleep:\n        duration: 30s\n
"},{"location":"operations/update/","title":"Update","text":"

The update operation defines resources that should be updated in a Kubernetes cluster.

If the resource to be updated doesn't exist in the cluster, the step will fail.

"},{"location":"operations/update/#configuration","title":"Configuration","text":"

The full structure of the Update is documented here.

"},{"location":"operations/update/#features","title":"Features","text":"Supported features Bindings support Outputs support Templating support Operation checks support"},{"location":"operations/update/#examples","title":"Examples","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - update:\n        # use a specific file\n        file: my-configmap.yaml\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example-multi\nspec:\n  steps:\n  - try:\n    - update:\n        # use glob pattern\n        file: \"configs/*.yaml\"\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - update:\n        # use an URL\n        file: https://raw.githubusercontent.com/kyverno/chainsaw/main/testdata/resource/valid.yaml\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - update:\n        # specify resource inline\n        resource:\n          apiVersion: v1\n          kind: ConfigMap\n          metadata:\n            name: chainsaw-quick-start\n          data:\n            foo: bar\n
"},{"location":"operations/update/#operation-check","title":"Operation check","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - update:\n        file: my-configmap.yaml\n        expect:\n        - match:\n            # this check applies only if the match\n            # statement below evaluates to `true`\n            apiVersion: v1\n            kind: ConfigMap\n          check:\n            # an error is expected, this will:\n            # - succeed if the operation failed\n            # - fail if the operation succeeded\n            ($error != null): true\n
"},{"location":"operations/helpers/","title":"Kubectl helpers","text":"

Kubectl helpers are declarative versions of kubectl imperative commands.

"},{"location":"operations/helpers/#implementation","title":"Implementation","text":"

Helpers are implemented as syntactic sugars.

They are translated into their corresponding kubectl commands and executed as such.

"},{"location":"operations/helpers/#kubeconfig","title":"KUBECONFIG","text":"
  • Chainsaw always executes commands in the context of a temporary KUBECONFIG, built from the configured target cluster.
  • This specific KUBECONFIG has a single cluster, auth info and context configured (all named chainsaw).
"},{"location":"operations/helpers/#helpers","title":"Helpers","text":"
  • Describe
  • Events
  • Get
  • Pods logs
  • Wait
"},{"location":"operations/helpers/describe/","title":"Describe","text":"

Show details of a specific resource or group of resources.

Deprecated syntax

You can specify the resource directly instead of using apiVersion and kind.

This is a deprecated syntax though and will be removed in a future version.

"},{"location":"operations/helpers/describe/#configuration","title":"Configuration","text":"

The full structure of the Describe resource is documented here.

"},{"location":"operations/helpers/describe/#features","title":"Features","text":"Supported features Bindings support Outputs support Templating support Operation checks support"},{"location":"operations/helpers/describe/#clustered-resources","title":"Clustered resources","text":"

When used with a clustered resource, the namespace is ignored and is not added to the corresponding kubectl command.

"},{"location":"operations/helpers/describe/#test-namespace","title":"Test namespace","text":"

When used with a namespaced resource, Chainsaw will default the scope to the ephemeral test namespace.

"},{"location":"operations/helpers/describe/#all-namespaces","title":"All namespaces","text":"

When used with a namespaced resource, it is possible to consider all namespaces in the cluster by setting namespace: '*'.

"},{"location":"operations/helpers/describe/#examples","title":"Examples","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try: ...\n    catch:\n    - describe:\n        # describe all pods in the test namespace\n        apiVersion: v1\n        kind: Pod\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try: ...\n    catch:\n    - describe:\n        apiVersion: v1\n        kind: Pod\n        # describe pods that have a name starting with the provided `my-pod`\n        name: my-pod\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try: ...\n    catch:\n    - describe:\n        apiVersion: v1\n        kind: Pod\n        # describe pods in the namespace `foo`\n        namespace: foo\n
"},{"location":"operations/helpers/describe/#label-selector","title":"Label selector","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try: ...\n    catch:\n    - describe:\n        apiVersion: v1\n        kind: Pod\n        # describe pods using a label selector query\n        selector: app=my-app\n
"},{"location":"operations/helpers/describe/#show-events","title":"Show events","text":"

Tip

By default, showEventsis true.

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try: ...\n    catch:\n    - describe:\n        apiVersion: v1\n        kind: Pod\n        showEvents: false\n
"},{"location":"operations/helpers/events/","title":"Events","text":"

Display one or many events.

"},{"location":"operations/helpers/events/#configuration","title":"Configuration","text":"

The full structure of the Events resource is documented here.

"},{"location":"operations/helpers/events/#features","title":"Features","text":"Supported features Bindings support Outputs support Templating support Operation checks support"},{"location":"operations/helpers/events/#test-namespace","title":"Test namespace","text":"

When used with a namespaced resource, Chainsaw will default the scope to the ephemeral test namespace.

"},{"location":"operations/helpers/events/#all-namespaces","title":"All namespaces","text":"

When used with a namespaced resource, it is possible to consider all namespaces in the cluster by setting namespace: '*'.

"},{"location":"operations/helpers/events/#examples","title":"Examples","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try: ...\n    catch:\n    # get all events in the test namespace\n    - events: {}\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try: ...\n    catch:\n    # get events in a specific namespace\n    - events:\n        namespace: foo\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try: ...\n    catch:\n    # get event by name\n    - events:\n        name: my-event\n
"},{"location":"operations/helpers/events/#label-selector","title":"Label selector","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try: ...\n    catch:\n    - events:\n        # get events using a label selector query\n        selector: app=my-app\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try: ...\n    catch:\n    - events:\n        # get events using a label selector query\n        selector: app=my-app\n        namespace: foo\n
"},{"location":"operations/helpers/events/#format","title":"Format","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try: ...\n    catch:\n    - events:\n        format: json\n
"},{"location":"operations/helpers/get/","title":"Get","text":"

Display one or many resources.

Deprecated syntax

You can specify the resource directly instead of using apiVersion and kind.

This is a deprecated syntax though and will be removed in a future version.

"},{"location":"operations/helpers/get/#configuration","title":"Configuration","text":"

The full structure of the Get resource is documented here.

"},{"location":"operations/helpers/get/#features","title":"Features","text":"Supported features Bindings support Outputs support Templating support Operation checks support"},{"location":"operations/helpers/get/#clustered-resources","title":"Clustered resources","text":"

When used with a clustered resource, the namespace is ignored and is not added to the corresponding kubectl command.

"},{"location":"operations/helpers/get/#test-namespace","title":"Test namespace","text":"

When used with a namespaced resource, Chainsaw will default the scope to the ephemeral test namespace.

"},{"location":"operations/helpers/get/#all-namespaces","title":"All namespaces","text":"

When used with a namespaced resource, it is possible to consider all namespaces in the cluster by setting namespace: '*'.

"},{"location":"operations/helpers/get/#examples","title":"Examples","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try: ...\n    catch:\n    # get all pods in the test namespace\n    - get:\n        apiVersion: v1\n        kind: Pod\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try: ...\n    catch:\n    - get:\n        apiVersion: v1\n        kind: Pod\n        # get pods that have a name starting with the provided `my-pod`\n        name: my-pod\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try: ...\n    catch:\n    - get:\n        apiVersion: v1\n        kind: Pod\n        # get pods in the namespace `foo`\n        namespace: foo\n
"},{"location":"operations/helpers/get/#label-selector","title":"Label selector","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try: ...\n    catch:\n    - get:\n        apiVersion: v1\n        kind: Pod\n        # get pods using a label selector query\n        selector: app=my-app\n
"},{"location":"operations/helpers/get/#format","title":"Format","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try: ...\n    catch:\n    - get:\n        apiVersion: v1\n        kind: Pod\n        format: json\n
"},{"location":"operations/helpers/logs/","title":"Pods logs","text":"

Print the logs for a container in a pod or specified resource.

"},{"location":"operations/helpers/logs/#configuration","title":"Configuration","text":"

The full structure of the PodLogs resource is documented here.

"},{"location":"operations/helpers/logs/#features","title":"Features","text":"Supported features Bindings support Outputs support Templating support Operation checks support"},{"location":"operations/helpers/logs/#test-namespace","title":"Test namespace","text":"

Chainsaw will default the scope to the ephemeral test namespace.

"},{"location":"operations/helpers/logs/#all-namespaces","title":"All namespaces","text":"

It is possible to consider all namespaces in the cluster by setting namespace: '*'.

"},{"location":"operations/helpers/logs/#examples","title":"Examples","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try: ...\n    catch:\n    # all pods in the test namespace\n    - podLogs: {}\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try: ...\n    catch:\n    - podLogs:\n        # pods that have a name starting with the provided `my-pod`\n        name: my-pod\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try: ...\n    catch:\n    - podLogs:\n        # pods in the namespace `foo`\n        namespace: foo\n
"},{"location":"operations/helpers/logs/#label-selector","title":"Label selector","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try: ...\n    catch:\n    - podLogs:\n        # match pods using a label selector query\n        selector: app=my-app\n
"},{"location":"operations/helpers/logs/#tail","title":"Tail","text":"

Tip

By default, tail will be 10 when a label selector is used, and all if a pod name is specified.

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try: ...\n    catch:\n    - podLogs:\n        tail: 30\n
"},{"location":"operations/helpers/logs/#container","title":"Container","text":"

Tip

By default logs from all containers will be fetched.

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try: ...\n    catch:\n    - podLogs:\n        container: nginx\n
"},{"location":"operations/helpers/wait/","title":"Wait","text":"

Wait for a specific condition on one or many resources.

Deprecated syntax

You can specify the resource directly instead of using apiVersion and kind.

This is a deprecated syntax though and will be removed in a future version.

"},{"location":"operations/helpers/wait/#configuration","title":"Configuration","text":"

The full structure of the Wait resource is documented here.

"},{"location":"operations/helpers/wait/#features","title":"Features","text":"Supported features Bindings support Outputs support Templating support Operation checks support"},{"location":"operations/helpers/wait/#clustered-resources","title":"Clustered resources","text":"

When used with a clustered resource, the namespace is ignored and is not added to the corresponding kubectl command.

"},{"location":"operations/helpers/wait/#all-resources","title":"All resources","text":"

If you don't specify a name or a selector, the wait operation will consider all resources.

"},{"location":"operations/helpers/wait/#test-namespace","title":"Test namespace","text":"

When used with a namespaced resource, Chainsaw will default the scope to the ephemeral test namespace.

"},{"location":"operations/helpers/wait/#all-namespaces","title":"All namespaces","text":"

When used with a namespaced resource, it is possible to consider all namespaces in the cluster by setting namespace: '*'.

"},{"location":"operations/helpers/wait/#examples","title":"Examples","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    # wait all pods are ready in the test namespace\n    - wait:\n        apiVersion: v1\n        kind: Pod\n        timeout: 1m\n        for:\n          condition:\n            name: Ready\n            value: 'true'\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - wait:\n        apiVersion: v1\n        kind: Pod\n        # wait a specific pod is ready in the test namespace\n        name: my-pod\n        timeout: 1m\n        for:\n          condition:\n            name: Ready\n            value: 'true'\n---\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - wait:\n        apiVersion: v1\n        kind: Pod\n        # wait all pods are ready in the namespace `foo`\n        namespace: foo\n        timeout: 1m\n        for:\n          condition:\n            name: Ready\n            value: 'true'\n
"},{"location":"operations/helpers/wait/#label-selector","title":"Label selector","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - wait:\n        apiVersion: v1\n        kind: Pod\n        # match pods using a label selector query\n        selector: app=foo\n        timeout: 1m\n        for:\n          condition:\n            name: Ready\n            value: 'true'\n
"},{"location":"operations/helpers/wait/#deletion","title":"Deletion","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - wait:\n        apiVersion: v1\n        kind: Pod\n        timeout: 1m\n        for:\n          # wait for deletion\n          deletion: {}\n
"},{"location":"operations/helpers/wait/#format","title":"Format","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - wait:\n        apiVersion: v1\n        kind: Pod\n        format: json\n
"},{"location":"quick-start/","title":"Getting started","text":"

Chainsaw is a tool primarily developed to run end-to-end tests in Kubernetes clusters.

It is meant to test Kubernetes operators work as expected by running a sequence of steps and asserting various conditions.

"},{"location":"quick-start/#why-we-made-it","title":"Why we made it?","text":"

While developing Kyverno we need to run end-to-end tests to make sure our admission controller works as expected.

A typical Kyverno end-to-end test

Kyverno can validate, mutate and generate resources based on policies installed in a cluster and a typical test is:

  1. Create a policy
  2. Create a resource
  3. Check that Kyverno acted as expected
  4. Cleanup and move to the next test
"},{"location":"quick-start/#how-to-use-it","title":"How to use it?","text":"

Chainsaw is built with CI tools in mind - you only really need to download and execute it in your build script.

However, installing it on your local machine is entirely possible.

"},{"location":"quick-start/assertion-trees/","title":"Use assertions","text":"

Chainsaw allows declaring complex assertions with a simple and no-code approach, allowing assertions based on comparisons beyond simple equality, working with arrays, and other scenarios that could not be achieved before.

Tip

Under the hood, Chainsaw uses kyverno-json assertion trees. Refer to the assertion trees documentation for more details on the supported syntax.

"},{"location":"quick-start/assertion-trees/#basic-assertion","title":"Basic assertion","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - assert:\n        resource:\n          apiVersion: apps/v1\n          kind: Deployment\n          metadata:\n            name: coredns\n            namespace: kube-system\n          spec:\n            replicas: 2\n

When asking Chainsaw to execute the assertion above, it will look for a deployment named coredns in the kube-system namespace and will compare the existing resource with the (partial) resource definition contained in the assertion.

In this specific case, if the field spec.replicas is set to 2 in the existing resource, the assertion will be considered valid. If it is not equal to 2 the assertion will be considered failed.

This is the most basic assertion Chainsaw can evaluate.

"},{"location":"quick-start/assertion-trees/#slightly-less-basic-assertion","title":"Slightly less basic assertion","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - assert:\n        resource:\n          apiVersion: apps/v1\n          kind: Deployment\n          metadata:\n            labels:\n              k8s-app: kube-dns\n            namespace: kube-system\n          spec:\n            replicas: 2\n

This time we are not providing a resource name.

Chainsaw will look up all deployments with the k8s-app: kube-dns label in the kube-system namespace. The assertion will be considered valid if at least one deployment matches the (partial) resource definition contained in the assertion. If none match, the assertion will be considered failed.

Apart from the resource lookup process being a little bit more interesting, this kind of assertion is essentially the same as the previous one. Chainsaw is basically making a decision by comparing an actual and expected resource.

"},{"location":"quick-start/assertion-trees/#beyond-simple-equality","title":"Beyond simple equality","text":"

The assertion below will check that the number of replicas for a deployment is greater than 1 AND less than 4.

Chainsaw doesn't need to know the exact expected number of replicas. The (replicas > 1 && replicas < 4) expression will be evaluated until the result is true or the operation timeout expires (making the assertion fail).

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - assert:\n        resource:\n          apiVersion: apps/v1\n          kind: Deployment\n          metadata:\n            name: coredns\n            namespace: kube-system\n          spec:\n            (replicas > `1` && replicas < `4`): true\n

Tip

To indicate that a key or value in the YAML document is an expression, simply place the element between parentheses:

  • this is an expression -> interpreted as a string
  • (this is an expression) -> interpreted as a JMESPath expression
"},{"location":"quick-start/assertion-trees/#working-with-arrays","title":"Working with arrays","text":"

Chainsaw query language makes it easy to assert on arrays. You can filter and transform arrays to select what you want to assert.

"},{"location":"quick-start/assertion-trees/#filtering","title":"Filtering","text":"

In the example below we are creating a resource, then we assert that a condition with type == 'Ready' exists and has a field matching status: 'True':

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - apply:\n        resource:\n          apiVersion: tempo.grafana.com/v1alpha1\n          kind: TempoStack\n          metadata:\n            name: simplest\n          spec:\n            storage:\n              secret:\n                name: minio\n                type: s3\n            ...\n    - assert:\n        resource:\n          apiVersion: tempo.grafana.com/v1alpha1\n          kind: TempoStack\n          metadata:\n            name: simplest\n          status:\n            # filter conditions array to keep elements where `type == 'Ready'`\n            # and assert there's a single element matching the filter\n            # and that this element status is `True`\n            (conditions[?type == 'Ready']):\n            - status: 'True'\n
"},{"location":"quick-start/assertion-trees/#iterating","title":"Iterating","text":"

Being able to filter arrays allows selecting the elements to be processed.

On top of that, Chainsaw allows iterating over array elements to validate each item separately.

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - assert:\n        resource:\n          apiVersion: apps/v1\n          kind: Deployment\n          metadata:\n            labels:\n              k8s-app: kube-dns\n            namespace: kube-system\n          spec:\n            template:\n              spec:\n                # the `~` modifier tells Chainsaw to iterate over the array elements\n                ~.(containers):\n                  securityContext: {}\n

This assertion uses the ~ modifier and Chainsaw will evaluate descendants once per element in the array.

"},{"location":"quick-start/assertion-trees/#comprehensive-reporting","title":"Comprehensive reporting","text":"

Chainsaw offers detailed resource diffs upon assertion failures.

In the example below, the assertion failure message (metadata.annotations.foo: Invalid value: \"null\": Expected value: \"bar\") is augmented with a resource diff.

It provides a clear view of discrepancies between expected and actual resources and gives more context around the specific failure (we can easily identify the owner of the offending pod for example).

| 09:55:50 | deployment | step-1   | ASSERT    | RUN   | v1/Pod @ chainsaw-rare-liger/*\n| 09:56:20 | deployment | step-1   | ASSERT    | ERROR | v1/Pod @ chainsaw-rare-liger/*\n    === ERROR\n    ---------------------------------------------------\n    v1/Pod/chainsaw-rare-liger/example-5477b4ff8c-tnhd9\n    ---------------------------------------------------\n    * metadata.annotations.foo: Invalid value: \"null\": Expected value: \"bar\"\n\n    --- expected\n    +++ actual\n    @@ -1,10 +1,16 @@\n      apiVersion: v1\n      kind: Pod\n      metadata:\n    -  annotations:\n    -    foo: bar\n        labels:\n          app: nginx\n    +  name: example-5477b4ff8c-tnhd9\n        namespace: chainsaw-rare-liger\n    +  ownerReferences:\n    +  - apiVersion: apps/v1\n    +    blockOwnerDeletion: true\n    +    controller: true\n    +    kind: ReplicaSet\n    +    name: example-5477b4ff8c\n    +    uid: 118abe16-ec42-4894-83db-64479c4aac6f\n      spec: {}\n| 09:56:20 | deployment | step-1   | TRY       | DONE  |\n
"},{"location":"quick-start/assertion-trees/#next-step","title":"Next step","text":"

To continue our exploration of the main Chainsaw features, let's look at bindings and resource templating next.

"},{"location":"quick-start/bindings/","title":"Use bindings","text":"

You can think of bindings as a side context where you can store and retrieve data based on keys.

This is particularly useful when some data is only known at runtime. For example, to pass data from one operation to another, to implement resource templating, to fetch data from an external system, etc.

Chainsaw offers some built-in bindings you can directly use in your tests but you can also create your own bindings if needed.

"},{"location":"quick-start/bindings/#inheritance","title":"Inheritance","text":"

Bindings can be configured at the test, step or operation level.

All bindings configured at a given level are automatically inherited in child levels.

JMESPath

Chainsaw uses the JMESPath language, and bindings are implemented using lexical scoping.

"},{"location":"quick-start/bindings/#immutability","title":"Immutability","text":"

Bindings are immutable. This means two bindings can have the same name without overwriting each other.

When a binding is registered it potentially hides other bindings with the same name.

When this binding goes out of scope, previously registered bindings with the same name become visible again.

"},{"location":"quick-start/bindings/#built-in-bindings","title":"Built-in bindings","text":"

The $namespace binding is a good example of a built-in binding provided by Chainsaw. It contains the name of the ephemeral namespace used to execute a test (by default Chainsaw will create an ephemeral namespace for each test).

In the operation below, we are assigning the value of the $namespace binding to an environment variable, and echo it in a script:

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - script:\n        env:\n          # assign the value of the `$namespace` binding\n          # to the environment variable `FOO`\n        - name: FOO\n          value: ($namespace)\n        content: echo $FOO\n
"},{"location":"quick-start/bindings/#custom-bindings","title":"Custom bindings","text":"

On top of built-in bindings, you can also create your own ones, combine bindings together, call JMESPath functions using bindings as arguments, etc.

In the test below we create custom bindings at different levels in the test, combine them by calling the join function, assign the result to an environment variable, and echo it in a script:

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  # bindings can be declared at the test level\n  bindings:\n  - name: chainsaw\n    value: chainsaw\n  steps:\n    # bindings can also be declared at the step level\n  - bindings:\n    - name: hello\n      value: hello\n    try:\n    - script:\n        # bindings can also be declared at the operation level\n        bindings:\n        - name: awesome\n          value: awesome\n        env:\n          # combined bindings together using the `join` functions and\n          # assign the result to the GREETINGS environment variable\n        - name: GREETINGS\n          value: (join(' ', [$hello, $chainsaw, 'is', $awesome]))\n        content: echo $GREETINGS\n
"},{"location":"quick-start/bindings/#next-step","title":"Next step","text":"

Let's see how bindings can be useful with resource templating.

"},{"location":"quick-start/cleanup/","title":"Control your cleanup","text":"

Unless configured differently, by default Chainsaw will automatically remove the resources it created after a test finishes.

Cleanup happens in reverse order of creation (created last, cleaned up first). This is important, especially when the controller being tested makes use of finalizers.

Overriding cleanup timeout

Note that Chainsaw performs a blocking deletion, that is, it will wait until the resource is not present anymore in the cluster before proceeding with the next resource cleanup.

"},{"location":"quick-start/cleanup/#timeout","title":"Timeout","text":"

A global cleanup timeout can be defined at the configuration level or using command line flags.

It can also be overridden on a per-test or per-step basis but not at the operation level.

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  timeouts:\n    # cleanup timeout at the test level\n    cleanup: 30s\n  steps:\n  - timeouts:\n      # cleanup timeout at the step level\n      cleanup: 2m\n    try: ...\n
"},{"location":"quick-start/cleanup/#automatic-cleanup","title":"Automatic cleanup","text":"

After a test, every resource created by Chainsaw will be automatically deleted. This applies to create and apply operations.

In the logs below we can see Chainsaw deletes the previously created resource:

    | 15:21:29 | quick-start | @setup   | CREATE    | OK    | v1/Namespace @ chainsaw-cute-cod\n    | 15:21:29 | quick-start | step-1   | TRY       | RUN   |\n    | 15:21:29 | quick-start | step-1   | APPLY     | RUN   | v1/ConfigMap @ chainsaw-cute-cod/chainsaw-quick-start\n    | 15:21:29 | quick-start | step-1   | CREATE    | OK    | v1/ConfigMap @ chainsaw-cute-cod/chainsaw-quick-start\n    | 15:21:29 | quick-start | step-1   | APPLY     | DONE  | v1/ConfigMap @ chainsaw-cute-cod/chainsaw-quick-start\n    | 15:21:29 | quick-start | step-1   | ASSERT    | RUN   | v1/ConfigMap @ chainsaw-cute-cod/chainsaw-quick-start\n    | 15:21:29 | quick-start | step-1   | ASSERT    | DONE  | v1/ConfigMap @ chainsaw-cute-cod/chainsaw-quick-start\n    | 15:21:29 | quick-start | step-1   | TRY       | DONE  |\n    === step cleanup process start ===\n    | 15:21:29 | quick-start | step-1   | CLEANUP   | RUN   |\n    | 15:21:29 | quick-start | step-1   | DELETE    | RUN   | v1/ConfigMap @ chainsaw-cute-cod/chainsaw-quick-start\n    | 15:21:29 | quick-start | step-1   | DELETE    | OK    | v1/ConfigMap @ chainsaw-cute-cod/chainsaw-quick-start\n    | 15:21:29 | quick-start | step-1   | DELETE    | DONE  | v1/ConfigMap @ chainsaw-cute-cod/chainsaw-quick-start\n    | 15:21:29 | quick-start | step-1   | CLEANUP   | DONE  |\n    === step cleanup process end ===\n    === test cleanup process start ===\n    | 15:21:29 | quick-start | @cleanup | DELETE    | RUN   | v1/Namespace @ chainsaw-cute-cod\n    | 15:21:29 | quick-start | @cleanup | DELETE    | OK    | v1/Namespace @ chainsaw-cute-cod\n    | 15:21:34 | quick-start | @cleanup | DELETE    | DONE  | v1/Namespace @ chainsaw-cute-cod\n    === test cleanup process end ===\n
"},{"location":"quick-start/cleanup/#manual-cleanup","title":"Manual cleanup","text":"

Under certain circumstances, automatic cleanup is not enough and we want to execute custom operations.

Chainsaw allows registering cleanup operations that will be run after automatic cleanup. Custom cleanup operations live at the test step level:

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n    # this step will create a local cluster\n  - try:\n    - script:\n        timeout: 1m\n        content: |\n          kind create cluster --name dynamic --kubeconfig ./dynamic\n    # at cleanup time, we want to delete the local cluster we created\n    # and remove the associated kubeconfig\n    cleanup:\n    - script:\n        content: |\n          kind delete cluster --name dynamic\n    - script:\n        content: |\n          rm -f ./dynamic\n
"},{"location":"quick-start/cleanup/#next-step","title":"Next step","text":"

At this point, we covered the main Chainsaw features.

Look at the next steps section to find out what to do next.

"},{"location":"quick-start/completion/","title":"Shell completion","text":"

Once installed, use chainsaw completion command to generate and register the autocompletion script for the specified shell.

Supported shells are:

  • bash
  • fish
  • powershell
  • zsh
"},{"location":"quick-start/first-test/","title":"Create a test","text":"

To create a Chainsaw test all you need to do is to create one (or more) YAML file(s).

The recommended approach is to create one folder per test, with a chainsaw-test.yaml file containing one (or more) test definition(s). The test definition can reference other files in the same folder or anywhere else on the file system as needed.

Tip

While chainsaw supports other syntaxes, we strongly recommend the explicit approach.

"},{"location":"quick-start/first-test/#what-is-a-test","title":"What is a test?","text":"

To put it simply, a test can be represented as an ordered sequence of test steps.

In turn, a test step can be represented as an ordered sequence of operations.

When one of the operations fails the test is considered failed.

If all operations succeed the test is considered successful.

"},{"location":"quick-start/first-test/#lets-write-our-first-test","title":"Let's write our first test","text":"

For this quick start, we will create a (very simple) Test with one step and two operations:

  1. Create a ConfigMap from a manifest
  2. Verify the ConfigMap was created and contains the expected data

Follow the instructions below to create the folder and files defining our first test.

"},{"location":"quick-start/first-test/#create-a-test-folder","title":"Create a test folder","text":"
# create test folder\nmkdir chainsaw-quick-start\n\n# enter test folder\ncd chainsaw-quick-start\n
"},{"location":"quick-start/first-test/#create-a-configmap-manifest","title":"Create a ConfigMap manifest","text":"
# create a ConfigMap\ncat > configmap.yaml << EOF\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: chainsaw-quick-start\ndata:\n  foo: bar\nEOF\n
"},{"location":"quick-start/first-test/#create-a-test-manifest","title":"Create a test manifest","text":"

By default, Chainsaw will look for a file named chainsaw-test.yaml in every folder.

# create test file\ncat > chainsaw-test.yaml << EOF\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: quick-start\nspec:\n  steps:\n  - try:\n    # first operation: create the config map\n    - apply:\n        # file is relative to the test folder\n        file: configmap.yaml\n    # second operation: verify the config map exists and contains the expected data\n    - assert:\n        # file is relative to the test folder\n        file: configmap.yaml\nEOF\n
"},{"location":"quick-start/first-test/#next-step","title":"Next step","text":"

Now we have created our first test, you can continue to the next section to execute it.

"},{"location":"quick-start/install/","title":"Installation","text":"

You can install the pre-compiled binary (in several ways), compile from sources, or run with Docker.

We also provide a GitHub action to easily install Chainsaw in your workflows.

"},{"location":"quick-start/install/#install-the-pre-compiled-binary","title":"Install the pre-compiled binary","text":""},{"location":"quick-start/install/#homebrew-tap","title":"Homebrew tap","text":"

add tap:

brew tap kyverno/chainsaw https://github.com/kyverno/chainsaw\n

install chainsaw:

brew install kyverno/chainsaw/chainsaw\n

Don't forget to specify the tap name

Homebrew core already has a tool named chainsaw.

Be sure that you specify the tap name when installing to install the right tool.

"},{"location":"quick-start/install/#manually","title":"Manually","text":"

Download the pre-compiled binaries for your system from the releases page and copy them to the desired location.

"},{"location":"quick-start/install/#install-using-go-install","title":"Install using go install","text":"

You can install with go install with:

go install github.com/kyverno/chainsaw@latest\n
"},{"location":"quick-start/install/#run-with-docker","title":"Run with Docker","text":"

Chainsaw is also available as a Docker image which you can pull and run:

docker pull ghcr.io/kyverno/chainsaw:<version>\n

Warning

Since Chainsaw relies on files for its operation (like test definitions), you will need to bind mount the necessary directories when running it via Docker.

docker run --rm                             \\\n    -v ./testdata/e2e/:/chainsaw/           \\\n    -v ${HOME}/.kube/:/etc/kubeconfig/      \\\n    -e KUBECONFIG=/etc/kubeconfig/config    \\\n    --network=host                          \\\n    ghcr.io/kyverno/chainsaw:<version>      \\\n    test /chainsaw --config /chainsaw/config.yaml\n
"},{"location":"quick-start/install/#compile-from-sources","title":"Compile from sources","text":"

clone:

git clone https://github.com/kyverno/chainsaw.git\n

build the binaries:

cd chainsaw\ngo mod tidy\nmake build\n

verify it works:

./chainsaw version\n
"},{"location":"quick-start/install/#install-using-nix-package","title":"Install using Nix Package","text":"

To install kyverno-chainsaw, refer to the documentation.

"},{"location":"quick-start/install/#on-nixos","title":"On NixOS","text":"
nix-env -iA nixos.kyverno-chainsaw\n
"},{"location":"quick-start/install/#on-non-nixos","title":"On Non-NixOS","text":"
nix-env -iA nixpkgs.kyverno-chainsaw\n

Warning

Using nix-env permanently modifies a local profile of installed packages. This must be updated and maintained by the user in the same way as with a traditional package manager, foregoing many of the benefits that make Nix uniquely powerful. Using nix-shell or a NixOS configuration is recommended instead.

"},{"location":"quick-start/install/#using-nixos-configuration","title":"Using NixOS Configuration","text":"

Add the following Nix code to your NixOS Configuration, usually located in /etc/nixos/configuration.nix :

environment.systemPackages = [\n  pkgs.kyverno-chainsaw\n];\n
"},{"location":"quick-start/install/#using-nix-shell","title":"Using nix-shell","text":"

A nix-shell will temporarily modify your $PATH environment variable. This can be used to try a piece of software before deciding to permanently install it. Use the following command to install kyverno-chainsaw :

nix-shell -p kyverno-chainsaw\n
"},{"location":"quick-start/install/#github-action","title":"GitHub action","text":"

A GitHub action is available to install Chainsaw in your workflows. See the GitHub action dedicated documentation.

"},{"location":"quick-start/next-steps/","title":"Next steps","text":"

We covered the main features of Chainsaw in this Getting started sections.

While this should help you understand Chainsaw better, there are a lot of other things Chainsaw can do for you.

Tip

If there's anything you would like to be improved, please reach out, we will be happy to discuss and improve as much as we can.

To continue exploring the capabilities of Chainsaw:

  • Look at the Chainsaw Configuration options
  • Read the documentation dedicated to Test
  • Read the documentation dedicated to Operations
  • Consult the Reference documentation
  • Browse our Examples and Guides
  • Engage with our Community and start contributing
"},{"location":"quick-start/operation-outputs/","title":"Use operation outputs","text":"

Operation outputs can be useful for communicating and reusing computation results across operations.

"},{"location":"quick-start/operation-outputs/#lifetime-of-outputs","title":"Lifetime of outputs","text":"

Once an output has been added to the bindings context, this binding will be available to all following operations in the same step.

Currently, outputs do not cross the step boundaries.

"},{"location":"quick-start/operation-outputs/#matching","title":"Matching","text":"

An output supports an optional match field. The match is used to conditionally create a binding.

In the case of applying a file, for example, the file may contain multiple resources. The match can be used to select the resource to use for creating the binding.

"},{"location":"quick-start/operation-outputs/#load-an-existing-resource","title":"Load an existing resource","text":"

The example below invokes a kubectl command to get a configmap from the cluster in json format.

The json output is then parsed and added to the $cm binding and the next operation performs an assertion on it by reading the binding instead of querying the cluster.

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - script:\n        content: kubectl get cm quick-start -n $NAMESPACE -o json\n        outputs:\n          # parse stdout json output and bind the result to `$cm`\n        - name: cm\n          value: (json_parse($stdout))\n    - assert:\n        resource:\n          ($cm):\n            metadata:\n              (uid != null): true\n
"},{"location":"quick-start/operation-outputs/#match-a-resource","title":"Match a resource","text":"

The example below applies resources from a file.

When the resource being applied is a configmap, we bind the resource to an output to print its UID in the next operation.

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - apply:\n        file: ./resources.yaml\n        outputs:\n          # match the configmap resource and bind it to `$cm`\n        - match:\n            apiVersion: v1\n            kind: ConfigMap\n          name: cm\n          value: (@)\n    - script:\n        env:\n        - name: UID\n          value: ($cm.metadata.uid)\n        content: echo $UID\n
"},{"location":"quick-start/operation-outputs/#next-step","title":"Next step","text":"

In the next section, we will look at the three main elements of a test step, the try, catch and finally blocks.

"},{"location":"quick-start/resource-templating/","title":"Use resource templating","text":"

Chainsaw simplifies dynamic resource configuration with native resource templating support.

Sometimes things we need to create resources or assertions are only known at runtime.

In the past, users have created all sorts of hacks using tools like envsubst for dynamic substitution of env-variables. Those workarounds usually lack flexibility and introduce new problems like hiding the real resources from Chainsaw, preventing it from cleaning resources properly.

Tip

Resource templating is heavily based on bindings and uses JMESPath language.

"},{"location":"quick-start/resource-templating/#leverage-bindings","title":"Leverage bindings","text":"

In the template below, we are using the $namespace binding at two different places, effectively injecting the ephemeral namespace name in the name and the data.foo fields:

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - assert:\n      resource:\n        apiVersion: v1\n        kind: ConfigMap\n        metadata:\n          name: ($namespace)\n        data:\n          foo: ($namespace)\n
"},{"location":"quick-start/resource-templating/#leverage-jmespath","title":"Leverage JMESPath","text":"

In the template below, we are using the JMESPath join function to create a unique resource name:

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - apply:\n      resource:\n        apiVersion: v1\n        kind: ConfigMap\n        metadata:\n          name: (join('-', [$namespace, 'cm']))\n        data:\n          foo: bar\n
"},{"location":"quick-start/resource-templating/#next-step","title":"Next step","text":"

Combining bindings and templates with operation outputs allows even more flexibility to pass arbitrary data from one operation to another.

"},{"location":"quick-start/resources/","title":"Additional resources","text":"

Resources, blog posts and videos talking about Chainsaw:

  • Kyverno Chainsaw - The ultimate end-to-end testing tool!
  • Kyverno Chainsaw - Exploring the Power of Assertion Trees!
  • Nirmata Office Hours for Kyverno- Episode 9- Demonstrate Kyverno Chainsaw
  • Kubebuilder Community Meeting - February 1, 2024
  • Kyverno Chainsaw 0.1.4 - Awesome new features!
  • Mastering Kubernetes Testing with Kyverno Chainsaw!
"},{"location":"quick-start/resources/#chainsaw-video-review","title":"Chainsaw video review","text":"

If you haven't watched the video below yet, we strongly recommend watching it to discover Chainsaw!

"},{"location":"quick-start/run-tests/","title":"Run tests","text":"

After installing chainsaw and writing tests, the next natural step is to run Chainsaw to execute the tests.

"},{"location":"quick-start/run-tests/#create-a-local-cluster","title":"Create a local cluster","text":"

To use Chainsaw you will need a Kubernetes cluster, Chainsaw won't create one for you.

Not a cluster management tool

We consider this is not the responsibility of Chainsaw to manage clusters. There are plenty of solutions to create and manage local clusters that will do that better than Chainsaw.

The command below will create a local cluster using kind. Use the tool of your choice or directly jump to the next section if you already have a KUBECONFIG configured and pointing to a valid cluster.

# create cluster\nkind create cluster --image \"kindest/node:v1.29.4\"\n
"},{"location":"quick-start/run-tests/#run-chainsaw","title":"Run Chainsaw","text":"

Now you can run the chainsaw test command.

> chainsaw test\n\nVersion: (devel)\nLoading default configuration...\n- Using test file: chainsaw-test.yaml\n- TestDirs [.]\n- SkipDelete false\n- FailFast false\n- ReportFormat ''\n- ReportName ''\n- Namespace ''\n- FullName false\n- IncludeTestRegex ''\n- ExcludeTestRegex ''\n- ApplyTimeout 5s\n- AssertTimeout 30s\n- CleanupTimeout 30s\n- DeleteTimeout 15s\n- ErrorTimeout 30s\n- ExecTimeout 5s\nLoading tests...\n- quick-start (.)\nRunning tests...\n=== RUN   chainsaw\n=== PAUSE chainsaw\n=== CONT  chainsaw\n=== RUN   chainsaw/quick-start\n=== PAUSE chainsaw/quick-start\n=== CONT  chainsaw/quick-start\n    | 10:44:26 | quick-start | @setup   | CREATE    | OK    | v1/Namespace @ chainsaw-immense-jay\n    | 10:44:26 | quick-start | step-1   | TRY       | RUN   |\n    | 10:44:26 | quick-start | step-1   | APPLY     | RUN   | v1/ConfigMap @ chainsaw-immense-jay/chainsaw-quick-start\n    | 10:44:26 | quick-start | step-1   | CREATE    | OK    | v1/ConfigMap @ chainsaw-immense-jay/chainsaw-quick-start\n    | 10:44:26 | quick-start | step-1   | APPLY     | DONE  | v1/ConfigMap @ chainsaw-immense-jay/chainsaw-quick-start\n    | 10:44:26 | quick-start | step-1   | ASSERT    | RUN   | v1/ConfigMap @ chainsaw-immense-jay/chainsaw-quick-start\n    | 10:44:26 | quick-start | step-1   | ASSERT    | DONE  | v1/ConfigMap @ chainsaw-immense-jay/chainsaw-quick-start\n    | 10:44:26 | quick-start | step-1   | TRY       | DONE  |\n    | 10:44:26 | quick-start | @cleanup | DELETE    | RUN   | v1/ConfigMap @ chainsaw-immense-jay/chainsaw-quick-start\n    | 10:44:26 | quick-start | @cleanup | DELETE    | OK    | v1/ConfigMap @ chainsaw-immense-jay/chainsaw-quick-start\n    | 10:44:26 | quick-start | @cleanup | DELETE    | DONE  | v1/ConfigMap @ chainsaw-immense-jay/chainsaw-quick-start\n    | 10:44:26 | quick-start | @cleanup | DELETE    | RUN   | v1/Namespace @ chainsaw-immense-jay\n    | 10:44:26 | quick-start | @cleanup | DELETE    | OK    | v1/Namespace @ chainsaw-immense-jay\n    | 10:44:31 | quick-start | @cleanup | DELETE    | DONE  | v1/Namespace @ chainsaw-immense-jay\n--- PASS: chainsaw (0.00s)\n    --- PASS: chainsaw/quick-start (5.25s)\nPASS\nTests Summary...\n- Passed  tests 1\n- Failed  tests 0\n- Skipped tests 0\nDone.\n

Tip

Chainsaw expects a path to the test folder and will discover tests by analyzing files recursively. When no path is provided Chainsaw will use the current path by default (.).

"},{"location":"quick-start/run-tests/#next-step","title":"Next step","text":"

The test above demonstrates the most basic usage of Chainsaw. In the next sections, we will look at the main features that make Chainsaw a very unique tool.

"},{"location":"quick-start/timeouts/","title":"Control your timeouts","text":"

Timeouts in Chainsaw are specified per type of operation. This is handy because the timeout varies greatly depending on the nature of an operation.

For example, applying a manifest in a cluster is expected to be reasonably fast, while validating a resource can be a long operation.

"},{"location":"quick-start/timeouts/#inheritance","title":"Inheritance","text":"

Timeouts can be configured globally and at the test, step or individual operation level.

All timeouts configured at a given level are automatically inherited in child levels. When looking up a timeout, the most specific one takes precedence over the others.

Info

To learn more about timeouts and how to configure global values, see the timeouts configuration page.

"},{"location":"quick-start/timeouts/#at-the-test-level","title":"At the test level","text":"

When a timeout is configured at the test level it will apply to all operations and steps in the test, unless overridden at a more specific level.

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  # timeouts configured at the test level will apply to all operations and steps\n  # unless overriden at the step level and/or individual operation level\n  timeouts:\n    apply: 5s\n    assert: 1m\n    # ...\n  steps:\n  - try:\n    - apply:\n        resource:\n          apiVersion: tempo.grafana.com/v1alpha1\n          kind: TempoStack\n          metadata:\n            name: simplest\n          spec:\n            storage:\n              secret:\n                name: minio\n                type: s3\n            # ...\n    - assert:\n        resource:\n          apiVersion: tempo.grafana.com/v1alpha1\n          kind: TempoStack\n          metadata:\n            name: simplest\n          status:\n            (conditions[?type == 'Ready']):\n            - status: 'True'\n
"},{"location":"quick-start/timeouts/#at-the-step-level","title":"At the step level","text":"

When a timeout is configured at the step level it will apply to all operations in the step, unless overridden at a more specific level.

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n    # timeouts configured at the step level will apply to all operations\n    # in the step unless overriden at the individual operation level\n  - timeouts:\n      apply: 5s\n      # ...\n    try:\n    - apply:\n        resource:\n          apiVersion: tempo.grafana.com/v1alpha1\n          kind: TempoStack\n          metadata:\n            name: simplest\n          spec:\n            storage:\n              secret:\n                name: minio\n                type: s3\n            # ...\n    # timeouts configured at the step level will apply to all operations\n    # in the step unless overriden at the individual operation level\n  - timeouts:\n      assert: 1m\n      # ...\n    try:\n    - assert:\n        resource:\n          apiVersion: tempo.grafana.com/v1alpha1\n          kind: TempoStack\n          metadata:\n            name: simplest\n          status:\n            (conditions[?type == 'Ready']):\n            - status: 'True'\n
"},{"location":"quick-start/timeouts/#at-the-operation-level","title":"At the operation level","text":"

When a timeout is configured at the operation level, it takes precedence over all timeouts configured at upper levels.

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - apply:\n        # timeout configured at the operation level takes precedence\n        # over timeouts configured at upper levels\n        timeout: 5s\n        resource:\n          apiVersion: tempo.grafana.com/v1alpha1\n          kind: TempoStack\n          metadata:\n            name: simplest\n          spec:\n            storage:\n              secret:\n                name: minio\n                type: s3\n            # ...\n    - assert:\n        # timeout configured at the operation level takes precedence\n        # over timeouts configured at upper levels\n        timeout: 1m\n        resource:\n          apiVersion: tempo.grafana.com/v1alpha1\n          kind: TempoStack\n          metadata:\n            name: simplest\n          status:\n            (conditions[?type == 'Ready']):\n            - status: 'True'\n
"},{"location":"quick-start/timeouts/#next-step","title":"Next step","text":"

In the next section, we will see how Chainsaw manages cleanup.

"},{"location":"quick-start/try-catch/","title":"Use try, catch and finally","text":"

A test step is made of 3 main blocks used to determine the actions Chainsaw will perform when executing the step, depending on operations outcome.

  • The try block (required)
  • The catch block (optional)
  • The finally block (optional)

Operations defined in the try block are executed first, then:

  • If an operation fails to execute, Chainsaw won't execute the remaining operations and will execute all operations defined in the catch block instead (if any).
  • If all operations succeed, Chainsaw will NOT execute operations defined in the catch block (if any).
  • Regardless of the step outcome (success or failure), Chainsaw will execute all operations defined in the finally block (if any).

Note

Note that all operations coming from the catch or finally blocks are executed. If one operation fails, Chainsaw will mark the test as failed and continue executing with the next operation.

"},{"location":"quick-start/try-catch/#cleanup","title":"Cleanup","text":"

At the end of a test, Chainsaw automatically cleans up the resources created during the test (cleanup is done in the opposite order of creation).

All operations from the catch and finally blocks are executed before the cleanup process kicks in. This order allows analyzing the resources that potentially caused the step failure before they are deleted.

"},{"location":"quick-start/try-catch/#catch","title":"Catch","text":"

Operations in a catch block are executed only when the step is considered failed.

This is particularly useful to collect additional information to help understand what caused the failure.

In the example below, the test contains a catch block to collect events in the cluster when an operation fails in the step.

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n  - try:\n    - apply:\n        # ...\n    - assert:\n        # ...\n    # collect events in the `catch` block\n    # will be executed only if an operation failed\n    catch:\n    - events: {}\n
"},{"location":"quick-start/try-catch/#finally","title":"Finally","text":"

Operations in a finally block will always execute regardless of the success or failure of the test step.

This is particularly useful to perform manual cleanup.

In the example below we create a local cluster in a script operation. The cluster deletion script is added to the finally block, guaranteeing the cluster will be deleted regardless of the test outcome.

apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n    # create a local cluster\n  - try:\n    - script:\n        timeout: 1m\n        content: |\n          kind create cluster --name dynamic --kubeconfig ./dynamic\n    - apply:\n        # ...\n    - assert:\n        # ...\n    # add cluster deletion script in the `finally` block\n    # to guarantee the cluster will be deleted after the test\n    finally:\n    - script:\n        content: |\n          kind delete cluster --name dynamic\n    - script:\n        content: |\n          rm -f ./dynamic\n
"},{"location":"quick-start/try-catch/#next-step","title":"Next step","text":"

Every operation in a test must be executed in a timely fashion. In the next section, we will see how you can control your timeouts.

"},{"location":"reference/","title":"Reference documentation","text":"

Info

Select an item in the navigation menu to browse a specific page.

"},{"location":"reference/builtins/","title":"Built-in bindings","text":"

Chainsaw provides built-in bindings listed below.

"},{"location":"reference/builtins/#common","title":"Common","text":"Name Purpose Type $values Values provided when invoking chainsaw with --values flag any $namespace Name of the current test namespace string $client Kubernetes client chainsaw is connected to (if not running with --no-cluster) object $config Kubernetes client config chainsaw is connected to (if not running with --no-cluster) object"},{"location":"reference/builtins/#in-tests","title":"In tests","text":"Name Purpose Type $test.id Current test id int

Note

  • $test.id starts at 1 for the first test
"},{"location":"reference/builtins/#in-steps","title":"In steps","text":"Name Purpose Type $step.id Current step id int

Note

  • $step.id starts at 1 for the first step
"},{"location":"reference/builtins/#in-operations","title":"In operations","text":"Name Purpose Type $operation.id Current operation id int $operation.resourceId Current resource id int

Note

  • $operation.id starts at 1 for the first operation
  • $operation.resourceId maps to the resource id (starting at 1) in case the operation loads a file that contains multiple resources (the same operation is repeated once per resource)
"},{"location":"reference/builtins/#in-checks-and-outputs","title":"In checks and outputs","text":"Name Purpose Type @ The state of the resource (if any) at the end of the operation any $error The error message (if any) at the end of the operation string $stdout The content of the standard console output (if any) at the end of the operation string $stderr The content of the standard console error output (if any) at the end of the operation string

Note

  • $stdout and $stderr are only available in script and command operations
"},{"location":"reference/json-schemas/","title":"JSON schemas","text":"

JSON schemas for Chainsaw are available:

  • Configuration (v1alpha1)
  • Configuration (v1alpha2)
  • Test (v1alpha1)

They can be used to enable validation and autocompletion in your IDE.

"},{"location":"reference/json-schemas/#vs-code","title":"VS code","text":"

In VS code, simply add a comment on top of your YAML resources.

"},{"location":"reference/json-schemas/#test","title":"Test","text":"
# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json\napiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: basic\nspec:\n  steps:\n  - try:\n    - apply:\n        file: configmap.yaml\n    - assert:\n        file: configmap-assert.yaml\n
"},{"location":"reference/json-schemas/#configuration","title":"Configuration","text":"
# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/configuration-chainsaw-v1alpha2.json\napiVersion: chainsaw.kyverno.io/v1alpha2\nkind: Configuration\nmetadata:\n  name: example\nspec:\n  timeouts:\n    apply: 45s\n    assert: 20s\n    cleanup: 45s\n    delete: 25s\n    error: 10s\n    exec: 45s\n  cleanup:\n    skipDelete: false\n  execution:\n    failFast: true\n    parallel: 4\n
"},{"location":"reference/json-schemas/#exporting-schemas","title":"Exporting schemas","text":"

Chainsaw can also export JSON schemas locally if you don't want to reference them from GitHub:

chainsaw export schemas <local path>\n

See chainsaw export schemas command documentation for more details.

"},{"location":"reference/apis/chainsaw.v1alpha1/","title":"chainsaw (v1alpha1)","text":"

Package v1alpha1 contains API Schema definitions for the v1alpha1 API group.

"},{"location":"reference/apis/chainsaw.v1alpha1/#resource-types","title":"Resource Types","text":"
  • Configuration
  • Test
"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Configuration","title":"Configuration","text":"

Configuration is the resource that contains the configuration used to run tests.

Field Type Required Inline Description apiVersion string chainsaw.kyverno.io/v1alpha1 kind string Configuration metadata meta/v1.ObjectMeta

Standard object's metadata.

spec ConfigurationSpec

Configuration spec.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Test","title":"Test","text":"

Test is the resource that contains a test definition.

Field Type Required Inline Description apiVersion string chainsaw.kyverno.io/v1alpha1 kind string Test metadata meta/v1.ObjectMeta

Standard object's metadata.

spec TestSpec

Test spec.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Apply","title":"Apply","text":"

Appears in:

  • Operation

Apply represents a set of configurations or resources that should be applied during testing.

Field Type Required Inline Description timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

bindings []Binding

Bindings defines additional binding key/values.

outputs []Output

Outputs defines output bindings.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

FileRefOrResource FileRefOrResource

FileRefOrResource provides a reference to the resources to be applied.

template bool

Template determines whether resources should be considered for templating.

dryRun bool

DryRun determines whether the file should be applied in dry run mode.

expect []Expectation

Expect defines a list of matched checks to validate the operation outcome.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Assert","title":"Assert","text":"

Appears in:

  • Operation

Assert represents a test condition that is expected to hold true during the testing process.

Field Type Required Inline Description timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

bindings []Binding

Bindings defines additional binding key/values.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

FileRefOrCheck FileRefOrCheck

FileRefOrAssert provides a reference to the assertion.

template bool

Template determines whether resources should be considered for templating.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Binding","title":"Binding","text":"

Appears in:

  • Apply
  • Assert
  • Command
  • Create
  • Delete
  • Error
  • Output
  • Patch
  • Script
  • TestSpec
  • TestStepSpec
  • Update

Binding represents a key/value set as a binding in an executing test.

Field Type Required Inline Description name string

Name the name of the binding.

value policy/v1alpha1.Any

Value value of the binding.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Catch","title":"Catch","text":"

Appears in:

  • ConfigurationSpec
  • TestSpec
  • TestStepSpec

Catch defines actions to be executed on failure.

Field Type Required Inline Description description string

Description contains a description of the operation.

podLogs PodLogs

PodLogs determines the pod logs collector to execute.

events Events

Events determines the events collector to execute.

describe Describe

Describe determines the resource describe collector to execute.

wait Wait

Wait determines the resource wait collector to execute.

get Get

Get determines the resource get collector to execute.

delete Delete

Delete represents a deletion operation.

command Command

Command defines a command to run.

script Script

Script defines a script to run.

sleep Sleep

Sleep defines zzzz.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Cluster","title":"Cluster","text":"

Appears in:

  • Apply
  • Assert
  • Command
  • ConfigurationSpec
  • Create
  • Delete
  • Describe
  • Error
  • Events
  • Get
  • Patch
  • PodLogs
  • Script
  • TestSpec
  • TestStepSpec
  • Update
  • Wait

Cluster defines cluster config and context.

Field Type Required Inline Description kubeconfig string

Kubeconfig is the path to the referenced file.

context string

Context is the name of the context to use.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Command","title":"Command","text":"

Appears in:

  • Catch
  • Finally
  • Operation

Command describes a command to run as a part of a test step.

Field Type Required Inline Description timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

bindings []Binding

Bindings defines additional binding key/values.

outputs []Output

Outputs defines output bindings.

env []Binding

Env defines additional environment variables.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

entrypoint string

Entrypoint is the command entry point to run.

args []string

Args is the command arguments.

skipLogOutput bool

SkipLogOutput removes the output from the command. Useful for sensitive logs or to reduce noise.

check policy/v1alpha1.Any

Check is an assertion tree to validate the operation outcome.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Condition","title":"Condition","text":"

Appears in:

  • For

Condition represents parameters for waiting on a specific condition of a resource.

Field Type Required Inline Description name string

Name defines the specific condition to wait for, e.g., \"Available\", \"Ready\".

value string

Value defines the specific condition status to wait for, e.g., \"True\", \"False\".

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-ConfigurationSpec","title":"ConfigurationSpec","text":"

Appears in:

  • Configuration

ConfigurationSpec contains the configuration used to run tests.

Field Type Required Inline Description timeouts Timeouts

Global timeouts configuration. Applies to all tests/test steps if not overridden.

skipDelete bool

If set, do not delete the resources after running the tests (implies SkipClusterDelete).

template bool

Template determines whether resources should be considered for templating.

failFast bool

FailFast determines whether the test should stop upon encountering the first failure.

parallel int

The maximum number of tests to run at once.

deletionPropagationPolicy meta/v1.DeletionPropagation

DeletionPropagationPolicy decides if a deletion will propagate to the dependents of the object, and how the garbage collector will handle the propagation.

reportFormat ReportFormatType

ReportFormat determines test report format (JSON reportPath string

ReportPath defines the path.

reportName string

ReportName defines the name of report to create. It defaults to \"chainsaw-report\".

namespace string

Namespace defines the namespace to use for tests. If not specified, every test will execute in a random ephemeral namespace unless the namespace is overridden in a the test spec.

namespaceTemplate policy/v1alpha1.Any

NamespaceTemplate defines a template to create the test namespace.

fullName bool

FullName makes use of the full test case folder path instead of the folder name.

excludeTestRegex string

ExcludeTestRegex is used to exclude tests based on a regular expression.

includeTestRegex string

IncludeTestRegex is used to include tests based on a regular expression.

repeatCount int

RepeatCount indicates how many times the tests should be executed.

testFile string

TestFile is the name of the file containing the test to run. If no extension is provided, chainsaw will try with .yaml first and .yml if needed.

forceTerminationGracePeriod meta/v1.Duration

ForceTerminationGracePeriod forces the termination grace period on pods, statefulsets, daemonsets and deployments.

delayBeforeCleanup meta/v1.Duration

DelayBeforeCleanup adds a delay between the time a test ends and the time cleanup starts.

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

catch []Catch

Catch defines what the tests steps will execute when an error happens. This will be combined with catch handlers defined at the test and step levels.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Create","title":"Create","text":"

Appears in:

  • Operation

Create represents a set of resources that should be created. If a resource already exists in the cluster it will fail.

Field Type Required Inline Description timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

bindings []Binding

Bindings defines additional binding key/values.

outputs []Output

Outputs defines output bindings.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

FileRefOrResource FileRefOrResource

FileRefOrResource provides a reference to the file containing the resources to be created.

template bool

Template determines whether resources should be considered for templating.

dryRun bool

DryRun determines whether the file should be applied in dry run mode.

expect []Expectation

Expect defines a list of matched checks to validate the operation outcome.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Delete","title":"Delete","text":"

Appears in:

  • Catch
  • Finally
  • Operation

Delete is a reference to an object that should be deleted

Field Type Required Inline Description timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

bindings []Binding

Bindings defines additional binding key/values.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

template bool

Template determines whether resources should be considered for templating.

ref ObjectReference

ObjectReference determines objects to be deleted.

expect []Expectation

Expect defines a list of matched checks to validate the operation outcome.

deletionPropagationPolicy meta/v1.DeletionPropagation

DeletionPropagationPolicy decides if a deletion will propagate to the dependents of the object, and how the garbage collector will handle the propagation. Overrides the deletion propagation policy set in the Configuration, the Test and the TestStep.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Deletion","title":"Deletion","text":"

Appears in:

  • For

Deletion represents parameters for waiting on a resource's deletion.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Describe","title":"Describe","text":"

Appears in:

  • Catch
  • Finally

Describe defines how to describe resources.

Field Type Required Inline Description timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

ResourceReference ResourceReference

ResourceReference referenced resource type.

ObjectLabelsSelector ObjectLabelsSelector

ObjectLabelsSelector determines the selection process of referenced objects.

showEvents bool

Show Events indicates whether to include related events.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Error","title":"Error","text":"

Appears in:

  • Operation

Error represents an anticipated error condition that may arise during testing. Instead of treating such an error as a test failure, it acknowledges it as expected.

Field Type Required Inline Description timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

bindings []Binding

Bindings defines additional binding key/values.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

FileRefOrCheck FileRefOrCheck

FileRefOrAssert provides a reference to the expected error.

template bool

Template determines whether resources should be considered for templating.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Events","title":"Events","text":"

Appears in:

  • Catch
  • Finally

Events defines how to collect events.

Field Type Required Inline Description timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

ObjectLabelsSelector ObjectLabelsSelector

ObjectLabelsSelector determines the selection process of referenced objects.

format Format

Format determines the output format (json or yaml).

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Expectation","title":"Expectation","text":"

Appears in:

  • Apply
  • Create
  • Delete
  • Patch
  • Update

Expectation represents a check to be applied on the result of an operation with a match filter to determine if the verification should be considered.

Field Type Required Inline Description match policy/v1alpha1.Any

Match defines the matching statement.

check policy/v1alpha1.Any

Check defines the verification statement.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-FileRef","title":"FileRef","text":"

Appears in:

  • FileRefOrCheck
  • FileRefOrResource

FileRef represents a file reference.

Field Type Required Inline Description file string

File is the path to the referenced file. This can be a direct path to a file or an expression that matches multiple files, such as \"manifest/*.yaml\" for all YAML files within the \"manifest\" directory.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-FileRefOrCheck","title":"FileRefOrCheck","text":"

Appears in:

  • Assert
  • Error

FileRefOrCheck represents a file reference or resource.

Field Type Required Inline Description FileRef FileRef

FileRef provides a reference to the file containing the resources to be applied.

resource policy/v1alpha1.Any

Check provides a check used in assertions.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-FileRefOrResource","title":"FileRefOrResource","text":"

Appears in:

  • Apply
  • Create
  • Patch
  • Update

FileRefOrResource represents a file reference or resource.

Field Type Required Inline Description FileRef FileRef

FileRef provides a reference to the file containing the resources to be applied.

resource meta/v1/unstructured.Unstructured

Resource provides a resource to be applied.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Finally","title":"Finally","text":"

Appears in:

  • TestStepSpec

Finally defines actions to be executed at the end of a test.

Field Type Required Inline Description description string

Description contains a description of the operation.

podLogs PodLogs

PodLogs determines the pod logs collector to execute.

events Events

Events determines the events collector to execute.

describe Describe

Describe determines the resource describe collector to execute.

wait Wait

Wait determines the resource wait collector to execute.

get Get

Get determines the resource get collector to execute.

delete Delete

Delete represents a deletion operation.

command Command

Command defines a command to run.

script Script

Script defines a script to run.

sleep Sleep

Sleep defines zzzz.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-For","title":"For","text":"

Appears in:

  • Wait

For specifies the condition to wait for.

Field Type Required Inline Description deletion Deletion

Deletion specifies parameters for waiting on a resource's deletion.

condition Condition

Condition specifies the condition to wait for.

jsonPath JsonPath

JsonPath specifies the json path condition to wait for.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Format","title":"Format","text":"

(Alias of string)

Appears in:

  • Events
  • Get
  • Wait

Format determines the output format (json or yaml).

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Get","title":"Get","text":"

Appears in:

  • Catch
  • Finally

Get defines how to get resources.

Field Type Required Inline Description timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

ResourceReference ResourceReference

ResourceReference referenced resource type.

ObjectLabelsSelector ObjectLabelsSelector

ObjectLabelsSelector determines the selection process of referenced objects.

format Format

Format determines the output format (json or yaml).

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-JsonPath","title":"JsonPath","text":"

Appears in:

  • For

JsonPath represents parameters for waiting on a json path of a resource.

Field Type Required Inline Description path string

Path defines the json path to wait for, e.g. '{.status.phase}'.

value string

Value defines the expected value to wait for, e.g., \"Running\".

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-ObjectLabelsSelector","title":"ObjectLabelsSelector","text":"

Appears in:

  • Describe
  • Events
  • Get
  • PodLogs
  • Wait

ObjectLabelsSelector represents a strategy to select objects. For a single object name and namespace are used to identify the object. For multiple objects use selector.

Field Type Required Inline Description namespace string

Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/

name string

Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

selector string

Selector defines labels selector.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-ObjectReference","title":"ObjectReference","text":"

Appears in:

  • Delete

ObjectReference represents one or more objects with a specific apiVersion and kind. For a single object name and namespace are used to identify the object. For multiple objects use labels.

Field Type Required Inline Description ObjectType ObjectType

ObjectType determines the type of referenced objects.

ObjectSelector ObjectSelector

ObjectSelector determines the selection process of referenced objects.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-ObjectSelector","title":"ObjectSelector","text":"

Appears in:

  • ObjectReference

ObjectSelector represents a strategy to select objects. For a single object name and namespace are used to identify the object. For multiple objects use labels.

Field Type Required Inline Description namespace string

Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/

name string

Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names

labels map[string]string

Label selector to match objects to delete

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-ObjectType","title":"ObjectType","text":"

Appears in:

  • ObjectReference

ObjectType represents a specific apiVersion and kind.

Field Type Required Inline Description apiVersion string

API version of the referent.

kind string

Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Operation","title":"Operation","text":"

Appears in:

  • TestStepSpec

Operation defines a single operation, only one action is permitted for a given operation.

Field Type Required Inline Description OperationBase OperationBase

OperationBase defines common elements to all operations.

apply Apply

Apply represents resources that should be applied for this test step. This can include things like configuration settings or any other resources that need to be available during the test.

assert Assert

Assert represents an assertion to be made. It checks whether the conditions specified in the assertion hold true.

command Command

Command defines a command to run.

create Create

Create represents a creation operation.

delete Delete

Delete represents a deletion operation.

error Error

Error represents the expected errors for this test step. If any of these errors occur, the test will consider them as expected; otherwise, they will be treated as test failures.

patch Patch

Patch represents a patch operation.

script Script

Script defines a script to run.

sleep Sleep

Sleep defines zzzz.

update Update

Update represents an update operation.

wait Wait

Wait determines the resource wait collector to execute.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-OperationBase","title":"OperationBase","text":"

Appears in:

  • Operation

OperationBase defines common elements to all operations.

Field Type Required Inline Description description string

Description contains a description of the operation.

continueOnError bool

ContinueOnError determines whether a test should continue or not in case the operation was not successful. Even if the test continues executing, it will still be reported as failed.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Output","title":"Output","text":"

Appears in:

  • Apply
  • Command
  • Create
  • Patch
  • Script
  • Update

Output represents an output binding with a match to determine if the binding must be considered or not.

Field Type Required Inline Description Binding Binding

Binding determines the binding to create when the match succeeds.

match policy/v1alpha1.Any

Match defines the matching statement.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Patch","title":"Patch","text":"

Appears in:

  • Operation

Patch represents a set of resources that should be patched. If a resource doesn't exist yet in the cluster it will fail.

Field Type Required Inline Description timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

bindings []Binding

Bindings defines additional binding key/values.

outputs []Output

Outputs defines output bindings.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

FileRefOrResource FileRefOrResource

FileRefOrResource provides a reference to the file containing the resources to be patched.

template bool

Template determines whether resources should be considered for templating.

dryRun bool

DryRun determines whether the file should be applied in dry run mode.

expect []Expectation

Expect defines a list of matched checks to validate the operation outcome.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-PodLogs","title":"PodLogs","text":"

Appears in:

  • Catch
  • Finally

PodLogs defines how to collect pod logs.

Field Type Required Inline Description timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

ObjectLabelsSelector ObjectLabelsSelector

ObjectLabelsSelector determines the selection process of referenced objects.

container string

Container in pod to get logs from else --all-containers is used.

tail int

Tail is the number of last lines to collect from pods. If omitted or zero, then the default is 10 if you use a selector, or -1 (all) if you use a pod name. This matches default behavior of kubectl logs.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-ReportFormatType","title":"ReportFormatType","text":"

(Alias of string)

Appears in:

  • ConfigurationSpec
"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-ResourceReference","title":"ResourceReference","text":"

Appears in:

  • Describe
  • Get
  • Wait

ResourceReference represents a resource (API), it can be represented with a resource or a kind. Optionally an apiVersion can be specified.

Field Type Required Inline Description apiVersion string

API version of the referent.

kind string

Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds

resource string

Resource name of the referent.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Script","title":"Script","text":"

Appears in:

  • Catch
  • Finally
  • Operation

Script describes a script to run as a part of a test step.

Field Type Required Inline Description timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

bindings []Binding

Bindings defines additional binding key/values.

outputs []Output

Outputs defines output bindings.

env []Binding

Env defines additional environment variables.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

content string

Content defines a shell script (run with \"sh -c ...\").

skipLogOutput bool

SkipLogOutput removes the output from the command. Useful for sensitive logs or to reduce noise.

check policy/v1alpha1.Any

Check is an assertion tree to validate the operation outcome.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Sleep","title":"Sleep","text":"

Appears in:

  • Catch
  • Finally
  • Operation

Sleep represents a duration while nothing happens.

Field Type Required Inline Description duration meta/v1.Duration

Duration is the delay used for sleeping.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-TestSpec","title":"TestSpec","text":"

Appears in:

  • Test

TestSpec contains the test spec.

Field Type Required Inline Description description string

Description contains a description of the test.

timeouts Timeouts

Timeouts for the test. Overrides the global timeouts set in the Configuration on a per operation basis.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

skip bool

Skip determines whether the test should skipped.

concurrent bool

Concurrent determines whether the test should run concurrently with other tests.

skipDelete bool

SkipDelete determines whether the resources created by the test should be deleted after the test is executed.

template bool

Template determines whether resources should be considered for templating.

namespace string

Namespace determines whether the test should run in a random ephemeral namespace or not.

namespaceTemplate policy/v1alpha1.Any

NamespaceTemplate defines a template to create the test namespace.

bindings []Binding

Bindings defines additional binding key/values.

steps []TestStep

Steps defining the test.

catch []Catch

Catch defines what the steps will execute when an error happens. This will be combined with catch handlers defined at the step level.

forceTerminationGracePeriod meta/v1.Duration

ForceTerminationGracePeriod forces the termination grace period on pods, statefulsets, daemonsets and deployments.

delayBeforeCleanup meta/v1.Duration

DelayBeforeCleanup adds a delay between the time a test ends and the time cleanup starts.

deletionPropagationPolicy meta/v1.DeletionPropagation

DeletionPropagationPolicy decides if a deletion will propagate to the dependents of the object, and how the garbage collector will handle the propagation. Overrides the deletion propagation policy set in the Configuration.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-TestStep","title":"TestStep","text":"

Appears in:

  • TestSpec

TestStep contains the test step definition used in a test spec.

Field Type Required Inline Description name string

Name of the step.

TestStepSpec TestStepSpec

TestStepSpec of the step.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-TestStepSpec","title":"TestStepSpec","text":"

Appears in:

  • TestStep

TestStepSpec defines the desired state and behavior for each test step.

Field Type Required Inline Description description string

Description contains a description of the test step.

timeouts Timeouts

Timeouts for the test step. Overrides the global timeouts set in the Configuration and the timeouts eventually set in the Test.

deletionPropagationPolicy meta/v1.DeletionPropagation

DeletionPropagationPolicy decides if a deletion will propagate to the dependents of the object, and how the garbage collector will handle the propagation. Overrides the deletion propagation policy set in both the Configuration and the Test.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

skipDelete bool

SkipDelete determines whether the resources created by the step should be deleted after the test step is executed.

template bool

Template determines whether resources should be considered for templating.

bindings []Binding

Bindings defines additional binding key/values.

try []Operation

Try defines what the step will try to execute.

catch []Catch

Catch defines what the step will execute when an error happens.

finally []Finally

Finally defines what the step will execute after the step is terminated.

cleanup []Finally

Cleanup defines what will be executed after the test is terminated.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Timeouts","title":"Timeouts","text":"

Appears in:

  • ConfigurationSpec
  • TestSpec
  • TestStepSpec

Timeouts contains timeouts per operation.

Field Type Required Inline Description apply meta/v1.Duration

Apply defines the timeout for the apply operation

assert meta/v1.Duration

Assert defines the timeout for the assert operation

cleanup meta/v1.Duration

Cleanup defines the timeout for the cleanup operation

delete meta/v1.Duration

Delete defines the timeout for the delete operation

error meta/v1.Duration

Error defines the timeout for the error operation

exec meta/v1.Duration

Exec defines the timeout for exec operations

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Update","title":"Update","text":"

Appears in:

  • Operation

Update represents a set of resources that should be updated. If a resource does not exist in the cluster it will fail.

Field Type Required Inline Description timeout meta/v1.Duration

Timeout for the operation. Overrides the global timeout set in the Configuration.

bindings []Binding

Bindings defines additional binding key/values.

outputs []Output

Outputs defines output bindings.

cluster string

Cluster defines the target cluster (default cluster will be used if not specified and/or overridden).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

FileRefOrResource FileRefOrResource

FileRefOrResource provides a reference to the file containing the resources to be created.

template bool

Template determines whether resources should be considered for templating.

dryRun bool

DryRun determines whether the file should be applied in dry run mode.

expect []Expectation

Expect defines a list of matched checks to validate the operation outcome.

"},{"location":"reference/apis/chainsaw.v1alpha1/#chainsaw-kyverno-io-v1alpha1-Wait","title":"Wait","text":"

Appears in:

  • Catch
  • Finally
  • Operation

Wait specifies how to perform wait operations on resources.

Field Type Required Inline Description timeout meta/v1.Duration

Timeout for the operation. Specifies how long to wait for the condition to be met before timing out.

cluster string

Cluster defines the target cluster where the wait operation will be performed (default cluster will be used if not specified).

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

ResourceReference ResourceReference

ResourceReference referenced resource type.

ObjectLabelsSelector ObjectLabelsSelector

ObjectLabelsSelector determines the selection process of referenced objects.

for For

For specifies the condition to wait for.

format Format

Format determines the output format (json or yaml).

"},{"location":"reference/apis/chainsaw.v1alpha2/","title":"chainsaw (v1alpha2)","text":"

Package v1alpha2 contains API Schema definitions for the v1alpha2 API group.

"},{"location":"reference/apis/chainsaw.v1alpha2/#resource-types","title":"Resource Types","text":"
  • Configuration
"},{"location":"reference/apis/chainsaw.v1alpha2/#chainsaw-kyverno-io-v1alpha2-Configuration","title":"Configuration","text":"

Configuration is the resource that contains the configuration used to run tests.

Field Type Required Inline Description apiVersion string chainsaw.kyverno.io/v1alpha2 kind string Configuration metadata meta/v1.ObjectMeta

Standard object's metadata.

spec ConfigurationSpec

Configuration spec.

"},{"location":"reference/apis/chainsaw.v1alpha2/#chainsaw-kyverno-io-v1alpha2-Cleanup","title":"Cleanup","text":"

Appears in:

  • ConfigurationSpec

Cleanup options contain the configuration used for cleaning up resources.

Field Type Required Inline Description skipDelete bool

If set, do not delete the resources after running a test.

delayBeforeCleanup meta/v1.Duration

DelayBeforeCleanup adds a delay between the time a test ends and the time cleanup starts.

"},{"location":"reference/apis/chainsaw.v1alpha2/#chainsaw-kyverno-io-v1alpha2-ConfigurationSpec","title":"ConfigurationSpec","text":"

Appears in:

  • Configuration

ConfigurationSpec contains the configuration used to run tests.

Field Type Required Inline Description cleanup Cleanup

Cleanup contains cleanup configuration.

clusters map[string]Cluster

Clusters holds a registry to clusters to support multi-cluster tests.

deletion DeletionOptions

Deletion contains the global deletion configuration.

discovery Discovery

Discovery contains tests discovery configuration.

error ErrorOptions

Error contains the global error configuration.

execution Execution

Execution contains tests execution configuration.

namespace Namespace

Namespace contains properties for the namespace to use for tests.

report Report

Report contains properties for the report.

templating Templating

Templating contains the templating config.

timeouts Timeouts

Global timeouts configuration. Applies to all tests/test steps if not overridden.

"},{"location":"reference/apis/chainsaw.v1alpha2/#chainsaw-kyverno-io-v1alpha2-DeletionOptions","title":"DeletionOptions","text":"

Appears in:

  • ConfigurationSpec

DeletionOptions contains the configuration used for deleting resources.

Field Type Required Inline Description propagation meta/v1.DeletionPropagation

Propagation decides if a deletion will propagate to the dependents of the object, and how the garbage collector will handle the propagation.

"},{"location":"reference/apis/chainsaw.v1alpha2/#chainsaw-kyverno-io-v1alpha2-Discovery","title":"Discovery","text":"

Appears in:

  • ConfigurationSpec

Discovery options contain the discovery configuration used when discovering tests in folders.

Field Type Required Inline Description excludeTestRegex string

ExcludeTestRegex is used to exclude tests based on a regular expression.

includeTestRegex string

IncludeTestRegex is used to include tests based on a regular expression.

testFile string

TestFile is the name of the file containing the test to run. If no extension is provided, chainsaw will try with .yaml first and .yml if needed.

fullName bool

FullName makes use of the full test case folder path instead of the folder name.

"},{"location":"reference/apis/chainsaw.v1alpha2/#chainsaw-kyverno-io-v1alpha2-ErrorOptions","title":"ErrorOptions","text":"

Appears in:

  • ConfigurationSpec

ErrorOptions contains the global error configuration.

Field Type Required Inline Description catch []Catch

Catch defines what the tests steps will execute when an error happens. This will be combined with catch handlers defined at the test and step levels.

"},{"location":"reference/apis/chainsaw.v1alpha2/#chainsaw-kyverno-io-v1alpha2-Execution","title":"Execution","text":"

Appears in:

  • ConfigurationSpec

Execution options determine how tests are run.

Field Type Required Inline Description failFast bool

FailFast determines whether the test should stop upon encountering the first failure.

parallel int

The maximum number of tests to run at once.

repeatCount int

RepeatCount indicates how many times the tests should be executed.

forceTerminationGracePeriod meta/v1.Duration

ForceTerminationGracePeriod forces the termination grace period on pods, statefulsets, daemonsets and deployments.

"},{"location":"reference/apis/chainsaw.v1alpha2/#chainsaw-kyverno-io-v1alpha2-Namespace","title":"Namespace","text":"

Appears in:

  • ConfigurationSpec

Namespace options contain the configuration used to allocate a namespace for each test.

Field Type Required Inline Description name string

Name defines the namespace to use for tests. If not specified, every test will execute in a random ephemeral namespace unless the namespace is overridden in a the test spec.

template policy/v1alpha1.Any

Template defines a template to create the test namespace.

"},{"location":"reference/apis/chainsaw.v1alpha2/#chainsaw-kyverno-io-v1alpha2-Report","title":"Report","text":"

Appears in:

  • ConfigurationSpec

Report options contain the configuration used for reporting.

Field Type Required Inline Description format ReportFormatType

ReportFormat determines test report format (JSON path string

ReportPath defines the path.

name string

ReportName defines the name of report to create. It defaults to \"chainsaw-report\".

"},{"location":"reference/apis/chainsaw.v1alpha2/#chainsaw-kyverno-io-v1alpha2-ReportFormatType","title":"ReportFormatType","text":"

(Alias of string)

Appears in:

  • Report
"},{"location":"reference/apis/chainsaw.v1alpha2/#chainsaw-kyverno-io-v1alpha2-Templating","title":"Templating","text":"

Appears in:

  • ConfigurationSpec

Templating options contain the templating configuration.

Field Type Required Inline Description enabled bool

Enabled determines whether resources should be considered for templating.

"},{"location":"reference/commands/chainsaw/","title":"chainsaw","text":""},{"location":"reference/commands/chainsaw/#chainsaw","title":"chainsaw","text":"

Stronger tool for e2e testing

chainsaw [flags]\n
"},{"location":"reference/commands/chainsaw/#options","title":"Options","text":"
  -h, --help   help for chainsaw\n
"},{"location":"reference/commands/chainsaw/#see-also","title":"SEE ALSO","text":"
  • chainsaw assert - Evaluate assertion
  • chainsaw build - Build commands
  • chainsaw completion - Generate the autocompletion script for the specified shell
  • chainsaw create - Create Chainsaw resources
  • chainsaw docs - Generate reference documentation
  • chainsaw export - Export commands
  • chainsaw lint - Lint a file or read from standard input
  • chainsaw migrate - Migrate resources to Chainsaw
  • chainsaw test - Run tests
  • chainsaw version - Print the version informations
"},{"location":"reference/commands/chainsaw_assert/","title":"chainsaw assert","text":""},{"location":"reference/commands/chainsaw_assert/#chainsaw-assert","title":"chainsaw assert","text":"

Evaluate assertion

chainsaw assert [flags] [FILE]\n
"},{"location":"reference/commands/chainsaw_assert/#options","title":"Options","text":"
      --clustered                           Defines if the resource is clustered (only applies when resource is loaded from a file)\n  -f, --file string                         Path to the file to assert or '-' to read from stdin\n  -h, --help                                help for assert\n      --kube-as string                      Username to impersonate for the operation\n      --kube-as-group stringArray           Group to impersonate for the operation, this flag can be repeated to specify multiple groups.\n      --kube-as-uid string                  UID to impersonate for the operation\n      --kube-certificate-authority string   Path to a cert file for the certificate authority\n      --kube-client-certificate string      Path to a client certificate file for TLS\n      --kube-client-key string              Path to a client key file for TLS\n      --kube-cluster string                 The name of the kubeconfig cluster to use\n      --kube-context string                 The name of the kubeconfig context to use\n      --kube-disable-compression            If true, opt-out of response compression for all requests to the server\n      --kube-insecure-skip-tls-verify       If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure\n  -n, --kube-namespace string               If present, the namespace scope for this CLI request\n      --kube-password string                Password for basic authentication to the API server\n      --kube-proxy-url string               If provided, this URL will be used to connect via proxy\n      --kube-request-timeout string         The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default \"0\")\n      --kube-server string                  The address and port of the Kubernetes API server\n      --kube-tls-server-name string         If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used.\n      --kube-token string                   Bearer token for authentication to the API server\n      --kube-user string                    The name of the kubeconfig user to use\n      --kube-username string                Username for basic authentication to the API server\n      --namespace string                    Namespace to use (default \"default\")\n      --no-color                            Removes output colors\n  -r, --resource string                     Path to the file containing the resource\n      --timeout duration                    The assert timeout to use (default 30s)\n
"},{"location":"reference/commands/chainsaw_assert/#see-also","title":"SEE ALSO","text":"
  • chainsaw - Stronger tool for e2e testing
"},{"location":"reference/commands/chainsaw_build/","title":"chainsaw build","text":""},{"location":"reference/commands/chainsaw_build/#chainsaw-build","title":"chainsaw build","text":"

Build commands

chainsaw build [flags]\n
"},{"location":"reference/commands/chainsaw_build/#options","title":"Options","text":"
  -h, --help   help for build\n
"},{"location":"reference/commands/chainsaw_build/#see-also","title":"SEE ALSO","text":"
  • chainsaw - Stronger tool for e2e testing
  • chainsaw build docs - Build tests documentation
"},{"location":"reference/commands/chainsaw_build_docs/","title":"chainsaw build docs","text":""},{"location":"reference/commands/chainsaw_build_docs/#chainsaw-build-docs","title":"chainsaw build docs","text":"

Build tests documentation

chainsaw build docs [flags]\n
"},{"location":"reference/commands/chainsaw_build_docs/#options","title":"Options","text":"
      --catalog string         Path to the built test catalog file\n  -h, --help                   help for docs\n      --readme-file string     Name of the built docs file (default \"README.md\")\n      --test-dir stringArray   Directories containing test cases to run\n      --test-file string       Name of the test file (default \"chainsaw-test\")\n
"},{"location":"reference/commands/chainsaw_build_docs/#see-also","title":"SEE ALSO","text":"
  • chainsaw build - Build commands
"},{"location":"reference/commands/chainsaw_completion/","title":"chainsaw completion","text":""},{"location":"reference/commands/chainsaw_completion/#chainsaw-completion","title":"chainsaw completion","text":"

Generate the autocompletion script for the specified shell

"},{"location":"reference/commands/chainsaw_completion/#synopsis","title":"Synopsis","text":"

Generate the autocompletion script for chainsaw for the specified shell. See each sub-command's help for details on how to use the generated script.

"},{"location":"reference/commands/chainsaw_completion/#options","title":"Options","text":"
  -h, --help   help for completion\n
"},{"location":"reference/commands/chainsaw_completion/#see-also","title":"SEE ALSO","text":"
  • chainsaw - Stronger tool for e2e testing
  • chainsaw completion bash - Generate the autocompletion script for bash
  • chainsaw completion fish - Generate the autocompletion script for fish
  • chainsaw completion powershell - Generate the autocompletion script for powershell
  • chainsaw completion zsh - Generate the autocompletion script for zsh
"},{"location":"reference/commands/chainsaw_completion_bash/","title":"chainsaw completion bash","text":""},{"location":"reference/commands/chainsaw_completion_bash/#chainsaw-completion-bash","title":"chainsaw completion bash","text":"

Generate the autocompletion script for bash

"},{"location":"reference/commands/chainsaw_completion_bash/#synopsis","title":"Synopsis","text":"

Generate the autocompletion script for the bash shell.

This script depends on the 'bash-completion' package. If it is not installed already, you can install it via your OS's package manager.

To load completions in your current shell session:

source <(chainsaw completion bash)\n

To load completions for every new session, execute once:

"},{"location":"reference/commands/chainsaw_completion_bash/#linux","title":"Linux:","text":"
chainsaw completion bash > /etc/bash_completion.d/chainsaw\n
"},{"location":"reference/commands/chainsaw_completion_bash/#macos","title":"macOS:","text":"
chainsaw completion bash > $(brew --prefix)/etc/bash_completion.d/chainsaw\n

You will need to start a new shell for this setup to take effect.

chainsaw completion bash\n
"},{"location":"reference/commands/chainsaw_completion_bash/#options","title":"Options","text":"
  -h, --help              help for bash\n      --no-descriptions   disable completion descriptions\n
"},{"location":"reference/commands/chainsaw_completion_bash/#see-also","title":"SEE ALSO","text":"
  • chainsaw completion - Generate the autocompletion script for the specified shell
"},{"location":"reference/commands/chainsaw_completion_fish/","title":"chainsaw completion fish","text":""},{"location":"reference/commands/chainsaw_completion_fish/#chainsaw-completion-fish","title":"chainsaw completion fish","text":"

Generate the autocompletion script for fish

"},{"location":"reference/commands/chainsaw_completion_fish/#synopsis","title":"Synopsis","text":"

Generate the autocompletion script for the fish shell.

To load completions in your current shell session:

chainsaw completion fish | source\n

To load completions for every new session, execute once:

chainsaw completion fish > ~/.config/fish/completions/chainsaw.fish\n

You will need to start a new shell for this setup to take effect.

chainsaw completion fish [flags]\n
"},{"location":"reference/commands/chainsaw_completion_fish/#options","title":"Options","text":"
  -h, --help              help for fish\n      --no-descriptions   disable completion descriptions\n
"},{"location":"reference/commands/chainsaw_completion_fish/#see-also","title":"SEE ALSO","text":"
  • chainsaw completion - Generate the autocompletion script for the specified shell
"},{"location":"reference/commands/chainsaw_completion_powershell/","title":"chainsaw completion powershell","text":""},{"location":"reference/commands/chainsaw_completion_powershell/#chainsaw-completion-powershell","title":"chainsaw completion powershell","text":"

Generate the autocompletion script for powershell

"},{"location":"reference/commands/chainsaw_completion_powershell/#synopsis","title":"Synopsis","text":"

Generate the autocompletion script for powershell.

To load completions in your current shell session:

chainsaw completion powershell | Out-String | Invoke-Expression\n

To load completions for every new session, add the output of the above command to your powershell profile.

chainsaw completion powershell [flags]\n
"},{"location":"reference/commands/chainsaw_completion_powershell/#options","title":"Options","text":"
  -h, --help              help for powershell\n      --no-descriptions   disable completion descriptions\n
"},{"location":"reference/commands/chainsaw_completion_powershell/#see-also","title":"SEE ALSO","text":"
  • chainsaw completion - Generate the autocompletion script for the specified shell
"},{"location":"reference/commands/chainsaw_completion_zsh/","title":"chainsaw completion zsh","text":""},{"location":"reference/commands/chainsaw_completion_zsh/#chainsaw-completion-zsh","title":"chainsaw completion zsh","text":"

Generate the autocompletion script for zsh

"},{"location":"reference/commands/chainsaw_completion_zsh/#synopsis","title":"Synopsis","text":"

Generate the autocompletion script for the zsh shell.

If shell completion is not already enabled in your environment you will need to enable it. You can execute the following once:

echo \"autoload -U compinit; compinit\" >> ~/.zshrc\n

To load completions in your current shell session:

source <(chainsaw completion zsh)\n

To load completions for every new session, execute once:

"},{"location":"reference/commands/chainsaw_completion_zsh/#linux","title":"Linux:","text":"
chainsaw completion zsh > \"${fpath[1]}/_chainsaw\"\n
"},{"location":"reference/commands/chainsaw_completion_zsh/#macos","title":"macOS:","text":"
chainsaw completion zsh > $(brew --prefix)/share/zsh/site-functions/_chainsaw\n

You will need to start a new shell for this setup to take effect.

chainsaw completion zsh [flags]\n
"},{"location":"reference/commands/chainsaw_completion_zsh/#options","title":"Options","text":"
  -h, --help              help for zsh\n      --no-descriptions   disable completion descriptions\n
"},{"location":"reference/commands/chainsaw_completion_zsh/#see-also","title":"SEE ALSO","text":"
  • chainsaw completion - Generate the autocompletion script for the specified shell
"},{"location":"reference/commands/chainsaw_create/","title":"chainsaw create","text":""},{"location":"reference/commands/chainsaw_create/#chainsaw-create","title":"chainsaw create","text":"

Create Chainsaw resources

chainsaw create [flags]\n
"},{"location":"reference/commands/chainsaw_create/#options","title":"Options","text":"
  -h, --help   help for create\n
"},{"location":"reference/commands/chainsaw_create/#see-also","title":"SEE ALSO","text":"
  • chainsaw - Stronger tool for e2e testing
  • chainsaw create test - Create a Chainsaw test
"},{"location":"reference/commands/chainsaw_create_test/","title":"chainsaw create test","text":""},{"location":"reference/commands/chainsaw_create_test/#chainsaw-create-test","title":"chainsaw create test","text":"

Create a Chainsaw test

chainsaw create test [flags]\n
"},{"location":"reference/commands/chainsaw_create_test/#options","title":"Options","text":"
      --description   If set, adds description when applicable (default true)\n      --force         If set, existing test will be deleted if needed\n  -h, --help          help for test\n      --save          If set, created test will be saved\n
"},{"location":"reference/commands/chainsaw_create_test/#see-also","title":"SEE ALSO","text":"
  • chainsaw create - Create Chainsaw resources
"},{"location":"reference/commands/chainsaw_docs/","title":"chainsaw docs","text":""},{"location":"reference/commands/chainsaw_docs/#chainsaw-docs","title":"chainsaw docs","text":"

Generate reference documentation

chainsaw docs [flags]\n
"},{"location":"reference/commands/chainsaw_docs/#options","title":"Options","text":"
      --autogenTag      Determines if the generated docs should contain a timestamp (default true)\n  -h, --help            help for docs\n  -o, --output string   Output path (default \".\")\n      --website         Website version\n
"},{"location":"reference/commands/chainsaw_docs/#see-also","title":"SEE ALSO","text":"
  • chainsaw - Stronger tool for e2e testing
"},{"location":"reference/commands/chainsaw_export/","title":"chainsaw export","text":""},{"location":"reference/commands/chainsaw_export/#chainsaw-export","title":"chainsaw export","text":"

Export commands

chainsaw export [flags]\n
"},{"location":"reference/commands/chainsaw_export/#options","title":"Options","text":"
  -h, --help   help for export\n
"},{"location":"reference/commands/chainsaw_export/#see-also","title":"SEE ALSO","text":"
  • chainsaw - Stronger tool for e2e testing
  • chainsaw export schemas - Export JSON schemas
"},{"location":"reference/commands/chainsaw_export_schemas/","title":"chainsaw export schemas","text":""},{"location":"reference/commands/chainsaw_export_schemas/#chainsaw-export-schemas","title":"chainsaw export schemas","text":"

Export JSON schemas

chainsaw export schemas [flags]\n
"},{"location":"reference/commands/chainsaw_export_schemas/#options","title":"Options","text":"
  -h, --help   help for schemas\n
"},{"location":"reference/commands/chainsaw_export_schemas/#see-also","title":"SEE ALSO","text":"
  • chainsaw export - Export commands
"},{"location":"reference/commands/chainsaw_lint/","title":"chainsaw lint","text":""},{"location":"reference/commands/chainsaw_lint/#chainsaw-lint","title":"chainsaw lint","text":"

Lint a file or read from standard input

"},{"location":"reference/commands/chainsaw_lint/#synopsis","title":"Synopsis","text":"

Use chainsaw lint to lint a specific file or read from standard input for either test or configuration.

chainsaw lint [test|configuration] [flags]\n
"},{"location":"reference/commands/chainsaw_lint/#options","title":"Options","text":"
  -f, --file string   Specify the file to lint or '-' for standard input\n  -h, --help          help for lint\n
"},{"location":"reference/commands/chainsaw_lint/#see-also","title":"SEE ALSO","text":"
  • chainsaw - Stronger tool for e2e testing
"},{"location":"reference/commands/chainsaw_migrate/","title":"chainsaw migrate","text":""},{"location":"reference/commands/chainsaw_migrate/#chainsaw-migrate","title":"chainsaw migrate","text":"

Migrate resources to Chainsaw

chainsaw migrate [flags]\n
"},{"location":"reference/commands/chainsaw_migrate/#options","title":"Options","text":"
  -h, --help   help for migrate\n
"},{"location":"reference/commands/chainsaw_migrate/#see-also","title":"SEE ALSO","text":"
  • chainsaw - Stronger tool for e2e testing
  • chainsaw migrate kuttl - Migrate KUTTL resources to Chainsaw
"},{"location":"reference/commands/chainsaw_migrate_kuttl/","title":"chainsaw migrate kuttl","text":""},{"location":"reference/commands/chainsaw_migrate_kuttl/#chainsaw-migrate-kuttl","title":"chainsaw migrate kuttl","text":"

Migrate KUTTL resources to Chainsaw

chainsaw migrate kuttl [flags]\n
"},{"location":"reference/commands/chainsaw_migrate_kuttl/#options","title":"Options","text":"
  -h, --help   help for kuttl\n
"},{"location":"reference/commands/chainsaw_migrate_kuttl/#see-also","title":"SEE ALSO","text":"
  • chainsaw migrate - Migrate resources to Chainsaw
  • chainsaw migrate kuttl config - Migrate KUTTL config to Chainsaw
  • chainsaw migrate kuttl tests - Migrate KUTTL tests to Chainsaw
"},{"location":"reference/commands/chainsaw_migrate_kuttl_config/","title":"chainsaw migrate kuttl config","text":""},{"location":"reference/commands/chainsaw_migrate_kuttl_config/#chainsaw-migrate-kuttl-config","title":"chainsaw migrate kuttl config","text":"

Migrate KUTTL config to Chainsaw

chainsaw migrate kuttl config [flags]\n
"},{"location":"reference/commands/chainsaw_migrate_kuttl_config/#options","title":"Options","text":"
      --cleanup   If set, delete converted files\n  -h, --help      help for config\n      --save      If set, converted files will be saved\n
"},{"location":"reference/commands/chainsaw_migrate_kuttl_config/#see-also","title":"SEE ALSO","text":"
  • chainsaw migrate kuttl - Migrate KUTTL resources to Chainsaw
"},{"location":"reference/commands/chainsaw_migrate_kuttl_tests/","title":"chainsaw migrate kuttl tests","text":""},{"location":"reference/commands/chainsaw_migrate_kuttl_tests/#chainsaw-migrate-kuttl-tests","title":"chainsaw migrate kuttl tests","text":"

Migrate KUTTL tests to Chainsaw

chainsaw migrate kuttl tests [flags]\n
"},{"location":"reference/commands/chainsaw_migrate_kuttl_tests/#options","title":"Options","text":"
      --cleanup   If set, delete converted files\n  -h, --help      help for tests\n      --save      If set, converted files will be saved\n
"},{"location":"reference/commands/chainsaw_migrate_kuttl_tests/#see-also","title":"SEE ALSO","text":"
  • chainsaw migrate kuttl - Migrate KUTTL resources to Chainsaw
"},{"location":"reference/commands/chainsaw_test/","title":"chainsaw test","text":""},{"location":"reference/commands/chainsaw_test/#chainsaw-test","title":"chainsaw test","text":"

Run tests

chainsaw test [flags]... [test directories]...\n
"},{"location":"reference/commands/chainsaw_test/#options","title":"Options","text":"
      --apply-timeout duration                    The apply timeout to use as default for configuration (default 5s)\n      --assert-timeout duration                   The assert timeout to use as default for configuration (default 30s)\n      --cleanup-delay duration                    Adds a delay between the time a test ends and the time cleanup starts\n      --cleanup-timeout duration                  The cleanup timeout to use as default for configuration (default 30s)\n      --cluster strings                           Register cluster (format <cluster name>=<kubeconfig path>:[context name])\n      --config string                             Chainsaw configuration file\n      --delete-timeout duration                   The delete timeout to use as default for configuration (default 15s)\n      --deletion-propagation-policy string        The deletion propagation policy (Foreground|Background|Orphan) (default \"Background\")\n      --error-timeout duration                    The error timeout to use as default for configuration (default 30s)\n      --exclude-test-regex string                 Regular expression to exclude tests\n      --exec-timeout duration                     The exec timeout to use as default for configuration (default 5s)\n      --fail-fast                                 Stop the test upon encountering the first failure\n      --force-termination-grace-period duration   If specified, overrides termination grace periods in applicable resources\n      --full-name                                 Use full test case folder path instead of folder name\n  -h, --help                                      help for test\n      --include-test-regex string                 Regular expression to include tests\n      --kube-as string                            Username to impersonate for the operation\n      --kube-as-group stringArray                 Group to impersonate for the operation, this flag can be repeated to specify multiple groups.\n      --kube-as-uid string                        UID to impersonate for the operation\n      --kube-certificate-authority string         Path to a cert file for the certificate authority\n      --kube-client-certificate string            Path to a client certificate file for TLS\n      --kube-client-key string                    Path to a client key file for TLS\n      --kube-cluster string                       The name of the kubeconfig cluster to use\n      --kube-context string                       The name of the kubeconfig context to use\n      --kube-disable-compression                  If true, opt-out of response compression for all requests to the server\n      --kube-insecure-skip-tls-verify             If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure\n  -n, --kube-namespace string                     If present, the namespace scope for this CLI request\n      --kube-password string                      Password for basic authentication to the API server\n      --kube-proxy-url string                     If provided, this URL will be used to connect via proxy\n      --kube-request-timeout string               The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default \"0\")\n      --kube-server string                        The address and port of the Kubernetes API server\n      --kube-tls-server-name string               If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used.\n      --kube-token string                         Bearer token for authentication to the API server\n      --kube-user string                          The name of the kubeconfig user to use\n      --kube-username string                      Username for basic authentication to the API server\n      --namespace string                          Namespace to use for tests\n      --no-cluster                                Runs without cluster\n      --no-color                                  Removes output colors\n      --parallel int                              The maximum number of tests to run at once\n      --pause-on-failure                          Pause test execution failure (implies no concurrency)\n      --repeat-count int                          Number of times to repeat each test (default 1)\n      --report-format string                      Test report format (JSON|XML|nil)\n      --report-name string                        The name of the report to create (default \"chainsaw-report\")\n      --report-path string                        The path of the report to create\n      --selector strings                          Selector (label query) to filter on\n      --skip-delete                               If set, do not delete the resources after running the tests\n      --template                                  If set, resources will be considered for templating (default true)\n      --test-dir strings                          Directories containing test cases to run\n      --test-file string                          Name of the test file (default \"chainsaw-test\")\n      --values strings                            Values passed to the tests\n
"},{"location":"reference/commands/chainsaw_test/#see-also","title":"SEE ALSO","text":"
  • chainsaw - Stronger tool for e2e testing
"},{"location":"reference/commands/chainsaw_version/","title":"chainsaw version","text":""},{"location":"reference/commands/chainsaw_version/#chainsaw-version","title":"chainsaw version","text":"

Print the version informations

chainsaw version [flags]\n
"},{"location":"reference/commands/chainsaw_version/#options","title":"Options","text":"
  -h, --help   help for version\n
"},{"location":"reference/commands/chainsaw_version/#see-also","title":"SEE ALSO","text":"
  • chainsaw - Stronger tool for e2e testing
"},{"location":"reference/jp/functions/","title":"Functions","text":"

Experimental functions

Experimental functions are denoted by the x_ prefix.

These are functions that are subject to signature change in a future version.

"},{"location":"reference/jp/functions/#built-in-functions","title":"built-in functions","text":"Name Signature abs abs(number) avg avg(array[number]) ceil ceil(number) contains contains(array|string, any) ends_with ends_with(string, string) find_first find_first(string, string, number, number) find_last find_last(string, string, number, number) floor floor(number) from_items from_items(array[array]) group_by group_by(array, expref) items items(object) join join(string, array[string]) keys keys(object) length length(string|array|object) lower lower(string) map map(expref, array) max max(array[number]|array[string]) max_by max_by(array, expref) merge merge(object) min min(array[number]|array[string]) min_by min_by(array, expref) not_null not_null(any) pad_left pad_left(string, number, string) pad_right pad_right(string, number, string) replace replace(string, string, string, number) reverse reverse(array|string) sort sort(array[string]|array[number]) sort_by sort_by(array, expref) split split(string, string, number) starts_with starts_with(string, string) sum sum(array[number]) to_array to_array(any) to_number to_number(any) to_string to_string(any) trim trim(string, string) trim_left trim_left(string, string) trim_right trim_right(string, string) type type(any) upper upper(string) values values(object) zip zip(array, array)"},{"location":"reference/jp/functions/#kyverno-json-functions","title":"kyverno-json functions","text":"Name Signature at at(array, any) concat concat(string, string) json_parse json_parse(string) wildcard wildcard(string, string)"},{"location":"reference/jp/functions/#kyverno-functions","title":"kyverno functions","text":"Name Signature compare compare(string, string) equal_fold equal_fold(string, string) replace replace(string, string, string, number) replace_all replace_all(string, string, string) to_upper to_upper(string) to_lower to_lower(string) trim trim(string, string) trim_prefix trim_prefix(string, string) split split(string, string) regex_replace_all regex_replace_all(string, string|number, string|number) regex_replace_all_literal regex_replace_all_literal(string, string|number, string|number) regex_match regex_match(string, string|number) pattern_match pattern_match(string, string|number) label_match label_match(object, object) to_boolean to_boolean(string) add add(any, any) sum sum(array) subtract subtract(any, any) multiply multiply(any, any) divide divide(any, any) modulo modulo(any, any) round round(number, number) base64_decode base64_decode(string) base64_encode base64_encode(string) time_since time_since(string, string, string) time_now time_now() time_now_utc time_now_utc() path_canonicalize path_canonicalize(string) truncate truncate(string, number) semver_compare semver_compare(string, string) parse_json parse_json(string) parse_yaml parse_yaml(string) lookup lookup(object|array, string|number) items items(object|array, string, string) object_from_lists object_from_lists(array, array) random random(string) x509_decode x509_decode(string) time_to_cron time_to_cron(string) time_add time_add(string, string) time_parse time_parse(string, string) time_utc time_utc(string) time_diff time_diff(string, string) time_before time_before(string, string) time_after time_after(string, string) time_between time_between(string, string, string) time_truncate time_truncate(string, string)"},{"location":"reference/jp/functions/#chainsaw-functions","title":"chainsaw functions","text":"Name Signature env env(string) x_k8s_get x_k8s_get(any, string, string, string, string) x_k8s_list x_k8s_list(any, string, string, string) x_k8s_exists x_k8s_exists(any, string, string, string, string) x_k8s_resource_exists x_k8s_resource_exists(any, string, string) x_k8s_server_version x_k8s_server_version(any)"},{"location":"step/","title":"What is a test step?","text":"

A test step is made of four main components used to determine the actions Chainsaw will perform when executing the step.

  1. The try statement (required)
  2. The catch statement (optional)
  3. The finally statement (optional)
  4. The cleanup statement (optional)
"},{"location":"step/#syntax","title":"Syntax","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  steps:\n    # `try` defines operations to execute in the step\n  - try: [...]\n    # `catch` defines operations to execute when the step fails\n    catch: [...]\n    # `finally` defines operations to execute at the end of the step\n    finally: [...]\n    # `cleanup` defines operations to execute at the end of the test\n    cleanup: [...]\n
"},{"location":"step/#reference","title":"Reference","text":"

The full structure of the TestStepSpec is documented here.

"},{"location":"step/#lifecycle","title":"Lifecycle","text":""},{"location":"step/#try-catch-finally-flow","title":"Try, Catch, Finally flow","text":"

Operations defined in the try block are executed first, then:

  • If an operation fails to execute, Chainsaw won't execute the remaining operations and will execute all operations defined in the catch block instead (if any).
  • If all operations succeed, Chainsaw will NOT execute operations defined in the catch block (if any).
  • Regardless of the step outcome (success or failure), Chainsaw will execute all operations defined in the finally block (if any).

Tip

Note that all operations coming from the catch or finally blocks are executed. If one operation fails, Chainsaw will mark the test as failed and continue executing with the next operations.

"},{"location":"step/#without-failure","title":"Without failure","text":"
sequenceDiagram\n    autonumber\n\n    participant S as Step N\n\n    box Try block\n    participant T1 as Op 1\n    participant T2 as Op N\n    end\n    box Catch block\n    end\n    box Finally block\n    participant F1 as Op 1\n    participant F2 as Op N\n    end\n    participant S1 as Step N+1\n\n    S  -->> T1 : try\n    T1 ->>  T2 : success\n    T2 -->> S  : done\n    S  -->> F1 : finally\n    F1 ->>  F2 : done\n    F2 -->> S  : done\n    S  -->> S1 : next step
  1. Step starts by executing operations in the try block
  2. Operations in the try block execute sequentially
  3. All operations in the try block terminate
  4. Step starts executing operations in the finally block
  5. Operations in the finally block execute sequentially
  6. All operations in the finally block terminate
  7. Next step starts executing
"},{"location":"step/#with-failure","title":"With failure","text":"
sequenceDiagram\n    autonumber\n\n    participant S as Step N\n\n    box Try block\n    participant T1 as Op 1\n    participant T2 as Op N\n    end\n    box Catch block\n    participant C1 as Op 1\n    participant C2 as Op N\n    end\n    box Finally block\n    participant F1 as Op 1\n    participant F2 as Op N\n    end\n\n    S  -->> T1 : try\n    T1 ->>  T2 : success\n    T2 -->> S  : error\n    S  -->> C1 : catch\n    C1 ->>  C2 : done\n    C2 -->> S  : done\n    S  -->> F1 : finally\n    F1 ->>  F2 : done\n    F2 -->> S  : done
  1. Step starts by executing operations in the try block
  2. Operations in the try block execute sequentially until an error happens
  3. Operations in the try block stop when an error occurs
  4. Step starts executing operations in the catch block
  5. Operations in the catch block execute sequentially
  6. All operations in the catch block terminate
  7. Step starts executing operations in the finally block
  8. Operations in the finally block execute sequentially
  9. All operations in the finally block terminate
"},{"location":"step/#cleanup","title":"Cleanup","text":"

In addition to try, catch and finally blocks, the cleanup block of steps is better illustrated in the Test lifecycle diagrams.

"},{"location":"step/catch/","title":"catch","text":"

A catch statement is also a sequence of operations.

Operations contained in a catch statement will be executed only if the step failed when executing the operations in the step's try statement.

Tip

All operations of a catch statement will be executed regardless of the success or failure of each of them.

"},{"location":"step/catch/#operations","title":"Operations","text":"

A catch statement supports only the following operations:

  • Command
  • Delete
  • Describe
  • Events
  • Get
  • Pod logs
  • Script
  • Sleep
  • Wait
"},{"location":"step/catch/#inheritance","title":"Inheritance","text":"

Under certain circumstances, it can be useful to configure catch blocks at a higher level than the step grain. At the test or configuration level.

This allows for declaring common catch statements we want to execute when an error occurs. Those catch blocks are combined to produce the final catch block in the following order:

  1. catch statements from the configuration level are executed first (if any)
  2. catch statements from the test level are executed next (if any)
  3. catch statements from the step level are executed last (if any)
"},{"location":"step/cleanup/","title":"cleanup","text":"

A cleanup statement is similar to a finally statement but will execute after the test finishes executing, while finally executes after the step finishes executing.

Tip

All operations of a cleanup statement will be executed regardless of the success or failure of each of them.

"},{"location":"step/cleanup/#operations","title":"Operations","text":"

A cleanup statement supports only the following operations:

  • Command
  • Delete
  • Describe
  • Events
  • Get
  • Pod logs
  • Script
  • Sleep
  • Wait
"},{"location":"step/finally/","title":"finally","text":"

A finally statement is similar to a catch statement but will always execute after the try and eventual catch statements finished executing regardless of the success or failure of the test step.

Tip

All operations of a finally statement will be executed regardless of the success or failure of each of them.

"},{"location":"step/finally/#operations","title":"Operations","text":"

A finally statement supports only the following operations:

  • Command
  • Delete
  • Describe
  • Events
  • Get
  • Pod logs
  • Script
  • Sleep
  • Wait
"},{"location":"step/try/","title":"try","text":"

A try statement is a sequence of operations executed in the same order they are declared. If an operation fails the entire step is considered failed.

"},{"location":"step/try/#operations","title":"Operations","text":"

A try statement supports all operations:

  • Apply
  • Assert
  • Command
  • Create
  • Delete
  • Error
  • Patch
  • Script
  • Sleep
  • Update
  • Wait
"},{"location":"test/","title":"Writing Chainsaw tests","text":"

This documentation focuses on providing a breakdown of the Chainsaw test structure and how to use it.

"},{"location":"test/#what-is-a-test","title":"What is a test?","text":"

To put it simply, a test can be represented as an ordered sequence of test steps.

In turn, a test step can be represented as an ordered sequence of operations.

  • When an operation fails the test is considered failed
  • If all operations succeed the test is considered successful
"},{"location":"test/#definition-approach","title":"Definition approach","text":"

Chainsaw supports two different test definition approaches:

Tip

While Chainsaw supports two test definition approaches, we strongly recommend the explicit one.

  • The explicit approach (strongly recommended)
  • The conventional approach
"},{"location":"test/#general-concepts","title":"General concepts","text":"

The concepts below are at the heart of Chainsaw:

  • Inheritance
  • Test namespace
  • Bindings
  • Templating
  • Outputs
  • References
  • Operation checks
"},{"location":"test/#test-and-step-specs","title":"Test and Step specs","text":"

Browse the test and step specs to learn all the details and options:

  • Test spec
  • Test step spec
"},{"location":"test/conventional/","title":"Conventional approach","text":"

Warning

While Chainsaw supports the conventional approach, we strongly recommend the explicit one.

If you are new to Chainsaw we suggest you skip this section and jump directly to the Explicit approach.

"},{"location":"test/conventional/#introduction","title":"Introduction","text":"

The conventional approach is the simplest and less verbose one.

You provide bare Kubernetes resource manifests and Chainsaw will use those manifests to create, update, or assert expectations against a cluster.

"},{"location":"test/conventional/#limitations","title":"Limitations","text":"

While this syntax is simple, it suffers lots of limitations. It doesn't support deletion operations, commands, scripts, and all Chainsaw helpers.

It is also impossible to specify additional configuration per test, step or individual operation (timeouts, additional verifications, etc...), making this approach highly limited.

It also relies a lot on file naming conventions which can be error prone.

Finally, this approach doesn't encourage reusing files across tests and leads to duplication, making maintenance harder.

"},{"location":"test/conventional/#file-naming-convention","title":"File naming convention","text":"

Manifest files must follow a specific naming convention:

<step index>-<name|assert|errors>.yaml\n

As an example, 00-configmap.yaml, 01-assert.yaml and 02-errors.yaml are valid file names.

"},{"location":"test/conventional/#assembling-steps","title":"Assembling steps","text":"

It's perfectly valid to have multiple files for the same step.

Let's say we have the following files 00-resources.yaml, 00-more-resources.yaml, 00-assert.yaml and 00-errors.yaml:

  • 00-resources.yaml and 00-more-resources.yaml contain resources that will be applied in step 00
  • 00-assert.yaml contains assert statements in step 00
  • 00-errors.yaml contains error statements in step 00

With the four files above, Chainsaw will assemble a test step made of the combination of all those files.

"},{"location":"test/conventional/#loading-process","title":"Loading process","text":"

The logic to determine the content of a step is always:

  • The step index is obtained from the beginning of the file name, it must be composed of two numbers between 0 and 9 (from 00 to 99)
  • The next character acts as a separator and is expected to be -
  • The rest of the file name (without extension) is then evaluated
    • If it is equal to assert, the content is considered assertion statements
    • If it is equal to error, the content is considered error statements
    • Else the content is considered resources to be applied
  • The extension must be .yaml or .yml
"},{"location":"test/conventional/#example","title":"Example","text":""},{"location":"test/conventional/#01-configmapyaml","title":"01-configmap.yaml","text":"

The manifest below contains a config map in a file called 01-configmap.yaml. Chainsaw will associate this manifest with an apply operation in step 01.

apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: chainsaw-quick-start\ndata:\n  foo: bar\n
"},{"location":"test/conventional/#02-assertyaml","title":"02-assert.yaml","text":"

The manifest below contains an assertion statement in a file called 02-assert.yaml. Chainsaw will associate this manifest with an assert operation in step 02.

apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: chainsaw-quick-start\ndata:\n  foo: bar\n
"},{"location":"test/conventional/#03-errorsyaml","title":"03-errors.yaml","text":"

The manifest below contains an error statement in a file called 03-errors.yaml. Chainsaw will associate this manifest with an error operation in step 03.

apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: chainsaw-quick-start\ndata:\n  lorem: ipsum\n
"},{"location":"test/conventional/#conclusion","title":"Conclusion","text":"

This test will first create a config map, then assert the content of the config map contains the foo: bar data, and then verify that the config map does not contain the lorem: ipsum data.

For such a simple test, the conventional approach works reasonably well but will quickly become limited when the test scenarios get more complex.

Look at the explicit approach for a lot more flexible solution.

"},{"location":"test/explicit/","title":"Explicit approach","text":"

The explicit is a bit more verbose than the conventional one but offers far more flexibility and features:

  • It does not rely on file naming conventions for operations ordering
  • It encourages file reuse across tests, reducing duplication and maintenance
  • It offers the flexibility to provide additional configurations like timeouts, complex logic, etc...
  • It supports all operations without restrictions
"},{"location":"test/explicit/#the-test-resource","title":"The Test resource","text":"

A Test resource, like any other Kubernetes resource, has an apiVersion, kind and metadata section.

It also comes with a spec section used to declaratively represent the test logic, steps and operations, as well as other configuration elements belonging to the test being defined.

Reference documentation

The full structure of the Test resource is documented here.

"},{"location":"test/explicit/#example","title":"Example","text":""},{"location":"test/explicit/#chainsaw-testyaml","title":"chainsaw-test.yaml","text":"

The Test below illustrates a simple test. Chainsaw will load the Test and steps defined in its spec section.

It's worth noting that:

  • The test defines its own timeouts
  • It also states that this test should not be executed in parallel with other tests
  • It has multiple steps, most of them reference files that can be used in other tests if needed
  • It uses an arbitrary shell script
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  # state that this test should not be executed in parallel with other tests\n  concurrent: false\n  # timeouts for this specific test\n  timeouts:\n    apply: 10s\n    assert: 10s\n    error: 10s\n  steps:\n  # step 1\n  # apply a configmap to the cluster\n  # the path to the configmap is relative to the folder\n  # containing the test, hence allow reusing manifests\n  # across multiple tests\n  - try:\n    - apply:\n        file: ../resources/configmap.yaml\n  # step 2\n  # execute assert statements against existing resources\n  # in the cluster\n  - try:\n    - assert:\n        file: ../resources/configmap-assert.yaml\n  # step 3\n  # execute error statements against existing resources\n  # in the cluster\n  - try:\n    - error:\n        file: ../resources/configmap-error.yaml\n  # step 4\n  # execute an arbitrary shell script\n  - try:\n    - script:\n        content: echo \"goodbye\"\n
"},{"location":"test/explicit/#conclusion","title":"Conclusion","text":"

While this test is simple, it illustrates the differences with the conventional approach.

The purpose here is only to present the explicit approach and there are a lot more features to discuss, we will cover them in the next sections.

"},{"location":"test/spec/","title":"Test spec","text":"

A Chainsaw test is mostly made of steps.

That being said, there are a couple of other interesting fields too.

"},{"location":"test/spec/#syntax","title":"Syntax","text":"
apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: example\nspec:\n  # test configuration\n  concurrent: false\n  bindings:\n  - name: foo\n    value: bar\n  timeouts:\n    apply: 1s\n    assert: 2m\n    delete: 30s\n  ...\n  steps:\n  # step 1\n  - try: ...\n  # step 2\n  - try: ...\n    catch: ...\n  # step 3\n  - try: ...\n    catch: ...\n    finally: ...\n
"},{"location":"test/spec/#reference","title":"Reference","text":"

The full structure of the TestSpec is documented here.

"},{"location":"test/spec/#lifecycle","title":"Lifecycle","text":""},{"location":"test/spec/#cleanup","title":"Cleanup","text":"

At the end of the test, Chainsaw cleans up resources it created during the test, in the opposite order of creation.

By default, when a step fails, Chainsaw stops the execution and the remaining steps are not executed. The cleanup process starts at the moment the test stops executing.

Tip

Note that when a failure happens during cleanup, the test is marked as failed and Chainsaw continues executing cleanup for the remaining steps.

"},{"location":"test/spec/#without-failure","title":"Without failure","text":"
sequenceDiagram\n    autonumber\n    participant T as Test\n    participant S1 as Step 1\n    participant S2 as Step 2\n    participant S3 as Step 3\n\n    T  ->> S1: execute\n    S1 ->> S2: execute\n    S2 ->> S3: execute\n\n    S3 -->> S2: cleanup\n    S2 -->> S1: cleanup\n    S1 -->> T: cleanup
  1. Test starts by executing Step 1
  2. Step 1 terminates -> Step 2 starts executing
  3. Step 2 terminates -> Step 3 starts executing
  4. Step 3 terminates -> Cleanup for Step 3 starts
  5. Cleanup for Step 3 terminates -> Cleanup for Step 2 starts
  6. Cleanup for Step 2 terminates -> Cleanup for Step 1 is executed
"},{"location":"test/spec/#with-failure","title":"With failure","text":"
sequenceDiagram\n    autonumber\n    participant T as Test\n    participant S1 as Step 1\n    participant S2 as Step 2\n    participant S3 as Step 3\n\n    T  ->> S1: execute\n    S1 ->> S2: execute (fail)\n\n    Note left of S3: Step 3 is NOT executed\n\n    S2 -->> S1: cleanup\n    S1 -->> T: cleanup
  1. Test starts by executing Step 1
  2. Step 1 terminates -> Step 2 starts executing
  3. Step 2 fails -> Cleanup for Step 2 starts
  4. Cleanup for Step 2 terminates -> Cleanup for Step 1 is executed
"},{"location":"test/spec/#supported-elements","title":"Supported elements","text":""},{"location":"test/spec/#namespace","title":"Namespace","text":"

The namespace the test should run into, see Namespace selection.

"},{"location":"test/spec/#namespace-template","title":"Namespace template","text":"

Eventually provide a template if you something specific, see Namespace template.

"},{"location":"test/spec/#timeouts","title":"Timeouts","text":"

All timeouts can be specified per test, see Control your timeouts.

"},{"location":"test/spec/#clusters","title":"Clusters","text":"

Additional clusters can be registered at the test level, see Multi-cluster options.

"},{"location":"test/spec/#cluster","title":"Cluster","text":"

The cluster (by name) used to run the test, see Multi-cluster setup.

"},{"location":"test/spec/#bindings","title":"Bindings","text":"

Bindings can be registered at the test level and inherited in all steps, see Bindings.

"},{"location":"test/spec/#catch","title":"Catch","text":"

Catch blocks can be specified at the test level to declare common catch statements.

"},{"location":"test/spec/#template","title":"Template","text":"

Chainsaw allows templating configuration on a per test basis.

"},{"location":"test/spec/#concurrency","title":"Concurrency","text":"

Controlling concurrency per test is also possible, see Concurrency control.

"},{"location":"test/spec/#skip","title":"Skip","text":"

In case you need to skip a test for whatever reason, use skip: true.

"},{"location":"test/spec/#steps","title":"Steps","text":"

Steps are what tests will execute when they are run, see Test step spec dedicated section.

"}]} \ No newline at end of file diff --git a/0.2.3/sitemap.xml b/0.2.3/sitemap.xml new file mode 100644 index 000000000..f67b7f7d7 --- /dev/null +++ b/0.2.3/sitemap.xml @@ -0,0 +1,548 @@ + + + + https://kyverno.github.io/chainsaw/0.2.3/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/cicd/gh-action/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/community/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/configuration/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/configuration/file/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/configuration/flags/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/configuration/options/cleanup/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/configuration/options/clusters/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/configuration/options/deletion/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/configuration/options/discovery/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/configuration/options/error/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/configuration/options/execution/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/configuration/options/label-selectors/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/configuration/options/namespace/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/configuration/options/no-cluster/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/configuration/options/pause/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/configuration/options/report/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/configuration/options/templating/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/configuration/options/timeouts/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/configuration/options/values/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/examples/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/examples/concurrency/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/examples/crds/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/examples/events/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/examples/inline/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/examples/kube-version/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/examples/label-selectors/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/examples/multi-cluster/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/examples/negative-testing/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/examples/non-resource-assertions/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/examples/test-output/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/examples/values/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/general/bindings/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/general/checks/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/general/inheritance/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/general/namespace/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/general/outputs/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/general/references/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/general/templating/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/guides/kuttl-migration/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/guides/lint/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/guides/test-docs/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/operations/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/operations/apply/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/operations/assert/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/operations/command/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/operations/create/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/operations/delete/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/operations/error/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/operations/patch/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/operations/script/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/operations/sleep/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/operations/update/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/operations/helpers/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/operations/helpers/describe/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/operations/helpers/events/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/operations/helpers/get/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/operations/helpers/logs/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/operations/helpers/wait/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/quick-start/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/quick-start/assertion-trees/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/quick-start/bindings/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/quick-start/cleanup/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/quick-start/completion/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/quick-start/first-test/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/quick-start/install/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/quick-start/next-steps/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/quick-start/operation-outputs/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/quick-start/resource-templating/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/quick-start/resources/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/quick-start/run-tests/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/quick-start/timeouts/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/quick-start/try-catch/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/builtins/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/json-schemas/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/apis/chainsaw.v1alpha1/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/apis/chainsaw.v1alpha2/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/commands/chainsaw/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/commands/chainsaw_assert/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/commands/chainsaw_build/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/commands/chainsaw_build_docs/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/commands/chainsaw_completion/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/commands/chainsaw_completion_bash/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/commands/chainsaw_completion_fish/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/commands/chainsaw_completion_powershell/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/commands/chainsaw_completion_zsh/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/commands/chainsaw_create/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/commands/chainsaw_create_test/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/commands/chainsaw_docs/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/commands/chainsaw_export/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/commands/chainsaw_export_schemas/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/commands/chainsaw_lint/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/commands/chainsaw_migrate/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/commands/chainsaw_migrate_kuttl/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/commands/chainsaw_migrate_kuttl_config/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/commands/chainsaw_migrate_kuttl_tests/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/commands/chainsaw_test/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/commands/chainsaw_version/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/reference/jp/functions/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/step/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/step/catch/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/step/cleanup/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/step/finally/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/step/try/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/test/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/test/conventional/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/test/explicit/ + 2024-05-29 + daily + + + https://kyverno.github.io/chainsaw/0.2.3/test/spec/ + 2024-05-29 + daily + + \ No newline at end of file diff --git a/0.2.3/sitemap.xml.gz b/0.2.3/sitemap.xml.gz new file mode 100644 index 000000000..15432272d Binary files /dev/null and b/0.2.3/sitemap.xml.gz differ diff --git a/0.2.3/static/extra.css b/0.2.3/static/extra.css new file mode 100644 index 000000000..3d032e23e --- /dev/null +++ b/0.2.3/static/extra.css @@ -0,0 +1,8 @@ +body>header>nav>a>img { + border-radius: 10%; + border: 1px solid #555; +} + +td:nth-child(1) { + text-wrap: nowrap; +} \ No newline at end of file diff --git a/0.2.3/static/favicon.ico b/0.2.3/static/favicon.ico new file mode 100644 index 000000000..b86d20813 Binary files /dev/null and b/0.2.3/static/favicon.ico differ diff --git a/0.2.3/static/kyverno-chainsaw-horizontal.png b/0.2.3/static/kyverno-chainsaw-horizontal.png new file mode 100644 index 000000000..0c2c819c6 Binary files /dev/null and b/0.2.3/static/kyverno-chainsaw-horizontal.png differ diff --git a/0.2.3/static/kyverno-chainsaw-logo.png b/0.2.3/static/kyverno-chainsaw-logo.png new file mode 100644 index 000000000..5a8e6d6a6 Binary files /dev/null and b/0.2.3/static/kyverno-chainsaw-logo.png differ diff --git a/0.2.3/static/repo-card.js b/0.2.3/static/repo-card.js new file mode 100644 index 000000000..f40690f9d --- /dev/null +++ b/0.2.3/static/repo-card.js @@ -0,0 +1,106 @@ +['DOMContentLoaded'].forEach(function (e) { + window.addEventListener(e, async function () { + console.log(e) + async function get(url) { + const now = new Date().getTime(); + const prevResp = JSON.parse(localStorage.getItem(url)); + if (prevResp && Math.abs(now - prevResp.time) < 60000 * 60) { + return prevResp.data; + } + const resp = await fetch(url); + const json = await resp.json(); + localStorage.setItem(url, JSON.stringify({ time: now, data: json })); + return json; + } + + function setCard(el, data, theme) { + data.description = (data.description || '').replace(/:\w+:/g, function (match) { + const name = match.substring(1, match.length - 1); + const emoji = emojis[name]; + + if (emoji) { + return `${name}`; + } + + return match; + }); + + el.innerHTML = ` +
+
+ + + ${data.name} + +
+ +
${data.description}
+
+
+ + ${data.language} +
+
+ +   ${data.stargazers_count} +
+
+ +   ${data.forks} +
+
+
+ `; + } + + function promiseGet(url) { + return new Promise((resolve, reject) => { + const now = new Date().getTime(); + const prevResp = JSON.parse(localStorage.getItem(url)); + if (prevResp && Math.abs(now - prevResp.time) < 60000 * 30) { + resolve(prevResp.data); + } else { + localStorage.removeItem(url) + fetch(url).then(resp => { + json = resp.json(); + resolve(json); + }).catch(err => { + reject(err); + }) + } + }); + } + + const emojis = await get('https://api.github.com/emojis'); + const colors = await get('https://raw.githubusercontent.com/ozh/github-colors/master/colors.json'); + + const themes = { + 'light-default': { + background: 'white', + borderColor: '#e1e4e8', + color: '#586069', + linkColor: '#0366d6', + }, + 'dark-theme': { + background: 'rgb(13, 17, 23)', + borderColor: 'rgb(48, 54, 61)', + color: 'rgb(139, 148, 158)', + linkColor: 'rgb(88, 166, 255)', + } + }; + + for (const el of document.querySelectorAll('.repo-card')) { + const name = el.getAttribute('data-repo'); + const theme = themes[el.getAttribute('data-theme') || 'light-default']; + const url = `https://api.github.com/repos/${name}`; + promiseGet(url).then(data => { + const now = new Date().getTime(); + if (!JSON.parse(localStorage.getItem(url))) { + localStorage.setItem(url, JSON.stringify({ time: now, data: data })); + } + setCard(el, data, theme); + }); + } + }); +}); \ No newline at end of file diff --git a/0.2.3/step/catch/index.html b/0.2.3/step/catch/index.html new file mode 100644 index 000000000..3db5c60df --- /dev/null +++ b/0.2.3/step/catch/index.html @@ -0,0 +1 @@ + catch - Chainsaw

catch

A catch statement is also a sequence of operations.

Operations contained in a catch statement will be executed only if the step failed when executing the operations in the step's try statement.

Tip

All operations of a catch statement will be executed regardless of the success or failure of each of them.

Operations

A catch statement supports only the following operations:

Inheritance

Under certain circumstances, it can be useful to configure catch blocks at a higher level than the step grain. At the test or configuration level.

This allows for declaring common catch statements we want to execute when an error occurs. Those catch blocks are combined to produce the final catch block in the following order:

  1. catch statements from the configuration level are executed first (if any)
  2. catch statements from the test level are executed next (if any)
  3. catch statements from the step level are executed last (if any)
\ No newline at end of file diff --git a/0.2.3/step/cleanup/index.html b/0.2.3/step/cleanup/index.html new file mode 100644 index 000000000..3ec922da3 --- /dev/null +++ b/0.2.3/step/cleanup/index.html @@ -0,0 +1 @@ + cleanup - Chainsaw

cleanup

A cleanup statement is similar to a finally statement but will execute after the test finishes executing, while finally executes after the step finishes executing.

Tip

All operations of a cleanup statement will be executed regardless of the success or failure of each of them.

Operations

A cleanup statement supports only the following operations:

\ No newline at end of file diff --git a/0.2.3/step/finally/index.html b/0.2.3/step/finally/index.html new file mode 100644 index 000000000..fd5c5512a --- /dev/null +++ b/0.2.3/step/finally/index.html @@ -0,0 +1 @@ + finally - Chainsaw

finally

A finally statement is similar to a catch statement but will always execute after the try and eventual catch statements finished executing regardless of the success or failure of the test step.

Tip

All operations of a finally statement will be executed regardless of the success or failure of each of them.

Operations

A finally statement supports only the following operations:

\ No newline at end of file diff --git a/0.2.3/step/index.html b/0.2.3/step/index.html new file mode 100644 index 000000000..e3e8653a9 --- /dev/null +++ b/0.2.3/step/index.html @@ -0,0 +1,64 @@ + What is a test step? - Chainsaw

What is a test step?

A test step is made of four main components used to determine the actions Chainsaw will perform when executing the step.

  1. The try statement (required)
  2. The catch statement (optional)
  3. The finally statement (optional)
  4. The cleanup statement (optional)

Syntax

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  steps:
+    # `try` defines operations to execute in the step
+  - try: [...]
+    # `catch` defines operations to execute when the step fails
+    catch: [...]
+    # `finally` defines operations to execute at the end of the step
+    finally: [...]
+    # `cleanup` defines operations to execute at the end of the test
+    cleanup: [...]
+

Reference

The full structure of the TestStepSpec is documented here.

Lifecycle

Try, Catch, Finally flow

Operations defined in the try block are executed first, then:

  • If an operation fails to execute, Chainsaw won't execute the remaining operations and will execute all operations defined in the catch block instead (if any).
  • If all operations succeed, Chainsaw will NOT execute operations defined in the catch block (if any).
  • Regardless of the step outcome (success or failure), Chainsaw will execute all operations defined in the finally block (if any).

Tip

Note that all operations coming from the catch or finally blocks are executed. If one operation fails, Chainsaw will mark the test as failed and continue executing with the next operations.

Without failure

sequenceDiagram
+    autonumber
+
+    participant S as Step N
+
+    box Try block
+    participant T1 as Op 1
+    participant T2 as Op N
+    end
+    box Catch block
+    end
+    box Finally block
+    participant F1 as Op 1
+    participant F2 as Op N
+    end
+    participant S1 as Step N+1
+
+    S  -->> T1 : try
+    T1 ->>  T2 : success
+    T2 -->> S  : done
+    S  -->> F1 : finally
+    F1 ->>  F2 : done
+    F2 -->> S  : done
+    S  -->> S1 : next step
  1. Step starts by executing operations in the try block
  2. Operations in the try block execute sequentially
  3. All operations in the try block terminate
  4. Step starts executing operations in the finally block
  5. Operations in the finally block execute sequentially
  6. All operations in the finally block terminate
  7. Next step starts executing

With failure

sequenceDiagram
+    autonumber
+
+    participant S as Step N
+
+    box Try block
+    participant T1 as Op 1
+    participant T2 as Op N
+    end
+    box Catch block
+    participant C1 as Op 1
+    participant C2 as Op N
+    end
+    box Finally block
+    participant F1 as Op 1
+    participant F2 as Op N
+    end
+
+    S  -->> T1 : try
+    T1 ->>  T2 : success
+    T2 -->> S  : error
+    S  -->> C1 : catch
+    C1 ->>  C2 : done
+    C2 -->> S  : done
+    S  -->> F1 : finally
+    F1 ->>  F2 : done
+    F2 -->> S  : done
  1. Step starts by executing operations in the try block
  2. Operations in the try block execute sequentially until an error happens
  3. Operations in the try block stop when an error occurs
  4. Step starts executing operations in the catch block
  5. Operations in the catch block execute sequentially
  6. All operations in the catch block terminate
  7. Step starts executing operations in the finally block
  8. Operations in the finally block execute sequentially
  9. All operations in the finally block terminate

Cleanup

In addition to try, catch and finally blocks, the cleanup block of steps is better illustrated in the Test lifecycle diagrams.

\ No newline at end of file diff --git a/0.2.3/step/try/index.html b/0.2.3/step/try/index.html new file mode 100644 index 000000000..ed2f9260b --- /dev/null +++ b/0.2.3/step/try/index.html @@ -0,0 +1 @@ + try - Chainsaw
\ No newline at end of file diff --git a/0.2.3/test/conventional/index.html b/0.2.3/test/conventional/index.html new file mode 100644 index 000000000..9c8ea8a71 --- /dev/null +++ b/0.2.3/test/conventional/index.html @@ -0,0 +1,20 @@ + Conventional approach - Chainsaw

Conventional approach

Warning

While Chainsaw supports the conventional approach, we strongly recommend the explicit one.

If you are new to Chainsaw we suggest you skip this section and jump directly to the Explicit approach.

Introduction

The conventional approach is the simplest and less verbose one.

You provide bare Kubernetes resource manifests and Chainsaw will use those manifests to create, update, or assert expectations against a cluster.

Limitations

While this syntax is simple, it suffers lots of limitations. It doesn't support deletion operations, commands, scripts, and all Chainsaw helpers.

It is also impossible to specify additional configuration per test, step or individual operation (timeouts, additional verifications, etc...), making this approach highly limited.

It also relies a lot on file naming conventions which can be error prone.

Finally, this approach doesn't encourage reusing files across tests and leads to duplication, making maintenance harder.

File naming convention

Manifest files must follow a specific naming convention:

<step index>-<name|assert|errors>.yaml
+

As an example, 00-configmap.yaml, 01-assert.yaml and 02-errors.yaml are valid file names.

Assembling steps

It's perfectly valid to have multiple files for the same step.

Let's say we have the following files 00-resources.yaml, 00-more-resources.yaml, 00-assert.yaml and 00-errors.yaml:

  • 00-resources.yaml and 00-more-resources.yaml contain resources that will be applied in step 00
  • 00-assert.yaml contains assert statements in step 00
  • 00-errors.yaml contains error statements in step 00

With the four files above, Chainsaw will assemble a test step made of the combination of all those files.

Loading process

The logic to determine the content of a step is always:

  • The step index is obtained from the beginning of the file name, it must be composed of two numbers between 0 and 9 (from 00 to 99)
  • The next character acts as a separator and is expected to be -
  • The rest of the file name (without extension) is then evaluated
    • If it is equal to assert, the content is considered assertion statements
    • If it is equal to error, the content is considered error statements
    • Else the content is considered resources to be applied
  • The extension must be .yaml or .yml

Example

01-configmap.yaml

The manifest below contains a config map in a file called 01-configmap.yaml. Chainsaw will associate this manifest with an apply operation in step 01.

apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: chainsaw-quick-start
+data:
+  foo: bar
+

02-assert.yaml

The manifest below contains an assertion statement in a file called 02-assert.yaml. Chainsaw will associate this manifest with an assert operation in step 02.

apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: chainsaw-quick-start
+data:
+  foo: bar
+

03-errors.yaml

The manifest below contains an error statement in a file called 03-errors.yaml. Chainsaw will associate this manifest with an error operation in step 03.

apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: chainsaw-quick-start
+data:
+  lorem: ipsum
+

Conclusion

This test will first create a config map, then assert the content of the config map contains the foo: bar data, and then verify that the config map does not contain the lorem: ipsum data.

For such a simple test, the conventional approach works reasonably well but will quickly become limited when the test scenarios get more complex.

Look at the explicit approach for a lot more flexible solution.

\ No newline at end of file diff --git a/0.2.3/test/explicit/index.html b/0.2.3/test/explicit/index.html new file mode 100644 index 000000000..57a19fefe --- /dev/null +++ b/0.2.3/test/explicit/index.html @@ -0,0 +1,39 @@ + Explicit approach - Chainsaw

Explicit approach

The explicit is a bit more verbose than the conventional one but offers far more flexibility and features:

  • It does not rely on file naming conventions for operations ordering
  • It encourages file reuse across tests, reducing duplication and maintenance
  • It offers the flexibility to provide additional configurations like timeouts, complex logic, etc...
  • It supports all operations without restrictions

The Test resource

A Test resource, like any other Kubernetes resource, has an apiVersion, kind and metadata section.

It also comes with a spec section used to declaratively represent the test logic, steps and operations, as well as other configuration elements belonging to the test being defined.

Reference documentation

The full structure of the Test resource is documented here.

Example

chainsaw-test.yaml

The Test below illustrates a simple test. Chainsaw will load the Test and steps defined in its spec section.

It's worth noting that:

  • The test defines its own timeouts
  • It also states that this test should not be executed in parallel with other tests
  • It has multiple steps, most of them reference files that can be used in other tests if needed
  • It uses an arbitrary shell script
apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  # state that this test should not be executed in parallel with other tests
+  concurrent: false
+  # timeouts for this specific test
+  timeouts:
+    apply: 10s
+    assert: 10s
+    error: 10s
+  steps:
+  # step 1
+  # apply a configmap to the cluster
+  # the path to the configmap is relative to the folder
+  # containing the test, hence allow reusing manifests
+  # across multiple tests
+  - try:
+    - apply:
+        file: ../resources/configmap.yaml
+  # step 2
+  # execute assert statements against existing resources
+  # in the cluster
+  - try:
+    - assert:
+        file: ../resources/configmap-assert.yaml
+  # step 3
+  # execute error statements against existing resources
+  # in the cluster
+  - try:
+    - error:
+        file: ../resources/configmap-error.yaml
+  # step 4
+  # execute an arbitrary shell script
+  - try:
+    - script:
+        content: echo "goodbye"
+

Conclusion

While this test is simple, it illustrates the differences with the conventional approach.

The purpose here is only to present the explicit approach and there are a lot more features to discuss, we will cover them in the next sections.

\ No newline at end of file diff --git a/0.2.3/test/index.html b/0.2.3/test/index.html new file mode 100644 index 000000000..d3f844190 --- /dev/null +++ b/0.2.3/test/index.html @@ -0,0 +1 @@ + Writing Chainsaw tests - Chainsaw

Writing Chainsaw tests

This documentation focuses on providing a breakdown of the Chainsaw test structure and how to use it.

What is a test?

To put it simply, a test can be represented as an ordered sequence of test steps.

In turn, a test step can be represented as an ordered sequence of operations.

  • When an operation fails the test is considered failed
  • If all operations succeed the test is considered successful

Definition approach

Chainsaw supports two different test definition approaches:

Tip

While Chainsaw supports two test definition approaches, we strongly recommend the explicit one.

General concepts

The concepts below are at the heart of Chainsaw:

Test and Step specs

Browse the test and step specs to learn all the details and options:

\ No newline at end of file diff --git a/0.2.3/test/spec/index.html b/0.2.3/test/spec/index.html new file mode 100644 index 000000000..aa42cbea9 --- /dev/null +++ b/0.2.3/test/spec/index.html @@ -0,0 +1,52 @@ + Test spec - Chainsaw

Test spec

A Chainsaw test is mostly made of steps.

That being said, there are a couple of other interesting fields too.

Syntax

apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  name: example
+spec:
+  # test configuration
+  concurrent: false
+  bindings:
+  - name: foo
+    value: bar
+  timeouts:
+    apply: 1s
+    assert: 2m
+    delete: 30s
+  ...
+  steps:
+  # step 1
+  - try: ...
+  # step 2
+  - try: ...
+    catch: ...
+  # step 3
+  - try: ...
+    catch: ...
+    finally: ...
+

Reference

The full structure of the TestSpec is documented here.

Lifecycle

Cleanup

At the end of the test, Chainsaw cleans up resources it created during the test, in the opposite order of creation.

By default, when a step fails, Chainsaw stops the execution and the remaining steps are not executed. The cleanup process starts at the moment the test stops executing.

Tip

Note that when a failure happens during cleanup, the test is marked as failed and Chainsaw continues executing cleanup for the remaining steps.

Without failure

sequenceDiagram
+    autonumber
+    participant T as Test
+    participant S1 as Step 1
+    participant S2 as Step 2
+    participant S3 as Step 3
+
+    T  ->> S1: execute
+    S1 ->> S2: execute
+    S2 ->> S3: execute
+
+    S3 -->> S2: cleanup
+    S2 -->> S1: cleanup
+    S1 -->> T: cleanup
  1. Test starts by executing Step 1
  2. Step 1 terminates -> Step 2 starts executing
  3. Step 2 terminates -> Step 3 starts executing
  4. Step 3 terminates -> Cleanup for Step 3 starts
  5. Cleanup for Step 3 terminates -> Cleanup for Step 2 starts
  6. Cleanup for Step 2 terminates -> Cleanup for Step 1 is executed

With failure

sequenceDiagram
+    autonumber
+    participant T as Test
+    participant S1 as Step 1
+    participant S2 as Step 2
+    participant S3 as Step 3
+
+    T  ->> S1: execute
+    S1 ->> S2: execute (fail)
+
+    Note left of S3: Step 3 is NOT executed
+
+    S2 -->> S1: cleanup
+    S1 -->> T: cleanup
  1. Test starts by executing Step 1
  2. Step 1 terminates -> Step 2 starts executing
  3. Step 2 fails -> Cleanup for Step 2 starts
  4. Cleanup for Step 2 terminates -> Cleanup for Step 1 is executed

Supported elements

Namespace

The namespace the test should run into, see Namespace selection.

Namespace template

Eventually provide a template if you something specific, see Namespace template.

Timeouts

All timeouts can be specified per test, see Control your timeouts.

Clusters

Additional clusters can be registered at the test level, see Multi-cluster options.

Cluster

The cluster (by name) used to run the test, see Multi-cluster setup.

Bindings

Bindings can be registered at the test level and inherited in all steps, see Bindings.

Catch

Catch blocks can be specified at the test level to declare common catch statements.

Template

Chainsaw allows templating configuration on a per test basis.

Concurrency

Controlling concurrency per test is also possible, see Concurrency control.

Skip

In case you need to skip a test for whatever reason, use skip: true.

Steps

Steps are what tests will execute when they are run, see Test step spec dedicated section.

\ No newline at end of file diff --git a/latest b/latest index f4778493c..373f8c6f0 120000 --- a/latest +++ b/latest @@ -1 +1 @@ -0.2.2 \ No newline at end of file +0.2.3 \ No newline at end of file diff --git a/versions.json b/versions.json index f2bfd655d..5b2c83e65 100644 --- a/versions.json +++ b/versions.json @@ -7,12 +7,17 @@ ] }, { - "version": "0.2.2", - "title": "0.2.2", + "version": "0.2.3", + "title": "0.2.3", "aliases": [ "latest" ] }, + { + "version": "0.2.2", + "title": "0.2.2", + "aliases": [] + }, { "version": "0.2.1", "title": "0.2.1",