From b5bc3686e0ec1b357cae95cff63ce591f2b1263a Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Fri, 30 Dec 2022 19:28:21 +0100 Subject: [PATCH 01/41] feat: New validation hook to check if scale target is already managed Signed-off-by: Jorge Turrado --- .devcontainer/Dockerfile | 13 +- .gitignore | 3 +- BUILD.md | 73 +- CHANGELOG.md | 1 + Dockerfile | 3 +- Dockerfile.adapter | 3 +- Dockerfile.webhooks | 37 + Makefile | 25 +- PROJECT | 11 +- apis/keda/v1alpha1/scaledobject_types.go | 6 +- apis/keda/v1alpha1/scaledobject_webhook.go | 192 ++++ .../v1alpha1/scaledobject_webhook_test.go | 331 ++++++ apis/keda/v1alpha1/withtriggers_types.go | 2 +- {adapter => cmd/adapter}/main.go | 2 +- main.go => cmd/operator/main.go | 2 +- cmd/webhooks/main.go | 180 ++++ config/default/kustomization.yaml | 1 + config/manager/manager.yaml | 1 + config/metrics-server/deployment.yaml | 1 + config/rbac/role.yaml | 30 + config/rbac/role_binding.yaml | 14 + config/webhooks/kustomization.yaml | 12 + config/webhooks/secret.yaml | 11 + config/webhooks/service.yaml | 20 + config/webhooks/validation_webhooks.yaml | 36 + config/webhooks/webhooks.yaml | 88 ++ controllers/keda/hpa.go | 28 +- controllers/keda/scaledobject_controller.go | 44 +- go.mod | 8 +- go.sum | 61 +- hack/boilerplate.go.txt | 2 +- .../clientset/versioned/clientset.go | 2 +- pkg/generated/clientset/versioned/doc.go | 2 +- .../versioned/fake/clientset_generated.go | 2 +- pkg/generated/clientset/versioned/fake/doc.go | 2 +- .../clientset/versioned/fake/register.go | 2 +- .../clientset/versioned/scheme/doc.go | 2 +- .../clientset/versioned/scheme/register.go | 2 +- .../v1alpha1/clustertriggerauthentication.go | 2 +- .../versioned/typed/keda/v1alpha1/doc.go | 2 +- .../versioned/typed/keda/v1alpha1/fake/doc.go | 2 +- .../fake/fake_clustertriggerauthentication.go | 2 +- .../keda/v1alpha1/fake/fake_keda_client.go | 2 +- .../keda/v1alpha1/fake/fake_scaledjob.go | 2 +- .../keda/v1alpha1/fake/fake_scaledobject.go | 2 +- .../fake/fake_triggerauthentication.go | 2 +- .../keda/v1alpha1/generated_expansion.go | 2 +- .../typed/keda/v1alpha1/keda_client.go | 2 +- .../typed/keda/v1alpha1/scaledjob.go | 2 +- .../typed/keda/v1alpha1/scaledobject.go | 2 +- .../keda/v1alpha1/triggerauthentication.go | 2 +- .../informers/externalversions/factory.go | 2 +- .../informers/externalversions/generic.go | 2 +- .../internalinterfaces/factory_interfaces.go | 2 +- .../externalversions/keda/interface.go | 2 +- .../v1alpha1/clustertriggerauthentication.go | 2 +- .../keda/v1alpha1/interface.go | 2 +- .../keda/v1alpha1/scaledjob.go | 2 +- .../keda/v1alpha1/scaledobject.go | 2 +- .../keda/v1alpha1/triggerauthentication.go | 2 +- .../v1alpha1/clustertriggerauthentication.go | 2 +- .../keda/v1alpha1/expansion_generated.go | 2 +- .../listers/keda/v1alpha1/scaledjob.go | 2 +- .../listers/keda/v1alpha1/scaledobject.go | 2 +- .../keda/v1alpha1/triggerauthentication.go | 2 +- pkg/provider/provider.go | 8 +- pkg/scaling/resolver/scale_resolvers.go | 2 +- pkg/util/env_resolver.go | 11 +- .../scaled_object_validation_test.go | 187 ++++ vendor/github.com/go-logr/logr/funcr/funcr.go | 787 ++++++++++++++ vendor/github.com/onsi/ginkgo/v2/.gitignore | 7 + vendor/github.com/onsi/ginkgo/v2/CHANGELOG.md | 593 +++++++++++ .../github.com/onsi/ginkgo/v2/CONTRIBUTING.md | 13 + vendor/github.com/onsi/ginkgo/v2/LICENSE | 20 + vendor/github.com/onsi/ginkgo/v2/README.md | 115 +++ vendor/github.com/onsi/ginkgo/v2/RELEASING.md | 23 + .../onsi/ginkgo/v2/config/deprecated.go | 69 ++ vendor/github.com/onsi/ginkgo/v2/core_dsl.go | 741 ++++++++++++++ .../onsi/ginkgo/v2/decorator_dsl.go | 133 +++ .../onsi/ginkgo/v2/deprecated_dsl.go | 135 +++ .../ginkgo/v2/formatter/colorable_others.go | 41 + .../ginkgo/v2/formatter/colorable_windows.go | 809 +++++++++++++++ .../onsi/ginkgo/v2/formatter/formatter.go | 195 ++++ .../github.com/onsi/ginkgo/v2/ginkgo_t_dsl.go | 45 + .../onsi/ginkgo/v2/internal/counter.go | 9 + .../onsi/ginkgo/v2/internal/failer.go | 99 ++ .../onsi/ginkgo/v2/internal/focus.go | 125 +++ .../onsi/ginkgo/v2/internal/global/init.go | 17 + .../onsi/ginkgo/v2/internal/group.go | 363 +++++++ .../interrupt_handler/interrupt_handler.go | 162 +++ .../sigquit_swallower_unix.go | 15 + .../sigquit_swallower_windows.go | 8 + .../onsi/ginkgo/v2/internal/node.go | 895 ++++++++++++++++ .../onsi/ginkgo/v2/internal/ordering.go | 121 +++ .../ginkgo/v2/internal/output_interceptor.go | 250 +++++ .../v2/internal/output_interceptor_unix.go | 62 ++ .../v2/internal/output_interceptor_win.go | 7 + .../parallel_support/client_server.go | 70 ++ .../internal/parallel_support/http_client.go | 156 +++ .../internal/parallel_support/http_server.go | 223 ++++ .../internal/parallel_support/rpc_client.go | 123 +++ .../internal/parallel_support/rpc_server.go | 75 ++ .../parallel_support/server_handler.go | 209 ++++ .../ginkgo/v2/internal/progress_report.go | 288 ++++++ .../ginkgo/v2/internal/progress_report_bsd.go | 11 + .../v2/internal/progress_report_unix.go | 11 + .../ginkgo/v2/internal/progress_report_win.go | 8 + .../onsi/ginkgo/v2/internal/report_entry.go | 39 + .../onsi/ginkgo/v2/internal/spec.go | 87 ++ .../onsi/ginkgo/v2/internal/spec_context.go | 90 ++ .../onsi/ginkgo/v2/internal/suite.go | 966 ++++++++++++++++++ .../internal/testingtproxy/testing_t_proxy.go | 128 +++ .../onsi/ginkgo/v2/internal/tree.go | 77 ++ .../onsi/ginkgo/v2/internal/writer.go | 140 +++ .../ginkgo/v2/reporters/default_reporter.go | 750 ++++++++++++++ .../v2/reporters/deprecated_reporter.go | 149 +++ .../onsi/ginkgo/v2/reporters/json_report.go | 60 ++ .../onsi/ginkgo/v2/reporters/junit_report.go | 349 +++++++ .../onsi/ginkgo/v2/reporters/reporter.go | 29 + .../ginkgo/v2/reporters/teamcity_report.go | 101 ++ .../onsi/ginkgo/v2/reporting_dsl.go | 162 +++ vendor/github.com/onsi/ginkgo/v2/table_dsl.go | 302 ++++++ .../onsi/ginkgo/v2/types/code_location.go | 92 ++ .../github.com/onsi/ginkgo/v2/types/config.go | 739 ++++++++++++++ .../onsi/ginkgo/v2/types/deprecated_types.go | 141 +++ .../ginkgo/v2/types/deprecation_support.go | 177 ++++ .../onsi/ginkgo/v2/types/enum_support.go | 43 + .../github.com/onsi/ginkgo/v2/types/errors.go | 621 +++++++++++ .../onsi/ginkgo/v2/types/file_filter.go | 106 ++ .../github.com/onsi/ginkgo/v2/types/flags.go | 489 +++++++++ .../onsi/ginkgo/v2/types/label_filter.go | 347 +++++++ .../onsi/ginkgo/v2/types/report_entry.go | 190 ++++ .../github.com/onsi/ginkgo/v2/types/types.go | 910 +++++++++++++++++ .../onsi/ginkgo/v2/types/version.go | 3 + .../open-policy-agent/cert-controller/LICENSE | 201 ++++ .../open-policy-agent/cert-controller/NOTICE | 3 + .../cert-controller/pkg/rotator/rotator.go | 779 ++++++++++++++ vendor/golang.org/x/mod/modfile/read.go | 2 +- vendor/golang.org/x/mod/modfile/rule.go | 3 + vendor/golang.org/x/mod/module/module.go | 4 +- .../x/tools/go/gcexportdata/gcexportdata.go | 6 +- .../x/tools/go/internal/gcimporter/iimport.go | 4 +- .../go/internal/gcimporter/ureader_yes.go | 93 +- .../x/tools/go/internal/pkgbits/decoder.go | 5 +- .../x/tools/go/internal/pkgbits/encoder.go | 18 +- .../x/tools/go/internal/pkgbits/reloc.go | 4 +- .../golang.org/x/tools/go/packages/golist.go | 9 +- .../x/tools/go/packages/packages.go | 42 +- .../internal/fastwalk/fastwalk_darwin.go | 119 +++ .../internal/fastwalk/fastwalk_dirent_ino.go | 6 +- .../fastwalk/fastwalk_dirent_namlen_bsd.go | 4 +- .../tools/internal/fastwalk/fastwalk_unix.go | 4 +- .../x/tools/internal/gocommand/invoke.go | 83 +- .../x/tools/internal/gocommand/version.go | 13 +- .../x/tools/internal/imports/fix.go | 9 +- .../x/tools/internal/imports/mod.go | 22 +- .../x/tools/internal/imports/zstdlib.go | 418 +++++--- vendor/modules.txt | 22 +- version/version.go | 2 +- 159 files changed, 17391 insertions(+), 321 deletions(-) create mode 100644 Dockerfile.webhooks create mode 100644 apis/keda/v1alpha1/scaledobject_webhook.go create mode 100644 apis/keda/v1alpha1/scaledobject_webhook_test.go rename {adapter => cmd/adapter}/main.go (99%) rename main.go => cmd/operator/main.go (99%) create mode 100644 cmd/webhooks/main.go create mode 100644 config/webhooks/kustomization.yaml create mode 100644 config/webhooks/secret.yaml create mode 100644 config/webhooks/service.yaml create mode 100644 config/webhooks/validation_webhooks.yaml create mode 100644 config/webhooks/webhooks.yaml create mode 100644 tests/internals/scaled_object_validation/scaled_object_validation_test.go create mode 100644 vendor/github.com/go-logr/logr/funcr/funcr.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/.gitignore create mode 100644 vendor/github.com/onsi/ginkgo/v2/CHANGELOG.md create mode 100644 vendor/github.com/onsi/ginkgo/v2/CONTRIBUTING.md create mode 100644 vendor/github.com/onsi/ginkgo/v2/LICENSE create mode 100644 vendor/github.com/onsi/ginkgo/v2/README.md create mode 100644 vendor/github.com/onsi/ginkgo/v2/RELEASING.md create mode 100644 vendor/github.com/onsi/ginkgo/v2/config/deprecated.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/core_dsl.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/decorator_dsl.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/deprecated_dsl.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/formatter/colorable_others.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/formatter/colorable_windows.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/formatter/formatter.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/ginkgo_t_dsl.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/counter.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/failer.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/focus.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/global/init.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/group.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/interrupt_handler/interrupt_handler.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/interrupt_handler/sigquit_swallower_unix.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/interrupt_handler/sigquit_swallower_windows.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/node.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/ordering.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_unix.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_win.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/client_server.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/http_client.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/http_server.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/rpc_client.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/rpc_server.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/server_handler.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/progress_report.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/progress_report_bsd.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/progress_report_unix.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/progress_report_win.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/report_entry.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/spec.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/spec_context.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/suite.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/testingtproxy/testing_t_proxy.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/tree.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/internal/writer.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/reporters/default_reporter.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/reporters/deprecated_reporter.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/reporters/json_report.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/reporters/junit_report.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/reporters/reporter.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/reporters/teamcity_report.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/reporting_dsl.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/table_dsl.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/types/code_location.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/types/config.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/types/deprecated_types.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/types/deprecation_support.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/types/enum_support.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/types/errors.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/types/file_filter.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/types/flags.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/types/label_filter.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/types/report_entry.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/types/types.go create mode 100644 vendor/github.com/onsi/ginkgo/v2/types/version.go create mode 100644 vendor/github.com/open-policy-agent/cert-controller/LICENSE create mode 100644 vendor/github.com/open-policy-agent/cert-controller/NOTICE create mode 100644 vendor/github.com/open-policy-agent/cert-controller/pkg/rotator/rotator.go create mode 100644 vendor/golang.org/x/tools/internal/fastwalk/fastwalk_darwin.go diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 5427231cafe..ee8ffe3f4f2 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -91,9 +91,12 @@ RUN apt-get update \ # Enable go modules ENV GO111MODULE=on -ENV OPERATOR_RELEASE_VERSION=v1.0.1 -RUN curl -LO https://github.com/operator-framework/operator-sdk/releases/download/${OPERATOR_RELEASE_VERSION}/operator-sdk-${OPERATOR_RELEASE_VERSION}-x86_64-linux-gnu \ - && chmod +x operator-sdk-${OPERATOR_RELEASE_VERSION}-x86_64-linux-gnu \ +ENV OPERATOR_RELEASE_VERSION=v1.26.0 +RUN ARCH=$(case $(uname -m) in x86_64) echo -n amd64 ;; aarch64) echo -n arm64 ;; *) echo -n $(uname -m) ;; esac) \ + && OS=$(uname | awk '{print tolower($0)}') \ + && OPERATOR_SDK_DL_URL=https://github.com/operator-framework/operator-sdk/releases/download/${OPERATOR_RELEASE_VERSION} \ + && curl -LO ${OPERATOR_SDK_DL_URL}/operator-sdk_${OS}_${ARCH} \ + && chmod +x operator-sdk_${OS}_${ARCH} \ && mkdir -p /usr/local/bin/ \ - && cp operator-sdk-${OPERATOR_RELEASE_VERSION}-x86_64-linux-gnu /usr/local/bin/operator-sdk \ - && rm operator-sdk-${OPERATOR_RELEASE_VERSION}-x86_64-linux-gnu + && cp operator-sdk_${OS}_${ARCH} /usr/local/bin/operator-sdk \ + && rm operator-sdk_${OS}_${ARCH} diff --git a/.gitignore b/.gitignore index b68788386a5..51f73f9d700 100644 --- a/.gitignore +++ b/.gitignore @@ -39,8 +39,9 @@ apiserver.local.config/ cover.out -# GO debug binary +# GO debug binaries cmd/manager/debug.test +__debug_bin # GO Test result report.xml diff --git a/BUILD.md b/BUILD.md index 01c53268925..8abbe18ae41 100644 --- a/BUILD.md +++ b/BUILD.md @@ -137,7 +137,7 @@ Follow these instructions if you want to debug the KEDA operator using VS Code. "type": "go", "request": "launch", "mode": "debug", - "program": "${workspaceFolder}/main.go", + "program": "${workspaceFolder}/cmd/operator/main.go", "env": { "WATCH_NAMESPACE": "", "KEDA_CLUSTER_OBJECT_NAMESPACE": "keda" @@ -173,7 +173,7 @@ Follow these instructions if you want to debug the KEDA metrics server using VS "type": "go", "request": "launch", "mode": "auto", - "program": "${workspaceFolder}/adapter/main.go", + "program": "${workspaceFolder}/cmd/adapter/main.go", "env": { "WATCH_NAMESPACE": "", "KEDA_CLUSTER_OBJECT_NAMESPACE": "keda" @@ -223,6 +223,64 @@ You can query list metrics executing `curl --insecure https://localhost:6443/api If you prefer to use an authenticated user, you can use a user or service account with access over external metrics API adding their token as authorization header in `curl`, ie: `curl -H "Authorization:Bearer TOKEN" --insecure https://localhost:6443/apis/external.metrics.k8s.io/v1beta1/` +### Webhooks + +Follow these instructions if you want to debug the KEDA webhook using VS Code. + +1. Create a `launch.json` file inside the `.vscode/` folder in the repo with the following configuration: + ```json + { + "configurations": [ + { + "name": "Launch webhooks", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/cmd/webhooks/main.go", + "env": { + "WATCH_NAMESPACE": "", + "KEDA_CLUSTER_OBJECT_NAMESPACE": "keda" + }, + "args": [ + "--zap-log-level=debug", + "--zap-encoder=console", + "--zap-time-encoding=rfc3339" + ] + }, + ] + } + ``` + Refer to [this](https://code.visualstudio.com/docs/editor/debugging) for more information about debugging with VS Code. +2. Expose your local instance to internet. If you can't expose it directly, you can use something like [localtunnel](https://theboroer.github.io/localtunnel-www/) using the command `lt --port 9443 --local-https --allow-invalid-cert` after installing the tool. + +3. Update the `validation_webhooks.yaml` in `config/webhooks`, replacing the section (but not commiting this change) + ```yaml + webhooks: + - admissionReviewVersions: + - v1 + clientConfig: + service: + name: keda-webhook-service + namespace: keda + path: /validate-keda-sh-v1alpha1-scaledobject + ``` + with the section: + ```yaml + webhooks: + - admissionReviewVersions: + - v1 + clientConfig: + url: "https://${YOUR_URL}/validate-keda-sh-v1alpha1-scaledobject" + ``` + **Note:** You could need to define also the key `caBundle` with the CA bundle encoded in base64 if the cluster can get it during the manifest apply (this happens with localtunnel for instance) + +4. Deploy CRDs and KEDA into `keda` namespace + ```bash + make deploy + ``` +5. Set breakpoints in the code as required. +6. Select `Run > Start Debugging` or press `F5` to start debugging. + ## Miscellaneous ### How to use devcontainers and a local Kubernetes cluster @@ -236,24 +294,21 @@ To solve this and be able to work with devcontainers and a local cluster, you sh You can change default log levels for both KEDA Operator and Metrics Server. KEDA Operator uses [Operator SDK logging](https://sdk.operatorframework.io/docs/building-operators/golang/references/logging/) mechanism. -### KEDA Operator logging +### KEDA Operator and webhooks logging -To change the logging level, find `--zap-log-level=` argument in Operator Deployment section in `config/manager/manager.yaml` file, - modify its value and redeploy. +To change the logging level, find `--zap-log-level=` argument in Operator Deployment section in `config/manager/manager.yaml` file or in Webhooks Deployment section in `config/webhooks/webhooks.yaml` file, modify its value and redeploy. Allowed values are `debug`, `info`, `error`, or an integer value greater than `0`, specified as string Default value: `info` -To change the logging format, find `--zap-encoder=` argument in Operator Deployment section in `config/manager/manager.yaml` file, - modify its value and redeploy. +To change the logging format, find `--zap-encoder=` argument in Operator Deployment section in `config/manager/manager.yaml` file or in Webhooks Deployment section in `config/webhooks/webhooks.yaml` file, modify its value and redeploy. Allowed values are `json` and `console` Default value: `console` -To change the logging time encoding, find `--zap-time-encoding=` argument in Operator Deployment section in `config/manager/manager.yaml` file, - modify its value and redeploy. +To change the logging time encoding, find `--zap-time-encoding=` argument in Operator Deployment section in `config/manager/manager.yaml` file or in Webhooks Deployment section in `config/webhooks/webhooks.yaml` file, modify its value and redeploy. Allowed values are `epoch`, `millis`, `nano`, `iso8601`, `rfc3339` or `rfc3339nano` diff --git a/CHANGELOG.md b/CHANGELOG.md index 84da4a92191..af2a2293016 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ To learn more about active deprecations, we recommend checking [GitHub Discussio Here is an overview of all **stable** additions: - **General**: Introduce new ArangoDB Scaler ([#4000](https://github.com/kedacore/keda/issues/4000)) +- **General**: Validate incoming ScaledObjects to ensure the workload isn't already autoscaled ([#3755](https://github.com/kedacore/keda/issues/3755)) Here is an overview of all new **experimental** features: diff --git a/Dockerfile b/Dockerfile index e390a643c05..31a125bb7e5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,8 +12,7 @@ COPY Makefile Makefile # Copy the go source COPY hack/ hack/ COPY version/ version/ -COPY main.go main.go -COPY adapter/ adapter/ +COPY cmd/ cmd/ COPY apis/ apis/ COPY controllers/ controllers/ COPY pkg/ pkg/ diff --git a/Dockerfile.adapter b/Dockerfile.adapter index 61bac26616b..555dba47e6d 100644 --- a/Dockerfile.adapter +++ b/Dockerfile.adapter @@ -12,8 +12,7 @@ COPY Makefile Makefile # Copy the go source COPY hack/ hack/ COPY version/ version/ -COPY main.go main.go -COPY adapter/ adapter/ +COPY cmd/ cmd/ COPY apis/ apis/ COPY controllers/ controllers/ COPY pkg/ pkg/ diff --git a/Dockerfile.webhooks b/Dockerfile.webhooks new file mode 100644 index 00000000000..2739a8da48a --- /dev/null +++ b/Dockerfile.webhooks @@ -0,0 +1,37 @@ +# Build the manager binary +FROM --platform=$BUILDPLATFORM ghcr.io/kedacore/build-tools:1.18.8 AS builder + +ARG BUILD_VERSION=main +ARG GIT_COMMIT=HEAD +ARG GIT_VERSION=main + +WORKDIR /workspace + +COPY Makefile Makefile + +# Copy the go source +COPY hack/ hack/ +COPY version/ version/ +COPY cmd/ cmd/ +COPY apis/ apis/ +COPY controllers/ controllers/ +COPY pkg/ pkg/ +COPY vendor/ vendor/ +COPY go.mod go.mod +COPY go.sum go.sum + +# Build +# https://www.docker.com/blog/faster-multi-platform-builds-dockerfile-cross-compilation-guide/ +ARG TARGETOS +ARG TARGETARCH +RUN VERSION=${BUILD_VERSION} GIT_COMMIT=${GIT_COMMIT} GIT_VERSION=${GIT_VERSION} TARGET_OS=$TARGETOS ARCH=$TARGETARCH make webhooks + +# Use distroless as minimal base image to package the manager binary +# Refer to https://github.com/GoogleContainerTools/distroless for more details +FROM gcr.io/distroless/static:nonroot +WORKDIR / +COPY --from=builder /workspace/bin/keda-webhooks . +# 65532 is numeric for nonroot +USER 65532:65532 + +ENTRYPOINT ["/keda-webhooks", "--zap-log-level=info", "--zap-encoder=console"] diff --git a/Makefile b/Makefile index 2e03d01939a..cfea052d013 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,7 @@ IMAGE_REPO ?= kedacore IMAGE_CONTROLLER = $(IMAGE_REGISTRY)/$(IMAGE_REPO)/keda$(SUFFIX):$(VERSION) IMAGE_ADAPTER = $(IMAGE_REGISTRY)/$(IMAGE_REPO)/keda-metrics-apiserver$(SUFFIX):$(VERSION) +IMAGE_WEBHOOKS = $(IMAGE_REGISTRY)/$(IMAGE_REPO)/keda-webhooks$(SUFFIX):$(VERSION) BUILD_TOOLS_GO_VERSION = 1.18.8 IMAGE_BUILD_TOOLS = $(IMAGE_REGISTRY)/$(IMAGE_REPO)/build-tools:$(BUILD_TOOLS_GO_VERSION) @@ -156,8 +157,10 @@ pkg/mock/mock_scaling/mock_executor/mock_interface.go: pkg/scaling/executor/scal pkg/mock/mock_scaler/mock_scaler.go: pkg/scalers/scaler.go $(MOCKGEN) -destination=$@ -package=mock_scalers -source=$^ pkg/mock/mock_scale/mock_interfaces.go: vendor/k8s.io/client-go/scale/interfaces.go + mkdir -p pkg/mock/mock_scale $(MOCKGEN) k8s.io/client-go/scale ScalesGetter,ScaleInterface > $@ pkg/mock/mock_client/mock_interfaces.go: vendor/sigs.k8s.io/controller-runtime/pkg/client/interfaces.go + mkdir -p pkg/mock/mock_client $(MOCKGEN) sigs.k8s.io/controller-runtime/pkg/client Patch,Reader,Writer,StatusClient,StatusWriter,Client,WithWatch,FieldIndexer > $@ pkg/scalers/liiklus/mocks/mock_liiklus.go: $(MOCKGEN) -destination=$@ github.com/kedacore/keda/v2/pkg/scalers/liiklus LiiklusServiceClient @@ -168,13 +171,16 @@ pkg/scalers/liiklus/mocks/mock_liiklus.go: ##@ Build -build: generate fmt vet manager adapter ## Build Operator (manager) and Metrics Server (adapter) binaries. +build: generate fmt vet manager adapter webhooks ## Build Operator (manager), Metrics Server (adapter) and Admision Web Hooks (webhooks) binaries. manager: generate - ${GO_BUILD_VARS} go build -ldflags $(GO_LDFLAGS) -mod=vendor -o bin/keda main.go + ${GO_BUILD_VARS} go build -ldflags $(GO_LDFLAGS) -mod=vendor -o bin/keda cmd/operator/main.go adapter: generate - ${GO_BUILD_VARS} go build -ldflags $(GO_LDFLAGS) -mod=vendor -o bin/keda-adapter adapter/main.go + ${GO_BUILD_VARS} go build -ldflags $(GO_LDFLAGS) -mod=vendor -o bin/keda-adapter cmd/adapter/main.go + +webhooks: generate + ${GO_BUILD_VARS} go build -ldflags $(GO_LDFLAGS) -mod=vendor -o bin/keda-webhooks cmd/webhooks/main.go run: manifests generate ## Run a controller from your host. WATCH_NAMESPACE="" go run -ldflags $(GO_LDFLAGS) ./main.go $(ARGS) @@ -182,10 +188,12 @@ run: manifests generate ## Run a controller from your host. docker-build: ## Build docker images with the KEDA Operator and Metrics Server. DOCKER_BUILDKIT=1 docker build . -t ${IMAGE_CONTROLLER} --build-arg BUILD_VERSION=${VERSION} --build-arg GIT_VERSION=${GIT_VERSION} --build-arg GIT_COMMIT=${GIT_COMMIT} DOCKER_BUILDKIT=1 docker build -f Dockerfile.adapter -t ${IMAGE_ADAPTER} . --build-arg BUILD_VERSION=${VERSION} --build-arg GIT_VERSION=${GIT_VERSION} --build-arg GIT_COMMIT=${GIT_COMMIT} + DOCKER_BUILDKIT=1 docker build -f Dockerfile.webhooks -t ${IMAGE_WEBHOOKS} . --build-arg BUILD_VERSION=${VERSION} --build-arg GIT_VERSION=${GIT_VERSION} --build-arg GIT_COMMIT=${GIT_COMMIT} publish: docker-build ## Push images on to Container Registry (default: ghcr.io). docker push $(IMAGE_CONTROLLER) docker push $(IMAGE_ADAPTER) + docker push $(IMAGE_WEBHOOKS) publish-controller-multiarch: ## Build and push multi-arch Docker image for KEDA Operator. docker buildx build --output=type=${OUTPUT_TYPE} --platform=${BUILD_PLATFORMS} . -t ${IMAGE_CONTROLLER} --build-arg BUILD_VERSION=${VERSION} --build-arg GIT_VERSION=${GIT_VERSION} --build-arg GIT_COMMIT=${GIT_COMMIT} @@ -193,13 +201,18 @@ publish-controller-multiarch: ## Build and push multi-arch Docker image for KEDA publish-adapter-multiarch: ## Build and push multi-arch Docker image for KEDA Metrics Server. docker buildx build --output=type=${OUTPUT_TYPE} --platform=${BUILD_PLATFORMS} -f Dockerfile.adapter -t ${IMAGE_ADAPTER} . --build-arg BUILD_VERSION=${VERSION} --build-arg GIT_VERSION=${GIT_VERSION} --build-arg GIT_COMMIT=${GIT_COMMIT} -publish-multiarch: publish-controller-multiarch publish-adapter-multiarch ## Push multi-arch Docker images on to Container Registry (default: ghcr.io). +publish-webhooks-multiarch: ## Build and push multi-arch Docker image for KEDA Hooks. + docker buildx build --output=type=${OUTPUT_TYPE} --platform=${BUILD_PLATFORMS} -f Dockerfile.webhooks -t ${IMAGE_WEBHOOKS} . --build-arg BUILD_VERSION=${VERSION} --build-arg GIT_VERSION=${GIT_VERSION} --build-arg GIT_COMMIT=${GIT_COMMIT} + +publish-multiarch: publish-controller-multiarch publish-adapter-multiarch publish-webhooks-multiarch ## Push multi-arch Docker images on to Container Registry (default: ghcr.io). release: manifests kustomize set-version ## Produce new KEDA release in keda-$(VERSION).yaml file. cd config/manager && \ $(KUSTOMIZE) edit set image ghcr.io/kedacore/keda=${IMAGE_CONTROLLER} cd config/metrics-server && \ $(KUSTOMIZE) edit set image ghcr.io/kedacore/keda-metrics-apiserver=${IMAGE_ADAPTER} + cd config/webhooks && \ + $(KUSTOMIZE) edit set image ghcr.io/kedacore/keda-webhooks=${IMAGE_WEBHOOKS} # Need this workaround to mitigate a problem with inserting labels into selectors, # until this issue is solved: https://github.com/kubernetes-sigs/kustomize/issues/1009 @sed -i".out" -e 's@version:[ ].*@version: $(VERSION)@g' config/default/kustomize-config/metadataLabelTransformer.yaml @@ -209,6 +222,7 @@ release: manifests kustomize set-version ## Produce new KEDA release in keda-$(V sign-images: ## Sign KEDA images published on GitHub Container Registry COSIGN_EXPERIMENTAL=1 cosign sign ${COSIGN_FLAGS} $(IMAGE_CONTROLLER) COSIGN_EXPERIMENTAL=1 cosign sign ${COSIGN_FLAGS} $(IMAGE_ADAPTER) + COSIGN_EXPERIMENTAL=1 cosign sign ${COSIGN_FLAGS} $(IMAGE_WEBHOOKS) .PHONY: set-version set-version: @@ -246,6 +260,9 @@ deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in $(KUSTOMIZE) edit add annotation --force cloud.google.com/workload-identity-provider:${GCP_WI_PROVIDER} cloud.google.com/service-account-email:${TF_GCP_SA_EMAIL} cloud.google.com/gcloud-run-as-user:${NON_ROOT_USER_ID}; \ fi + cd config/webhooks && \ + $(KUSTOMIZE) edit set image ghcr.io/kedacore/keda-webhooks=${IMAGE_WEBHOOKS} + # Need this workaround to mitigate a problem with inserting labels into selectors, # until this issue is solved: https://github.com/kubernetes-sigs/kustomize/issues/1009 @sed -i".out" -e 's@version:[ ].*@version: $(VERSION)@g' config/default/kustomize-config/metadataLabelTransformer.yaml diff --git a/PROJECT b/PROJECT index c54c3e7fdd2..e4706926e50 100644 --- a/PROJECT +++ b/PROJECT @@ -12,8 +12,11 @@ resources: domain: keda.sh group: keda kind: ScaledObject - path: github.com/kedacore/keda/v2/apis/keda/v1alpha1 + path: github.com/kedacore/keda/apis/keda/v1alpha1 version: v1alpha1 + webhooks: + validation: true + webhookVersion: v1 - api: crdVersion: v1 namespaced: true @@ -21,7 +24,7 @@ resources: domain: keda.sh group: keda kind: ScaledJob - path: github.com/kedacore/keda/v2/apis/keda/v1alpha1 + path: github.com/kedacore/keda/apis/keda/v1alpha1 version: v1alpha1 - api: crdVersion: v1 @@ -30,7 +33,7 @@ resources: domain: keda.sh group: keda kind: TriggerAuthentication - path: github.com/kedacore/keda/v2/apis/keda/v1alpha1 + path: github.com/kedacore/keda/apis/keda/v1alpha1 version: v1alpha1 - api: crdVersion: v1 @@ -39,6 +42,6 @@ resources: domain: keda.sh group: keda kind: ClusterTriggerAuthentication - path: github.com/kedacore/keda/v2/apis/keda/v1alpha1 + path: github.com/kedacore/keda/apis/keda/v1alpha1 version: v1alpha1 version: "3" diff --git a/apis/keda/v1alpha1/scaledobject_types.go b/apis/keda/v1alpha1/scaledobject_types.go index 316b8e5118e..3d319330c85 100644 --- a/apis/keda/v1alpha1/scaledobject_types.go +++ b/apis/keda/v1alpha1/scaledobject_types.go @@ -46,6 +46,8 @@ type ScaledObject struct { Status ScaledObjectStatus `json:"status,omitempty"` } +const ScaledObjectOwnerAnnotation = "scaledobject.keda.sh/name" + // HealthStatus is the status for a ScaledObject's health type HealthStatus struct { // +optional @@ -184,6 +186,6 @@ func init() { } // GenerateIdentifier returns identifier for the object in for "kind.namespace.name" -func (s *ScaledObject) GenerateIdentifier() string { - return GenerateIdentifier("ScaledObject", s.Namespace, s.Name) +func (so *ScaledObject) GenerateIdentifier() string { + return GenerateIdentifier("ScaledObject", so.Namespace, so.Name) } diff --git a/apis/keda/v1alpha1/scaledobject_webhook.go b/apis/keda/v1alpha1/scaledobject_webhook.go new file mode 100644 index 00000000000..eb8469024a8 --- /dev/null +++ b/apis/keda/v1alpha1/scaledobject_webhook.go @@ -0,0 +1,192 @@ +/* +Copyright 2023 The KEDA Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + "context" + "encoding/json" + "fmt" + + autoscalingv2 "k8s.io/api/autoscaling/v2" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" +) + +var scaledobjectlog = logf.Log.WithName("scaledobject-validation-webhook") + +var kc client.Client + +const ( + defaultAPI = "apps/v1" + defaultKind = "Deployment" +) + +func (so *ScaledObject) SetupWebhookWithManager(mgr ctrl.Manager) error { + kc = mgr.GetClient() + return ctrl.NewWebhookManagedBy(mgr). + For(so). + Complete() +} + +// +kubebuilder:webhook:path=/validate-keda-sh-v1alpha1-scaledobject,mutating=false,failurePolicy=ignore,sideEffects=None,groups=keda.sh,resources=scaledobjects,verbs=create;update,versions=v1alpha1,name=vscaledobject.kb.io,admissionReviewVersions=v1 +// +kubebuilder:rbac:groups=admissionregistration.k8s.io,resources=validatingwebhookconfigurations,verbs=get;list;watch;update;patch +// +kubebuilder:rbac:groups="",namespace=keda,resources=secrets,verbs=get;list;watch;create;update;patch;delete + +var _ webhook.Validator = &ScaledObject{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +func (so *ScaledObject) ValidateCreate() error { + val, _ := json.MarshalIndent(so, "", " ") + scaledobjectlog.V(1).Info(fmt.Sprintf("validating scaledobject creation for %s", string(val))) + return validateWorkload(so) +} + +func (so *ScaledObject) ValidateUpdate(old runtime.Object) error { + val, _ := json.MarshalIndent(so, "", " ") + scaledobjectlog.V(1).Info(fmt.Sprintf("validating scaledobject update for %s", string(val))) + return validateWorkload(so) +} + +func validateWorkload(so *ScaledObject) error { + err := verifyScaledObjects(so) + if err != nil { + return err + } + return verifyHpas(so) +} + +func verifyHpas(incomingSo *ScaledObject) error { + hpaList := &autoscalingv2.HorizontalPodAutoscalerList{} + opt := &client.ListOptions{ + Namespace: incomingSo.Namespace, + } + err := kc.List(context.Background(), hpaList, opt) + if err != nil { + return err + } + + for _, hpa := range hpaList.Items { + val, _ := json.MarshalIndent(hpa, "", " ") + scaledobjectlog.V(1).Info(fmt.Sprintf("checking hpa %s: %v", hpa.Name, string(val))) + hpaTarget := hpa.Spec.ScaleTargetRef + incomingSoTarget := incomingSo.Spec.ScaleTargetRef + + // prepare default values + hpatargetAPI := defaultAPI + if hpaTarget.APIVersion != "" { + hpatargetAPI = hpaTarget.APIVersion + } + hpaTargetKind := defaultKind + if hpaTarget.Kind != "" { + hpaTargetKind = hpaTarget.Kind + } + incomingSotargetAPI := defaultAPI + if incomingSoTarget.APIVersion != "" { + incomingSotargetAPI = incomingSoTarget.APIVersion + } + incomingSoTargetKind := defaultKind + if incomingSoTarget.Kind != "" { + incomingSoTargetKind = incomingSoTarget.Kind + } + + if hpatargetAPI == incomingSotargetAPI && + hpaTargetKind == incomingSoTargetKind && + hpaTarget.Name == incomingSoTarget.Name { + owned := false + ownerName := "" + for _, owner := range hpa.OwnerReferences { + if owner.Kind == incomingSo.Kind { + ownerName = owner.Name + if owner.Name == incomingSo.Name { + owned = true + } + } + } + + if !owned { + var err error + if len(hpa.OwnerReferences) == 0 { + err = fmt.Errorf("the workload '%s' of type '%s/%s' is already managed by the hpa '%s'", incomingSoTarget.Name, incomingSotargetAPI, incomingSoTargetKind, hpa.Name) + } else { + err = fmt.Errorf("the workload '%s' of type '%s/%s' is already managed by the ScaledObject '%s'", incomingSoTarget.Name, incomingSotargetAPI, incomingSoTargetKind, ownerName) + } + + scaledobjectlog.Error(err, "validation error") + return err + } + } + } + scaledobjectlog.V(1).Info(fmt.Sprintf("scaledobject %s is valid", incomingSo.Name)) + return nil +} + +func verifyScaledObjects(incomingSo *ScaledObject) error { + soList := &ScaledObjectList{} + opt := &client.ListOptions{ + Namespace: incomingSo.Namespace, + } + err := kc.List(context.Background(), soList, opt) + if err != nil { + return err + } + + for _, so := range soList.Items { + if so.Name == incomingSo.Name { + continue + } + val, _ := json.MarshalIndent(so, "", " ") + scaledobjectlog.V(1).Info(fmt.Sprintf("checking scaledobject %s: %v", so.Name, string(val))) + soTarget := so.Spec.ScaleTargetRef + incomingTarget := incomingSo.Spec.ScaleTargetRef + + // prepare default values + sotargetAPI := defaultAPI + if soTarget.APIVersion != "" { + sotargetAPI = soTarget.APIVersion + } + soTargetKind := defaultKind + if soTarget.Kind != "" { + soTargetKind = soTarget.Kind + } + incomingSotargetAPI := defaultAPI + if incomingTarget.APIVersion != "" { + incomingSotargetAPI = incomingTarget.APIVersion + } + incomingSoTargetKind := defaultKind + if incomingTarget.Kind != "" { + incomingSoTargetKind = incomingTarget.Kind + } + + if sotargetAPI == incomingSotargetAPI && + soTargetKind == incomingSoTargetKind && + soTarget.Name == incomingTarget.Name { + err = fmt.Errorf("the workload '%s' of type '%s/%s' is already managed by the ScaledObject '%s'", soTarget.Name, sotargetAPI, soTargetKind, so.Name) + scaledobjectlog.Error(err, "validation error") + return err + } + } + + scaledobjectlog.V(1).Info(fmt.Sprintf("scaledobject %s is valid", incomingSo.Name)) + return nil +} + +func (so *ScaledObject) ValidateDelete() error { + return nil +} diff --git a/apis/keda/v1alpha1/scaledobject_webhook_test.go b/apis/keda/v1alpha1/scaledobject_webhook_test.go new file mode 100644 index 00000000000..14cabb3b00b --- /dev/null +++ b/apis/keda/v1alpha1/scaledobject_webhook_test.go @@ -0,0 +1,331 @@ +/* +Copyright 2023 The KEDA Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "path/filepath" + "testing" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + admissionv1beta1 "k8s.io/api/admission/v1beta1" + v2 "k8s.io/api/autoscaling/v2" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/utils/pointer" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + //+kubebuilder:scaffold:imports +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment +var ctx context.Context +var cancel context.CancelFunc + +const ( + deploymentName = "deploymentName" + soName = "test-so" +) + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Webhook Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + ctx, cancel = context.WithCancel(context.Background()) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: false, + WebhookInstallOptions: envtest.WebhookInstallOptions{ + Paths: []string{filepath.Join("..", "..", "..", "config", "webhooks")}, + }, + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + scheme := runtime.NewScheme() + err = AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + err = clientgoscheme.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + err = admissionv1beta1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + + // start webhook server using Manager + webhookInstallOptions := &testEnv.WebhookInstallOptions + mgr, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme, + Host: webhookInstallOptions.LocalServingHost, + Port: webhookInstallOptions.LocalServingPort, + CertDir: webhookInstallOptions.LocalServingCertDir, + LeaderElection: false, + MetricsBindAddress: "0", + }) + Expect(err).NotTo(HaveOccurred()) + + err = (&ScaledObject{}).SetupWebhookWithManager(mgr) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:webhook + + go func() { + defer GinkgoRecover() + err = mgr.Start(ctx) + Expect(err).NotTo(HaveOccurred()) + }() + + // wait for the webhook server to get ready + dialer := &net.Dialer{Timeout: time.Second} + addrPort := fmt.Sprintf("%s:%d", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort) + Eventually(func() error { + conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) + if err != nil { + return err + } + conn.Close() + return nil + }).Should(Succeed()) + +}) + +var _ = It("should validate the so creation when there isn't any hpa", func() { + + namespaceName := "valid" + namespace := createNamespace(namespaceName) + so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment") + + err := k8sClient.Create(context.Background(), namespace) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), so) + Expect(err).ToNot(HaveOccurred()) +}) + +var _ = It("should validate the so creation when it's own hpa is already generated", func() { + + hpaName := "test-so-hpa" + namespaceName := "own-hpa" + namespace := createNamespace(namespaceName) + so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment") + hpa := createHpa(hpaName, namespaceName, "apps/v1", "Deployment", so) + + err := k8sClient.Create(context.Background(), namespace) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), hpa) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), so) + Expect(err).ToNot(HaveOccurred()) +}) + +var _ = It("should validate the so update when it's own hpa is already generated", func() { + + hpaName := "test-so-hpa" + namespaceName := "update-so" + namespace := createNamespace(namespaceName) + so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment") + hpa := createHpa(hpaName, namespaceName, "apps/v1", "Deployment", so) + + err := k8sClient.Create(context.Background(), namespace) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), hpa) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), so) + Expect(err).ToNot(HaveOccurred()) + + so.Spec.MaxReplicaCount = pointer.Int32(7) + err = k8sClient.Update(context.Background(), so) + Expect(err).ToNot(HaveOccurred()) +}) + +var _ = It("shouldn't validate the so creation when there is another unmanaged hpa", func() { + + hpaName := "test-unmanaged-hpa" + namespaceName := "unmanaged-hpa" + namespace := createNamespace(namespaceName) + hpa := createHpa(hpaName, namespaceName, "apps/v1", "Deployment", nil) + so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment") + + err := k8sClient.Create(context.Background(), namespace) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), hpa) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), so) + Expect(err).To(HaveOccurred()) +}) + +var _ = It("shouldn't validate the so creation when there is another so", func() { + + so2Name := "test-so2" + namespaceName := "managed-hpa" + namespace := createNamespace(namespaceName) + so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment") + so2 := createScaledObject(so2Name, namespaceName, "apps/v1", "Deployment") + + err := k8sClient.Create(context.Background(), namespace) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), so2) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), so) + Expect(err).To(HaveOccurred()) +}) + +var _ = It("shouldn't validate the so creation when there is another hpa with custom apis", func() { + + hpaName := "test-custom-hpa" + namespaceName := "custom-apis" + namespace := createNamespace(namespaceName) + so := createScaledObject(soName, namespaceName, "custom-api", "custom-kind") + hpa := createHpa(hpaName, namespaceName, "custom-api", "custom-kind", nil) + + err := k8sClient.Create(context.Background(), namespace) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), hpa) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), so) + Expect(err).To(HaveOccurred()) +}) + +var _ = AfterSuite(func() { + cancel() + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) + +func createNamespace(name string) *v1.Namespace { + return &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: name}, + } +} + +func createScaledObject(name, namespace, targetAPI, targetKind string) *ScaledObject { + return &ScaledObject{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + UID: types.UID(name), + }, + TypeMeta: metav1.TypeMeta{ + Kind: "ScaledObject", + APIVersion: "keda.sh", + }, + Spec: ScaledObjectSpec{ + ScaleTargetRef: &ScaleTarget{ + Name: deploymentName, + APIVersion: targetAPI, + Kind: targetKind, + }, + IdleReplicaCount: pointer.Int32(1), + MinReplicaCount: pointer.Int32(5), + MaxReplicaCount: pointer.Int32(10), + Triggers: []ScaleTriggers{ + { + Type: "cron", + Metadata: map[string]string{ + "timezone": "UTC", + "start": "0 * * * *", + "end": "1 * * * *", + "desiredReplicas": "1", + }, + }, + }, + }, + } +} + +func createHpa(name, namespace, targetAPI, targetKind string, owner *ScaledObject) *v2.HorizontalPodAutoscaler { + hpa := &v2.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, + Spec: v2.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: v2.CrossVersionObjectReference{ + Name: deploymentName, + APIVersion: targetAPI, + Kind: targetKind, + }, + MinReplicas: pointer.Int32(5), + MaxReplicas: 10, + Metrics: []v2.MetricSpec{ + { + Resource: &v2.ResourceMetricSource{ + Name: v1.ResourceCPU, + Target: v2.MetricTarget{ + AverageUtilization: pointer.Int32(30), + Type: v2.AverageValueMetricType, + }, + }, + Type: v2.ResourceMetricSourceType, + }, + }, + }, + } + + if owner != nil { + hpa.OwnerReferences = append(hpa.OwnerReferences, metav1.OwnerReference{ + Kind: owner.Kind, + Name: owner.Name, + APIVersion: owner.APIVersion, + UID: owner.UID, + }) + } + + return hpa +} diff --git a/apis/keda/v1alpha1/withtriggers_types.go b/apis/keda/v1alpha1/withtriggers_types.go index 5d280499498..32df0e91c23 100644 --- a/apis/keda/v1alpha1/withtriggers_types.go +++ b/apis/keda/v1alpha1/withtriggers_types.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/adapter/main.go b/cmd/adapter/main.go similarity index 99% rename from adapter/main.go rename to cmd/adapter/main.go index cd7e428a769..360b970084a 100644 --- a/adapter/main.go +++ b/cmd/adapter/main.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/main.go b/cmd/operator/main.go similarity index 99% rename from main.go rename to cmd/operator/main.go index d4e31ee389f..b78200adce5 100644 --- a/main.go +++ b/cmd/operator/main.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/cmd/webhooks/main.go b/cmd/webhooks/main.go new file mode 100644 index 00000000000..8b6039c7b7d --- /dev/null +++ b/cmd/webhooks/main.go @@ -0,0 +1,180 @@ +/* +Copyright 2023 The KEDA Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "flag" + "fmt" + "os" + "runtime" + + "github.com/open-policy-agent/cert-controller/pkg/rotator" + "github.com/spf13/pflag" + apimachineryruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + _ "k8s.io/client-go/plugin/pkg/client/auth" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/manager" + + kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1" + "github.com/kedacore/keda/v2/pkg/k8s" + kedautil "github.com/kedacore/keda/v2/pkg/util" + "github.com/kedacore/keda/v2/version" + //+kubebuilder:scaffold:imports +) + +var webhooks = []rotator.WebhookInfo{ + { + Name: "keda-validating-webhooks", + Type: rotator.Validating, + }, +} + +var ( + scheme = apimachineryruntime.NewScheme() + setupLog = ctrl.Log.WithName("setup") + secretName = "kedaorg-webhook-secret" // #nosec + serviceName = "keda-webhook-service" + caName = "kedaorg-ca" + caOrganization = "kedaorg" + // DNSName is ..svc + dnsName = fmt.Sprintf("%s.%s.svc", serviceName, kedautil.GetPodNamespace()) +) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + + utilruntime.Must(kedav1alpha1.AddToScheme(scheme)) + //+kubebuilder:scaffold:scheme +} + +func main() { + var metricsAddr string + var probeAddr string + var adapterClientRequestQPS float32 + var adapterClientRequestBurst int + var webhookCertDir string + var disableCertRotation bool + var tlsMinVersion string + pflag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") + pflag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + pflag.Float32Var(&adapterClientRequestQPS, "kube-api-qps", 20.0, "Set the QPS rate for throttling requests sent to the apiserver") + pflag.IntVar(&adapterClientRequestBurst, "kube-api-burst", 30, "Set the burst for throttling requests sent to the apiserver") + pflag.StringVar(&webhookCertDir, "webhook-cert-dir", "/certs", "Webhook certificates dir to use. Defaults to /certs") + pflag.BoolVar(&disableCertRotation, "disable-cert-rotation", false, "disable automatic generation and rotation of webhook TLS certificates/keys") + pflag.StringVar(&tlsMinVersion, "tls-min-version", "1.3", "Minimum TLS version") + + opts := zap.Options{} + opts.BindFlags(flag.CommandLine) + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + pflag.Parse() + + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + + cfg := ctrl.GetConfigOrDie() + cfg.QPS = adapterClientRequestQPS + cfg.Burst = adapterClientRequestBurst + + mgr, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme, + LeaderElection: false, + MetricsBindAddress: metricsAddr, + Port: 9443, + HealthProbeBindAddress: probeAddr, + CertDir: webhookCertDir, + }) + if err != nil { + setupLog.Error(err, "unable to start webhooks") + os.Exit(1) + } + + // Make sure certs are generated and valid if cert rotation is enabled. + setupFinished := make(chan struct{}) + if !disableCertRotation { + setupLog.V(1).Info("setting up cert rotation") + if err := rotator.AddRotator(mgr, &rotator.CertRotator{ + SecretKey: types.NamespacedName{ + Namespace: kedautil.GetPodNamespace(), + Name: secretName, + }, + CertDir: webhookCertDir, + CAName: caName, + CAOrganization: caOrganization, + DNSName: dnsName, + IsReady: setupFinished, + Webhooks: webhooks, + RestartOnSecretRefresh: true, + }); err != nil { + setupLog.Error(err, "unable to set up cert rotation") + os.Exit(1) + } + } else { + close(setupFinished) + } + + //+kubebuilder:scaffold:builder + + _, kubeVersion, err := k8s.InitScaleClient(mgr) + if err != nil { + setupLog.Error(err, "unable to init scale client") + os.Exit(1) + } + + setupLog.Info("Starting webhooks") + setupLog.Info(fmt.Sprintf("KEDA Version: %s", version.Version)) + setupLog.Info(fmt.Sprintf("Git Commit: %s", version.GitCommit)) + setupLog.Info(fmt.Sprintf("Go Version: %s", runtime.Version())) + setupLog.Info(fmt.Sprintf("Go OS/Arch: %s/%s", runtime.GOOS, runtime.GOARCH)) + setupLog.Info(fmt.Sprintf("Running on Kubernetes %s", kubeVersion.PrettyVersion), "version", kubeVersion.Version) + + go setupWebhook(mgr, tlsMinVersion, setupFinished) + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up health check") + os.Exit(1) + } + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up ready check") + os.Exit(1) + } + + ctx := ctrl.SetupSignalHandler() + + if err := mgr.Start(ctx); err != nil { + setupLog.Error(err, "problem running webhooks") + os.Exit(1) + } +} + +func setupWebhook(mgr manager.Manager, tlsMinVersion string, setupFinished chan struct{}) { + // Block until the setup (certificate generation) finishes. + <-setupFinished + + // setup webhooks + if err := (&kedav1alpha1.ScaledObject{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "ScaledObject") + os.Exit(1) + } + + setupLog.V(1).Info("setting up webhook server") + hookServer := mgr.GetWebhookServer() + hookServer.TLSMinVersion = tlsMinVersion +} diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index a9a8390e9ea..71eeec6800f 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -29,3 +29,4 @@ resources: - ../manager - ../metrics-server - ../service_account +- ../webhooks diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 255cd14624f..5f623752c08 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -63,6 +63,7 @@ spec: - name: KEDA_HTTP_DEFAULT_TIMEOUT value: "" securityContext: + runAsNonRoot: true capabilities: drop: - ALL diff --git a/config/metrics-server/deployment.yaml b/config/metrics-server/deployment.yaml index 43d4c7cec8d..9a38fe709dc 100644 --- a/config/metrics-server/deployment.yaml +++ b/config/metrics-server/deployment.yaml @@ -72,6 +72,7 @@ spec: - mountPath: /apiserver.local.config/certificates name: certs securityContext: + runAsNonRoot: true capabilities: drop: - ALL diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 1846281cdbf..50fdd338b85 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -43,6 +43,16 @@ rules: - '*/scale' verbs: - '*' +- apiGroups: + - admissionregistration.k8s.io + resources: + - validatingwebhookconfigurations + verbs: + - get + - list + - patch + - update + - watch - apiGroups: - apps resources: @@ -99,3 +109,23 @@ rules: - triggerauthentications/status verbs: - '*' +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + creationTimestamp: null + name: keda-operator + namespace: keda +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch diff --git a/config/rbac/role_binding.yaml b/config/rbac/role_binding.yaml index 5df22274191..c43c8f78abf 100644 --- a/config/rbac/role_binding.yaml +++ b/config/rbac/role_binding.yaml @@ -10,3 +10,17 @@ subjects: - kind: ServiceAccount name: keda-operator namespace: keda +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: keda-operator + namespace: keda +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: keda-operator +subjects: +- kind: ServiceAccount + name: keda-operator + namespace: keda diff --git a/config/webhooks/kustomization.yaml b/config/webhooks/kustomization.yaml new file mode 100644 index 00000000000..03c532e1d3a --- /dev/null +++ b/config/webhooks/kustomization.yaml @@ -0,0 +1,12 @@ +resources: +- webhooks.yaml +- secret.yaml +- service.yaml +- validation_webhooks.yaml + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +images: +- name: ghcr.io/kedacore/keda-webhooks + newName: ghcr.io/kedacore/keda-webhooks + newTag: main diff --git a/config/webhooks/secret.yaml b/config/webhooks/secret.yaml new file mode 100644 index 00000000000..619ba6f6d99 --- /dev/null +++ b/config/webhooks/secret.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: kedaorg-webhook-secret + namespace: keda + labels: + app: keda-webhooks + app.kubernetes.io/name: keda-webhooks + app.kubernetes.io/version: latest + app.kubernetes.io/component: webhooks + app.kubernetes.io/part-of: keda-operator diff --git a/config/webhooks/service.yaml b/config/webhooks/service.yaml new file mode 100644 index 00000000000..c7e4baf4661 --- /dev/null +++ b/config/webhooks/service.yaml @@ -0,0 +1,20 @@ + +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: service + app.kubernetes.io/instance: webhook-service + app.kubernetes.io/component: webhook + app.kubernetes.io/created-by: keda + app.kubernetes.io/part-of: keda + app.kubernetes.io/managed-by: kustomize + name: keda-webhook-service + namespace: keda +spec: + ports: + - port: 443 + protocol: TCP + targetPort: 9443 + selector: + app: keda-webhooks diff --git a/config/webhooks/validation_webhooks.yaml b/config/webhooks/validation_webhooks.yaml new file mode 100644 index 00000000000..31300bddc5f --- /dev/null +++ b/config/webhooks/validation_webhooks.yaml @@ -0,0 +1,36 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + labels: + app.kubernetes.io/name: service + app.kubernetes.io/instance: webhook-service + app.kubernetes.io/component: webhook + app.kubernetes.io/created-by: keda + app.kubernetes.io/part-of: keda + app.kubernetes.io/managed-by: kustomize + name: keda-validating-webhooks +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: keda-webhook-service + namespace: keda + path: /validate-keda-sh-v1alpha1-scaledobject + failurePolicy: Ignore + matchPolicy: Equivalent + name: vscaledobject.kb.io + namespaceSelector: {} + objectSelector: {} + rules: + - apiGroups: + - keda.sh + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - scaledobjects + sideEffects: None + timeoutSeconds: 10 diff --git a/config/webhooks/webhooks.yaml b/config/webhooks/webhooks.yaml new file mode 100644 index 00000000000..0c28c51cc14 --- /dev/null +++ b/config/webhooks/webhooks.yaml @@ -0,0 +1,88 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: keda-webhooks + namespace: keda + labels: + app: keda-webhooks + app.kubernetes.io/name: keda-webhooks + app.kubernetes.io/version: latest + app.kubernetes.io/component: webhooks + app.kubernetes.io/part-of: keda-operator +spec: + replicas: 1 + selector: + matchLabels: + app: keda-webhooks + template: + metadata: + labels: + app: keda-webhooks + name: keda-webhooks + name: keda-webhooks + spec: + securityContext: + runAsNonRoot: true + serviceAccountName: keda-operator + containers: + - name: keda-webhooks + image: ghcr.io/kedacore/keda-webhooks:latest + command: + - /keda-webhooks + args: + - --zap-log-level=info + - --zap-encoder=console + - --zap-time-encoding=rfc3339 + imagePullPolicy: Always + resources: + requests: + cpu: 100m + memory: 100Mi + limits: + cpu: 1000m + memory: 1000Mi + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 25 + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 20 + ports: + - containerPort: 9443 + name: http + protocol: TCP + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: WATCH_NAMESPACE + value: "" + - name: KEDA_HTTP_DEFAULT_TIMEOUT + value: "" + securityContext: + runAsNonRoot: true + capabilities: + drop: + - ALL + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + seccompProfile: + type: RuntimeDefault + volumeMounts: + - mountPath: /certs + name: cert + readOnly: true + terminationGracePeriodSeconds: 10 + nodeSelector: + kubernetes.io/os: linux + volumes: + - name: cert + secret: + defaultMode: 420 + secretName: kedaorg-webhook-secret diff --git a/controllers/keda/hpa.go b/controllers/keda/hpa.go index 85e1f37ab56..46a254ccd81 100644 --- a/controllers/keda/hpa.go +++ b/controllers/keda/hpa.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1" + "github.com/kedacore/keda/v2/apis/keda/v1alpha1" kedacontrollerutil "github.com/kedacore/keda/v2/controllers/keda/util" "github.com/kedacore/keda/v2/pkg/scaling/executor" version "github.com/kedacore/keda/v2/version" @@ -41,7 +41,7 @@ const ( ) // createAndDeployNewHPA creates and deploy HPA in the cluster for specified ScaledObject -func (r *ScaledObjectReconciler) createAndDeployNewHPA(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject, gvkr *kedav1alpha1.GroupVersionKindResource) error { +func (r *ScaledObjectReconciler) createAndDeployNewHPA(ctx context.Context, logger logr.Logger, scaledObject *v1alpha1.ScaledObject, gvkr *v1alpha1.GroupVersionKindResource) error { hpaName := getHPAName(scaledObject) logger.Info("Creating a new HPA", "HPA.Namespace", scaledObject.Namespace, "HPA.Name", hpaName) hpa, err := r.newHPAForScaledObject(ctx, logger, scaledObject, gvkr) @@ -70,7 +70,7 @@ func (r *ScaledObjectReconciler) createAndDeployNewHPA(ctx context.Context, logg } // newHPAForScaledObject returns HPA as it is specified in ScaledObject -func (r *ScaledObjectReconciler) newHPAForScaledObject(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject, gvkr *kedav1alpha1.GroupVersionKindResource) (*autoscalingv2.HorizontalPodAutoscaler, error) { +func (r *ScaledObjectReconciler) newHPAForScaledObject(ctx context.Context, logger logr.Logger, scaledObject *v1alpha1.ScaledObject, gvkr *v1alpha1.GroupVersionKindResource) (*autoscalingv2.HorizontalPodAutoscaler, error) { scaledObjectMetricSpecs, err := r.getScaledObjectMetricSpecs(ctx, logger, scaledObject) if err != nil { return nil, err @@ -148,7 +148,7 @@ func (r *ScaledObjectReconciler) newHPAForScaledObject(ctx context.Context, logg } // updateHPAIfNeeded checks whether update of HPA is needed -func (r *ScaledObjectReconciler) updateHPAIfNeeded(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject, foundHpa *autoscalingv2.HorizontalPodAutoscaler, gvkr *kedav1alpha1.GroupVersionKindResource) error { +func (r *ScaledObjectReconciler) updateHPAIfNeeded(ctx context.Context, logger logr.Logger, scaledObject *v1alpha1.ScaledObject, foundHpa *autoscalingv2.HorizontalPodAutoscaler, gvkr *v1alpha1.GroupVersionKindResource) error { hpa, err := r.newHPAForScaledObject(ctx, logger, scaledObject, gvkr) if err != nil { logger.Error(err, "Failed to create new HPA resource", "HPA.Namespace", scaledObject.Namespace, "HPA.Name", getHPAName(scaledObject)) @@ -181,7 +181,7 @@ func (r *ScaledObjectReconciler) updateHPAIfNeeded(ctx context.Context, logger l } // deleteAndCreateHpa delete old HPA and create new one -func (r *ScaledObjectReconciler) renameHPA(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject, foundHpa *autoscalingv2.HorizontalPodAutoscaler, gvkr *kedav1alpha1.GroupVersionKindResource) error { +func (r *ScaledObjectReconciler) renameHPA(ctx context.Context, logger logr.Logger, scaledObject *v1alpha1.ScaledObject, foundHpa *autoscalingv2.HorizontalPodAutoscaler, gvkr *v1alpha1.GroupVersionKindResource) error { logger.Info("Deleting old HPA", "HPA.Namespace", scaledObject.Namespace, "HPA.Name", foundHpa.Name) if err := r.Client.Delete(ctx, foundHpa); err != nil { logger.Error(err, "Failed to delete old HPA", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name) @@ -192,7 +192,7 @@ func (r *ScaledObjectReconciler) renameHPA(ctx context.Context, logger logr.Logg } // getScaledObjectMetricSpecs returns MetricSpec for HPA, generater from Triggers defitinion in ScaledObject -func (r *ScaledObjectReconciler) getScaledObjectMetricSpecs(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject) ([]autoscalingv2.MetricSpec, error) { +func (r *ScaledObjectReconciler) getScaledObjectMetricSpecs(ctx context.Context, logger logr.Logger, scaledObject *v1alpha1.ScaledObject) ([]autoscalingv2.MetricSpec, error) { var scaledObjectMetricSpecs []autoscalingv2.MetricSpec var externalMetricNames []string var resourceMetricNames []string @@ -218,7 +218,7 @@ func (r *ScaledObjectReconciler) getScaledObjectMetricSpecs(ctx context.Context, // add the scaledobject.keda.sh/name label. This is how the MetricsAdapter will know which scaledobject a metric is for when the HPA queries it. metricSpec.External.Metric.Selector = &metav1.LabelSelector{MatchLabels: make(map[string]string)} - metricSpec.External.Metric.Selector.MatchLabels["scaledobject.keda.sh/name"] = scaledObject.Name + metricSpec.External.Metric.Selector.MatchLabels[v1alpha1.ScaledObjectOwnerAnnotation] = scaledObject.Name externalMetricNames = append(externalMetricNames, externalMetricName) } } @@ -246,9 +246,9 @@ func (r *ScaledObjectReconciler) getScaledObjectMetricSpecs(ctx context.Context, return scaledObjectMetricSpecs, nil } -func updateHealthStatus(scaledObject *kedav1alpha1.ScaledObject, externalMetricNames []string, status *kedav1alpha1.ScaledObjectStatus) { +func updateHealthStatus(scaledObject *v1alpha1.ScaledObject, externalMetricNames []string, status *v1alpha1.ScaledObjectStatus) { health := scaledObject.Status.Health - newHealth := make(map[string]kedav1alpha1.HealthStatus) + newHealth := make(map[string]v1alpha1.HealthStatus) for _, metricName := range externalMetricNames { entry, exists := health[metricName] if exists { @@ -259,19 +259,19 @@ func updateHealthStatus(scaledObject *kedav1alpha1.ScaledObject, externalMetricN } // getHPAName returns generated HPA name for ScaledObject specified in the parameter -func getHPAName(scaledObject *kedav1alpha1.ScaledObject) string { +func getHPAName(scaledObject *v1alpha1.ScaledObject) string { if scaledObject.Spec.Advanced != nil && scaledObject.Spec.Advanced.HorizontalPodAutoscalerConfig != nil && scaledObject.Spec.Advanced.HorizontalPodAutoscalerConfig.Name != "" { return scaledObject.Spec.Advanced.HorizontalPodAutoscalerConfig.Name } return getDefaultHpaName(scaledObject) } -func getDefaultHpaName(scaledObject *kedav1alpha1.ScaledObject) string { +func getDefaultHpaName(scaledObject *v1alpha1.ScaledObject) string { return fmt.Sprintf("keda-hpa-%s", scaledObject.Name) } // getHPAMinReplicas returns MinReplicas based on definition in ScaledObject or default value if not defined -func getHPAMinReplicas(scaledObject *kedav1alpha1.ScaledObject) *int32 { +func getHPAMinReplicas(scaledObject *v1alpha1.ScaledObject) *int32 { if scaledObject.Spec.MinReplicaCount != nil && *scaledObject.Spec.MinReplicaCount > 0 { return scaledObject.Spec.MinReplicaCount } @@ -280,7 +280,7 @@ func getHPAMinReplicas(scaledObject *kedav1alpha1.ScaledObject) *int32 { } // getHPAMaxReplicas returns MaxReplicas based on definition in ScaledObject or default value if not defined -func getHPAMaxReplicas(scaledObject *kedav1alpha1.ScaledObject) int32 { +func getHPAMaxReplicas(scaledObject *v1alpha1.ScaledObject) int32 { if scaledObject.Spec.MaxReplicaCount != nil { return *scaledObject.Spec.MaxReplicaCount } diff --git a/controllers/keda/scaledobject_controller.go b/controllers/keda/scaledobject_controller.go index 0b83c4f8378..48033eac5d0 100644 --- a/controllers/keda/scaledobject_controller.go +++ b/controllers/keda/scaledobject_controller.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -41,7 +41,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/predicate" - kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1" + "github.com/kedacore/keda/v2/apis/keda/v1alpha1" kedacontrollerutil "github.com/kedacore/keda/v2/controllers/keda/util" "github.com/kedacore/keda/v2/pkg/eventreason" "github.com/kedacore/keda/v2/pkg/prommetrics" @@ -120,7 +120,7 @@ func (r *ScaledObjectReconciler) SetupWithManager(mgr ctrl.Manager, options cont // predicate.GenerationChangedPredicate{} ignore updates to ScaledObject Status // (in this case metadata.Generation does not change) // so reconcile loop is not started on Status updates - For(&kedav1alpha1.ScaledObject{}, builder.WithPredicates( + For(&v1alpha1.ScaledObject{}, builder.WithPredicates( predicate.Or( kedacontrollerutil.PausedReplicasPredicate{}, kedacontrollerutil.ScaleObjectReadyConditionPredicate{}, @@ -136,7 +136,7 @@ func (r *ScaledObjectReconciler) Reconcile(ctx context.Context, req ctrl.Request reqLogger := log.FromContext(ctx) // Fetch the ScaledObject instance - scaledObject := &kedav1alpha1.ScaledObject{} + scaledObject := &v1alpha1.ScaledObject{} err := r.Client.Get(ctx, req.NamespacedName, scaledObject) if err != nil { if errors.IsNotFound(err) { @@ -166,7 +166,7 @@ func (r *ScaledObjectReconciler) Reconcile(ctx context.Context, req ctrl.Request // ensure Status Conditions are initialized if !scaledObject.Status.Conditions.AreInitialized() { - conditions := kedav1alpha1.GetInitializedConditions() + conditions := v1alpha1.GetInitializedConditions() if err := kedacontrollerutil.SetStatusConditions(ctx, r.Client, reqLogger, scaledObject, conditions); err != nil { return ctrl.Result{}, err } @@ -186,7 +186,7 @@ func (r *ScaledObjectReconciler) Reconcile(ctx context.Context, req ctrl.Request r.Recorder.Event(scaledObject, corev1.EventTypeNormal, eventreason.ScaledObjectReady, "ScaledObject is ready for scaling") } reqLogger.V(1).Info(msg) - conditions.SetReadyCondition(metav1.ConditionTrue, kedav1alpha1.ScaledObjectConditionReadySucccesReason, msg) + conditions.SetReadyCondition(metav1.ConditionTrue, v1alpha1.ScaledObjectConditionReadySucccesReason, msg) } if err := kedacontrollerutil.SetStatusConditions(ctx, r.Client, reqLogger, scaledObject, &conditions); err != nil { @@ -197,7 +197,7 @@ func (r *ScaledObjectReconciler) Reconcile(ctx context.Context, req ctrl.Request } // reconcileScaledObject implements reconciler logic for ScaledObject -func (r *ScaledObjectReconciler) reconcileScaledObject(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject) (string, error) { +func (r *ScaledObjectReconciler) reconcileScaledObject(ctx context.Context, logger logr.Logger, scaledObject *v1alpha1.ScaledObject) (string, error) { // Check scale target Name is specified if scaledObject.Spec.ScaleTargetRef.Name == "" { err := fmt.Errorf("ScaledObject.spec.scaleTargetRef.name is missing") @@ -249,22 +249,20 @@ func (r *ScaledObjectReconciler) reconcileScaledObject(ctx context.Context, logg } logger.Info("Initializing Scaling logic according to ScaledObject Specification") } - return kedav1alpha1.ScaledObjectConditionReadySuccessMessage, nil + return v1alpha1.ScaledObjectConditionReadySuccessMessage, nil } // ensureScaledObjectLabel ensures that scaledobject.keda.sh/name= label exist in the ScaledObject // This is how the MetricsAdapter will know which ScaledObject a metric is for when the HPA queries it. -func (r *ScaledObjectReconciler) ensureScaledObjectLabel(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject) error { - const labelScaledObjectName = "scaledobject.keda.sh/name" - +func (r *ScaledObjectReconciler) ensureScaledObjectLabel(ctx context.Context, logger logr.Logger, scaledObject *v1alpha1.ScaledObject) error { if scaledObject.Labels == nil { - scaledObject.Labels = map[string]string{labelScaledObjectName: scaledObject.Name} + scaledObject.Labels = map[string]string{v1alpha1.ScaledObjectOwnerAnnotation: scaledObject.Name} } else { - value, found := scaledObject.Labels[labelScaledObjectName] + value, found := scaledObject.Labels[v1alpha1.ScaledObjectOwnerAnnotation] if found && value == scaledObject.Name { return nil } - scaledObject.Labels[labelScaledObjectName] = scaledObject.Name + scaledObject.Labels[v1alpha1.ScaledObjectOwnerAnnotation] = scaledObject.Name } logger.V(1).Info("Adding \"scaledobject.keda.sh/name\" label on ScaledObject", "value", scaledObject.Name) @@ -272,7 +270,7 @@ func (r *ScaledObjectReconciler) ensureScaledObjectLabel(ctx context.Context, lo } // checkTargetResourceIsScalable checks if resource targeted for scaling exists and exposes /scale subresource -func (r *ScaledObjectReconciler) checkTargetResourceIsScalable(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject) (kedav1alpha1.GroupVersionKindResource, error) { +func (r *ScaledObjectReconciler) checkTargetResourceIsScalable(ctx context.Context, logger logr.Logger, scaledObject *v1alpha1.ScaledObject) (v1alpha1.GroupVersionKindResource, error) { gvkr, err := kedautil.ParseGVKR(r.restMapper, scaledObject.Spec.ScaleTargetRef.APIVersion, scaledObject.Spec.ScaleTargetRef.Kind) if err != nil { logger.Error(err, "Failed to parse Group, Version, Kind, Resource", "apiVersion", scaledObject.Spec.ScaleTargetRef.APIVersion, "kind", scaledObject.Spec.ScaleTargetRef.Kind) @@ -340,7 +338,7 @@ func (r *ScaledObjectReconciler) checkTargetResourceIsScalable(ctx context.Conte // checkTriggers checks that general trigger metadata are valid, it checks: // - triggerNames in ScaledObject are unique // - useCachedMetrics is defined only for a supported triggers -func (r *ScaledObjectReconciler) checkTriggers(scaledObject *kedav1alpha1.ScaledObject) error { +func (r *ScaledObjectReconciler) checkTriggers(scaledObject *v1alpha1.ScaledObject) error { triggersCount := len(scaledObject.Spec.Triggers) if triggersCount > 1 { @@ -370,7 +368,7 @@ func (r *ScaledObjectReconciler) checkTriggers(scaledObject *kedav1alpha1.Scaled // checkReplicaCountBoundsAreValid checks that Idle/Min/Max ReplicaCount defined in ScaledObject are correctly specified // ie. that Min is not greater then Max or Idle greater or equal to Min -func (r *ScaledObjectReconciler) checkReplicaCountBoundsAreValid(scaledObject *kedav1alpha1.ScaledObject) error { +func (r *ScaledObjectReconciler) checkReplicaCountBoundsAreValid(scaledObject *v1alpha1.ScaledObject) error { min := int32(0) if scaledObject.Spec.MinReplicaCount != nil { min = *getHPAMinReplicas(scaledObject) @@ -389,7 +387,7 @@ func (r *ScaledObjectReconciler) checkReplicaCountBoundsAreValid(scaledObject *k } // ensureHPAForScaledObjectExists ensures that in cluster exist up-to-date HPA for specified ScaledObject, returns true if a new HPA was created -func (r *ScaledObjectReconciler) ensureHPAForScaledObjectExists(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject, gvkr *kedav1alpha1.GroupVersionKindResource) (bool, error) { +func (r *ScaledObjectReconciler) ensureHPAForScaledObjectExists(ctx context.Context, logger logr.Logger, scaledObject *v1alpha1.ScaledObject, gvkr *v1alpha1.GroupVersionKindResource) (bool, error) { var hpaName string if scaledObject.Status.HpaName != "" { hpaName = scaledObject.Status.HpaName @@ -433,7 +431,7 @@ func (r *ScaledObjectReconciler) ensureHPAForScaledObjectExists(ctx context.Cont return false, nil } -func isHpaRenamed(scaledObject *kedav1alpha1.ScaledObject, foundHpa *autoscalingv2.HorizontalPodAutoscaler) bool { +func isHpaRenamed(scaledObject *v1alpha1.ScaledObject, foundHpa *autoscalingv2.HorizontalPodAutoscaler) bool { // if HPA name defined in SO -> check if equals to the found HPA if scaledObject.Spec.Advanced != nil && scaledObject.Spec.Advanced.HorizontalPodAutoscalerConfig != nil && scaledObject.Spec.Advanced.HorizontalPodAutoscalerConfig.Name != "" { return scaledObject.Spec.Advanced.HorizontalPodAutoscalerConfig.Name != foundHpa.Name @@ -443,7 +441,7 @@ func isHpaRenamed(scaledObject *kedav1alpha1.ScaledObject, foundHpa *autoscaling } // requestScaleLoop tries to start ScaleLoop handler for the respective ScaledObject -func (r *ScaledObjectReconciler) requestScaleLoop(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject) error { +func (r *ScaledObjectReconciler) requestScaleLoop(ctx context.Context, logger logr.Logger, scaledObject *v1alpha1.ScaledObject) error { logger.V(1).Info("Notify scaleHandler of an update in scaledObject") key, err := cache.MetaNamespaceKeyFunc(scaledObject) @@ -463,7 +461,7 @@ func (r *ScaledObjectReconciler) requestScaleLoop(ctx context.Context, logger lo } // stopScaleLoop stops ScaleLoop handler for the respective ScaledObject -func (r *ScaledObjectReconciler) stopScaleLoop(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject) error { +func (r *ScaledObjectReconciler) stopScaleLoop(ctx context.Context, logger logr.Logger, scaledObject *v1alpha1.ScaledObject) error { key, err := cache.MetaNamespaceKeyFunc(scaledObject) if err != nil { logger.Error(err, "Error getting key for scaledObject") @@ -479,7 +477,7 @@ func (r *ScaledObjectReconciler) stopScaleLoop(ctx context.Context, logger logr. } // scaledObjectGenerationChanged returns true if ScaledObject's Generation was changed, ie. ScaledObject.Spec was changed -func (r *ScaledObjectReconciler) scaledObjectGenerationChanged(logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject) (bool, error) { +func (r *ScaledObjectReconciler) scaledObjectGenerationChanged(logger logr.Logger, scaledObject *v1alpha1.ScaledObject) (bool, error) { key, err := cache.MetaNamespaceKeyFunc(scaledObject) if err != nil { logger.Error(err, "Error getting key for scaledObject") @@ -496,7 +494,7 @@ func (r *ScaledObjectReconciler) scaledObjectGenerationChanged(logger logr.Logge return true, nil } -func (r *ScaledObjectReconciler) updatePromMetrics(scaledObject *kedav1alpha1.ScaledObject, namespacedName string) { +func (r *ScaledObjectReconciler) updatePromMetrics(scaledObject *v1alpha1.ScaledObject, namespacedName string) { scaledObjectPromMetricsLock.Lock() defer scaledObjectPromMetricsLock.Unlock() diff --git a/go.mod b/go.mod index 54ee167110f..f7d67e69cfe 100644 --- a/go.mod +++ b/go.mod @@ -49,7 +49,9 @@ require ( github.com/mitchellh/hashstructure v1.1.0 github.com/newrelic/newrelic-client-go v1.1.0 github.com/onsi/ginkgo v1.16.5 + github.com/onsi/ginkgo/v2 v2.5.0 github.com/onsi/gomega v1.24.1 + github.com/open-policy-agent/cert-controller v0.5.0 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.14.0 @@ -274,15 +276,15 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.23.0 // indirect - golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect + golang.org/x/crypto v0.1.0 // indirect + golang.org/x/mod v0.6.0 // indirect golang.org/x/net v0.2.0 // indirect golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.3.0 // indirect golang.org/x/term v0.3.0 // indirect golang.org/x/text v0.5.0 // indirect golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect - golang.org/x/tools v0.1.12 // indirect + golang.org/x/tools v0.2.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 7ced9b1bddb..8e08fc7e76d 100644 --- a/go.sum +++ b/go.sum @@ -727,6 +727,7 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.5.0 h1:TRtrvv2vdQqzkwrQ1ke6vtXf7IK34RBUJafIy1wMwls= +github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= @@ -734,6 +735,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= +github.com/open-policy-agent/cert-controller v0.5.0 h1:j8WiSh+UYv2GdxlcxgfXv+QxZQYwdbXV3KsZ4fZsM5A= +github.com/open-policy-agent/cert-controller v0.5.0/go.mod h1:uOQW+2tMU51vSxy1Yt162oVUTMdqLuotC0aObQxrh6k= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/otiai10/copy v1.0.2/go.mod h1:c7RpqBkwMom4bYTSkLSym4VSJz/XtncWRAj/J4PEIMY= github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE= @@ -1007,10 +1010,62 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220513224357-95641704303c/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220725212005-46097bf591d3/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1163,8 +1218,9 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1338,6 +1394,7 @@ k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.80.2-0.20221028030830-9ae4992afb54 h1:hWRbsoRWt44OEBnYUd4ceLy4ofBoh+p9vauWp/I5Gdg= k8s.io/klog/v2 v2.80.2-0.20221028030830-9ae4992afb54/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-aggregator v0.23.2 h1:6CoZZqNdFc9benrgSJJ0GQGgFtKjI0y3UwlBbioXtc8= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20221123214604-86e75ddd809a h1:UR2YSPKAb8j3uL2yK8V+t2ElG4RoBxhJTxa5gg0ZtSo= k8s.io/kube-openapi v0.0.0-20221123214604-86e75ddd809a/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt index 9b9309580e4..81b42562136 100644 --- a/hack/boilerplate.go.txt +++ b/hack/boilerplate.go.txt @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/clientset/versioned/clientset.go b/pkg/generated/clientset/versioned/clientset.go index 5649eb80417..65a98ccdf87 100644 --- a/pkg/generated/clientset/versioned/clientset.go +++ b/pkg/generated/clientset/versioned/clientset.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/clientset/versioned/doc.go b/pkg/generated/clientset/versioned/doc.go index 71b4c83f182..f319a7e4f16 100644 --- a/pkg/generated/clientset/versioned/doc.go +++ b/pkg/generated/clientset/versioned/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/clientset/versioned/fake/clientset_generated.go b/pkg/generated/clientset/versioned/fake/clientset_generated.go index 133039cc4f0..bfa8e619a58 100644 --- a/pkg/generated/clientset/versioned/fake/clientset_generated.go +++ b/pkg/generated/clientset/versioned/fake/clientset_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/clientset/versioned/fake/doc.go b/pkg/generated/clientset/versioned/fake/doc.go index efcff48b56a..05677d419b9 100644 --- a/pkg/generated/clientset/versioned/fake/doc.go +++ b/pkg/generated/clientset/versioned/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/clientset/versioned/fake/register.go b/pkg/generated/clientset/versioned/fake/register.go index fe94b0abc14..35acdf52a53 100644 --- a/pkg/generated/clientset/versioned/fake/register.go +++ b/pkg/generated/clientset/versioned/fake/register.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/clientset/versioned/scheme/doc.go b/pkg/generated/clientset/versioned/scheme/doc.go index be55288e2b8..9472c556e15 100644 --- a/pkg/generated/clientset/versioned/scheme/doc.go +++ b/pkg/generated/clientset/versioned/scheme/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/clientset/versioned/scheme/register.go b/pkg/generated/clientset/versioned/scheme/register.go index 6850fd82efe..c43470e203e 100644 --- a/pkg/generated/clientset/versioned/scheme/register.go +++ b/pkg/generated/clientset/versioned/scheme/register.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/clustertriggerauthentication.go b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/clustertriggerauthentication.go index 2ccefa89719..b59f2955d9a 100644 --- a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/clustertriggerauthentication.go +++ b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/clustertriggerauthentication.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/doc.go b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/doc.go index 6854f7cc67e..6fd099df002 100644 --- a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/doc.go +++ b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/doc.go b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/doc.go index bc1b004cae8..41dbc0f3c40 100644 --- a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/doc.go +++ b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_clustertriggerauthentication.go b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_clustertriggerauthentication.go index 1ab640dcb4c..0e5f2f3e443 100644 --- a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_clustertriggerauthentication.go +++ b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_clustertriggerauthentication.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_keda_client.go b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_keda_client.go index 94e4ecbe653..3a04f230033 100644 --- a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_keda_client.go +++ b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_keda_client.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_scaledjob.go b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_scaledjob.go index ad1a5d686de..7fbd0b4b63f 100644 --- a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_scaledjob.go +++ b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_scaledjob.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_scaledobject.go b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_scaledobject.go index 21caff8586b..1216f5e9c89 100644 --- a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_scaledobject.go +++ b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_scaledobject.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_triggerauthentication.go b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_triggerauthentication.go index e0b8182ad09..4a6b8a2bb34 100644 --- a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_triggerauthentication.go +++ b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/fake/fake_triggerauthentication.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/generated_expansion.go b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/generated_expansion.go index 52811ec82bd..4bf70708dad 100644 --- a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/generated_expansion.go +++ b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/generated_expansion.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/keda_client.go b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/keda_client.go index 8479325dc2f..b765a8eae7c 100644 --- a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/keda_client.go +++ b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/keda_client.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/scaledjob.go b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/scaledjob.go index 27308ab82e0..c5493ad4db6 100644 --- a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/scaledjob.go +++ b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/scaledjob.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/scaledobject.go b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/scaledobject.go index 14768187689..eac695b9ea3 100644 --- a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/scaledobject.go +++ b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/scaledobject.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/triggerauthentication.go b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/triggerauthentication.go index c1704b2a284..81b9232e0fc 100644 --- a/pkg/generated/clientset/versioned/typed/keda/v1alpha1/triggerauthentication.go +++ b/pkg/generated/clientset/versioned/typed/keda/v1alpha1/triggerauthentication.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/informers/externalversions/factory.go b/pkg/generated/informers/externalversions/factory.go index 6f8566b3837..6274acbbe74 100644 --- a/pkg/generated/informers/externalversions/factory.go +++ b/pkg/generated/informers/externalversions/factory.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/informers/externalversions/generic.go b/pkg/generated/informers/externalversions/generic.go index 101510b5e0b..69ffbf1f4c3 100644 --- a/pkg/generated/informers/externalversions/generic.go +++ b/pkg/generated/informers/externalversions/generic.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go b/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go index d6cef41dd02..41f55602c7d 100644 --- a/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go +++ b/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/informers/externalversions/keda/interface.go b/pkg/generated/informers/externalversions/keda/interface.go index 9c24e4b4290..a256b990e9e 100644 --- a/pkg/generated/informers/externalversions/keda/interface.go +++ b/pkg/generated/informers/externalversions/keda/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/informers/externalversions/keda/v1alpha1/clustertriggerauthentication.go b/pkg/generated/informers/externalversions/keda/v1alpha1/clustertriggerauthentication.go index 9b92b1f38a4..a98fea79eee 100644 --- a/pkg/generated/informers/externalversions/keda/v1alpha1/clustertriggerauthentication.go +++ b/pkg/generated/informers/externalversions/keda/v1alpha1/clustertriggerauthentication.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/informers/externalversions/keda/v1alpha1/interface.go b/pkg/generated/informers/externalversions/keda/v1alpha1/interface.go index dde7c7bc063..3b8cbce7f00 100644 --- a/pkg/generated/informers/externalversions/keda/v1alpha1/interface.go +++ b/pkg/generated/informers/externalversions/keda/v1alpha1/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/informers/externalversions/keda/v1alpha1/scaledjob.go b/pkg/generated/informers/externalversions/keda/v1alpha1/scaledjob.go index fd19d48318e..92d1e92db02 100644 --- a/pkg/generated/informers/externalversions/keda/v1alpha1/scaledjob.go +++ b/pkg/generated/informers/externalversions/keda/v1alpha1/scaledjob.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/informers/externalversions/keda/v1alpha1/scaledobject.go b/pkg/generated/informers/externalversions/keda/v1alpha1/scaledobject.go index 413c0d83f1b..ff8ac1ccbb6 100644 --- a/pkg/generated/informers/externalversions/keda/v1alpha1/scaledobject.go +++ b/pkg/generated/informers/externalversions/keda/v1alpha1/scaledobject.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/informers/externalversions/keda/v1alpha1/triggerauthentication.go b/pkg/generated/informers/externalversions/keda/v1alpha1/triggerauthentication.go index 96757d97bc7..fdc79159c0c 100644 --- a/pkg/generated/informers/externalversions/keda/v1alpha1/triggerauthentication.go +++ b/pkg/generated/informers/externalversions/keda/v1alpha1/triggerauthentication.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/listers/keda/v1alpha1/clustertriggerauthentication.go b/pkg/generated/listers/keda/v1alpha1/clustertriggerauthentication.go index 820893e6ac1..b98825e2444 100644 --- a/pkg/generated/listers/keda/v1alpha1/clustertriggerauthentication.go +++ b/pkg/generated/listers/keda/v1alpha1/clustertriggerauthentication.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/listers/keda/v1alpha1/expansion_generated.go b/pkg/generated/listers/keda/v1alpha1/expansion_generated.go index 7ef7c99c594..6dee045ea17 100644 --- a/pkg/generated/listers/keda/v1alpha1/expansion_generated.go +++ b/pkg/generated/listers/keda/v1alpha1/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/listers/keda/v1alpha1/scaledjob.go b/pkg/generated/listers/keda/v1alpha1/scaledjob.go index 4e386424499..def46b3ea21 100644 --- a/pkg/generated/listers/keda/v1alpha1/scaledjob.go +++ b/pkg/generated/listers/keda/v1alpha1/scaledjob.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/listers/keda/v1alpha1/scaledobject.go b/pkg/generated/listers/keda/v1alpha1/scaledobject.go index e91896fda3f..91a9e93016a 100644 --- a/pkg/generated/listers/keda/v1alpha1/scaledobject.go +++ b/pkg/generated/listers/keda/v1alpha1/scaledobject.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/generated/listers/keda/v1alpha1/triggerauthentication.go b/pkg/generated/listers/keda/v1alpha1/triggerauthentication.go index ad36edfe5ee..51638c94289 100644 --- a/pkg/generated/listers/keda/v1alpha1/triggerauthentication.go +++ b/pkg/generated/listers/keda/v1alpha1/triggerauthentication.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 7241bb6a14e..9c95debb744 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -31,7 +31,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/custom-metrics-apiserver/pkg/provider" - kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1" + "github.com/kedacore/keda/v2/apis/keda/v1alpha1" "github.com/kedacore/keda/v2/pkg/fallback" "github.com/kedacore/keda/v2/pkg/metricsservice" prommetrics "github.com/kedacore/keda/v2/pkg/prommetrics/adapter" @@ -98,7 +98,7 @@ func (p *KedaProvider) GetExternalMetric(ctx context.Context, namespace string, } // selector is in form: `scaledobject.keda.sh/name: scaledobject-name` - scaledObjectName := selector.Get("scaledobject.keda.sh/name") + scaledObjectName := selector.Get(v1alpha1.ScaledObjectOwnerAnnotation) metrics, promMetrics, err := p.grpcClient.GetMetrics(ctx, scaledObjectName, namespace, info.Metric) logger.V(1).WithValues("scaledObjectName", scaledObjectName, "scaledObjectNamespace", namespace, "metrics", metrics).Info("Receiving metrics") @@ -128,7 +128,7 @@ func (p *KedaProvider) GetExternalMetric(ctx context.Context, namespace string, // ------ Deprecated way of getting metric directly from MS ------ // // --------------------------------------------------------------- // // Get Metrics by querying directly the external service - scaledObjects := &kedav1alpha1.ScaledObjectList{} + scaledObjects := &v1alpha1.ScaledObjectList{} opts := []client.ListOption{ client.InNamespace(namespace), client.MatchingLabels(selector), diff --git a/pkg/scaling/resolver/scale_resolvers.go b/pkg/scaling/resolver/scale_resolvers.go index afd58ea2fcf..9d71e619bee 100644 --- a/pkg/scaling/resolver/scale_resolvers.go +++ b/pkg/scaling/resolver/scale_resolvers.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/util/env_resolver.go b/pkg/util/env_resolver.go index 99b828dfe79..b8e9cfa8763 100644 --- a/pkg/util/env_resolver.go +++ b/pkg/util/env_resolver.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -77,6 +77,15 @@ func GetClusterObjectNamespace() (string, error) { return strData, nil } +// GetPodNamespace returns the namespace for the pod +func GetPodNamespace() string { + ns, found := os.LookupEnv("POD_NAMESPACE") + if !found { + return "keda" + } + return ns +} + // GetRestrictSecretAccess retrieves the value of the environment variable of KEDA_RESTRICT_SECRET_ACCESS func GetRestrictSecretAccess() string { return os.Getenv(RestrictSecretAccessEnvVar) diff --git a/tests/internals/scaled_object_validation/scaled_object_validation_test.go b/tests/internals/scaled_object_validation/scaled_object_validation_test.go new file mode 100644 index 00000000000..9fb88d14ba4 --- /dev/null +++ b/tests/internals/scaled_object_validation/scaled_object_validation_test.go @@ -0,0 +1,187 @@ +//go:build e2e +// +build e2e + +package cache_metrics_test + +import ( + "fmt" + "os" + "testing" + "text/template" + + "github.com/stretchr/testify/assert" + + . "github.com/kedacore/keda/v2/tests/helper" +) + +const ( + testName = "scaled-object-validation-test" +) + +var ( + testNamespace = fmt.Sprintf("%s-ns", testName) + deploymentName = fmt.Sprintf("%s-deployment", testName) + scaledObject1Name = fmt.Sprintf("%s-so1", testName) + scaledObject2Name = fmt.Sprintf("%s-so2", testName) + hpaName = fmt.Sprintf("%s-hpa", testName) +) + +type templateData struct { + TestNamespace string + DeploymentName string + ScaledObjectName string + HpaName string +} + +const ( + deploymentTemplate = ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{.DeploymentName}} + namespace: {{.TestNamespace}} + labels: + app: {{.DeploymentName}} +spec: + replicas: 1 + selector: + matchLabels: + app: {{.DeploymentName}} + template: + metadata: + labels: + app: {{.DeploymentName}} + spec: + containers: + - name: {{.DeploymentName}} + image: nginx + resources: + requests: + cpu: 10m +` + + scaledObjectTemplate = ` +apiVersion: keda.sh/v1alpha1 +kind: ScaledObject +metadata: + name: {{.ScaledObjectName}} + namespace: {{.TestNamespace}} +spec: + scaleTargetRef: + name: {{.DeploymentName}} + triggers: + - type: cpu + metadata: + type: Utilization + value: "50" +` + hpaTemplate = ` +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{.HpaName}} + namespace: {{.TestNamespace}} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{.DeploymentName}} + minReplicas: 1 + maxReplicas: 1 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 50 +` +) + +func TestScaledObjectValidations(t *testing.T) { + // setup + t.Log("--- setting up ---") + // Create kubernetes resources + kc := GetKubernetesClient(t) + data, templates := getTemplateData() + + CreateKubernetesResources(t, kc, testNamespace, data, templates) + + testWithNotScaledWorkload(t, data) + + testScaledWorkloadByOtherScaledObject(t, data) + + testScaledWorkloadByOtherHpa(t, data) + + DeleteKubernetesResources(t, kc, testNamespace, data, templates) +} + +func testWithNotScaledWorkload(t *testing.T, data templateData) { + t.Log("--- unscaled workload ---") + + data.ScaledObjectName = scaledObject1Name + err := kubectlApplyWithErrors(t, data, "scaledObjectTemplate", scaledObjectTemplate) + assert.NoErrorf(t, err, "cannot deploy the scaledObject - %s", err) + + KubectlDeleteWithTemplate(t, data, "scaledObjectTemplate", scaledObjectTemplate) +} + +func testScaledWorkloadByOtherScaledObject(t *testing.T, data templateData) { + t.Log("--- already scaled workload by other scaledobject---") + + data.ScaledObjectName = scaledObject1Name + err := kubectlApplyWithErrors(t, data, "scaledObjectTemplate", scaledObjectTemplate) + assert.NoErrorf(t, err, "cannot deploy the scaledObject - %s", err) + + data.ScaledObjectName = scaledObject2Name + err = kubectlApplyWithErrors(t, data, "scaledObjectTemplate", scaledObjectTemplate) + assert.Errorf(t, err, "can deploy the scaledObject - %s", err) + assert.Contains(t, err.Error(), fmt.Sprintf("the workload '%s' of type 'apps/v1/Deployment' is already managed by the ScaledObject '%s", deploymentName, scaledObject1Name)) + + data.ScaledObjectName = scaledObject1Name + KubectlDeleteWithTemplate(t, data, "scaledObjectTemplate", scaledObjectTemplate) +} + +func testScaledWorkloadByOtherHpa(t *testing.T, data templateData) { + t.Log("--- already scaled workload by other hpa---") + + data.HpaName = hpaName + err := kubectlApplyWithErrors(t, data, "hpaTemplate", hpaTemplate) + assert.NoErrorf(t, err, "cannot deploy the hpa - %s", err) + + data.ScaledObjectName = scaledObject1Name + err = kubectlApplyWithErrors(t, data, "scaledObjectTemplate", scaledObjectTemplate) + assert.Errorf(t, err, "can deploy the scaledObject - %s", err) + assert.Contains(t, err.Error(), fmt.Sprintf("the workload '%s' of type 'apps/v1/Deployment' is already managed by the hpa '%s", deploymentName, hpaName)) + + KubectlDeleteWithTemplate(t, data, "hpaTemplate", hpaTemplate) +} + +func kubectlApplyWithErrors(t *testing.T, data interface{}, templateName string, config string) error { + t.Logf("Applying template: %s", templateName) + + tmpl, err := template.New("kubernetes resource template").Parse(config) + assert.NoErrorf(t, err, "cannot parse template - %s", err) + + tempFile, err := os.CreateTemp("", templateName) + assert.NoErrorf(t, err, "cannot create temp file - %s", err) + if err != nil { + defer tempFile.Close() + defer os.Remove(tempFile.Name()) + } + + err = tmpl.Execute(tempFile, data) + assert.NoErrorf(t, err, "cannot insert data into template - %s", err) + + _, err = ExecuteCommand(fmt.Sprintf("kubectl apply -f %s", tempFile.Name())) + return err +} + +func getTemplateData() (templateData, []Template) { + return templateData{ + TestNamespace: testNamespace, + DeploymentName: deploymentName, + }, []Template{ + {Name: "deploymentTemplate", Config: deploymentTemplate}, + } +} diff --git a/vendor/github.com/go-logr/logr/funcr/funcr.go b/vendor/github.com/go-logr/logr/funcr/funcr.go new file mode 100644 index 00000000000..7accdb0c400 --- /dev/null +++ b/vendor/github.com/go-logr/logr/funcr/funcr.go @@ -0,0 +1,787 @@ +/* +Copyright 2021 The logr Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package funcr implements formatting of structured log messages and +// optionally captures the call site and timestamp. +// +// The simplest way to use it is via its implementation of a +// github.com/go-logr/logr.LogSink with output through an arbitrary +// "write" function. See New and NewJSON for details. +// +// Custom LogSinks +// +// For users who need more control, a funcr.Formatter can be embedded inside +// your own custom LogSink implementation. This is useful when the LogSink +// needs to implement additional methods, for example. +// +// Formatting +// +// This will respect logr.Marshaler, fmt.Stringer, and error interfaces for +// values which are being logged. When rendering a struct, funcr will use Go's +// standard JSON tags (all except "string"). +package funcr + +import ( + "bytes" + "encoding" + "fmt" + "path/filepath" + "reflect" + "runtime" + "strconv" + "strings" + "time" + + "github.com/go-logr/logr" +) + +// New returns a logr.Logger which is implemented by an arbitrary function. +func New(fn func(prefix, args string), opts Options) logr.Logger { + return logr.New(newSink(fn, NewFormatter(opts))) +} + +// NewJSON returns a logr.Logger which is implemented by an arbitrary function +// and produces JSON output. +func NewJSON(fn func(obj string), opts Options) logr.Logger { + fnWrapper := func(_, obj string) { + fn(obj) + } + return logr.New(newSink(fnWrapper, NewFormatterJSON(opts))) +} + +// Underlier exposes access to the underlying logging function. Since +// callers only have a logr.Logger, they have to know which +// implementation is in use, so this interface is less of an +// abstraction and more of a way to test type conversion. +type Underlier interface { + GetUnderlying() func(prefix, args string) +} + +func newSink(fn func(prefix, args string), formatter Formatter) logr.LogSink { + l := &fnlogger{ + Formatter: formatter, + write: fn, + } + // For skipping fnlogger.Info and fnlogger.Error. + l.Formatter.AddCallDepth(1) + return l +} + +// Options carries parameters which influence the way logs are generated. +type Options struct { + // LogCaller tells funcr to add a "caller" key to some or all log lines. + // This has some overhead, so some users might not want it. + LogCaller MessageClass + + // LogCallerFunc tells funcr to also log the calling function name. This + // has no effect if caller logging is not enabled (see Options.LogCaller). + LogCallerFunc bool + + // LogTimestamp tells funcr to add a "ts" key to log lines. This has some + // overhead, so some users might not want it. + LogTimestamp bool + + // TimestampFormat tells funcr how to render timestamps when LogTimestamp + // is enabled. If not specified, a default format will be used. For more + // details, see docs for Go's time.Layout. + TimestampFormat string + + // Verbosity tells funcr which V logs to produce. Higher values enable + // more logs. Info logs at or below this level will be written, while logs + // above this level will be discarded. + Verbosity int + + // RenderBuiltinsHook allows users to mutate the list of key-value pairs + // while a log line is being rendered. The kvList argument follows logr + // conventions - each pair of slice elements is comprised of a string key + // and an arbitrary value (verified and sanitized before calling this + // hook). The value returned must follow the same conventions. This hook + // can be used to audit or modify logged data. For example, you might want + // to prefix all of funcr's built-in keys with some string. This hook is + // only called for built-in (provided by funcr itself) key-value pairs. + // Equivalent hooks are offered for key-value pairs saved via + // logr.Logger.WithValues or Formatter.AddValues (see RenderValuesHook) and + // for user-provided pairs (see RenderArgsHook). + RenderBuiltinsHook func(kvList []interface{}) []interface{} + + // RenderValuesHook is the same as RenderBuiltinsHook, except that it is + // only called for key-value pairs saved via logr.Logger.WithValues. See + // RenderBuiltinsHook for more details. + RenderValuesHook func(kvList []interface{}) []interface{} + + // RenderArgsHook is the same as RenderBuiltinsHook, except that it is only + // called for key-value pairs passed directly to Info and Error. See + // RenderBuiltinsHook for more details. + RenderArgsHook func(kvList []interface{}) []interface{} + + // MaxLogDepth tells funcr how many levels of nested fields (e.g. a struct + // that contains a struct, etc.) it may log. Every time it finds a struct, + // slice, array, or map the depth is increased by one. When the maximum is + // reached, the value will be converted to a string indicating that the max + // depth has been exceeded. If this field is not specified, a default + // value will be used. + MaxLogDepth int +} + +// MessageClass indicates which category or categories of messages to consider. +type MessageClass int + +const ( + // None ignores all message classes. + None MessageClass = iota + // All considers all message classes. + All + // Info only considers info messages. + Info + // Error only considers error messages. + Error +) + +// fnlogger inherits some of its LogSink implementation from Formatter +// and just needs to add some glue code. +type fnlogger struct { + Formatter + write func(prefix, args string) +} + +func (l fnlogger) WithName(name string) logr.LogSink { + l.Formatter.AddName(name) + return &l +} + +func (l fnlogger) WithValues(kvList ...interface{}) logr.LogSink { + l.Formatter.AddValues(kvList) + return &l +} + +func (l fnlogger) WithCallDepth(depth int) logr.LogSink { + l.Formatter.AddCallDepth(depth) + return &l +} + +func (l fnlogger) Info(level int, msg string, kvList ...interface{}) { + prefix, args := l.FormatInfo(level, msg, kvList) + l.write(prefix, args) +} + +func (l fnlogger) Error(err error, msg string, kvList ...interface{}) { + prefix, args := l.FormatError(err, msg, kvList) + l.write(prefix, args) +} + +func (l fnlogger) GetUnderlying() func(prefix, args string) { + return l.write +} + +// Assert conformance to the interfaces. +var _ logr.LogSink = &fnlogger{} +var _ logr.CallDepthLogSink = &fnlogger{} +var _ Underlier = &fnlogger{} + +// NewFormatter constructs a Formatter which emits a JSON-like key=value format. +func NewFormatter(opts Options) Formatter { + return newFormatter(opts, outputKeyValue) +} + +// NewFormatterJSON constructs a Formatter which emits strict JSON. +func NewFormatterJSON(opts Options) Formatter { + return newFormatter(opts, outputJSON) +} + +// Defaults for Options. +const defaultTimestampFormat = "2006-01-02 15:04:05.000000" +const defaultMaxLogDepth = 16 + +func newFormatter(opts Options, outfmt outputFormat) Formatter { + if opts.TimestampFormat == "" { + opts.TimestampFormat = defaultTimestampFormat + } + if opts.MaxLogDepth == 0 { + opts.MaxLogDepth = defaultMaxLogDepth + } + f := Formatter{ + outputFormat: outfmt, + prefix: "", + values: nil, + depth: 0, + opts: opts, + } + return f +} + +// Formatter is an opaque struct which can be embedded in a LogSink +// implementation. It should be constructed with NewFormatter. Some of +// its methods directly implement logr.LogSink. +type Formatter struct { + outputFormat outputFormat + prefix string + values []interface{} + valuesStr string + depth int + opts Options +} + +// outputFormat indicates which outputFormat to use. +type outputFormat int + +const ( + // outputKeyValue emits a JSON-like key=value format, but not strict JSON. + outputKeyValue outputFormat = iota + // outputJSON emits strict JSON. + outputJSON +) + +// PseudoStruct is a list of key-value pairs that gets logged as a struct. +type PseudoStruct []interface{} + +// render produces a log line, ready to use. +func (f Formatter) render(builtins, args []interface{}) string { + // Empirically bytes.Buffer is faster than strings.Builder for this. + buf := bytes.NewBuffer(make([]byte, 0, 1024)) + if f.outputFormat == outputJSON { + buf.WriteByte('{') + } + vals := builtins + if hook := f.opts.RenderBuiltinsHook; hook != nil { + vals = hook(f.sanitize(vals)) + } + f.flatten(buf, vals, false, false) // keys are ours, no need to escape + continuing := len(builtins) > 0 + if len(f.valuesStr) > 0 { + if continuing { + if f.outputFormat == outputJSON { + buf.WriteByte(',') + } else { + buf.WriteByte(' ') + } + } + continuing = true + buf.WriteString(f.valuesStr) + } + vals = args + if hook := f.opts.RenderArgsHook; hook != nil { + vals = hook(f.sanitize(vals)) + } + f.flatten(buf, vals, continuing, true) // escape user-provided keys + if f.outputFormat == outputJSON { + buf.WriteByte('}') + } + return buf.String() +} + +// flatten renders a list of key-value pairs into a buffer. If continuing is +// true, it assumes that the buffer has previous values and will emit a +// separator (which depends on the output format) before the first pair it +// writes. If escapeKeys is true, the keys are assumed to have +// non-JSON-compatible characters in them and must be evaluated for escapes. +// +// This function returns a potentially modified version of kvList, which +// ensures that there is a value for every key (adding a value if needed) and +// that each key is a string (substituting a key if needed). +func (f Formatter) flatten(buf *bytes.Buffer, kvList []interface{}, continuing bool, escapeKeys bool) []interface{} { + // This logic overlaps with sanitize() but saves one type-cast per key, + // which can be measurable. + if len(kvList)%2 != 0 { + kvList = append(kvList, noValue) + } + for i := 0; i < len(kvList); i += 2 { + k, ok := kvList[i].(string) + if !ok { + k = f.nonStringKey(kvList[i]) + kvList[i] = k + } + v := kvList[i+1] + + if i > 0 || continuing { + if f.outputFormat == outputJSON { + buf.WriteByte(',') + } else { + // In theory the format could be something we don't understand. In + // practice, we control it, so it won't be. + buf.WriteByte(' ') + } + } + + if escapeKeys { + buf.WriteString(prettyString(k)) + } else { + // this is faster + buf.WriteByte('"') + buf.WriteString(k) + buf.WriteByte('"') + } + if f.outputFormat == outputJSON { + buf.WriteByte(':') + } else { + buf.WriteByte('=') + } + buf.WriteString(f.pretty(v)) + } + return kvList +} + +func (f Formatter) pretty(value interface{}) string { + return f.prettyWithFlags(value, 0, 0) +} + +const ( + flagRawStruct = 0x1 // do not print braces on structs +) + +// TODO: This is not fast. Most of the overhead goes here. +func (f Formatter) prettyWithFlags(value interface{}, flags uint32, depth int) string { + if depth > f.opts.MaxLogDepth { + return `""` + } + + // Handle types that take full control of logging. + if v, ok := value.(logr.Marshaler); ok { + // Replace the value with what the type wants to get logged. + // That then gets handled below via reflection. + value = invokeMarshaler(v) + } + + // Handle types that want to format themselves. + switch v := value.(type) { + case fmt.Stringer: + value = invokeStringer(v) + case error: + value = invokeError(v) + } + + // Handling the most common types without reflect is a small perf win. + switch v := value.(type) { + case bool: + return strconv.FormatBool(v) + case string: + return prettyString(v) + case int: + return strconv.FormatInt(int64(v), 10) + case int8: + return strconv.FormatInt(int64(v), 10) + case int16: + return strconv.FormatInt(int64(v), 10) + case int32: + return strconv.FormatInt(int64(v), 10) + case int64: + return strconv.FormatInt(int64(v), 10) + case uint: + return strconv.FormatUint(uint64(v), 10) + case uint8: + return strconv.FormatUint(uint64(v), 10) + case uint16: + return strconv.FormatUint(uint64(v), 10) + case uint32: + return strconv.FormatUint(uint64(v), 10) + case uint64: + return strconv.FormatUint(v, 10) + case uintptr: + return strconv.FormatUint(uint64(v), 10) + case float32: + return strconv.FormatFloat(float64(v), 'f', -1, 32) + case float64: + return strconv.FormatFloat(v, 'f', -1, 64) + case complex64: + return `"` + strconv.FormatComplex(complex128(v), 'f', -1, 64) + `"` + case complex128: + return `"` + strconv.FormatComplex(v, 'f', -1, 128) + `"` + case PseudoStruct: + buf := bytes.NewBuffer(make([]byte, 0, 1024)) + v = f.sanitize(v) + if flags&flagRawStruct == 0 { + buf.WriteByte('{') + } + for i := 0; i < len(v); i += 2 { + if i > 0 { + buf.WriteByte(',') + } + k, _ := v[i].(string) // sanitize() above means no need to check success + // arbitrary keys might need escaping + buf.WriteString(prettyString(k)) + buf.WriteByte(':') + buf.WriteString(f.prettyWithFlags(v[i+1], 0, depth+1)) + } + if flags&flagRawStruct == 0 { + buf.WriteByte('}') + } + return buf.String() + } + + buf := bytes.NewBuffer(make([]byte, 0, 256)) + t := reflect.TypeOf(value) + if t == nil { + return "null" + } + v := reflect.ValueOf(value) + switch t.Kind() { + case reflect.Bool: + return strconv.FormatBool(v.Bool()) + case reflect.String: + return prettyString(v.String()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return strconv.FormatInt(int64(v.Int()), 10) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return strconv.FormatUint(uint64(v.Uint()), 10) + case reflect.Float32: + return strconv.FormatFloat(float64(v.Float()), 'f', -1, 32) + case reflect.Float64: + return strconv.FormatFloat(v.Float(), 'f', -1, 64) + case reflect.Complex64: + return `"` + strconv.FormatComplex(complex128(v.Complex()), 'f', -1, 64) + `"` + case reflect.Complex128: + return `"` + strconv.FormatComplex(v.Complex(), 'f', -1, 128) + `"` + case reflect.Struct: + if flags&flagRawStruct == 0 { + buf.WriteByte('{') + } + for i := 0; i < t.NumField(); i++ { + fld := t.Field(i) + if fld.PkgPath != "" { + // reflect says this field is only defined for non-exported fields. + continue + } + if !v.Field(i).CanInterface() { + // reflect isn't clear exactly what this means, but we can't use it. + continue + } + name := "" + omitempty := false + if tag, found := fld.Tag.Lookup("json"); found { + if tag == "-" { + continue + } + if comma := strings.Index(tag, ","); comma != -1 { + if n := tag[:comma]; n != "" { + name = n + } + rest := tag[comma:] + if strings.Contains(rest, ",omitempty,") || strings.HasSuffix(rest, ",omitempty") { + omitempty = true + } + } else { + name = tag + } + } + if omitempty && isEmpty(v.Field(i)) { + continue + } + if i > 0 { + buf.WriteByte(',') + } + if fld.Anonymous && fld.Type.Kind() == reflect.Struct && name == "" { + buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), flags|flagRawStruct, depth+1)) + continue + } + if name == "" { + name = fld.Name + } + // field names can't contain characters which need escaping + buf.WriteByte('"') + buf.WriteString(name) + buf.WriteByte('"') + buf.WriteByte(':') + buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), 0, depth+1)) + } + if flags&flagRawStruct == 0 { + buf.WriteByte('}') + } + return buf.String() + case reflect.Slice, reflect.Array: + buf.WriteByte('[') + for i := 0; i < v.Len(); i++ { + if i > 0 { + buf.WriteByte(',') + } + e := v.Index(i) + buf.WriteString(f.prettyWithFlags(e.Interface(), 0, depth+1)) + } + buf.WriteByte(']') + return buf.String() + case reflect.Map: + buf.WriteByte('{') + // This does not sort the map keys, for best perf. + it := v.MapRange() + i := 0 + for it.Next() { + if i > 0 { + buf.WriteByte(',') + } + // If a map key supports TextMarshaler, use it. + keystr := "" + if m, ok := it.Key().Interface().(encoding.TextMarshaler); ok { + txt, err := m.MarshalText() + if err != nil { + keystr = fmt.Sprintf("", err.Error()) + } else { + keystr = string(txt) + } + keystr = prettyString(keystr) + } else { + // prettyWithFlags will produce already-escaped values + keystr = f.prettyWithFlags(it.Key().Interface(), 0, depth+1) + if t.Key().Kind() != reflect.String { + // JSON only does string keys. Unlike Go's standard JSON, we'll + // convert just about anything to a string. + keystr = prettyString(keystr) + } + } + buf.WriteString(keystr) + buf.WriteByte(':') + buf.WriteString(f.prettyWithFlags(it.Value().Interface(), 0, depth+1)) + i++ + } + buf.WriteByte('}') + return buf.String() + case reflect.Ptr, reflect.Interface: + if v.IsNil() { + return "null" + } + return f.prettyWithFlags(v.Elem().Interface(), 0, depth) + } + return fmt.Sprintf(`""`, t.Kind().String()) +} + +func prettyString(s string) string { + // Avoid escaping (which does allocations) if we can. + if needsEscape(s) { + return strconv.Quote(s) + } + b := bytes.NewBuffer(make([]byte, 0, 1024)) + b.WriteByte('"') + b.WriteString(s) + b.WriteByte('"') + return b.String() +} + +// needsEscape determines whether the input string needs to be escaped or not, +// without doing any allocations. +func needsEscape(s string) bool { + for _, r := range s { + if !strconv.IsPrint(r) || r == '\\' || r == '"' { + return true + } + } + return false +} + +func isEmpty(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Complex64, reflect.Complex128: + return v.Complex() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + return false +} + +func invokeMarshaler(m logr.Marshaler) (ret interface{}) { + defer func() { + if r := recover(); r != nil { + ret = fmt.Sprintf("", r) + } + }() + return m.MarshalLog() +} + +func invokeStringer(s fmt.Stringer) (ret string) { + defer func() { + if r := recover(); r != nil { + ret = fmt.Sprintf("", r) + } + }() + return s.String() +} + +func invokeError(e error) (ret string) { + defer func() { + if r := recover(); r != nil { + ret = fmt.Sprintf("", r) + } + }() + return e.Error() +} + +// Caller represents the original call site for a log line, after considering +// logr.Logger.WithCallDepth and logr.Logger.WithCallStackHelper. The File and +// Line fields will always be provided, while the Func field is optional. +// Users can set the render hook fields in Options to examine logged key-value +// pairs, one of which will be {"caller", Caller} if the Options.LogCaller +// field is enabled for the given MessageClass. +type Caller struct { + // File is the basename of the file for this call site. + File string `json:"file"` + // Line is the line number in the file for this call site. + Line int `json:"line"` + // Func is the function name for this call site, or empty if + // Options.LogCallerFunc is not enabled. + Func string `json:"function,omitempty"` +} + +func (f Formatter) caller() Caller { + // +1 for this frame, +1 for Info/Error. + pc, file, line, ok := runtime.Caller(f.depth + 2) + if !ok { + return Caller{"", 0, ""} + } + fn := "" + if f.opts.LogCallerFunc { + if fp := runtime.FuncForPC(pc); fp != nil { + fn = fp.Name() + } + } + + return Caller{filepath.Base(file), line, fn} +} + +const noValue = "" + +func (f Formatter) nonStringKey(v interface{}) string { + return fmt.Sprintf("", f.snippet(v)) +} + +// snippet produces a short snippet string of an arbitrary value. +func (f Formatter) snippet(v interface{}) string { + const snipLen = 16 + + snip := f.pretty(v) + if len(snip) > snipLen { + snip = snip[:snipLen] + } + return snip +} + +// sanitize ensures that a list of key-value pairs has a value for every key +// (adding a value if needed) and that each key is a string (substituting a key +// if needed). +func (f Formatter) sanitize(kvList []interface{}) []interface{} { + if len(kvList)%2 != 0 { + kvList = append(kvList, noValue) + } + for i := 0; i < len(kvList); i += 2 { + _, ok := kvList[i].(string) + if !ok { + kvList[i] = f.nonStringKey(kvList[i]) + } + } + return kvList +} + +// Init configures this Formatter from runtime info, such as the call depth +// imposed by logr itself. +// Note that this receiver is a pointer, so depth can be saved. +func (f *Formatter) Init(info logr.RuntimeInfo) { + f.depth += info.CallDepth +} + +// Enabled checks whether an info message at the given level should be logged. +func (f Formatter) Enabled(level int) bool { + return level <= f.opts.Verbosity +} + +// GetDepth returns the current depth of this Formatter. This is useful for +// implementations which do their own caller attribution. +func (f Formatter) GetDepth() int { + return f.depth +} + +// FormatInfo renders an Info log message into strings. The prefix will be +// empty when no names were set (via AddNames), or when the output is +// configured for JSON. +func (f Formatter) FormatInfo(level int, msg string, kvList []interface{}) (prefix, argsStr string) { + args := make([]interface{}, 0, 64) // using a constant here impacts perf + prefix = f.prefix + if f.outputFormat == outputJSON { + args = append(args, "logger", prefix) + prefix = "" + } + if f.opts.LogTimestamp { + args = append(args, "ts", time.Now().Format(f.opts.TimestampFormat)) + } + if policy := f.opts.LogCaller; policy == All || policy == Info { + args = append(args, "caller", f.caller()) + } + args = append(args, "level", level, "msg", msg) + return prefix, f.render(args, kvList) +} + +// FormatError renders an Error log message into strings. The prefix will be +// empty when no names were set (via AddNames), or when the output is +// configured for JSON. +func (f Formatter) FormatError(err error, msg string, kvList []interface{}) (prefix, argsStr string) { + args := make([]interface{}, 0, 64) // using a constant here impacts perf + prefix = f.prefix + if f.outputFormat == outputJSON { + args = append(args, "logger", prefix) + prefix = "" + } + if f.opts.LogTimestamp { + args = append(args, "ts", time.Now().Format(f.opts.TimestampFormat)) + } + if policy := f.opts.LogCaller; policy == All || policy == Error { + args = append(args, "caller", f.caller()) + } + args = append(args, "msg", msg) + var loggableErr interface{} + if err != nil { + loggableErr = err.Error() + } + args = append(args, "error", loggableErr) + return f.prefix, f.render(args, kvList) +} + +// AddName appends the specified name. funcr uses '/' characters to separate +// name elements. Callers should not pass '/' in the provided name string, but +// this library does not actually enforce that. +func (f *Formatter) AddName(name string) { + if len(f.prefix) > 0 { + f.prefix += "/" + } + f.prefix += name +} + +// AddValues adds key-value pairs to the set of saved values to be logged with +// each log line. +func (f *Formatter) AddValues(kvList []interface{}) { + // Three slice args forces a copy. + n := len(f.values) + f.values = append(f.values[:n:n], kvList...) + + vals := f.values + if hook := f.opts.RenderValuesHook; hook != nil { + vals = hook(f.sanitize(vals)) + } + + // Pre-render values, so we don't have to do it on each Info/Error call. + buf := bytes.NewBuffer(make([]byte, 0, 1024)) + f.flatten(buf, vals, false, true) // escape user-provided keys + f.valuesStr = buf.String() +} + +// AddCallDepth increases the number of stack-frames to skip when attributing +// the log line to a file and line. +func (f *Formatter) AddCallDepth(depth int) { + f.depth += depth +} diff --git a/vendor/github.com/onsi/ginkgo/v2/.gitignore b/vendor/github.com/onsi/ginkgo/v2/.gitignore new file mode 100644 index 00000000000..edf0231cdf1 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +TODO.md +tmp/**/* +*.coverprofile +.vscode +.idea/ +*.log \ No newline at end of file diff --git a/vendor/github.com/onsi/ginkgo/v2/CHANGELOG.md b/vendor/github.com/onsi/ginkgo/v2/CHANGELOG.md new file mode 100644 index 00000000000..45c6f70637b --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/CHANGELOG.md @@ -0,0 +1,593 @@ +## 2.5.0 + +### Ginkgo output now includes a timeline-view of the spec + +This commit changes Ginkgo's default output. Spec details are now +presented as a **timeline** that includes events that occur during the spec +lifecycle interleaved with any GinkgoWriter content. This makes is much easier +to understand the flow of a spec and where a given failure occurs. + +The --progress, --slow-spec-threshold, --always-emit-ginkgo-writer flags +and the SuppressProgressReporting decorator have all been deprecated. Instead +the existing -v and -vv flags better capture the level of verbosity to display. However, +a new --show-node-events flag is added to include node `> Enter` and `< Exit` events +in the spec timeline. + +In addition, JUnit reports now include the timeline (rendered with -vv) and custom JUnit +reports can be configured and generated using +`GenerateJUnitReportWithConfig(report types.Report, dst string, config JunitReportConfig)` + +Code should continue to work unchanged with this version of Ginkgo - however if you have tooling that +was relying on the specific output format of Ginkgo you _may_ run into issues. Ginkgo's console output is not guaranteed to be stable for tooling and automation purposes. You should, instead, use Ginkgo's JSON format +to build tooling on top of as it has stronger guarantees to be stable from version to version. + +### Features +- Provide details about which timeout expired [0f2fa27] + +### Fixes +- Add Support Policy to docs [c70867a] + +### Maintenance +- Bump github.com/onsi/gomega from 1.22.1 to 1.23.0 (#1070) [bb3b4e2] + +## 2.4.0 + +### Features + +- DeferCleanup supports functions with multiple-return values [5e33c75] +- Add GinkgoLogr (#1067) [bf78c28] +- Introduction of 'MustPassRepeatedly' decorator (#1051) [047c02f] + +### Fixes +- correcting some typos (#1064) [1403d3c] +- fix flaky internal_integration interupt specs [2105ba3] +- Correct busted link in README [be6b5b9] + +### Maintenance +- Bump actions/checkout from 2 to 3 (#1062) [8a2f483] +- Bump golang.org/x/tools from 0.1.12 to 0.2.0 (#1065) [529c4e8] +- Bump github/codeql-action from 1 to 2 (#1061) [da09146] +- Bump actions/setup-go from 2 to 3 (#1060) [918040d] +- Bump github.com/onsi/gomega from 1.22.0 to 1.22.1 (#1053) [2098e4d] +- Bump nokogiri from 1.13.8 to 1.13.9 in /docs (#1066) [1d74122] +- Add GHA to dependabot config [4442772] + +## 2.3.1 + +## Fixes +Several users were invoking `ginkgo` by installing the latest version of the cli via `go install github.com/onsi/ginkgo/v2/ginkgo@latest`. When 2.3.0 was released this resulted in an influx of issues as CI systems failed due to a change in the internal contract between the Ginkgo CLI and the Ginkgo library. Ginkgo only supports running the same version of the library as the cli (which is why both are packaged in the same repository). + +With this patch release, the ginkgo CLI can now identify a version mismatch and emit a helpful error message. + +- Ginkgo cli can identify version mismatches and emit a helpful error message [bc4ae2f] +- further emphasize that a version match is required when running Ginkgo on CI and/or locally [2691dd8] + +### Maintenance +- bump gomega to v1.22.0 [822a937] + +## 2.3.0 + +### Interruptible Nodes and Timeouts + +Ginkgo now supports per-node and per-spec timeouts on interruptible nodes. Check out the [documentation for all the details](https://onsi.github.io/ginkgo/#spec-timeouts-and-interruptible-nodes) but the gist is you can now write specs like this: + +```go +It("is interruptible", func(ctx SpecContext) { // or context.Context instead of SpecContext, both are valid. + // do things until `ctx.Done()` is closed, for example: + req, err := http.NewRequestWithContext(ctx, "POST", "/build-widgets", nil) + Expect(err).NotTo(HaveOccured()) + _, err := http.DefaultClient.Do(req) + Expect(err).NotTo(HaveOccured()) + + Eventually(client.WidgetCount).WithContext(ctx).Should(Equal(17)) +}, NodeTimeout(time.Second*20), GracePeriod(5*time.Second)) +``` + +and have Ginkgo ensure that the node completes before the timeout elapses. If it does elapse, or if an external interrupt is received (e.g. `^C`) then Ginkgo will cancel the context and wait for the Grace Period for the node to exit before proceeding with any cleanup nodes associated with the spec. The `ctx` provided by Ginkgo can also be passed down to Gomega's `Eventually` to have all assertions within the node governed by a single deadline. + +### Features + +- Ginkgo now records any additional failures that occur during the cleanup of a failed spec. In prior versions this information was quietly discarded, but the introduction of a more rigorous approach to timeouts and interruptions allows Ginkgo to better track subsequent failures. +- `SpecContext` also provides a mechanism for third-party libraries to provide additional information when a Progress Report is generated. Gomega uses this to provide the current state of an `Eventually().WithContext()` assertion when a Progress Report is requested. +- DescribeTable now exits with an error if it is not passed any Entries [a4c9865] + +## Fixes +- fixes crashes on newer Ruby 3 installations by upgrading github-pages gem dependency [92c88d5] +- Make the outline command able to use the DSL import [1be2427] + +## Maintenance +- chore(docs): delete no meaning d [57c373c] +- chore(docs): Fix hyperlinks [30526d5] +- chore(docs): fix code blocks without language settings [cf611c4] +- fix intra-doc link [b541bcb] + +## 2.2.0 + +### Generate real-time Progress Reports [f91377c] + +Ginkgo can now generate Progress Reports to point users at the current running line of code (including a preview of the actual source code) and a best guess at the most relevant subroutines. + +These Progress Reports allow users to debug stuck or slow tests without exiting the Ginkgo process. A Progress Report can be generated at any time by sending Ginkgo a `SIGINFO` (`^T` on MacOS/BSD) or `SIGUSR1`. + +In addition, the user can specify `--poll-progress-after` and `--poll-progress-interval` to have Ginkgo start periodically emitting progress reports if a given node takes too long. These can be overriden/set on a per-node basis with the `PollProgressAfter` and `PollProgressInterval` decorators. + +Progress Reports are emitted to stdout, and also stored in the machine-redable report formats that Ginkgo supports. + +Ginkgo also uses this progress reporting infrastructure under the hood when handling timeouts and interrupts. This yields much more focused, useful, and informative stack traces than previously. + +### Features +- `BeforeSuite`, `AfterSuite`, `SynchronizedBeforeSuite`, `SynchronizedAfterSuite`, and `ReportAfterSuite` now support (the relevant subset of) decorators. These can be passed in _after_ the callback functions that are usually passed into these nodes. + + As a result the **signature of these methods has changed** and now includes a trailing `args ...interface{}`. For most users simply using the DSL, this change is transparent. However if you were assigning one of these functions to a custom variable (or passing it around) then your code may need to change to reflect the new signature. + +### Maintenance +- Modernize the invocation of Ginkgo in github actions [0ffde58] +- Update reocmmended CI settings in docs [896bbb9] +- Speed up unnecessarily slow integration test [6d3a90e] + +## 2.1.6 + +### Fixes +- Add `SuppressProgressReporting` decorator to turn off --progress announcements for a given node [dfef62a] +- chore: remove duplicate word in comments [7373214] + +## 2.1.5 + +### Fixes +- drop -mod=mod instructions; fixes #1026 [6ad7138] +- Ensure `CurrentSpecReport` and `AddReportEntry` are thread-safe [817c09b] +- remove stale importmap gcflags flag test [3cd8b93] +- Always emit spec summary [5cf23e2] - even when only one spec has failed +- Fix ReportAfterSuite usage in docs [b1864ad] +- fixed typo (#997) [219cc00] +- TrimRight is not designed to trim Suffix [71ebb74] +- refactor: replace strings.Replace with strings.ReplaceAll (#978) [143d208] +- fix syntax in examples (#975) [b69554f] + +### Maintenance +- Bump github.com/onsi/gomega from 1.20.0 to 1.20.1 (#1027) [e5dfce4] +- Bump tzinfo from 1.2.9 to 1.2.10 in /docs (#1006) [7ae91c4] +- Bump github.com/onsi/gomega from 1.19.0 to 1.20.0 (#1005) [e87a85a] +- test: add new Go 1.19 to test matrix (#1014) [bbefe12] +- Bump golang.org/x/tools from 0.1.11 to 0.1.12 (#1012) [9327906] +- Bump golang.org/x/tools from 0.1.10 to 0.1.11 (#993) [f44af96] +- Bump nokogiri from 1.13.3 to 1.13.6 in /docs (#981) [ef336aa] + +## 2.1.4 + +### Fixes +- Numerous documentation typos +- Prepend `when` when using `When` (this behavior was in 1.x but unintentionally lost during the 2.0 rewrite) [efce903] +- improve error message when a parallel process fails to report back [a7bd1fe] +- guard against concurrent map writes in DeprecationTracker [0976569] +- Invoke reporting nodes during dry-run (fixes #956 and #935) [aae4480] +- Fix ginkgo import circle [f779385] + +## 2.1.3 + +See [https://onsi.github.io/ginkgo/MIGRATING_TO_V2](https://onsi.github.io/ginkgo/MIGRATING_TO_V2) for details on V2. + +### Fixes +- Calling By in a container node now emits a useful error. [ff12cee] + +## 2.1.2 + +### Fixes + +- Track location of focused specs correctly in `ginkgo unfocus` [a612ff1] +- Profiling suites with focused specs no longer generates an erroneous failure message [8fbfa02] +- Several documentation typos fixed. Big thanks to everyone who helped catch them and report/fix them! + +## 2.1.1 + +See [https://onsi.github.io/ginkgo/MIGRATING_TO_V2](https://onsi.github.io/ginkgo/MIGRATING_TO_V2) for details on V2. + +### Fixes +- Suites that only import the new dsl packages are now correctly identified as Ginkgo suites [ec17e17] + +## 2.1.0 + +See [https://onsi.github.io/ginkgo/MIGRATING_TO_V2](https://onsi.github.io/ginkgo/MIGRATING_TO_V2) for details on V2. + +2.1.0 is a minor release with a few tweaks: + +- Introduce new DSL packages to enable users to pick-and-choose which portions of the DSL to dot-import. [90868e2] More details [here](https://onsi.github.io/ginkgo/#alternatives-to-dot-importing-ginkgo). +- Add error check for invalid/nil parameters to DescribeTable [6f8577e] +- Myriad docs typos fixed (thanks everyone!) [718542a, ecb7098, 146654c, a8f9913, 6bdffde, 03dcd7e] + +## 2.0.0 + +See [https://onsi.github.io/ginkgo/MIGRATING_TO_V2](https://onsi.github.io/ginkgo/MIGRATING_TO_V2) + +## 1.16.5 + +Ginkgo 2.0 now has a Release Candidate. 1.16.5 advertises the existence of the RC. +1.16.5 deprecates GinkgoParallelNode in favor of GinkgoParallelProcess + +You can silence the RC advertisement by setting an `ACK_GINKGO_RC=true` environment variable or creating a file in your home directory called `.ack-ginkgo-rc` + +## 1.16.4 + +### Fixes +1.16.4 retracts 1.16.3. There are no code changes. The 1.16.3 tag was associated with the wrong commit and an attempt to change it after-the-fact has proven problematic. 1.16.4 retracts 1.16.3 in Ginkgo's go.mod and creates a new, correctly tagged, release. + +## 1.16.3 + +### Features +- Measure is now deprecated and emits a deprecation warning. + +## 1.16.2 + +### Fixes +- Deprecations can be suppressed by setting an `ACK_GINKGO_DEPRECATIONS=` environment variable. + +## 1.16.1 + +### Fixes +- Suppress --stream deprecation warning on windows (#793) + +## 1.16.0 + +### Features +- Advertise Ginkgo 2.0. Introduce deprecations. [9ef1913] + - Update README.md to advertise that Ginkgo 2.0 is coming. + - Backport the 2.0 DeprecationTracker and start alerting users + about upcoming deprecations. + +- Add slim-sprig template functions to bootstrap/generate (#775) [9162b86] + +- Fix accidental reference to 1488 (#784) [9fb7fe4] + +## 1.15.2 + +### Fixes +- ignore blank `-focus` and `-skip` flags (#780) [e90a4a0] + +## 1.15.1 + +### Fixes +- reporters/junit: Use `system-out` element instead of `passed` (#769) [9eda305] + +## 1.15.0 + +### Features +- Adds 'outline' command to print the outline of specs/containers in a file (#754) [071c369] [6803cc3] [935b538] [06744e8] [0c40583] +- Add support for using template to generate tests (#752) [efb9e69] +- Add a Chinese Doc #755 (#756) [5207632] +- cli: allow multiple -focus and -skip flags (#736) [9a782fb] + +### Fixes +- Add _internal to filename of tests created with internal flag (#751) [43c12da] + +## 1.14.2 + +### Fixes +- correct handling windows backslash in import path (#721) [97f3d51] +- Add additional methods to GinkgoT() to improve compatibility with the testing.TB interface [b5fe44d] + +## 1.14.1 + +### Fixes +- Discard exported method declaration when running ginkgo bootstrap (#558) [f4b0240] + +## 1.14.0 + +### Features +- Defer running top-level container nodes until RunSpecs is called [d44dedf] +- [Document Ginkgo lifecycle](http://onsi.github.io/ginkgo/#understanding-ginkgos-lifecycle) +- Add `extensions/globals` package (#692) [3295c8f] - this can be helpful in contexts where you are test-driving your test-generation code (see [#692](https://github.com/onsi/ginkgo/pull/692)) +- Print Skip reason in JUnit reporter if one was provided [820dfab] + +## 1.13.0 + +### Features +- Add a version of table.Entry that allows dumping the entry parameters. (#689) [21eaef2] + +### Fixes +- Ensure integration tests pass in an environment sans GOPATH [606fba2] +- Add books package (#568) [fc0e44e] +- doc(readme): installation via "tools package" (#677) [83bb20e] +- Solve the undefined: unix.Dup2 compile error on mips64le (#680) [0624f75] +- Import package without dot (#687) [6321024] +- Fix integration tests to stop require GOPATH (#686) [a912ec5] + +## 1.12.3 + +### Fixes +- Print correct code location of failing table test (#666) [c6d7afb] + +## 1.12.2 + +### Fixes +- Update dependencies [ea4a036] + +## 1.12.1 + +### Fixes +- Make unfocus ("blur") much faster (#674) [8b18061] +- Fix typo (#673) [7fdcbe8] +- Test against 1.14 and remove 1.12 [d5c2ad6] +- Test if a coverprofile content is empty before checking its latest character (#670) [14d9fa2] +- replace tail package with maintained one. this fixes go get errors (#667) [4ba33d4] +- improve ginkgo performance - makes progress on #644 [a14f98e] +- fix convert integration tests [1f8ba69] +- fix typo successful -> successful (#663) [1ea49cf] +- Fix invalid link (#658) [b886136] +- convert utility : Include comments from source (#657) [1077c6d] +- Explain what BDD means [d79e7fb] +- skip race detector test on unsupported platform (#642) [f8ab89d] +- Use Dup2 from golang.org/x/sys/unix instead of syscallDup (#638) [5d53c55] +- Fix missing newline in combined coverage file (#641) [6a07ea2] +- check if a spec is run before returning SpecSummary (#645) [8850000] + +## 1.12.0 + +### Features +- Add module definition (#630) [78916ab] + +## 1.11.0 + +### Features +- Add syscall for riscv64 architecture [f66e896] +- teamcity reporter: output location of test failure as well as test definition (#626) [9869142] +- teamcity reporter: output newline after every service message (#625) [3cfa02d] +- Add support for go module when running `generate` command (#578) [9c89e3f] + +## 1.10.3 + +### Fixes +- Set go_import_path in travis.yml to allow internal packages in forks (#607) [3b721db] +- Add integration test [d90e0dc] +- Fix coverage files combining [e5dde8c] +- A new CLI option: -ginkgo.reportFile (#601) [034fd25] + +## 1.10.2 + +### Fixes +- speed up table entry generateIt() (#609) [5049dc5] +- Fix. Write errors to stderr instead of stdout (#610) [7bb3091] + +## 1.10.1 + +### Fixes +- stack backtrace: fix skipping (#600) [2a4c0bd] + +## 1.10.0 + +### Fixes +- stack backtrace: fix alignment and skipping [66915d6] +- fix typo in documentation [8f97b93] + +## 1.9.0 + +### Features +- Option to print output into report, when tests have passed [0545415] + +### Fixes +- Fixed typos in comments [0ecbc58] +- gofmt code [a7f8bfb] +- Simplify code [7454d00] +- Simplify concatenation, incrementation and function assignment [4825557] +- Avoid unnecessary conversions [9d9403c] +- JUnit: include more detailed information about panic [19cca4b] +- Print help to stdout when the user asks for help [4cb7441] + + +## 1.8.0 + +### New Features +- allow config of the vet flag for `go test` (#562) [3cd45fa] +- Support projects using go modules [d56ee76] + +### Fixes and Minor Improvements +- chore(godoc): fixes typos in Measurement funcs [dbaca8e] +- Optimize focus to avoid allocations [f493786] +- Ensure generated test file names are underscored [505cc35] + +## 1.7.0 + +### New Features +- Add JustAfterEach (#484) [0d4f080] + +### Fixes +- Correctly round suite time in junit reporter [2445fc1] +- Avoid using -i argument to go test for Golang 1.10+ [46bbc26] + +## 1.6.0 + +### New Features +- add --debug flag to emit node output to files (#499) [39febac] + +### Fixes +- fix: for `go vet` to pass [69338ec] +- docs: fix for contributing instructions [7004cb1] +- consolidate and streamline contribution docs (#494) [d848015] +- Make generated Junit file compatible with "Maven Surefire" (#488) [e51bee6] +- all: gofmt [000d317] +- Increase eventually timeout to 30s [c73579c] +- Clarify asynchronous test behavior [294d8f4] +- Travis badge should only show master [26d2143] + +## 1.5.0 5/10/2018 + +### New Features +- Supports go v1.10 (#443, #446, #451) [e873237, 468e89e, e37dbfe, a37f4c0, c0b857d, bca5260, 4177ca8] +- Add a When() synonym for Context() (#386) [747514b, 7484dad, 7354a07, dd826c8] +- Re-add noisySkippings flag [652e15c] +- Allow coverage to be displayed for focused specs (#367) [11459a8] +- Handle -outputdir flag (#364) [228e3a8] +- Handle -coverprofile flag (#355) [43392d5] + +### Fixes +- When using custom reporters register the custom reporters *before* the default reporter. This allows users to see the output of any print statements in their customer reporters. (#365) [8382b23] +- When running a test and calculating the coverage using the `-coverprofile` and `-outputdir` flags, Ginkgo fails with an error if the directory does not exist. This is due to an [issue in go 1.10](https://github.com/golang/go/issues/24588) (#446) [b36a6e0] +- `unfocus` command ignores vendor folder (#459) [e5e551c, c556e43, a3b6351, 9a820dd] +- Ignore packages whose tests are all ignored by go (#456) [7430ca7, 6d8be98] +- Increase the threshold when checking time measurements (#455) [2f714bf, 68f622c] +- Fix race condition in coverage tests (#423) [a5a8ff7, ab9c08b] +- Add an extra new line after reporting spec run completion for test2json [874520d] +- added name name field to junit reported testsuite [ae61c63] +- Do not set the run time of a spec when the dryRun flag is used (#438) [457e2d9, ba8e856] +- Process FWhen and FSpecify when unfocusing (#434) [9008c7b, ee65bd, df87dfe] +- Synchronies the access to the state of specs to avoid race conditions (#430) [7d481bc, ae6829d] +- Added Duration on GinkgoTestDescription (#383) [5f49dad, 528417e, 0747408, 329d7ed] +- Fix Ginkgo stack trace on failure for Specify (#415) [b977ede, 65ca40e, 6c46eb8] +- Update README with Go 1.6+, Golang -> Go (#409) [17f6b97, bc14b66, 20d1598] +- Use fmt.Errorf instead of errors.New(fmt.Sprintf (#401) [a299f56, 44e2eaa] +- Imports in generated code should follow conventions (#398) [0bec0b0, e8536d8] +- Prevent data race error when Recording a benchmark value from multiple go routines (#390) [c0c4881, 7a241e9] +- Replace GOPATH in Environment [4b883f0] + + +## 1.4.0 7/16/2017 + +- `ginkgo` now provides a hint if you accidentally forget to run `ginkgo bootstrap` to generate a `*_suite_test.go` file that actually invokes the Ginkgo test runner. [#345](https://github.com/onsi/ginkgo/pull/345) +- thanks to improvements in `go test -c` `ginkgo` no longer needs to fix Go's compilation output to ensure compilation errors are expressed relative to the CWD. [#357] +- `ginkgo watch -watchRegExp=...` allows you to specify a custom regular expression to watch. Only files matching the regular expression are watched for changes (the default is `\.go$`) [#356] +- `ginkgo` now always emits compilation output. Previously, only failed compilation output was printed out. [#277] +- `ginkgo -requireSuite` now fails the test run if there are `*_test.go` files but `go test` fails to detect any tests. Typically this means you forgot to run `ginkgo bootstrap` to generate a suite file. [#344] +- `ginkgo -timeout=DURATION` allows you to adjust the timeout for the entire test suite (default is 24 hours) [#248] + +## 1.3.0 3/28/2017 + +Improvements: + +- Significantly improved parallel test distribution. Now instead of pre-sharding test cases across workers (which can result in idle workers and poor test performance) Ginkgo uses a shared queue to keep all workers busy until all tests are complete. This improves test-time performance and consistency. +- `Skip(message)` can be used to skip the current test. +- Added `extensions/table` - a Ginkgo DSL for [Table Driven Tests](http://onsi.github.io/ginkgo/#table-driven-tests) +- Add `GinkgoRandomSeed()` - shorthand for `config.GinkgoConfig.RandomSeed` +- Support for retrying flaky tests with `--flakeAttempts` +- `ginkgo ./...` now recurses as you'd expect +- Added `Specify` a synonym for `It` +- Support colorise on Windows +- Broader support for various go compilation flags in the `ginkgo` CLI + +Bug Fixes: + +- Ginkgo tests now fail when you `panic(nil)` (#167) + +## 1.2.0 5/31/2015 + +Improvements + +- `ginkgo -coverpkg` calls down to `go test -coverpkg` (#160) +- `ginkgo -afterSuiteHook COMMAND` invokes the passed-in `COMMAND` after a test suite completes (#152) +- Relaxed requirement for Go 1.4+. `ginkgo` now works with Go v1.3+ (#166) + +## 1.2.0-beta + +Ginkgo now requires Go 1.4+ + +Improvements: + +- Call reporters in reverse order when announcing spec completion -- allows custom reporters to emit output before the default reporter does. +- Improved focus behavior. Now, this: + + ```golang + FDescribe("Some describe", func() { + It("A", func() {}) + + FIt("B", func() {}) + }) + ``` + + will run `B` but *not* `A`. This tends to be a common usage pattern when in the thick of writing and debugging tests. +- When `SIGINT` is received, Ginkgo will emit the contents of the `GinkgoWriter` before running the `AfterSuite`. Useful for debugging stuck tests. +- When `--progress` is set, Ginkgo will write test progress (in particular, Ginkgo will say when it is about to run a BeforeEach, AfterEach, It, etc...) to the `GinkgoWriter`. This is useful for debugging stuck tests and tests that generate many logs. +- Improved output when an error occurs in a setup or teardown block. +- When `--dryRun` is set, Ginkgo will walk the spec tree and emit to its reporter *without* actually running anything. Best paired with `-v` to understand which specs will run in which order. +- Add `By` to help document long `It`s. `By` simply writes to the `GinkgoWriter`. +- Add support for precompiled tests: + - `ginkgo build ` will now compile the package, producing a file named `package.test` + - The compiled `package.test` file can be run directly. This runs the tests in series. + - To run precompiled tests in parallel, you can run: `ginkgo -p package.test` +- Support `bootstrap`ping and `generate`ing [Agouti](http://agouti.org) specs. +- `ginkgo generate` and `ginkgo bootstrap` now honor the package name already defined in a given directory +- The `ginkgo` CLI ignores `SIGQUIT`. Prevents its stack dump from interlacing with the underlying test suite's stack dump. +- The `ginkgo` CLI now compiles tests into a temporary directory instead of the package directory. This necessitates upgrading to Go v1.4+. +- `ginkgo -notify` now works on Linux + +Bug Fixes: + +- If --skipPackages is used and all packages are skipped, Ginkgo should exit 0. +- Fix tempfile leak when running in parallel +- Fix incorrect failure message when a panic occurs during a parallel test run +- Fixed an issue where a pending test within a focused context (or a focused test within a pending context) would skip all other tests. +- Be more consistent about handling SIGTERM as well as SIGINT +- When interrupted while concurrently compiling test suites in the background, Ginkgo now cleans up the compiled artifacts. +- Fixed a long standing bug where `ginkgo -p` would hang if a process spawned by one of the Ginkgo parallel nodes does not exit. (Hooray!) + +## 1.1.0 (8/2/2014) + +No changes, just dropping the beta. + +## 1.1.0-beta (7/22/2014) +New Features: + +- `ginkgo watch` now monitors packages *and their dependencies* for changes. The depth of the dependency tree can be modified with the `-depth` flag. +- Test suites with a programmatic focus (`FIt`, `FDescribe`, etc...) exit with non-zero status code, even when they pass. This allows CI systems to detect accidental commits of focused test suites. +- `ginkgo -p` runs the testsuite in parallel with an auto-detected number of nodes. +- `ginkgo -tags=TAG_LIST` passes a list of tags down to the `go build` command. +- `ginkgo --failFast` aborts the test suite after the first failure. +- `ginkgo generate file_1 file_2` can take multiple file arguments. +- Ginkgo now summarizes any spec failures that occurred at the end of the test run. +- `ginkgo --randomizeSuites` will run tests *suites* in random order using the generated/passed-in seed. + +Improvements: + +- `ginkgo -skipPackage` now takes a comma-separated list of strings. If the *relative path* to a package matches one of the entries in the comma-separated list, that package is skipped. +- `ginkgo --untilItFails` no longer recompiles between attempts. +- Ginkgo now panics when a runnable node (`It`, `BeforeEach`, `JustBeforeEach`, `AfterEach`, `Measure`) is nested within another runnable node. This is always a mistake. Any test suites that panic because of this change should be fixed. + +Bug Fixes: + +- `ginkgo boostrap` and `ginkgo generate` no longer fail when dealing with `hyphen-separated-packages`. +- parallel specs are now better distributed across nodes - fixed a crashing bug where (for example) distributing 11 tests across 7 nodes would panic + +## 1.0.0 (5/24/2014) +New Features: + +- Add `GinkgoParallelNode()` - shorthand for `config.GinkgoConfig.ParallelNode` + +Improvements: + +- When compilation fails, the compilation output is rewritten to present a correct *relative* path. Allows ⌘-clicking in iTerm open the file in your text editor. +- `--untilItFails` and `ginkgo watch` now generate new random seeds between test runs, unless a particular random seed is specified. + +Bug Fixes: + +- `-cover` now generates a correctly combined coverprofile when running with in parallel with multiple `-node`s. +- Print out the contents of the `GinkgoWriter` when `BeforeSuite` or `AfterSuite` fail. +- Fix all remaining race conditions in Ginkgo's test suite. + +## 1.0.0-beta (4/14/2014) +Breaking changes: + +- `thirdparty/gomocktestreporter` is gone. Use `GinkgoT()` instead +- Modified the Reporter interface +- `watch` is now a subcommand, not a flag. + +DSL changes: + +- `BeforeSuite` and `AfterSuite` for setting up and tearing down test suites. +- `AfterSuite` is triggered on interrupt (`^C`) as well as exit. +- `SynchronizedBeforeSuite` and `SynchronizedAfterSuite` for setting up and tearing down singleton resources across parallel nodes. + +CLI changes: + +- `watch` is now a subcommand, not a flag +- `--nodot` flag can be passed to `ginkgo generate` and `ginkgo bootstrap` to avoid dot imports. This explicitly imports all exported identifiers in Ginkgo and Gomega. Refreshing this list can be done by running `ginkgo nodot` +- Additional arguments can be passed to specs. Pass them after the `--` separator +- `--skipPackage` flag takes a regexp and ignores any packages with package names passing said regexp. +- `--trace` flag prints out full stack traces when errors occur, not just the line at which the error occurs. + +Misc: + +- Start using semantic versioning +- Start maintaining changelog + +Major refactor: + +- Pull out Ginkgo's internal to `internal` +- Rename `example` everywhere to `spec` +- Much more! diff --git a/vendor/github.com/onsi/ginkgo/v2/CONTRIBUTING.md b/vendor/github.com/onsi/ginkgo/v2/CONTRIBUTING.md new file mode 100644 index 00000000000..1da92fe7ee2 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/CONTRIBUTING.md @@ -0,0 +1,13 @@ +# Contributing to Ginkgo + +Your contributions to Ginkgo are essential for its long-term maintenance and improvement. + +- Please **open an issue first** - describe what problem you are trying to solve and give the community a forum for input and feedback ahead of investing time in writing code! +- Ensure adequate test coverage: + - When adding to the Ginkgo library, add unit and/or integration tests (under the `integration` folder). + - When adding to the Ginkgo CLI, note that there are very few unit tests. Please add an integration test. +- Make sure all the tests succeed via `ginkgo -r -p` +- Vet your changes via `go vet ./...` +- Update the documentation. Ginkgo uses `godoc` comments and documentation in `docs/index.md`. You can run `bundle exec jekyll serve` in the `docs` directory to preview your changes. + +Thanks for supporting Ginkgo! \ No newline at end of file diff --git a/vendor/github.com/onsi/ginkgo/v2/LICENSE b/vendor/github.com/onsi/ginkgo/v2/LICENSE new file mode 100644 index 00000000000..9415ee72c17 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2013-2014 Onsi Fakhouri + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/onsi/ginkgo/v2/README.md b/vendor/github.com/onsi/ginkgo/v2/README.md new file mode 100644 index 00000000000..d0473a467c0 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/README.md @@ -0,0 +1,115 @@ +![Ginkgo](https://onsi.github.io/ginkgo/images/ginkgo.png) + +[![test](https://github.com/onsi/ginkgo/workflows/test/badge.svg?branch=master)](https://github.com/onsi/ginkgo/actions?query=workflow%3Atest+branch%3Amaster) | [Ginkgo Docs](https://onsi.github.io/ginkgo/) + +--- + +# Ginkgo + +Ginkgo is a mature testing framework for Go designed to help you write expressive specs. Ginkgo builds on top of Go's `testing` foundation and is complemented by the [Gomega](https://github.com/onsi/gomega) matcher library. Together, Ginkgo and Gomega let you express the intent behind your specs clearly: + +```go +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + ... +) + +Describe("Checking books out of the library", Label("library"), func() { + var library *libraries.Library + var book *books.Book + var valjean *users.User + BeforeEach(func() { + library = libraries.NewClient() + book = &books.Book{ + Title: "Les Miserables", + Author: "Victor Hugo", + } + valjean = users.NewUser("Jean Valjean") + }) + + When("the library has the book in question", func() { + BeforeEach(func(ctx SpecContext) { + Expect(library.Store(ctx, book)).To(Succeed()) + }) + + Context("and the book is available", func() { + It("lends it to the reader", func(ctx SpecContext) { + Expect(valjean.Checkout(ctx, library, "Les Miserables")).To(Succeed()) + Expect(valjean.Books()).To(ContainElement(book)) + Expect(library.UserWithBook(ctx, book)).To(Equal(valjean)) + }, SpecTimeout(time.Second * 5)) + }) + + Context("but the book has already been checked out", func() { + var javert *users.User + BeforeEach(func(ctx SpecContext) { + javert = users.NewUser("Javert") + Expect(javert.Checkout(ctx, library, "Les Miserables")).To(Succeed()) + }) + + It("tells the user", func(ctx SpecContext) { + err := valjean.Checkout(ctx, library, "Les Miserables") + Expect(error).To(MatchError("Les Miserables is currently checked out")) + }, SpecTimeout(time.Second * 5)) + + It("lets the user place a hold and get notified later", func(ctx SpecContext) { + Expect(valjean.Hold(ctx, library, "Les Miserables")).To(Succeed()) + Expect(valjean.Holds(ctx)).To(ContainElement(book)) + + By("when Javert returns the book") + Expect(javert.Return(ctx, library, book)).To(Succeed()) + + By("it eventually informs Valjean") + notification := "Les Miserables is ready for pick up" + Eventually(ctx, valjean.Notifications).Should(ContainElement(notification)) + + Expect(valjean.Checkout(ctx, library, "Les Miserables")).To(Succeed()) + Expect(valjean.Books(ctx)).To(ContainElement(book)) + Expect(valjean.Holds(ctx)).To(BeEmpty()) + }, SpecTimeout(time.Second * 10)) + }) + }) + + When("the library does not have the book in question", func() { + It("tells the reader the book is unavailable", func(ctx SpecContext) { + err := valjean.Checkout(ctx, library, "Les Miserables") + Expect(error).To(MatchError("Les Miserables is not in the library catalog")) + }, SpecTimeout(time.Second * 5)) + }) +}) +``` + +Jump to the [docs](https://onsi.github.io/ginkgo/) to learn more. It's easy to [bootstrap](https://onsi.github.io/ginkgo/#bootstrapping-a-suite) and start writing your [first specs](https://onsi.github.io/ginkgo/#adding-specs-to-a-suite). + +If you have a question, comment, bug report, feature request, etc. please open a [GitHub issue](https://github.com/onsi/ginkgo/issues/new), or visit the [Ginkgo Slack channel](https://app.slack.com/client/T029RQSE6/CQQ50BBNW). + +## Capabilities + +Whether writing basic unit specs, complex integration specs, or even performance specs - Ginkgo gives you an expressive Domain-Specific Language (DSL) that will be familiar to users coming from frameworks such as [Quick](https://github.com/Quick/Quick), [RSpec](https://rspec.info), [Jasmine](https://jasmine.github.io), and [Busted](https://lunarmodules.github.io/busted/). This style of testing is sometimes referred to as "Behavior-Driven Development" (BDD) though Ginkgo's utility extends beyond acceptance-level testing. + +With Ginkgo's DSL you can use nestable [`Describe`, `Context` and `When` container nodes](https://onsi.github.io/ginkgo/#organizing-specs-with-container-nodes) to help you organize your specs. [`BeforeEach` and `AfterEach` setup nodes](https://onsi.github.io/ginkgo/#extracting-common-setup-beforeeach) for setup and cleanup. [`It` and `Specify` subject nodes](https://onsi.github.io/ginkgo/#spec-subjects-it) that hold your assertions. [`BeforeSuite` and `AfterSuite` nodes](https://onsi.github.io/ginkgo/#suite-setup-and-cleanup-beforesuite-and-aftersuite) to prep for and cleanup after a suite... and [much more!](https://onsi.github.io/ginkgo/#writing-specs). + +At runtime, Ginkgo can run your specs in reproducibly [random order](https://onsi.github.io/ginkgo/#spec-randomization) and has sophisticated support for [spec parallelization](https://onsi.github.io/ginkgo/#spec-parallelization). In fact, running specs in parallel is as easy as + +```bash +ginkgo -p +``` + +By following [established patterns for writing parallel specs](https://onsi.github.io/ginkgo/#patterns-for-parallel-integration-specs) you can build even large, complex integration suites that parallelize cleanly and run performantly. And you don't have to worry about your spec suite hanging or leaving a mess behind - Ginkgo provides a per-node `context.Context` and the capability to interrupt the spec after a set period of time - and then clean up. + +As your suites grow Ginkgo helps you keep your specs organized with [labels](https://onsi.github.io/ginkgo/#spec-labels) and lets you easily run [subsets of specs](https://onsi.github.io/ginkgo/#filtering-specs), either [programmatically](https://onsi.github.io/ginkgo/#focused-specs) or on the [command line](https://onsi.github.io/ginkgo/#combining-filters). And Ginkgo's reporting infrastructure generates machine-readable output in a [variety of formats](https://onsi.github.io/ginkgo/#generating-machine-readable-reports) _and_ allows you to build your own [custom reporting infrastructure](https://onsi.github.io/ginkgo/#generating-reports-programmatically). + +Ginkgo ships with `ginkgo`, a [command line tool](https://onsi.github.io/ginkgo/#ginkgo-cli-overview) with support for generating, running, filtering, and profiling Ginkgo suites. You can even have Ginkgo automatically run your specs when it detects a change with `ginkgo watch`, enabling rapid feedback loops during test-driven development. + +And that's just Ginkgo! [Gomega](https://onsi.github.io/gomega/) brings a rich, mature, family of [assertions and matchers](https://onsi.github.io/gomega/#provided-matchers) to your suites. With Gomega you can easily mix [synchronous and asynchronous assertions](https://onsi.github.io/ginkgo/#patterns-for-asynchronous-testing) in your specs. You can even build your own set of expressive domain-specific matchers quickly and easily by composing Gomega's [existing building blocks](https://onsi.github.io/ginkgo/#building-custom-matchers). + +Happy Testing! + +## License + +Ginkgo is MIT-Licensed + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) diff --git a/vendor/github.com/onsi/ginkgo/v2/RELEASING.md b/vendor/github.com/onsi/ginkgo/v2/RELEASING.md new file mode 100644 index 00000000000..363815d7c7f --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/RELEASING.md @@ -0,0 +1,23 @@ +A Ginkgo release is a tagged git sha and a GitHub release. To cut a release: + +1. Ensure CHANGELOG.md is up to date. + - Use + ```bash + LAST_VERSION=$(git tag --sort=version:refname | tail -n1) + CHANGES=$(git log --pretty=format:'- %s [%h]' HEAD...$LAST_VERSION) + echo -e "## NEXT\n\n$CHANGES\n\n### Features\n\n### Fixes\n\n### Maintenance\n\n$(cat CHANGELOG.md)" > CHANGELOG.md + ``` + to update the changelog + - Categorize the changes into + - Breaking Changes (requires a major version) + - New Features (minor version) + - Fixes (fix version) + - Maintenance (which in general should not be mentioned in `CHANGELOG.md` as they have no user impact) +1. Update `VERSION` in `types/version.go` +1. Commit, push, and release: + ``` + git commit -m "vM.m.p" + git push + gh release create "vM.m.p" + git fetch --tags origin master + ``` \ No newline at end of file diff --git a/vendor/github.com/onsi/ginkgo/v2/config/deprecated.go b/vendor/github.com/onsi/ginkgo/v2/config/deprecated.go new file mode 100644 index 00000000000..a61021d0889 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/config/deprecated.go @@ -0,0 +1,69 @@ +package config + +// GinkgoConfigType has been deprecated and its equivalent now lives in +// the types package. You can no longer access Ginkgo configuration from the config +// package. Instead use the DSL's GinkgoConfiguration() function to get copies of the +// current configuration +// +// GinkgoConfigType is still here so custom V1 reporters do not result in a compilation error +// It will be removed in a future minor release of Ginkgo +type GinkgoConfigType = DeprecatedGinkgoConfigType +type DeprecatedGinkgoConfigType struct { + RandomSeed int64 + RandomizeAllSpecs bool + RegexScansFilePath bool + FocusStrings []string + SkipStrings []string + SkipMeasurements bool + FailOnPending bool + FailFast bool + FlakeAttempts int + EmitSpecProgress bool + DryRun bool + DebugParallel bool + + ParallelNode int + ParallelTotal int + SyncHost string + StreamHost string +} + +// DefaultReporterConfigType has been deprecated and its equivalent now lives in +// the types package. You can no longer access Ginkgo configuration from the config +// package. Instead use the DSL's GinkgoConfiguration() function to get copies of the +// current configuration +// +// DefaultReporterConfigType is still here so custom V1 reporters do not result in a compilation error +// It will be removed in a future minor release of Ginkgo +type DefaultReporterConfigType = DeprecatedDefaultReporterConfigType +type DeprecatedDefaultReporterConfigType struct { + NoColor bool + SlowSpecThreshold float64 + NoisyPendings bool + NoisySkippings bool + Succinct bool + Verbose bool + FullTrace bool + ReportPassed bool + ReportFile string +} + +// Sadly there is no way to gracefully deprecate access to these global config variables. +// Users who need access to Ginkgo's configuration should use the DSL's GinkgoConfiguration() method +// These new unwieldy type names exist to give users a hint when they try to compile and the compilation fails +type GinkgoConfigIsNoLongerAccessibleFromTheConfigPackageUseTheDSLsGinkgoConfigurationFunctionInstead struct{} + +// Sadly there is no way to gracefully deprecate access to these global config variables. +// Users who need access to Ginkgo's configuration should use the DSL's GinkgoConfiguration() method +// These new unwieldy type names exist to give users a hint when they try to compile and the compilation fails +var GinkgoConfig = GinkgoConfigIsNoLongerAccessibleFromTheConfigPackageUseTheDSLsGinkgoConfigurationFunctionInstead{} + +// Sadly there is no way to gracefully deprecate access to these global config variables. +// Users who need access to Ginkgo's configuration should use the DSL's GinkgoConfiguration() method +// These new unwieldy type names exist to give users a hint when they try to compile and the compilation fails +type DefaultReporterConfigIsNoLongerAccessibleFromTheConfigPackageUseTheDSLsGinkgoConfigurationFunctionInstead struct{} + +// Sadly there is no way to gracefully deprecate access to these global config variables. +// Users who need access to Ginkgo's configuration should use the DSL's GinkgoConfiguration() method +// These new unwieldy type names exist to give users a hint when they try to compile and the compilation fails +var DefaultReporterConfig = DefaultReporterConfigIsNoLongerAccessibleFromTheConfigPackageUseTheDSLsGinkgoConfigurationFunctionInstead{} diff --git a/vendor/github.com/onsi/ginkgo/v2/core_dsl.go b/vendor/github.com/onsi/ginkgo/v2/core_dsl.go new file mode 100644 index 00000000000..5a08574cd08 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/core_dsl.go @@ -0,0 +1,741 @@ +/* +Ginkgo is a testing framework for Go designed to help you write expressive tests. +https://github.com/onsi/ginkgo +MIT-Licensed + +The godoc documentation outlines Ginkgo's API. Since Ginkgo is a Domain-Specific Language it is important to +build a mental model for Ginkgo - the narrative documentation at https://onsi.github.io/ginkgo/ is designed to help you do that. +You should start there - even a brief skim will be helpful. At minimum you should skim through the https://onsi.github.io/ginkgo/#getting-started chapter. + +Ginkgo's is best paired with the Gomega matcher library: https://github.com/onsi/gomega + +You can run Ginkgo specs with go test - however we recommend using the ginkgo cli. It enables functionality +that go test does not (especially running suites in parallel). You can learn more at https://onsi.github.io/ginkgo/#ginkgo-cli-overview +or by running 'ginkgo help'. +*/ +package ginkgo + +import ( + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/go-logr/logr" + "github.com/onsi/ginkgo/v2/formatter" + "github.com/onsi/ginkgo/v2/internal" + "github.com/onsi/ginkgo/v2/internal/global" + "github.com/onsi/ginkgo/v2/internal/interrupt_handler" + "github.com/onsi/ginkgo/v2/internal/parallel_support" + "github.com/onsi/ginkgo/v2/reporters" + "github.com/onsi/ginkgo/v2/types" +) + +const GINKGO_VERSION = types.VERSION + +var flagSet types.GinkgoFlagSet +var deprecationTracker = types.NewDeprecationTracker() +var suiteConfig = types.NewDefaultSuiteConfig() +var reporterConfig = types.NewDefaultReporterConfig() +var suiteDidRun = false +var outputInterceptor internal.OutputInterceptor +var client parallel_support.Client + +func init() { + var err error + flagSet, err = types.BuildTestSuiteFlagSet(&suiteConfig, &reporterConfig) + exitIfErr(err) + writer := internal.NewWriter(os.Stdout) + GinkgoWriter = writer + GinkgoLogr = internal.GinkgoLogrFunc(writer) +} + +func exitIfErr(err error) { + if err != nil { + if outputInterceptor != nil { + outputInterceptor.Shutdown() + } + if client != nil { + client.Close() + } + fmt.Fprintln(formatter.ColorableStdErr, err.Error()) + os.Exit(1) + } +} + +func exitIfErrors(errors []error) { + if len(errors) > 0 { + if outputInterceptor != nil { + outputInterceptor.Shutdown() + } + if client != nil { + client.Close() + } + for _, err := range errors { + fmt.Fprintln(formatter.ColorableStdErr, err.Error()) + } + os.Exit(1) + } +} + +// The interface implemented by GinkgoWriter +type GinkgoWriterInterface interface { + io.Writer + + Print(a ...interface{}) + Printf(format string, a ...interface{}) + Println(a ...interface{}) + + TeeTo(writer io.Writer) + ClearTeeWriters() +} + +/* +SpecContext is the context object passed into nodes that are subject to a timeout or need to be notified of an interrupt. It implements the standard context.Context interface but also contains additional helpers to provide an extensibility point for Ginkgo. (As an example, Gomega's Eventually can use the methods defined on SpecContext to provide deeper integratoin with Ginkgo). + +You can do anything with SpecContext that you do with a typical context.Context including wrapping it with any of the context.With* methods. + +Ginkgo will cancel the SpecContext when a node is interrupted (e.g. by the user sending an interupt signal) or when a node has exceeded it's allowed run-time. Note, however, that even in cases where a node has a deadline, SpecContext will not return a deadline via .Deadline(). This is because Ginkgo does not use a WithDeadline() context to model node deadlines as Ginkgo needs control over the precise timing of the context cancellation to ensure it can provide an accurate progress report at the moment of cancellation. +*/ +type SpecContext = internal.SpecContext + +/* +GinkgoWriter implements a GinkgoWriterInterface and io.Writer + +When running in verbose mode (ginkgo -v) any writes to GinkgoWriter will be immediately printed +to stdout. Otherwise, GinkgoWriter will buffer any writes produced during the current test and flush them to screen +only if the current test fails. + +GinkgoWriter also provides convenience Print, Printf and Println methods and allows you to tee to a custom writer via GinkgoWriter.TeeTo(writer). +Writes to GinkgoWriter are immediately sent to any registered TeeTo() writers. You can unregister all TeeTo() Writers with GinkgoWriter.ClearTeeWriters() + +You can learn more at https://onsi.github.io/ginkgo/#logging-output +*/ +var GinkgoWriter GinkgoWriterInterface + +/* +GinkgoLogr is a logr.Logger that writes to GinkgoWriter +*/ +var GinkgoLogr logr.Logger + +// The interface by which Ginkgo receives *testing.T +type GinkgoTestingT interface { + Fail() +} + +/* +GinkgoConfiguration returns the configuration of the current suite. + +The first return value is the SuiteConfig which controls aspects of how the suite runs, +the second return value is the ReporterConfig which controls aspects of how Ginkgo's default +reporter emits output. + +Mutating the returned configurations has no effect. To reconfigure Ginkgo programmatically you need +to pass in your mutated copies into RunSpecs(). + +You can learn more at https://onsi.github.io/ginkgo/#overriding-ginkgos-command-line-configuration-in-the-suite +*/ +func GinkgoConfiguration() (types.SuiteConfig, types.ReporterConfig) { + return suiteConfig, reporterConfig +} + +/* +GinkgoRandomSeed returns the seed used to randomize spec execution order. It is +useful for seeding your own pseudorandom number generators to ensure +consistent executions from run to run, where your tests contain variability (for +example, when selecting random spec data). + +You can learn more at https://onsi.github.io/ginkgo/#spec-randomization +*/ +func GinkgoRandomSeed() int64 { + return suiteConfig.RandomSeed +} + +/* +GinkgoParallelProcess returns the parallel process number for the current ginkgo process +The process number is 1-indexed. You can use GinkgoParallelProcess() to shard access to shared +resources across your suites. You can learn more about patterns for sharding at https://onsi.github.io/ginkgo/#patterns-for-parallel-integration-specs + +For more on how specs are parallelized in Ginkgo, see http://onsi.github.io/ginkgo/#spec-parallelization +*/ +func GinkgoParallelProcess() int { + return suiteConfig.ParallelProcess +} + +/* +PauseOutputInterception() pauses Ginkgo's output interception. This is only relevant +when running in parallel and output to stdout/stderr is being intercepted. You generally +don't need to call this function - however there are cases when Ginkgo's output interception +mechanisms can interfere with external processes launched by the test process. + +In particular, if an external process is launched that has cmd.Stdout/cmd.Stderr set to os.Stdout/os.Stderr +then Ginkgo's output interceptor will hang. To circumvent this, set cmd.Stdout/cmd.Stderr to GinkgoWriter. +If, for some reason, you aren't able to do that, you can PauseOutputInterception() before starting the process +then ResumeOutputInterception() after starting it. + +Note that PauseOutputInterception() does not cause stdout writes to print to the console - +this simply stops intercepting and storing stdout writes to an internal buffer. +*/ +func PauseOutputInterception() { + if outputInterceptor == nil { + return + } + outputInterceptor.PauseIntercepting() +} + +// ResumeOutputInterception() - see docs for PauseOutputInterception() +func ResumeOutputInterception() { + if outputInterceptor == nil { + return + } + outputInterceptor.ResumeIntercepting() +} + +/* +RunSpecs is the entry point for the Ginkgo spec runner. + +You must call this within a Golang testing TestX(t *testing.T) function. +If you bootstrapped your suite with "ginkgo bootstrap" this is already +done for you. + +Ginkgo is typically configured via command-line flags. This configuration +can be overridden, however, and passed into RunSpecs as optional arguments: + + func TestMySuite(t *testing.T) { + RegisterFailHandler(gomega.Fail) + // fetch the current config + suiteConfig, reporterConfig := GinkgoConfiguration() + // adjust it + suiteConfig.SkipStrings = []string{"NEVER-RUN"} + reporterConfig.FullTrace = true + // pass it in to RunSpecs + RunSpecs(t, "My Suite", suiteConfig, reporterConfig) + } + +Note that some configuration changes can lead to undefined behavior. For example, +you should not change ParallelProcess or ParallelTotal as the Ginkgo CLI is responsible +for setting these and orchestrating parallel specs across the parallel processes. See http://onsi.github.io/ginkgo/#spec-parallelization +for more on how specs are parallelized in Ginkgo. + +You can also pass suite-level Label() decorators to RunSpecs. The passed-in labels will apply to all specs in the suite. +*/ +func RunSpecs(t GinkgoTestingT, description string, args ...interface{}) bool { + if suiteDidRun { + exitIfErr(types.GinkgoErrors.RerunningSuite()) + } + suiteDidRun = true + + suiteLabels := Labels{} + configErrors := []error{} + for _, arg := range args { + switch arg := arg.(type) { + case types.SuiteConfig: + suiteConfig = arg + case types.ReporterConfig: + reporterConfig = arg + case Labels: + suiteLabels = append(suiteLabels, arg...) + default: + configErrors = append(configErrors, types.GinkgoErrors.UnknownTypePassedToRunSpecs(arg)) + } + } + exitIfErrors(configErrors) + + configErrors = types.VetConfig(flagSet, suiteConfig, reporterConfig) + if len(configErrors) > 0 { + fmt.Fprintf(formatter.ColorableStdErr, formatter.F("{{red}}Ginkgo detected configuration issues:{{/}}\n")) + for _, err := range configErrors { + fmt.Fprintf(formatter.ColorableStdErr, err.Error()) + } + os.Exit(1) + } + + var reporter reporters.Reporter + if suiteConfig.ParallelTotal == 1 { + reporter = reporters.NewDefaultReporter(reporterConfig, formatter.ColorableStdOut) + outputInterceptor = internal.NoopOutputInterceptor{} + client = nil + } else { + reporter = reporters.NoopReporter{} + switch strings.ToLower(suiteConfig.OutputInterceptorMode) { + case "swap": + outputInterceptor = internal.NewOSGlobalReassigningOutputInterceptor() + case "none": + outputInterceptor = internal.NoopOutputInterceptor{} + default: + outputInterceptor = internal.NewOutputInterceptor() + } + client = parallel_support.NewClient(suiteConfig.ParallelHost) + if !client.Connect() { + client = nil + exitIfErr(types.GinkgoErrors.UnreachableParallelHost(suiteConfig.ParallelHost)) + } + defer client.Close() + } + + writer := GinkgoWriter.(*internal.Writer) + if reporterConfig.Verbosity().GTE(types.VerbosityLevelVerbose) && suiteConfig.ParallelTotal == 1 { + writer.SetMode(internal.WriterModeStreamAndBuffer) + } else { + writer.SetMode(internal.WriterModeBufferOnly) + } + + if reporterConfig.WillGenerateReport() { + registerReportAfterSuiteNodeForAutogeneratedReports(reporterConfig) + } + + err := global.Suite.BuildTree() + exitIfErr(err) + + suitePath, err := os.Getwd() + exitIfErr(err) + suitePath, err = filepath.Abs(suitePath) + exitIfErr(err) + + passed, hasFocusedTests := global.Suite.Run(description, suiteLabels, suitePath, global.Failer, reporter, writer, outputInterceptor, interrupt_handler.NewInterruptHandler(client), client, internal.RegisterForProgressSignal, suiteConfig) + outputInterceptor.Shutdown() + + flagSet.ValidateDeprecations(deprecationTracker) + if deprecationTracker.DidTrackDeprecations() { + fmt.Fprintln(formatter.ColorableStdErr, deprecationTracker.DeprecationsReport()) + } + + if !passed { + t.Fail() + } + + if passed && hasFocusedTests && strings.TrimSpace(os.Getenv("GINKGO_EDITOR_INTEGRATION")) == "" { + fmt.Println("PASS | FOCUSED") + os.Exit(types.GINKGO_FOCUS_EXIT_CODE) + } + return passed +} + +/* +Skip instructs Ginkgo to skip the current spec + +You can call Skip in any Setup or Subject node closure. + +For more on how to filter specs in Ginkgo see https://onsi.github.io/ginkgo/#filtering-specs +*/ +func Skip(message string, callerSkip ...int) { + skip := 0 + if len(callerSkip) > 0 { + skip = callerSkip[0] + } + cl := types.NewCodeLocationWithStackTrace(skip + 1) + global.Failer.Skip(message, cl) + panic(types.GinkgoErrors.UncaughtGinkgoPanic(cl)) +} + +/* +Fail notifies Ginkgo that the current spec has failed. (Gomega will call Fail for you automatically when an assertion fails.) + +Under the hood, Fail panics to end execution of the current spec. Ginkgo will catch this panic and proceed with +the subsequent spec. If you call Fail, or make an assertion, within a goroutine launched by your spec you must +add defer GinkgoRecover() to the goroutine to catch the panic emitted by Fail. + +You can call Fail in any Setup or Subject node closure. + +You can learn more about how Ginkgo manages failures here: https://onsi.github.io/ginkgo/#mental-model-how-ginkgo-handles-failure +*/ +func Fail(message string, callerSkip ...int) { + skip := 0 + if len(callerSkip) > 0 { + skip = callerSkip[0] + } + + cl := types.NewCodeLocationWithStackTrace(skip + 1) + global.Failer.Fail(message, cl) + panic(types.GinkgoErrors.UncaughtGinkgoPanic(cl)) +} + +/* +AbortSuite instructs Ginkgo to fail the current spec and skip all subsequent specs, thereby aborting the suite. + +You can call AbortSuite in any Setup or Subject node closure. + +You can learn more about how Ginkgo handles suite interruptions here: https://onsi.github.io/ginkgo/#interrupting-aborting-and-timing-out-suites +*/ +func AbortSuite(message string, callerSkip ...int) { + skip := 0 + if len(callerSkip) > 0 { + skip = callerSkip[0] + } + + cl := types.NewCodeLocationWithStackTrace(skip + 1) + global.Failer.AbortSuite(message, cl) + panic(types.GinkgoErrors.UncaughtGinkgoPanic(cl)) +} + +/* +GinkgoRecover should be deferred at the top of any spawned goroutine that (may) call `Fail` +Since Gomega assertions call fail, you should throw a `defer GinkgoRecover()` at the top of any goroutine that +calls out to Gomega + +Here's why: Ginkgo's `Fail` method records the failure and then panics to prevent +further assertions from running. This panic must be recovered. Normally, Ginkgo recovers the panic for you, +however if a panic originates on a goroutine *launched* from one of your specs there's no +way for Ginkgo to rescue the panic. To do this, you must remember to `defer GinkgoRecover()` at the top of such a goroutine. + +You can learn more about how Ginkgo manages failures here: https://onsi.github.io/ginkgo/#mental-model-how-ginkgo-handles-failure +*/ +func GinkgoRecover() { + e := recover() + if e != nil { + global.Failer.Panic(types.NewCodeLocationWithStackTrace(1), e) + } +} + +// pushNode is used by the various test construction DSL methods to push nodes onto the suite +// it handles returned errors, emits a detailed error message to help the user learn what they may have done wrong, then exits +func pushNode(node internal.Node, errors []error) bool { + exitIfErrors(errors) + exitIfErr(global.Suite.PushNode(node)) + return true +} + +/* +Describe nodes are Container nodes that allow you to organize your specs. A Describe node's closure can contain any number of +Setup nodes (e.g. BeforeEach, AfterEach, JustBeforeEach), and Subject nodes (i.e. It). + +Context and When nodes are aliases for Describe - use whichever gives your suite a better narrative flow. It is idomatic +to Describe the behavior of an object or function and, within that Describe, outline a number of Contexts and Whens. + +You can learn more at https://onsi.github.io/ginkgo/#organizing-specs-with-container-nodes +In addition, container nodes can be decorated with a variety of decorators. You can learn more here: https://onsi.github.io/ginkgo/#decorator-reference +*/ +func Describe(text string, args ...interface{}) bool { + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeContainer, text, args...)) +} + +/* +FDescribe focuses specs within the Describe block. +*/ +func FDescribe(text string, args ...interface{}) bool { + args = append(args, internal.Focus) + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeContainer, text, args...)) +} + +/* +PDescribe marks specs within the Describe block as pending. +*/ +func PDescribe(text string, args ...interface{}) bool { + args = append(args, internal.Pending) + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeContainer, text, args...)) +} + +/* +XDescribe marks specs within the Describe block as pending. + +XDescribe is an alias for PDescribe +*/ +var XDescribe = PDescribe + +/* Context is an alias for Describe - it generates the exact same kind of Container node */ +var Context, FContext, PContext, XContext = Describe, FDescribe, PDescribe, XDescribe + +/* When is an alias for Describe - it generates the exact same kind of Container node */ +func When(text string, args ...interface{}) bool { + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeContainer, "when "+text, args...)) +} + +/* When is an alias for Describe - it generates the exact same kind of Container node */ +func FWhen(text string, args ...interface{}) bool { + args = append(args, internal.Focus) + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeContainer, "when "+text, args...)) +} + +/* When is an alias for Describe - it generates the exact same kind of Container node */ +func PWhen(text string, args ...interface{}) bool { + args = append(args, internal.Pending) + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeContainer, "when "+text, args...)) +} + +var XWhen = PWhen + +/* +It nodes are Subject nodes that contain your spec code and assertions. + +Each It node corresponds to an individual Ginkgo spec. You cannot nest any other Ginkgo nodes within an It node's closure. + +You can pass It nodes bare functions (func() {}) or functions that receive a SpecContext or context.Context: func(ctx SpecContext) {} and func (ctx context.Context) {}. If the function takes a context then the It is deemed interruptible and Ginkgo will cancel the context in the event of a timeout (configured via the SpecTimeout() or NodeTimeout() decorators) or of an interrupt signal. + +You can learn more at https://onsi.github.io/ginkgo/#spec-subjects-it +In addition, subject nodes can be decorated with a variety of decorators. You can learn more here: https://onsi.github.io/ginkgo/#decorator-reference +*/ +func It(text string, args ...interface{}) bool { + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeIt, text, args...)) +} + +/* +FIt allows you to focus an individual It. +*/ +func FIt(text string, args ...interface{}) bool { + args = append(args, internal.Focus) + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeIt, text, args...)) +} + +/* +PIt allows you to mark an individual It as pending. +*/ +func PIt(text string, args ...interface{}) bool { + args = append(args, internal.Pending) + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeIt, text, args...)) +} + +/* +XIt allows you to mark an individual It as pending. + +XIt is an alias for PIt +*/ +var XIt = PIt + +/* +Specify is an alias for It - it can allow for more natural wording in some context. +*/ +var Specify, FSpecify, PSpecify, XSpecify = It, FIt, PIt, XIt + +/* +By allows you to better document complex Specs. + +Generally you should try to keep your Its short and to the point. This is not always possible, however, +especially in the context of integration tests that capture complex or lengthy workflows. + +By allows you to document such flows. By may be called within a Setup or Subject node (It, BeforeEach, etc...) +and will simply log the passed in text to the GinkgoWriter. If By is handed a function it will immediately run the function. + +By will also generate and attach a ReportEntry to the spec. This will ensure that By annotations appear in Ginkgo's machine-readable reports. + +Note that By does not generate a new Ginkgo node - rather it is simply synctactic sugar around GinkgoWriter and AddReportEntry +You can learn more about By here: https://onsi.github.io/ginkgo/#documenting-complex-specs-by +*/ +func By(text string, callback ...func()) { + exitIfErr(global.Suite.By(text, callback...)) +} + +/* +BeforeSuite nodes are suite-level Setup nodes that run just once before any specs are run. +When running in parallel, each parallel process will call BeforeSuite. + +You may only register *one* BeforeSuite handler per test suite. You typically do so in your bootstrap file at the top level. + +BeforeSuite can take a func() body, or an interruptible func(SpecContext)/func(context.Context) body. + +You cannot nest any other Ginkgo nodes within a BeforeSuite node's closure. +You can learn more here: https://onsi.github.io/ginkgo/#suite-setup-and-cleanup-beforesuite-and-aftersuite +*/ +func BeforeSuite(body interface{}, args ...interface{}) bool { + combinedArgs := []interface{}{body} + combinedArgs = append(combinedArgs, args...) + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeBeforeSuite, "", combinedArgs...)) +} + +/* +AfterSuite nodes are suite-level Setup nodes run after all specs have finished - regardless of whether specs have passed or failed. +AfterSuite node closures always run, even if Ginkgo receives an interrupt signal (^C), in order to ensure cleanup occurs. + +When running in parallel, each parallel process will call AfterSuite. + +You may only register *one* AfterSuite handler per test suite. You typically do so in your bootstrap file at the top level. + +AfterSuite can take a func() body, or an interruptible func(SpecContext)/func(context.Context) body. + +You cannot nest any other Ginkgo nodes within an AfterSuite node's closure. +You can learn more here: https://onsi.github.io/ginkgo/#suite-setup-and-cleanup-beforesuite-and-aftersuite +*/ +func AfterSuite(body interface{}, args ...interface{}) bool { + combinedArgs := []interface{}{body} + combinedArgs = append(combinedArgs, args...) + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeAfterSuite, "", combinedArgs...)) +} + +/* +SynchronizedBeforeSuite nodes allow you to perform some of the suite setup just once - on parallel process #1 - and then pass information +from that setup to the rest of the suite setup on all processes. This is useful for performing expensive or singleton setup once, then passing +information from that setup to all parallel processes. + +SynchronizedBeforeSuite accomplishes this by taking *two* function arguments and passing data between them. +The first function is only run on parallel process #1. The second is run on all processes, but *only* after the first function completes successfully. The functions have the following signatures: + +The first function (which only runs on process #1) can have any of the following the signatures: + + func() + func(ctx context.Context) + func(ctx SpecContext) + func() []byte + func(ctx context.Context) []byte + func(ctx SpecContext) []byte + +The byte array returned by the first function (if present) is then passed to the second function, which can have any of the following signature: + + func() + func(ctx context.Context) + func(ctx SpecContext) + func(data []byte) + func(ctx context.Context, data []byte) + func(ctx SpecContext, data []byte) + +If either function receives a context.Context/SpecContext it is considered interruptible. + +You cannot nest any other Ginkgo nodes within an SynchronizedBeforeSuite node's closure. +You can learn more, and see some examples, here: https://onsi.github.io/ginkgo/#parallel-suite-setup-and-cleanup-synchronizedbeforesuite-and-synchronizedaftersuite +*/ +func SynchronizedBeforeSuite(process1Body interface{}, allProcessBody interface{}, args ...interface{}) bool { + combinedArgs := []interface{}{process1Body, allProcessBody} + combinedArgs = append(combinedArgs, args...) + + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeSynchronizedBeforeSuite, "", combinedArgs...)) +} + +/* +SynchronizedAfterSuite nodes complement the SynchronizedBeforeSuite nodes in solving the problem of splitting clean up into a piece that runs on all processes +and a piece that must only run once - on process #1. + +SynchronizedAfterSuite accomplishes this by taking *two* function arguments. The first runs on all processes. The second runs only on parallel process #1 +and *only* after all other processes have finished and exited. This ensures that process #1, and any resources it is managing, remain alive until +all other processes are finished. These two functions can be bare functions (func()) or interruptible (func(context.Context)/func(SpecContext)) + +Note that you can also use DeferCleanup() in SynchronizedBeforeSuite to accomplish similar results. + +You cannot nest any other Ginkgo nodes within an SynchronizedAfterSuite node's closure. +You can learn more, and see some examples, here: https://onsi.github.io/ginkgo/#parallel-suite-setup-and-cleanup-synchronizedbeforesuite-and-synchronizedaftersuite +*/ +func SynchronizedAfterSuite(allProcessBody interface{}, process1Body interface{}, args ...interface{}) bool { + combinedArgs := []interface{}{allProcessBody, process1Body} + combinedArgs = append(combinedArgs, args...) + + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeSynchronizedAfterSuite, "", combinedArgs...)) +} + +/* +BeforeEach nodes are Setup nodes whose closures run before It node closures. When multiple BeforeEach nodes +are defined in nested Container nodes the outermost BeforeEach node closures are run first. + +BeforeEach can take a func() body, or an interruptible func(SpecContext)/func(context.Context) body. + +You cannot nest any other Ginkgo nodes within a BeforeEach node's closure. +You can learn more here: https://onsi.github.io/ginkgo/#extracting-common-setup-beforeeach +*/ +func BeforeEach(args ...interface{}) bool { + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeBeforeEach, "", args...)) +} + +/* +JustBeforeEach nodes are similar to BeforeEach nodes, however they are guaranteed to run *after* all BeforeEach node closures - just before the It node closure. +This can allow you to separate configuration from creation of resources for a spec. + +JustBeforeEach can take a func() body, or an interruptible func(SpecContext)/func(context.Context) body. + +You cannot nest any other Ginkgo nodes within a JustBeforeEach node's closure. +You can learn more and see some examples here: https://onsi.github.io/ginkgo/#separating-creation-and-configuration-justbeforeeach +*/ +func JustBeforeEach(args ...interface{}) bool { + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeJustBeforeEach, "", args...)) +} + +/* +AfterEach nodes are Setup nodes whose closures run after It node closures. When multiple AfterEach nodes +are defined in nested Container nodes the innermost AfterEach node closures are run first. + +Note that you can also use DeferCleanup() in other Setup or Subject nodes to accomplish similar results. + +AfterEach can take a func() body, or an interruptible func(SpecContext)/func(context.Context) body. + +You cannot nest any other Ginkgo nodes within an AfterEach node's closure. +You can learn more here: https://onsi.github.io/ginkgo/#spec-cleanup-aftereach-and-defercleanup +*/ +func AfterEach(args ...interface{}) bool { + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeAfterEach, "", args...)) +} + +/* +JustAfterEach nodes are similar to AfterEach nodes, however they are guaranteed to run *before* all AfterEach node closures - just after the It node closure. This can allow you to separate diagnostics collection from teardown for a spec. + +JustAfterEach can take a func() body, or an interruptible func(SpecContext)/func(context.Context) body. + +You cannot nest any other Ginkgo nodes within a JustAfterEach node's closure. +You can learn more and see some examples here: https://onsi.github.io/ginkgo/#separating-diagnostics-collection-and-teardown-justaftereach +*/ +func JustAfterEach(args ...interface{}) bool { + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeJustAfterEach, "", args...)) +} + +/* +BeforeAll nodes are Setup nodes that can occur inside Ordered containers. They run just once before any specs in the Ordered container run. + +Multiple BeforeAll nodes can be defined in a given Ordered container however they cannot be nested inside any other container. + +BeforeAll can take a func() body, or an interruptible func(SpecContext)/func(context.Context) body. + +You cannot nest any other Ginkgo nodes within a BeforeAll node's closure. +You can learn more about Ordered Containers at: https://onsi.github.io/ginkgo/#ordered-containers +And you can learn more about BeforeAll at: https://onsi.github.io/ginkgo/#setup-in-ordered-containers-beforeall-and-afterall +*/ +func BeforeAll(args ...interface{}) bool { + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeBeforeAll, "", args...)) +} + +/* +AfterAll nodes are Setup nodes that can occur inside Ordered containers. They run just once after all specs in the Ordered container have run. + +Multiple AfterAll nodes can be defined in a given Ordered container however they cannot be nested inside any other container. + +Note that you can also use DeferCleanup() in a BeforeAll node to accomplish similar behavior. + +AfterAll can take a func() body, or an interruptible func(SpecContext)/func(context.Context) body. + +You cannot nest any other Ginkgo nodes within an AfterAll node's closure. +You can learn more about Ordered Containers at: https://onsi.github.io/ginkgo/#ordered-containers +And you can learn more about AfterAll at: https://onsi.github.io/ginkgo/#setup-in-ordered-containers-beforeall-and-afterall +*/ +func AfterAll(args ...interface{}) bool { + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeAfterAll, "", args...)) +} + +/* +DeferCleanup can be called within any Setup or Subject node to register a cleanup callback that Ginkgo will call at the appropriate time to cleanup after the spec. + +DeferCleanup can be passed: +1. A function that takes no arguments and returns no values. +2. A function that returns multiple values. `DeferCleanup` will ignore all these return values except for the last one. If this last return value is a non-nil error `DeferCleanup` will fail the spec). +3. A function that takes a context.Context or SpecContext (and optionally returns multiple values). The resulting cleanup node is deemed interruptible and the passed-in context will be cancelled in the event of a timeout or interrupt. +4. A function that takes arguments (and optionally returns multiple values) followed by a list of arguments to pass to the function. +5. A function that takes SpecContext and a list of arguments (and optionally returns multiple values) followed by a list of arguments to pass to the function. + +For example: + + BeforeEach(func() { + DeferCleanup(os.SetEnv, "FOO", os.GetEnv("FOO")) + os.SetEnv("FOO", "BAR") + }) + +will register a cleanup handler that will set the environment variable "FOO" to it's current value (obtained by os.GetEnv("FOO")) after the spec runs and then sets the environment variable "FOO" to "BAR" for the current spec. + +Similarly: + + BeforeEach(func() { + DeferCleanup(func(ctx SpecContext, path) { + req, err := http.NewRequestWithContext(ctx, "POST", path, nil) + Expect(err).NotTo(HaveOccured()) + _, err := http.DefaultClient.Do(req) + Expect(err).NotTo(HaveOccured()) + }, "example.com/cleanup", NodeTimeout(time.Second*3)) + }) + +will register a cleanup handler that will have three seconds to successfully complete a request to the specified path. Note that we do not specify a context in the list of arguments passed to DeferCleanup - only in the signature of the function we pass in. Ginkgo will detect the requested context and supply a SpecContext when it invokes the cleanup node. If you want to pass in your own context in addition to the Ginkgo-provided SpecContext you must specify the SpecContext as the first argument (e.g. func(ctx SpecContext, otherCtx context.Context)). + +When DeferCleanup is called in BeforeEach, JustBeforeEach, It, AfterEach, or JustAfterEach the registered callback will be invoked when the spec completes (i.e. it will behave like an AfterEach node) +When DeferCleanup is called in BeforeAll or AfterAll the registered callback will be invoked when the ordered container completes (i.e. it will behave like an AfterAll node) +When DeferCleanup is called in BeforeSuite, SynchronizedBeforeSuite, AfterSuite, or SynchronizedAfterSuite the registered callback will be invoked when the suite completes (i.e. it will behave like an AfterSuite node) + +Note that DeferCleanup does not represent a node but rather dynamically generates the appropriate type of cleanup node based on the context in which it is called. As such you must call DeferCleanup within a Setup or Subject node, and not within a Container node. +You can learn more about DeferCleanup here: https://onsi.github.io/ginkgo/#cleaning-up-our-cleanup-code-defercleanup +*/ +func DeferCleanup(args ...interface{}) { + fail := func(message string, cl types.CodeLocation) { + global.Failer.Fail(message, cl) + } + pushNode(internal.NewCleanupNode(deprecationTracker, fail, args...)) +} diff --git a/vendor/github.com/onsi/ginkgo/v2/decorator_dsl.go b/vendor/github.com/onsi/ginkgo/v2/decorator_dsl.go new file mode 100644 index 00000000000..e43d9cbbb3c --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/decorator_dsl.go @@ -0,0 +1,133 @@ +package ginkgo + +import ( + "github.com/onsi/ginkgo/v2/internal" +) + +/* +Offset(uint) is a decorator that allows you to change the stack-frame offset used when computing the line number of the node in question. + +You can learn more here: https://onsi.github.io/ginkgo/#the-offset-decorator +You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorator-reference +*/ +type Offset = internal.Offset + +/* +FlakeAttempts(uint N) is a decorator that allows you to mark individual specs or spec containers as flaky. Ginkgo will run them up to `N` times until they pass. + +You can learn more here: https://onsi.github.io/ginkgo/#the-flakeattempts-decorator +You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorator-reference +*/ +type FlakeAttempts = internal.FlakeAttempts + +/* +MustPassRepeatedly(uint N) is a decorator that allows you to repeat the execution of individual specs or spec containers. Ginkgo will run them up to `N` times until they fail. + +You can learn more here: https://onsi.github.io/ginkgo/#the-mustpassrepeatedly-decorator +You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorator-reference +*/ +type MustPassRepeatedly = internal.MustPassRepeatedly + +/* +Focus is a decorator that allows you to mark a spec or container as focused. Identical to FIt and FDescribe. + +You can learn more here: https://onsi.github.io/ginkgo/#filtering-specs +You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorator-reference +*/ +const Focus = internal.Focus + +/* +Pending is a decorator that allows you to mark a spec or container as pending. Identical to PIt and PDescribe. + +You can learn more here: https://onsi.github.io/ginkgo/#filtering-specs +You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorator-reference +*/ +const Pending = internal.Pending + +/* +Serial is a decorator that allows you to mark a spec or container as serial. These specs will never run in parallel with other specs. +Tests in ordered containers cannot be marked as serial - mark the ordered container instead. + +You can learn more here: https://onsi.github.io/ginkgo/#serial-specs +You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorator-reference +*/ +const Serial = internal.Serial + +/* +Ordered is a decorator that allows you to mark a container as ordered. Tests in the container will always run in the order they appear. +They will never be randomized and they will never run in parallel with one another, though they may run in parallel with other specs. + +You can learn more here: https://onsi.github.io/ginkgo/#ordered-containers +You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorator-reference +*/ +const Ordered = internal.Ordered + +/* +OncePerOrdered is a decorator that allows you to mark outer BeforeEach, AfterEach, JustBeforeEach, and JustAfterEach setup nodes to run once +per ordered context. Normally these setup nodes run around each individual spec, with OncePerOrdered they will run once around the set of specs in an ordered container. +The behavior for non-Ordered containers/specs is unchanged. + +You can learn more here: https://onsi.github.io/ginkgo/#setup-around-ordered-containers-the-onceperordered-decorator +You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorator-reference +*/ +const OncePerOrdered = internal.OncePerOrdered + +/* +Label decorates specs with Labels. Multiple labels can be passed to Label and these can be arbitrary strings but must not include the following characters: "&|!,()/". +Labels can be applied to container and subject nodes, but not setup nodes. You can provide multiple Labels to a given node and a spec's labels is the union of all labels in its node hierarchy. + +You can learn more here: https://onsi.github.io/ginkgo/#spec-labels +You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorator-reference +*/ +func Label(labels ...string) Labels { + return Labels(labels) +} + +/* +Labels are the type for spec Label decorators. Use Label(...) to construct Labels. +You can learn more here: https://onsi.github.io/ginkgo/#spec-labels +*/ +type Labels = internal.Labels + +/* +PollProgressAfter allows you to override the configured value for --poll-progress-after for a particular node. + +Ginkgo will start emitting node progress if the node is still running after a duration of PollProgressAfter. This allows you to get quicker feedback about the state of a long-running spec. +*/ +type PollProgressAfter = internal.PollProgressAfter + +/* +PollProgressInterval allows you to override the configured value for --poll-progress-interval for a particular node. + +Once a node has been running for longer than PollProgressAfter Ginkgo will emit node progress periodically at an interval of PollProgresInterval. +*/ +type PollProgressInterval = internal.PollProgressInterval + +/* +NodeTimeout allows you to specify a timeout for an indivdiual node. The node cannot be a container and must be interruptible (i.e. it must be passed a function that accepts a SpecContext or context.Context). + +If the node does not exit within the specified NodeTimeout its context will be cancelled. The node wil then have a period of time controlled by the GracePeriod decorator (or global --grace-period command-line argument) to exit. If the node does not exit within GracePeriod Ginkgo will leak the node and proceed to any clean-up nodes associated with the current spec. +*/ +type NodeTimeout = internal.NodeTimeout + +/* +SpecTimeout allows you to specify a timeout for an indivdiual spec. SpecTimeout can only decorate interruptible It nodes. + +All nodes associated with the It node will need to complete before the SpecTimeout has elapsed. Individual nodes (e.g. BeforeEach) may be decorated with different NodeTimeouts - but these can only serve to provide a more stringent deadline for the node in question; they cannot extend the deadline past the SpecTimeout. + +If the spec does not complete within the specified SpecTimeout the currently running node will have its context cancelled. The node wil then have a period of time controlled by that node's GracePeriod decorator (or global --grace-period command-line argument) to exit. If the node does not exit within GracePeriod Ginkgo will leak the node and proceed to any clean-up nodes associated with the current spec. +*/ +type SpecTimeout = internal.SpecTimeout + +/* +GracePeriod denotes the period of time Ginkgo will wait for an interruptible node to exit once an interruption (whether due to a timeout or a user-invoked signal) has occurred. If both the global --grace-period cli flag and a GracePeriod decorator are specified the value in the decorator will take precedence. + +Nodes that do not finish within a GracePeriod will be leaked and Ginkgo will proceed to run subsequent nodes. In the event of a timeout, such leaks will be reported to the user. +*/ +type GracePeriod = internal.GracePeriod + +/* +SuppressProgressReporting is a decorator that allows you to disable progress reporting of a particular node. This is useful if `ginkgo -v -progress` is generating too much noise; particularly +if you have a `ReportAfterEach` node that is running for every skipped spec and is generating lots of progress reports. +*/ +const SuppressProgressReporting = internal.SuppressProgressReporting diff --git a/vendor/github.com/onsi/ginkgo/v2/deprecated_dsl.go b/vendor/github.com/onsi/ginkgo/v2/deprecated_dsl.go new file mode 100644 index 00000000000..f912bbec65a --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/deprecated_dsl.go @@ -0,0 +1,135 @@ +package ginkgo + +import ( + "time" + + "github.com/onsi/ginkgo/v2/internal" + "github.com/onsi/ginkgo/v2/internal/global" + "github.com/onsi/ginkgo/v2/reporters" + "github.com/onsi/ginkgo/v2/types" +) + +/* +Deprecated: Done Channel for asynchronous testing + +The Done channel pattern is no longer supported in Ginkgo 2.0. +See here for better patterns for asynchronous testing: https://onsi.github.io/ginkgo/#patterns-for-asynchronous-testing + +For a migration guide see: https://onsi.github.io/ginkgo/MIGRATING_TO_V2#removed-async-testing +*/ +type Done = internal.Done + +/* +Deprecated: Custom Ginkgo test reporters are deprecated in Ginkgo 2.0. + +Use Ginkgo's reporting nodes instead and 2.0 reporting infrastructure instead. You can learn more here: https://onsi.github.io/ginkgo/#reporting-infrastructure +For a migration guide see: https://onsi.github.io/ginkgo/MIGRATING_TO_V2#removed-custom-reporters +*/ +type Reporter = reporters.DeprecatedReporter + +/* +Deprecated: Custom Reporters have been removed in Ginkgo 2.0. RunSpecsWithDefaultAndCustomReporters will simply call RunSpecs() + +Use Ginkgo's reporting nodes instead and 2.0 reporting infrastructure instead. You can learn more here: https://onsi.github.io/ginkgo/#reporting-infrastructure +For a migration guide see: https://onsi.github.io/ginkgo/MIGRATING_TO_V2#removed-custom-reporters +*/ +func RunSpecsWithDefaultAndCustomReporters(t GinkgoTestingT, description string, _ []Reporter) bool { + deprecationTracker.TrackDeprecation(types.Deprecations.CustomReporter()) + return RunSpecs(t, description) +} + +/* +Deprecated: Custom Reporters have been removed in Ginkgo 2.0. RunSpecsWithCustomReporters will simply call RunSpecs() + +Use Ginkgo's reporting nodes instead and 2.0 reporting infrastructure instead. You can learn more here: https://onsi.github.io/ginkgo/#reporting-infrastructure +For a migration guide see: https://onsi.github.io/ginkgo/MIGRATING_TO_V2#removed-custom-reporters +*/ +func RunSpecsWithCustomReporters(t GinkgoTestingT, description string, _ []Reporter) bool { + deprecationTracker.TrackDeprecation(types.Deprecations.CustomReporter()) + return RunSpecs(t, description) +} + +/* +Deprecated: GinkgoTestDescription has been replaced with SpecReport. + +Use CurrentSpecReport() instead. +You can learn more here: https://onsi.github.io/ginkgo/#getting-a-report-for-the-current-spec +The SpecReport type is documented here: https://pkg.go.dev/github.com/onsi/ginkgo/v2/types#SpecReport +*/ +type DeprecatedGinkgoTestDescription struct { + FullTestText string + ComponentTexts []string + TestText string + + FileName string + LineNumber int + + Failed bool + Duration time.Duration +} +type GinkgoTestDescription = DeprecatedGinkgoTestDescription + +/* +Deprecated: CurrentGinkgoTestDescription has been replaced with CurrentSpecReport. + +Use CurrentSpecReport() instead. +You can learn more here: https://onsi.github.io/ginkgo/#getting-a-report-for-the-current-spec +The SpecReport type is documented here: https://pkg.go.dev/github.com/onsi/ginkgo/v2/types#SpecReport +*/ +func CurrentGinkgoTestDescription() DeprecatedGinkgoTestDescription { + deprecationTracker.TrackDeprecation( + types.Deprecations.CurrentGinkgoTestDescription(), + types.NewCodeLocation(1), + ) + report := global.Suite.CurrentSpecReport() + if report.State == types.SpecStateInvalid { + return GinkgoTestDescription{} + } + componentTexts := []string{} + componentTexts = append(componentTexts, report.ContainerHierarchyTexts...) + componentTexts = append(componentTexts, report.LeafNodeText) + + return DeprecatedGinkgoTestDescription{ + ComponentTexts: componentTexts, + FullTestText: report.FullText(), + TestText: report.LeafNodeText, + FileName: report.LeafNodeLocation.FileName, + LineNumber: report.LeafNodeLocation.LineNumber, + Failed: report.State.Is(types.SpecStateFailureStates), + Duration: report.RunTime, + } +} + +/* +Deprecated: GinkgoParallelNode() has been renamed to GinkgoParallelProcess() +*/ +func GinkgoParallelNode() int { + deprecationTracker.TrackDeprecation( + types.Deprecations.ParallelNode(), + types.NewCodeLocation(1), + ) + return GinkgoParallelProcess() +} + +/* +Deprecated: Benchmarker has been removed from Ginkgo 2.0 + +Use Gomega's gmeasure package instead. +You can learn more here: https://onsi.github.io/ginkgo/#benchmarking-code +*/ +type Benchmarker interface { + Time(name string, body func(), info ...interface{}) (elapsedTime time.Duration) + RecordValue(name string, value float64, info ...interface{}) + RecordValueWithPrecision(name string, value float64, units string, precision int, info ...interface{}) +} + +/* +Deprecated: Measure() has been removed from Ginkgo 2.0 + +Use Gomega's gmeasure package instead. +You can learn more here: https://onsi.github.io/ginkgo/#benchmarking-code +*/ +func Measure(_ ...interface{}) bool { + deprecationTracker.TrackDeprecation(types.Deprecations.Measure(), types.NewCodeLocation(1)) + return true +} diff --git a/vendor/github.com/onsi/ginkgo/v2/formatter/colorable_others.go b/vendor/github.com/onsi/ginkgo/v2/formatter/colorable_others.go new file mode 100644 index 00000000000..778bfd7c7ca --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/formatter/colorable_others.go @@ -0,0 +1,41 @@ +// +build !windows + +/* +These packages are used for colorize on Windows and contributed by mattn.jp@gmail.com + + * go-colorable: + * go-isatty: + +The MIT License (MIT) + +Copyright (c) 2016 Yasuhiro Matsumoto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package formatter + +import ( + "io" + "os" +) + +func newColorable(file *os.File) io.Writer { + return file +} diff --git a/vendor/github.com/onsi/ginkgo/v2/formatter/colorable_windows.go b/vendor/github.com/onsi/ginkgo/v2/formatter/colorable_windows.go new file mode 100644 index 00000000000..dd1d143cc20 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/formatter/colorable_windows.go @@ -0,0 +1,809 @@ +/* +These packages are used for colorize on Windows and contributed by mattn.jp@gmail.com + + * go-colorable: + * go-isatty: + +The MIT License (MIT) + +Copyright (c) 2016 Yasuhiro Matsumoto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package formatter + +import ( + "bytes" + "fmt" + "io" + "math" + "os" + "strconv" + "strings" + "syscall" + "unsafe" +) + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") + procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute") + procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") + procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW") + procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute") + procGetConsoleMode = kernel32.NewProc("GetConsoleMode") +) + +func isTerminal(fd uintptr) bool { + var st uint32 + r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0) + return r != 0 && e == 0 +} + +const ( + foregroundBlue = 0x1 + foregroundGreen = 0x2 + foregroundRed = 0x4 + foregroundIntensity = 0x8 + foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity) + backgroundBlue = 0x10 + backgroundGreen = 0x20 + backgroundRed = 0x40 + backgroundIntensity = 0x80 + backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity) +) + +type wchar uint16 +type short int16 +type dword uint32 +type word uint16 + +type coord struct { + x short + y short +} + +type smallRect struct { + left short + top short + right short + bottom short +} + +type consoleScreenBufferInfo struct { + size coord + cursorPosition coord + attributes word + window smallRect + maximumWindowSize coord +} + +type writer struct { + out io.Writer + handle syscall.Handle + lastbuf bytes.Buffer + oldattr word +} + +func newColorable(file *os.File) io.Writer { + if file == nil { + panic("nil passed instead of *os.File to NewColorable()") + } + + if isTerminal(file.Fd()) { + var csbi consoleScreenBufferInfo + handle := syscall.Handle(file.Fd()) + procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + return &writer{out: file, handle: handle, oldattr: csbi.attributes} + } else { + return file + } +} + +var color256 = map[int]int{ + 0: 0x000000, + 1: 0x800000, + 2: 0x008000, + 3: 0x808000, + 4: 0x000080, + 5: 0x800080, + 6: 0x008080, + 7: 0xc0c0c0, + 8: 0x808080, + 9: 0xff0000, + 10: 0x00ff00, + 11: 0xffff00, + 12: 0x0000ff, + 13: 0xff00ff, + 14: 0x00ffff, + 15: 0xffffff, + 16: 0x000000, + 17: 0x00005f, + 18: 0x000087, + 19: 0x0000af, + 20: 0x0000d7, + 21: 0x0000ff, + 22: 0x005f00, + 23: 0x005f5f, + 24: 0x005f87, + 25: 0x005faf, + 26: 0x005fd7, + 27: 0x005fff, + 28: 0x008700, + 29: 0x00875f, + 30: 0x008787, + 31: 0x0087af, + 32: 0x0087d7, + 33: 0x0087ff, + 34: 0x00af00, + 35: 0x00af5f, + 36: 0x00af87, + 37: 0x00afaf, + 38: 0x00afd7, + 39: 0x00afff, + 40: 0x00d700, + 41: 0x00d75f, + 42: 0x00d787, + 43: 0x00d7af, + 44: 0x00d7d7, + 45: 0x00d7ff, + 46: 0x00ff00, + 47: 0x00ff5f, + 48: 0x00ff87, + 49: 0x00ffaf, + 50: 0x00ffd7, + 51: 0x00ffff, + 52: 0x5f0000, + 53: 0x5f005f, + 54: 0x5f0087, + 55: 0x5f00af, + 56: 0x5f00d7, + 57: 0x5f00ff, + 58: 0x5f5f00, + 59: 0x5f5f5f, + 60: 0x5f5f87, + 61: 0x5f5faf, + 62: 0x5f5fd7, + 63: 0x5f5fff, + 64: 0x5f8700, + 65: 0x5f875f, + 66: 0x5f8787, + 67: 0x5f87af, + 68: 0x5f87d7, + 69: 0x5f87ff, + 70: 0x5faf00, + 71: 0x5faf5f, + 72: 0x5faf87, + 73: 0x5fafaf, + 74: 0x5fafd7, + 75: 0x5fafff, + 76: 0x5fd700, + 77: 0x5fd75f, + 78: 0x5fd787, + 79: 0x5fd7af, + 80: 0x5fd7d7, + 81: 0x5fd7ff, + 82: 0x5fff00, + 83: 0x5fff5f, + 84: 0x5fff87, + 85: 0x5fffaf, + 86: 0x5fffd7, + 87: 0x5fffff, + 88: 0x870000, + 89: 0x87005f, + 90: 0x870087, + 91: 0x8700af, + 92: 0x8700d7, + 93: 0x8700ff, + 94: 0x875f00, + 95: 0x875f5f, + 96: 0x875f87, + 97: 0x875faf, + 98: 0x875fd7, + 99: 0x875fff, + 100: 0x878700, + 101: 0x87875f, + 102: 0x878787, + 103: 0x8787af, + 104: 0x8787d7, + 105: 0x8787ff, + 106: 0x87af00, + 107: 0x87af5f, + 108: 0x87af87, + 109: 0x87afaf, + 110: 0x87afd7, + 111: 0x87afff, + 112: 0x87d700, + 113: 0x87d75f, + 114: 0x87d787, + 115: 0x87d7af, + 116: 0x87d7d7, + 117: 0x87d7ff, + 118: 0x87ff00, + 119: 0x87ff5f, + 120: 0x87ff87, + 121: 0x87ffaf, + 122: 0x87ffd7, + 123: 0x87ffff, + 124: 0xaf0000, + 125: 0xaf005f, + 126: 0xaf0087, + 127: 0xaf00af, + 128: 0xaf00d7, + 129: 0xaf00ff, + 130: 0xaf5f00, + 131: 0xaf5f5f, + 132: 0xaf5f87, + 133: 0xaf5faf, + 134: 0xaf5fd7, + 135: 0xaf5fff, + 136: 0xaf8700, + 137: 0xaf875f, + 138: 0xaf8787, + 139: 0xaf87af, + 140: 0xaf87d7, + 141: 0xaf87ff, + 142: 0xafaf00, + 143: 0xafaf5f, + 144: 0xafaf87, + 145: 0xafafaf, + 146: 0xafafd7, + 147: 0xafafff, + 148: 0xafd700, + 149: 0xafd75f, + 150: 0xafd787, + 151: 0xafd7af, + 152: 0xafd7d7, + 153: 0xafd7ff, + 154: 0xafff00, + 155: 0xafff5f, + 156: 0xafff87, + 157: 0xafffaf, + 158: 0xafffd7, + 159: 0xafffff, + 160: 0xd70000, + 161: 0xd7005f, + 162: 0xd70087, + 163: 0xd700af, + 164: 0xd700d7, + 165: 0xd700ff, + 166: 0xd75f00, + 167: 0xd75f5f, + 168: 0xd75f87, + 169: 0xd75faf, + 170: 0xd75fd7, + 171: 0xd75fff, + 172: 0xd78700, + 173: 0xd7875f, + 174: 0xd78787, + 175: 0xd787af, + 176: 0xd787d7, + 177: 0xd787ff, + 178: 0xd7af00, + 179: 0xd7af5f, + 180: 0xd7af87, + 181: 0xd7afaf, + 182: 0xd7afd7, + 183: 0xd7afff, + 184: 0xd7d700, + 185: 0xd7d75f, + 186: 0xd7d787, + 187: 0xd7d7af, + 188: 0xd7d7d7, + 189: 0xd7d7ff, + 190: 0xd7ff00, + 191: 0xd7ff5f, + 192: 0xd7ff87, + 193: 0xd7ffaf, + 194: 0xd7ffd7, + 195: 0xd7ffff, + 196: 0xff0000, + 197: 0xff005f, + 198: 0xff0087, + 199: 0xff00af, + 200: 0xff00d7, + 201: 0xff00ff, + 202: 0xff5f00, + 203: 0xff5f5f, + 204: 0xff5f87, + 205: 0xff5faf, + 206: 0xff5fd7, + 207: 0xff5fff, + 208: 0xff8700, + 209: 0xff875f, + 210: 0xff8787, + 211: 0xff87af, + 212: 0xff87d7, + 213: 0xff87ff, + 214: 0xffaf00, + 215: 0xffaf5f, + 216: 0xffaf87, + 217: 0xffafaf, + 218: 0xffafd7, + 219: 0xffafff, + 220: 0xffd700, + 221: 0xffd75f, + 222: 0xffd787, + 223: 0xffd7af, + 224: 0xffd7d7, + 225: 0xffd7ff, + 226: 0xffff00, + 227: 0xffff5f, + 228: 0xffff87, + 229: 0xffffaf, + 230: 0xffffd7, + 231: 0xffffff, + 232: 0x080808, + 233: 0x121212, + 234: 0x1c1c1c, + 235: 0x262626, + 236: 0x303030, + 237: 0x3a3a3a, + 238: 0x444444, + 239: 0x4e4e4e, + 240: 0x585858, + 241: 0x626262, + 242: 0x6c6c6c, + 243: 0x767676, + 244: 0x808080, + 245: 0x8a8a8a, + 246: 0x949494, + 247: 0x9e9e9e, + 248: 0xa8a8a8, + 249: 0xb2b2b2, + 250: 0xbcbcbc, + 251: 0xc6c6c6, + 252: 0xd0d0d0, + 253: 0xdadada, + 254: 0xe4e4e4, + 255: 0xeeeeee, +} + +func (w *writer) Write(data []byte) (n int, err error) { + var csbi consoleScreenBufferInfo + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + + er := bytes.NewBuffer(data) +loop: + for { + r1, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + if r1 == 0 { + break loop + } + + c1, _, err := er.ReadRune() + if err != nil { + break loop + } + if c1 != 0x1b { + fmt.Fprint(w.out, string(c1)) + continue + } + c2, _, err := er.ReadRune() + if err != nil { + w.lastbuf.WriteRune(c1) + break loop + } + if c2 != 0x5b { + w.lastbuf.WriteRune(c1) + w.lastbuf.WriteRune(c2) + continue + } + + var buf bytes.Buffer + var m rune + for { + c, _, err := er.ReadRune() + if err != nil { + w.lastbuf.WriteRune(c1) + w.lastbuf.WriteRune(c2) + w.lastbuf.Write(buf.Bytes()) + break loop + } + if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { + m = c + break + } + buf.Write([]byte(string(c))) + } + + var csbi consoleScreenBufferInfo + switch m { + case 'A': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.y -= short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'B': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.y += short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'C': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x -= short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'D': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + if n, err = strconv.Atoi(buf.String()); err == nil { + var csbi consoleScreenBufferInfo + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x += short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + } + case 'E': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x = 0 + csbi.cursorPosition.y += short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'F': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x = 0 + csbi.cursorPosition.y -= short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'G': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x = short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'H': + token := strings.Split(buf.String(), ";") + if len(token) != 2 { + continue + } + n1, err := strconv.Atoi(token[0]) + if err != nil { + continue + } + n2, err := strconv.Atoi(token[1]) + if err != nil { + continue + } + csbi.cursorPosition.x = short(n2) + csbi.cursorPosition.x = short(n1) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'J': + n, err := strconv.Atoi(buf.String()) + if err != nil { + continue + } + var cursor coord + switch n { + case 0: + cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} + case 1: + cursor = coord{x: csbi.window.left, y: csbi.window.top} + case 2: + cursor = coord{x: csbi.window.left, y: csbi.window.top} + } + var count, written dword + count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.size.y-csbi.cursorPosition.y)*csbi.size.x) + procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + case 'K': + n, err := strconv.Atoi(buf.String()) + if err != nil { + continue + } + var cursor coord + switch n { + case 0: + cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} + case 1: + cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y} + case 2: + cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y} + } + var count, written dword + count = dword(csbi.size.x - csbi.cursorPosition.x) + procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + case 'm': + attr := csbi.attributes + cs := buf.String() + if cs == "" { + procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.oldattr)) + continue + } + token := strings.Split(cs, ";") + for i := 0; i < len(token); i += 1 { + ns := token[i] + if n, err = strconv.Atoi(ns); err == nil { + switch { + case n == 0 || n == 100: + attr = w.oldattr + case 1 <= n && n <= 5: + attr |= foregroundIntensity + case n == 7: + attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) + case 22 == n || n == 25 || n == 25: + attr |= foregroundIntensity + case n == 27: + attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) + case 30 <= n && n <= 37: + attr = (attr & backgroundMask) + if (n-30)&1 != 0 { + attr |= foregroundRed + } + if (n-30)&2 != 0 { + attr |= foregroundGreen + } + if (n-30)&4 != 0 { + attr |= foregroundBlue + } + case n == 38: // set foreground color. + if i < len(token)-2 && (token[i+1] == "5" || token[i+1] == "05") { + if n256, err := strconv.Atoi(token[i+2]); err == nil { + if n256foreAttr == nil { + n256setup() + } + attr &= backgroundMask + attr |= n256foreAttr[n256] + i += 2 + } + } else { + attr = attr & (w.oldattr & backgroundMask) + } + case n == 39: // reset foreground color. + attr &= backgroundMask + attr |= w.oldattr & foregroundMask + case 40 <= n && n <= 47: + attr = (attr & foregroundMask) + if (n-40)&1 != 0 { + attr |= backgroundRed + } + if (n-40)&2 != 0 { + attr |= backgroundGreen + } + if (n-40)&4 != 0 { + attr |= backgroundBlue + } + case n == 48: // set background color. + if i < len(token)-2 && token[i+1] == "5" { + if n256, err := strconv.Atoi(token[i+2]); err == nil { + if n256backAttr == nil { + n256setup() + } + attr &= foregroundMask + attr |= n256backAttr[n256] + i += 2 + } + } else { + attr = attr & (w.oldattr & foregroundMask) + } + case n == 49: // reset foreground color. + attr &= foregroundMask + attr |= w.oldattr & backgroundMask + case 90 <= n && n <= 97: + attr = (attr & backgroundMask) + attr |= foregroundIntensity + if (n-90)&1 != 0 { + attr |= foregroundRed + } + if (n-90)&2 != 0 { + attr |= foregroundGreen + } + if (n-90)&4 != 0 { + attr |= foregroundBlue + } + case 100 <= n && n <= 107: + attr = (attr & foregroundMask) + attr |= backgroundIntensity + if (n-100)&1 != 0 { + attr |= backgroundRed + } + if (n-100)&2 != 0 { + attr |= backgroundGreen + } + if (n-100)&4 != 0 { + attr |= backgroundBlue + } + } + procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr)) + } + } + } + } + return len(data) - w.lastbuf.Len(), nil +} + +type consoleColor struct { + rgb int + red bool + green bool + blue bool + intensity bool +} + +func (c consoleColor) foregroundAttr() (attr word) { + if c.red { + attr |= foregroundRed + } + if c.green { + attr |= foregroundGreen + } + if c.blue { + attr |= foregroundBlue + } + if c.intensity { + attr |= foregroundIntensity + } + return +} + +func (c consoleColor) backgroundAttr() (attr word) { + if c.red { + attr |= backgroundRed + } + if c.green { + attr |= backgroundGreen + } + if c.blue { + attr |= backgroundBlue + } + if c.intensity { + attr |= backgroundIntensity + } + return +} + +var color16 = []consoleColor{ + consoleColor{0x000000, false, false, false, false}, + consoleColor{0x000080, false, false, true, false}, + consoleColor{0x008000, false, true, false, false}, + consoleColor{0x008080, false, true, true, false}, + consoleColor{0x800000, true, false, false, false}, + consoleColor{0x800080, true, false, true, false}, + consoleColor{0x808000, true, true, false, false}, + consoleColor{0xc0c0c0, true, true, true, false}, + consoleColor{0x808080, false, false, false, true}, + consoleColor{0x0000ff, false, false, true, true}, + consoleColor{0x00ff00, false, true, false, true}, + consoleColor{0x00ffff, false, true, true, true}, + consoleColor{0xff0000, true, false, false, true}, + consoleColor{0xff00ff, true, false, true, true}, + consoleColor{0xffff00, true, true, false, true}, + consoleColor{0xffffff, true, true, true, true}, +} + +type hsv struct { + h, s, v float32 +} + +func (a hsv) dist(b hsv) float32 { + dh := a.h - b.h + switch { + case dh > 0.5: + dh = 1 - dh + case dh < -0.5: + dh = -1 - dh + } + ds := a.s - b.s + dv := a.v - b.v + return float32(math.Sqrt(float64(dh*dh + ds*ds + dv*dv))) +} + +func toHSV(rgb int) hsv { + r, g, b := float32((rgb&0xFF0000)>>16)/256.0, + float32((rgb&0x00FF00)>>8)/256.0, + float32(rgb&0x0000FF)/256.0 + min, max := minmax3f(r, g, b) + h := max - min + if h > 0 { + if max == r { + h = (g - b) / h + if h < 0 { + h += 6 + } + } else if max == g { + h = 2 + (b-r)/h + } else { + h = 4 + (r-g)/h + } + } + h /= 6.0 + s := max - min + if max != 0 { + s /= max + } + v := max + return hsv{h: h, s: s, v: v} +} + +type hsvTable []hsv + +func toHSVTable(rgbTable []consoleColor) hsvTable { + t := make(hsvTable, len(rgbTable)) + for i, c := range rgbTable { + t[i] = toHSV(c.rgb) + } + return t +} + +func (t hsvTable) find(rgb int) consoleColor { + hsv := toHSV(rgb) + n := 7 + l := float32(5.0) + for i, p := range t { + d := hsv.dist(p) + if d < l { + l, n = d, i + } + } + return color16[n] +} + +func minmax3f(a, b, c float32) (min, max float32) { + if a < b { + if b < c { + return a, c + } else if a < c { + return a, b + } else { + return c, b + } + } else { + if a < c { + return b, c + } else if b < c { + return b, a + } else { + return c, a + } + } +} + +var n256foreAttr []word +var n256backAttr []word + +func n256setup() { + n256foreAttr = make([]word, 256) + n256backAttr = make([]word, 256) + t := toHSVTable(color16) + for i, rgb := range color256 { + c := t.find(rgb) + n256foreAttr[i] = c.foregroundAttr() + n256backAttr[i] = c.backgroundAttr() + } +} diff --git a/vendor/github.com/onsi/ginkgo/v2/formatter/formatter.go b/vendor/github.com/onsi/ginkgo/v2/formatter/formatter.go new file mode 100644 index 00000000000..43b16211d8d --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/formatter/formatter.go @@ -0,0 +1,195 @@ +package formatter + +import ( + "fmt" + "os" + "regexp" + "strings" +) + +// ColorableStdOut and ColorableStdErr enable color output support on Windows +var ColorableStdOut = newColorable(os.Stdout) +var ColorableStdErr = newColorable(os.Stderr) + +const COLS = 80 + +type ColorMode uint8 + +const ( + ColorModeNone ColorMode = iota + ColorModeTerminal + ColorModePassthrough +) + +var SingletonFormatter = New(ColorModeTerminal) + +func F(format string, args ...interface{}) string { + return SingletonFormatter.F(format, args...) +} + +func Fi(indentation uint, format string, args ...interface{}) string { + return SingletonFormatter.Fi(indentation, format, args...) +} + +func Fiw(indentation uint, maxWidth uint, format string, args ...interface{}) string { + return SingletonFormatter.Fiw(indentation, maxWidth, format, args...) +} + +type Formatter struct { + ColorMode ColorMode + colors map[string]string + styleRe *regexp.Regexp + preserveColorStylingTags bool +} + +func NewWithNoColorBool(noColor bool) Formatter { + if noColor { + return New(ColorModeNone) + } + return New(ColorModeTerminal) +} + +func New(colorMode ColorMode) Formatter { + f := Formatter{ + ColorMode: colorMode, + colors: map[string]string{ + "/": "\x1b[0m", + "bold": "\x1b[1m", + "underline": "\x1b[4m", + + "red": "\x1b[38;5;9m", + "orange": "\x1b[38;5;214m", + "coral": "\x1b[38;5;204m", + "magenta": "\x1b[38;5;13m", + "green": "\x1b[38;5;10m", + "dark-green": "\x1b[38;5;28m", + "yellow": "\x1b[38;5;11m", + "light-yellow": "\x1b[38;5;228m", + "cyan": "\x1b[38;5;14m", + "gray": "\x1b[38;5;243m", + "light-gray": "\x1b[38;5;246m", + "blue": "\x1b[38;5;12m", + }, + } + colors := []string{} + for color := range f.colors { + colors = append(colors, color) + } + f.styleRe = regexp.MustCompile("{{(" + strings.Join(colors, "|") + ")}}") + return f +} + +func (f Formatter) F(format string, args ...interface{}) string { + return f.Fi(0, format, args...) +} + +func (f Formatter) Fi(indentation uint, format string, args ...interface{}) string { + return f.Fiw(indentation, 0, format, args...) +} + +func (f Formatter) Fiw(indentation uint, maxWidth uint, format string, args ...interface{}) string { + out := fmt.Sprintf(f.style(format), args...) + + if indentation == 0 && maxWidth == 0 { + return out + } + + lines := strings.Split(out, "\n") + + if maxWidth != 0 { + outLines := []string{} + + maxWidth = maxWidth - indentation*2 + for _, line := range lines { + if f.length(line) <= maxWidth { + outLines = append(outLines, line) + continue + } + words := strings.Split(line, " ") + outWords := []string{words[0]} + length := uint(f.length(words[0])) + for _, word := range words[1:] { + wordLength := f.length(word) + if length+wordLength+1 <= maxWidth { + length += wordLength + 1 + outWords = append(outWords, word) + continue + } + outLines = append(outLines, strings.Join(outWords, " ")) + outWords = []string{word} + length = wordLength + } + if len(outWords) > 0 { + outLines = append(outLines, strings.Join(outWords, " ")) + } + } + + lines = outLines + } + + if indentation == 0 { + return strings.Join(lines, "\n") + } + + padding := strings.Repeat(" ", int(indentation)) + for i := range lines { + if lines[i] != "" { + lines[i] = padding + lines[i] + } + } + + return strings.Join(lines, "\n") +} + +func (f Formatter) length(styled string) uint { + n := uint(0) + inStyle := false + for _, b := range styled { + if inStyle { + if b == 'm' { + inStyle = false + } + continue + } + if b == '\x1b' { + inStyle = true + continue + } + n += 1 + } + return n +} + +func (f Formatter) CycleJoin(elements []string, joiner string, cycle []string) string { + if len(elements) == 0 { + return "" + } + n := len(cycle) + out := "" + for i, text := range elements { + out += cycle[i%n] + text + if i < len(elements)-1 { + out += joiner + } + } + out += "{{/}}" + return f.style(out) +} + +func (f Formatter) style(s string) string { + switch f.ColorMode { + case ColorModeNone: + return f.styleRe.ReplaceAllString(s, "") + case ColorModePassthrough: + return s + case ColorModeTerminal: + return f.styleRe.ReplaceAllStringFunc(s, func(match string) string { + if out, ok := f.colors[strings.Trim(match, "{}")]; ok { + return out + } + return match + }) + } + + return "" +} diff --git a/vendor/github.com/onsi/ginkgo/v2/ginkgo_t_dsl.go b/vendor/github.com/onsi/ginkgo/v2/ginkgo_t_dsl.go new file mode 100644 index 00000000000..1beeb114461 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/ginkgo_t_dsl.go @@ -0,0 +1,45 @@ +package ginkgo + +import "github.com/onsi/ginkgo/v2/internal/testingtproxy" + +/* +GinkgoT() implements an interface analogous to *testing.T and can be used with +third-party libraries that accept *testing.T through an interface. + +GinkgoT() takes an optional offset argument that can be used to get the +correct line number associated with the failure. + +You can learn more here: https://onsi.github.io/ginkgo/#using-third-party-libraries +*/ +func GinkgoT(optionalOffset ...int) GinkgoTInterface { + offset := 3 + if len(optionalOffset) > 0 { + offset = optionalOffset[0] + } + return testingtproxy.New(GinkgoWriter, Fail, Skip, DeferCleanup, CurrentSpecReport, offset) +} + +/* +The interface returned by GinkgoT(). This covers most of the methods in the testing package's T. +*/ +type GinkgoTInterface interface { + Cleanup(func()) + Setenv(kev, value string) + Error(args ...interface{}) + Errorf(format string, args ...interface{}) + Fail() + FailNow() + Failed() bool + Fatal(args ...interface{}) + Fatalf(format string, args ...interface{}) + Helper() + Log(args ...interface{}) + Logf(format string, args ...interface{}) + Name() string + Parallel() + Skip(args ...interface{}) + SkipNow() + Skipf(format string, args ...interface{}) + Skipped() bool + TempDir() string +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/counter.go b/vendor/github.com/onsi/ginkgo/v2/internal/counter.go new file mode 100644 index 00000000000..712d85afb0b --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/counter.go @@ -0,0 +1,9 @@ +package internal + +func MakeIncrementingIndexCounter() func() (int, error) { + idx := -1 + return func() (int, error) { + idx += 1 + return idx, nil + } +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/failer.go b/vendor/github.com/onsi/ginkgo/v2/internal/failer.go new file mode 100644 index 00000000000..e9bd9565fc7 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/failer.go @@ -0,0 +1,99 @@ +package internal + +import ( + "fmt" + "sync" + + "github.com/onsi/ginkgo/v2/types" +) + +type Failer struct { + lock *sync.Mutex + failure types.Failure + state types.SpecState +} + +func NewFailer() *Failer { + return &Failer{ + lock: &sync.Mutex{}, + state: types.SpecStatePassed, + } +} + +func (f *Failer) GetState() types.SpecState { + f.lock.Lock() + defer f.lock.Unlock() + return f.state +} + +func (f *Failer) GetFailure() types.Failure { + f.lock.Lock() + defer f.lock.Unlock() + return f.failure +} + +func (f *Failer) Panic(location types.CodeLocation, forwardedPanic interface{}) { + f.lock.Lock() + defer f.lock.Unlock() + + if f.state == types.SpecStatePassed { + f.state = types.SpecStatePanicked + f.failure = types.Failure{ + Message: "Test Panicked", + Location: location, + ForwardedPanic: fmt.Sprintf("%v", forwardedPanic), + } + } +} + +func (f *Failer) Fail(message string, location types.CodeLocation) { + f.lock.Lock() + defer f.lock.Unlock() + + if f.state == types.SpecStatePassed { + f.state = types.SpecStateFailed + f.failure = types.Failure{ + Message: message, + Location: location, + } + } +} + +func (f *Failer) Skip(message string, location types.CodeLocation) { + f.lock.Lock() + defer f.lock.Unlock() + + if f.state == types.SpecStatePassed { + f.state = types.SpecStateSkipped + f.failure = types.Failure{ + Message: message, + Location: location, + } + } +} + +func (f *Failer) AbortSuite(message string, location types.CodeLocation) { + f.lock.Lock() + defer f.lock.Unlock() + + if f.state == types.SpecStatePassed { + f.state = types.SpecStateAborted + f.failure = types.Failure{ + Message: message, + Location: location, + } + } +} + +func (f *Failer) Drain() (types.SpecState, types.Failure) { + f.lock.Lock() + defer f.lock.Unlock() + + failure := f.failure + outcome := f.state + + f.state = types.SpecStatePassed + f.failure = types.Failure{} + + return outcome, failure +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/focus.go b/vendor/github.com/onsi/ginkgo/v2/internal/focus.go new file mode 100644 index 00000000000..966ea0c1a27 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/focus.go @@ -0,0 +1,125 @@ +package internal + +import ( + "regexp" + "strings" + + "github.com/onsi/ginkgo/v2/types" +) + +/* + If a container marked as focus has a descendant that is also marked as focus, Ginkgo's policy is to + unmark the container's focus. This gives developers a more intuitive experience when debugging specs. + It is common to focus a container to just run a subset of specs, then identify the specific specs within the container to focus - + this policy allows the developer to simply focus those specific specs and not need to go back and turn the focus off of the container: + + As a common example, consider: + + FDescribe("something to debug", function() { + It("works", function() {...}) + It("works", function() {...}) + FIt("doesn't work", function() {...}) + It("works", function() {...}) + }) + + here the developer's intent is to focus in on the `"doesn't work"` spec and not to run the adjacent specs in the focused `"something to debug"` container. + The nested policy applied by this function enables this behavior. +*/ +func ApplyNestedFocusPolicyToTree(tree *TreeNode) { + var walkTree func(tree *TreeNode) bool + walkTree = func(tree *TreeNode) bool { + if tree.Node.MarkedPending { + return false + } + hasFocusedDescendant := false + for _, child := range tree.Children { + childHasFocus := walkTree(child) + hasFocusedDescendant = hasFocusedDescendant || childHasFocus + } + tree.Node.MarkedFocus = tree.Node.MarkedFocus && !hasFocusedDescendant + return tree.Node.MarkedFocus || hasFocusedDescendant + } + + walkTree(tree) +} + +/* + Ginkgo supports focussing specs using `FIt`, `FDescribe`, etc. - this is called "programmatic focus" + It also supports focussing specs using regular expressions on the command line (`-focus=`, `-skip=`) that match against spec text + and file filters (`-focus-files=`, `-skip-files=`) that match against code locations for nodes in specs. + + If any of the CLI flags are provided they take precedence. The file filters run first followed by the regex filters. + + This function sets the `Skip` property on specs by applying Ginkgo's focus policy: + - If there are no CLI arguments and no programmatic focus, do nothing. + - If there are no CLI arguments but a spec somewhere has programmatic focus, skip any specs that have no programmatic focus. + - If there are CLI arguments parse them and skip any specs that either don't match the focus filters or do match the skip filters. + + *Note:* specs with pending nodes are Skipped when created by NewSpec. +*/ +func ApplyFocusToSpecs(specs Specs, description string, suiteLabels Labels, suiteConfig types.SuiteConfig) (Specs, bool) { + focusString := strings.Join(suiteConfig.FocusStrings, "|") + skipString := strings.Join(suiteConfig.SkipStrings, "|") + + hasFocusCLIFlags := focusString != "" || skipString != "" || len(suiteConfig.SkipFiles) > 0 || len(suiteConfig.FocusFiles) > 0 || suiteConfig.LabelFilter != "" + + type SkipCheck func(spec Spec) bool + + // by default, skip any specs marked pending + skipChecks := []SkipCheck{func(spec Spec) bool { return spec.Nodes.HasNodeMarkedPending() }} + hasProgrammaticFocus := false + + if !hasFocusCLIFlags { + // check for programmatic focus + for _, spec := range specs { + if spec.Nodes.HasNodeMarkedFocus() && !spec.Nodes.HasNodeMarkedPending() { + skipChecks = append(skipChecks, func(spec Spec) bool { return !spec.Nodes.HasNodeMarkedFocus() }) + hasProgrammaticFocus = true + break + } + } + } + + if suiteConfig.LabelFilter != "" { + labelFilter, _ := types.ParseLabelFilter(suiteConfig.LabelFilter) + skipChecks = append(skipChecks, func(spec Spec) bool { + return !labelFilter(UnionOfLabels(suiteLabels, spec.Nodes.UnionOfLabels())) + }) + } + + if len(suiteConfig.FocusFiles) > 0 { + focusFilters, _ := types.ParseFileFilters(suiteConfig.FocusFiles) + skipChecks = append(skipChecks, func(spec Spec) bool { return !focusFilters.Matches(spec.Nodes.CodeLocations()) }) + } + + if len(suiteConfig.SkipFiles) > 0 { + skipFilters, _ := types.ParseFileFilters(suiteConfig.SkipFiles) + skipChecks = append(skipChecks, func(spec Spec) bool { return skipFilters.Matches(spec.Nodes.CodeLocations()) }) + } + + if focusString != "" { + // skip specs that don't match the focus string + re := regexp.MustCompile(focusString) + skipChecks = append(skipChecks, func(spec Spec) bool { return !re.MatchString(description + " " + spec.Text()) }) + } + + if skipString != "" { + // skip specs that match the skip string + re := regexp.MustCompile(skipString) + skipChecks = append(skipChecks, func(spec Spec) bool { return re.MatchString(description + " " + spec.Text()) }) + } + + // skip specs if shouldSkip() is true. note that we do nothing if shouldSkip() is false to avoid overwriting skip status established by the node's pending status + processedSpecs := Specs{} + for _, spec := range specs { + for _, skipCheck := range skipChecks { + if skipCheck(spec) { + spec.Skip = true + break + } + } + processedSpecs = append(processedSpecs, spec) + } + + return processedSpecs, hasProgrammaticFocus +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/global/init.go b/vendor/github.com/onsi/ginkgo/v2/internal/global/init.go new file mode 100644 index 00000000000..f2c0fd89c0d --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/global/init.go @@ -0,0 +1,17 @@ +package global + +import ( + "github.com/onsi/ginkgo/v2/internal" +) + +var Suite *internal.Suite +var Failer *internal.Failer + +func init() { + InitializeGlobals() +} + +func InitializeGlobals() { + Failer = internal.NewFailer() + Suite = internal.NewSuite() +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/group.go b/vendor/github.com/onsi/ginkgo/v2/internal/group.go new file mode 100644 index 00000000000..5c782d3ffcf --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/group.go @@ -0,0 +1,363 @@ +package internal + +import ( + "fmt" + "time" + + "github.com/onsi/ginkgo/v2/types" +) + +type runOncePair struct { + //nodeId should only run once... + nodeID uint + nodeType types.NodeType + //...for specs in a hierarchy that includes this context + containerID uint +} + +func (pair runOncePair) isZero() bool { + return pair.nodeID == 0 +} + +func runOncePairForNode(node Node, containerID uint) runOncePair { + return runOncePair{ + nodeID: node.ID, + nodeType: node.NodeType, + containerID: containerID, + } +} + +type runOncePairs []runOncePair + +func runOncePairsForSpec(spec Spec) runOncePairs { + pairs := runOncePairs{} + + containers := spec.Nodes.WithType(types.NodeTypeContainer) + for _, node := range spec.Nodes { + if node.NodeType.Is(types.NodeTypeBeforeAll | types.NodeTypeAfterAll) { + pairs = append(pairs, runOncePairForNode(node, containers.FirstWithNestingLevel(node.NestingLevel-1).ID)) + } else if node.NodeType.Is(types.NodeTypeBeforeEach|types.NodeTypeJustBeforeEach|types.NodeTypeAfterEach|types.NodeTypeJustAfterEach) && node.MarkedOncePerOrdered { + passedIntoAnOrderedContainer := false + firstOrderedContainerDeeperThanNode := containers.FirstSatisfying(func(container Node) bool { + passedIntoAnOrderedContainer = passedIntoAnOrderedContainer || container.MarkedOrdered + return container.NestingLevel >= node.NestingLevel && passedIntoAnOrderedContainer + }) + if firstOrderedContainerDeeperThanNode.IsZero() { + continue + } + pairs = append(pairs, runOncePairForNode(node, firstOrderedContainerDeeperThanNode.ID)) + } + } + + return pairs +} + +func (pairs runOncePairs) runOncePairFor(nodeID uint) runOncePair { + for i := range pairs { + if pairs[i].nodeID == nodeID { + return pairs[i] + } + } + return runOncePair{} +} + +func (pairs runOncePairs) hasRunOncePair(pair runOncePair) bool { + for i := range pairs { + if pairs[i] == pair { + return true + } + } + return false +} + +func (pairs runOncePairs) withType(nodeTypes types.NodeType) runOncePairs { + count := 0 + for i := range pairs { + if pairs[i].nodeType.Is(nodeTypes) { + count++ + } + } + + out, j := make(runOncePairs, count), 0 + for i := range pairs { + if pairs[i].nodeType.Is(nodeTypes) { + out[j] = pairs[i] + j++ + } + } + return out +} + +type group struct { + suite *Suite + specs Specs + runOncePairs map[uint]runOncePairs + runOnceTracker map[runOncePair]types.SpecState + + succeeded bool +} + +func newGroup(suite *Suite) *group { + return &group{ + suite: suite, + runOncePairs: map[uint]runOncePairs{}, + runOnceTracker: map[runOncePair]types.SpecState{}, + succeeded: true, + } +} + +func (g *group) initialReportForSpec(spec Spec) types.SpecReport { + return types.SpecReport{ + ContainerHierarchyTexts: spec.Nodes.WithType(types.NodeTypeContainer).Texts(), + ContainerHierarchyLocations: spec.Nodes.WithType(types.NodeTypeContainer).CodeLocations(), + ContainerHierarchyLabels: spec.Nodes.WithType(types.NodeTypeContainer).Labels(), + LeafNodeLocation: spec.FirstNodeWithType(types.NodeTypeIt).CodeLocation, + LeafNodeType: types.NodeTypeIt, + LeafNodeText: spec.FirstNodeWithType(types.NodeTypeIt).Text, + LeafNodeLabels: []string(spec.FirstNodeWithType(types.NodeTypeIt).Labels), + ParallelProcess: g.suite.config.ParallelProcess, + RunningInParallel: g.suite.isRunningInParallel(), + IsSerial: spec.Nodes.HasNodeMarkedSerial(), + IsInOrderedContainer: !spec.Nodes.FirstNodeMarkedOrdered().IsZero(), + MaxFlakeAttempts: spec.Nodes.GetMaxFlakeAttempts(), + MaxMustPassRepeatedly: spec.Nodes.GetMaxMustPassRepeatedly(), + } +} + +func (g *group) evaluateSkipStatus(spec Spec) (types.SpecState, types.Failure) { + if spec.Nodes.HasNodeMarkedPending() { + return types.SpecStatePending, types.Failure{} + } + if spec.Skip { + return types.SpecStateSkipped, types.Failure{} + } + if g.suite.interruptHandler.Status().Interrupted() || g.suite.skipAll { + return types.SpecStateSkipped, types.Failure{} + } + if !g.suite.deadline.IsZero() && g.suite.deadline.Before(time.Now()) { + return types.SpecStateSkipped, types.Failure{} + } + if !g.succeeded { + return types.SpecStateSkipped, g.suite.failureForLeafNodeWithMessage(spec.FirstNodeWithType(types.NodeTypeIt), + "Spec skipped because an earlier spec in an ordered container failed") + } + beforeOncePairs := g.runOncePairs[spec.SubjectID()].withType(types.NodeTypeBeforeAll | types.NodeTypeBeforeEach | types.NodeTypeJustBeforeEach) + for _, pair := range beforeOncePairs { + if g.runOnceTracker[pair].Is(types.SpecStateSkipped) { + return types.SpecStateSkipped, g.suite.failureForLeafNodeWithMessage(spec.FirstNodeWithType(types.NodeTypeIt), + fmt.Sprintf("Spec skipped because Skip() was called in %s", pair.nodeType)) + } + } + if g.suite.config.DryRun { + return types.SpecStatePassed, types.Failure{} + } + return g.suite.currentSpecReport.State, g.suite.currentSpecReport.Failure +} + +func (g *group) isLastSpecWithPair(specID uint, pair runOncePair) bool { + lastSpecID := uint(0) + for idx := range g.specs { + if g.specs[idx].Skip { + continue + } + sID := g.specs[idx].SubjectID() + if g.runOncePairs[sID].hasRunOncePair(pair) { + lastSpecID = sID + } + } + return lastSpecID == specID +} + +func (g *group) attemptSpec(isFinalAttempt bool, spec Spec) { + pairs := g.runOncePairs[spec.SubjectID()] + + nodes := spec.Nodes.WithType(types.NodeTypeBeforeAll) + nodes = append(nodes, spec.Nodes.WithType(types.NodeTypeBeforeEach)...).SortedByAscendingNestingLevel() + nodes = append(nodes, spec.Nodes.WithType(types.NodeTypeJustBeforeEach).SortedByAscendingNestingLevel()...) + nodes = append(nodes, spec.Nodes.FirstNodeWithType(types.NodeTypeIt)) + terminatingNode, terminatingPair := Node{}, runOncePair{} + + deadline := time.Time{} + if spec.SpecTimeout() > 0 { + deadline = time.Now().Add(spec.SpecTimeout()) + } + + for _, node := range nodes { + oncePair := pairs.runOncePairFor(node.ID) + if !oncePair.isZero() && g.runOnceTracker[oncePair].Is(types.SpecStatePassed) { + continue + } + g.suite.currentSpecReport.State, g.suite.currentSpecReport.Failure = g.suite.runNode(node, deadline, spec.Nodes.BestTextFor(node)) + g.suite.currentSpecReport.RunTime = time.Since(g.suite.currentSpecReport.StartTime) + if !oncePair.isZero() { + g.runOnceTracker[oncePair] = g.suite.currentSpecReport.State + } + if g.suite.currentSpecReport.State != types.SpecStatePassed { + terminatingNode, terminatingPair = node, oncePair + break + } + } + + afterNodeWasRun := map[uint]bool{} + includeDeferCleanups := false + for { + nodes := spec.Nodes.WithType(types.NodeTypeAfterEach) + nodes = append(nodes, spec.Nodes.WithType(types.NodeTypeAfterAll)...).SortedByDescendingNestingLevel() + nodes = append(spec.Nodes.WithType(types.NodeTypeJustAfterEach).SortedByDescendingNestingLevel(), nodes...) + if !terminatingNode.IsZero() { + nodes = nodes.WithinNestingLevel(terminatingNode.NestingLevel) + } + if includeDeferCleanups { + nodes = append(nodes, g.suite.cleanupNodes.WithType(types.NodeTypeCleanupAfterEach).Reverse()...) + nodes = append(nodes, g.suite.cleanupNodes.WithType(types.NodeTypeCleanupAfterAll).Reverse()...) + } + nodes = nodes.Filter(func(node Node) bool { + if afterNodeWasRun[node.ID] { + //this node has already been run on this attempt, don't rerun it + return false + } + pair := runOncePair{} + switch node.NodeType { + case types.NodeTypeCleanupAfterEach, types.NodeTypeCleanupAfterAll: + // check if we were generated in an AfterNode that has already run + if afterNodeWasRun[node.NodeIDWhereCleanupWasGenerated] { + return true // we were, so we should definitely run this cleanup now + } + // looks like this cleanup nodes was generated by a before node or it. + // the run-once status of a cleanup node is governed by the run-once status of its generator + pair = pairs.runOncePairFor(node.NodeIDWhereCleanupWasGenerated) + default: + pair = pairs.runOncePairFor(node.ID) + } + if pair.isZero() { + // this node is not governed by any run-once policy, we should run it + return true + } + // it's our last chance to run if we're the last spec for our oncePair + isLastSpecWithPair := g.isLastSpecWithPair(spec.SubjectID(), pair) + + switch g.suite.currentSpecReport.State { + case types.SpecStatePassed: //this attempt is passing... + return isLastSpecWithPair //...we should run-once if we'this is our last chance + case types.SpecStateSkipped: //the spec was skipped by the user... + if isLastSpecWithPair { + return true //...we're the last spec, so we should run the AfterNode + } + if !terminatingPair.isZero() && terminatingNode.NestingLevel == node.NestingLevel { + return true //...or, a run-once node at our nesting level was skipped which means this is our last chance to run + } + case types.SpecStateFailed, types.SpecStatePanicked: // the spec has failed... + if isFinalAttempt { + return true //...if this was the last attempt then we're the last spec to run and so the AfterNode should run + } + if !terminatingPair.isZero() { // ...and it failed in a run-once. which will be running again + if node.NodeType.Is(types.NodeTypeCleanupAfterEach | types.NodeTypeCleanupAfterAll) { + return terminatingNode.ID == node.NodeIDWhereCleanupWasGenerated // we should run this node if we're a clean-up generated by it + } else { + return terminatingNode.NestingLevel == node.NestingLevel // ...or if we're at the same nesting level + } + } + case types.SpecStateInterrupted, types.SpecStateAborted: // ...we've been interrupted and/or aborted + return true //...that means the test run is over and we should clean up the stack. Run the AfterNode + } + return false + }) + + if len(nodes) == 0 && includeDeferCleanups { + break + } + + for _, node := range nodes { + afterNodeWasRun[node.ID] = true + state, failure := g.suite.runNode(node, deadline, spec.Nodes.BestTextFor(node)) + g.suite.currentSpecReport.RunTime = time.Since(g.suite.currentSpecReport.StartTime) + if g.suite.currentSpecReport.State == types.SpecStatePassed || state == types.SpecStateAborted { + g.suite.currentSpecReport.State = state + g.suite.currentSpecReport.Failure = failure + } else if state.Is(types.SpecStateFailureStates) { + g.suite.currentSpecReport.AdditionalFailures = append(g.suite.currentSpecReport.AdditionalFailures, types.AdditionalFailure{State: state, Failure: failure}) + } + } + includeDeferCleanups = true + } + +} + +func (g *group) run(specs Specs) { + g.specs = specs + for _, spec := range g.specs { + g.runOncePairs[spec.SubjectID()] = runOncePairsForSpec(spec) + } + + for _, spec := range g.specs { + g.suite.selectiveLock.Lock() + g.suite.currentSpecReport = g.initialReportForSpec(spec) + g.suite.selectiveLock.Unlock() + + g.suite.currentSpecReport.State, g.suite.currentSpecReport.Failure = g.evaluateSkipStatus(spec) + g.suite.reporter.WillRun(g.suite.currentSpecReport) + g.suite.reportEach(spec, types.NodeTypeReportBeforeEach) + + skip := g.suite.config.DryRun || g.suite.currentSpecReport.State.Is(types.SpecStateFailureStates|types.SpecStateSkipped|types.SpecStatePending) + + g.suite.currentSpecReport.StartTime = time.Now() + if !skip { + + var maxAttempts = 1 + + if g.suite.currentSpecReport.MaxMustPassRepeatedly > 0 { + maxAttempts = max(1, spec.MustPassRepeatedly()) + } else if g.suite.config.FlakeAttempts > 0 { + maxAttempts = g.suite.config.FlakeAttempts + g.suite.currentSpecReport.MaxFlakeAttempts = maxAttempts + } else if g.suite.currentSpecReport.MaxFlakeAttempts > 0 { + maxAttempts = max(1, spec.FlakeAttempts()) + } + + for attempt := 0; attempt < maxAttempts; attempt++ { + g.suite.currentSpecReport.NumAttempts = attempt + 1 + g.suite.writer.Truncate() + g.suite.outputInterceptor.StartInterceptingOutput() + if attempt > 0 { + if g.suite.currentSpecReport.MaxMustPassRepeatedly > 0 { + g.suite.handleSpecEvent(types.SpecEvent{SpecEventType: types.SpecEventSpecRepeat, Attempt: attempt}) + } + if g.suite.currentSpecReport.MaxFlakeAttempts > 0 { + g.suite.handleSpecEvent(types.SpecEvent{SpecEventType: types.SpecEventSpecRetry, Attempt: attempt}) + } + } + + g.attemptSpec(attempt == maxAttempts-1, spec) + + g.suite.currentSpecReport.EndTime = time.Now() + g.suite.currentSpecReport.RunTime = g.suite.currentSpecReport.EndTime.Sub(g.suite.currentSpecReport.StartTime) + g.suite.currentSpecReport.CapturedGinkgoWriterOutput += string(g.suite.writer.Bytes()) + g.suite.currentSpecReport.CapturedStdOutErr += g.suite.outputInterceptor.StopInterceptingAndReturnOutput() + + if g.suite.currentSpecReport.MaxMustPassRepeatedly > 0 { + if g.suite.currentSpecReport.State.Is(types.SpecStateFailureStates | types.SpecStateSkipped) { + break + } + } + if g.suite.currentSpecReport.MaxFlakeAttempts > 0 { + if g.suite.currentSpecReport.State.Is(types.SpecStatePassed | types.SpecStateSkipped | types.SpecStateAborted | types.SpecStateInterrupted) { + break + } else if attempt < maxAttempts-1 { + af := types.AdditionalFailure{State: g.suite.currentSpecReport.State, Failure: g.suite.currentSpecReport.Failure} + af.Failure.Message = fmt.Sprintf("Failure recorded during attempt %d:\n%s", attempt+1, af.Failure.Message) + g.suite.currentSpecReport.AdditionalFailures = append(g.suite.currentSpecReport.AdditionalFailures, af) + } + } + } + } + + g.suite.reportEach(spec, types.NodeTypeReportAfterEach) + g.suite.processCurrentSpecReport() + if g.suite.currentSpecReport.State.Is(types.SpecStateFailureStates) { + g.succeeded = false + } + g.suite.selectiveLock.Lock() + g.suite.currentSpecReport = types.SpecReport{} + g.suite.selectiveLock.Unlock() + } +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/interrupt_handler/interrupt_handler.go b/vendor/github.com/onsi/ginkgo/v2/internal/interrupt_handler/interrupt_handler.go new file mode 100644 index 00000000000..ac6f5104083 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/interrupt_handler/interrupt_handler.go @@ -0,0 +1,162 @@ +package interrupt_handler + +import ( + "os" + "os/signal" + "sync" + "syscall" + "time" + + "github.com/onsi/ginkgo/v2/internal/parallel_support" +) + +const ABORT_POLLING_INTERVAL = 500 * time.Millisecond + +type InterruptCause uint + +const ( + InterruptCauseInvalid InterruptCause = iota + InterruptCauseSignal + InterruptCauseAbortByOtherProcess +) + +type InterruptLevel uint + +const ( + InterruptLevelUninterrupted InterruptLevel = iota + InterruptLevelCleanupAndReport + InterruptLevelReportOnly + InterruptLevelBailOut +) + +func (ic InterruptCause) String() string { + switch ic { + case InterruptCauseSignal: + return "Interrupted by User" + case InterruptCauseAbortByOtherProcess: + return "Interrupted by Other Ginkgo Process" + } + return "INVALID_INTERRUPT_CAUSE" +} + +type InterruptStatus struct { + Channel chan interface{} + Level InterruptLevel + Cause InterruptCause +} + +func (s InterruptStatus) Interrupted() bool { + return s.Level != InterruptLevelUninterrupted +} + +func (s InterruptStatus) Message() string { + return s.Cause.String() +} + +func (s InterruptStatus) ShouldIncludeProgressReport() bool { + return s.Cause != InterruptCauseAbortByOtherProcess +} + +type InterruptHandlerInterface interface { + Status() InterruptStatus +} + +type InterruptHandler struct { + c chan interface{} + lock *sync.Mutex + level InterruptLevel + cause InterruptCause + client parallel_support.Client + stop chan interface{} + signals []os.Signal +} + +func NewInterruptHandler(client parallel_support.Client, signals ...os.Signal) *InterruptHandler { + if len(signals) == 0 { + signals = []os.Signal{os.Interrupt, syscall.SIGTERM} + } + handler := &InterruptHandler{ + c: make(chan interface{}), + lock: &sync.Mutex{}, + stop: make(chan interface{}), + client: client, + signals: signals, + } + handler.registerForInterrupts() + return handler +} + +func (handler *InterruptHandler) Stop() { + close(handler.stop) +} + +func (handler *InterruptHandler) registerForInterrupts() { + // os signal handling + signalChannel := make(chan os.Signal, 1) + signal.Notify(signalChannel, handler.signals...) + + // cross-process abort handling + var abortChannel chan interface{} + if handler.client != nil { + abortChannel = make(chan interface{}) + go func() { + pollTicker := time.NewTicker(ABORT_POLLING_INTERVAL) + for { + select { + case <-pollTicker.C: + if handler.client.ShouldAbort() { + close(abortChannel) + pollTicker.Stop() + return + } + case <-handler.stop: + pollTicker.Stop() + return + } + } + }() + } + + go func(abortChannel chan interface{}) { + var interruptCause InterruptCause + for { + select { + case <-signalChannel: + interruptCause = InterruptCauseSignal + case <-abortChannel: + interruptCause = InterruptCauseAbortByOtherProcess + case <-handler.stop: + signal.Stop(signalChannel) + return + } + abortChannel = nil + + handler.lock.Lock() + oldLevel := handler.level + handler.cause = interruptCause + if handler.level == InterruptLevelUninterrupted { + handler.level = InterruptLevelCleanupAndReport + } else if handler.level == InterruptLevelCleanupAndReport { + handler.level = InterruptLevelReportOnly + } else if handler.level == InterruptLevelReportOnly { + handler.level = InterruptLevelBailOut + } + if handler.level != oldLevel { + close(handler.c) + handler.c = make(chan interface{}) + } + handler.lock.Unlock() + } + }(abortChannel) +} + +func (handler *InterruptHandler) Status() InterruptStatus { + handler.lock.Lock() + defer handler.lock.Unlock() + + return InterruptStatus{ + Level: handler.level, + Channel: handler.c, + Cause: handler.cause, + } +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/interrupt_handler/sigquit_swallower_unix.go b/vendor/github.com/onsi/ginkgo/v2/internal/interrupt_handler/sigquit_swallower_unix.go new file mode 100644 index 00000000000..bf0de496dc3 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/interrupt_handler/sigquit_swallower_unix.go @@ -0,0 +1,15 @@ +//go:build freebsd || openbsd || netbsd || dragonfly || darwin || linux || solaris +// +build freebsd openbsd netbsd dragonfly darwin linux solaris + +package interrupt_handler + +import ( + "os" + "os/signal" + "syscall" +) + +func SwallowSigQuit() { + c := make(chan os.Signal, 1024) + signal.Notify(c, syscall.SIGQUIT) +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/interrupt_handler/sigquit_swallower_windows.go b/vendor/github.com/onsi/ginkgo/v2/internal/interrupt_handler/sigquit_swallower_windows.go new file mode 100644 index 00000000000..fcf8da8335f --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/interrupt_handler/sigquit_swallower_windows.go @@ -0,0 +1,8 @@ +//go:build windows +// +build windows + +package interrupt_handler + +func SwallowSigQuit() { + //noop +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/node.go b/vendor/github.com/onsi/ginkgo/v2/internal/node.go new file mode 100644 index 00000000000..0878c6728d1 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/node.go @@ -0,0 +1,895 @@ +package internal + +import ( + "context" + "fmt" + "reflect" + "sort" + "time" + + "sync" + + "github.com/onsi/ginkgo/v2/types" +) + +var _global_node_id_counter = uint(0) +var _global_id_mutex = &sync.Mutex{} + +func UniqueNodeID() uint { + //There's a reace in the internal integration tests if we don't make + //accessing _global_node_id_counter safe across goroutines. + _global_id_mutex.Lock() + defer _global_id_mutex.Unlock() + _global_node_id_counter += 1 + return _global_node_id_counter +} + +type Node struct { + ID uint + NodeType types.NodeType + + Text string + Body func(SpecContext) + CodeLocation types.CodeLocation + NestingLevel int + HasContext bool + + SynchronizedBeforeSuiteProc1Body func(SpecContext) []byte + SynchronizedBeforeSuiteProc1BodyHasContext bool + SynchronizedBeforeSuiteAllProcsBody func(SpecContext, []byte) + SynchronizedBeforeSuiteAllProcsBodyHasContext bool + + SynchronizedAfterSuiteAllProcsBody func(SpecContext) + SynchronizedAfterSuiteAllProcsBodyHasContext bool + SynchronizedAfterSuiteProc1Body func(SpecContext) + SynchronizedAfterSuiteProc1BodyHasContext bool + + ReportEachBody func(types.SpecReport) + ReportAfterSuiteBody func(types.Report) + + MarkedFocus bool + MarkedPending bool + MarkedSerial bool + MarkedOrdered bool + MarkedOncePerOrdered bool + FlakeAttempts int + MustPassRepeatedly int + Labels Labels + PollProgressAfter time.Duration + PollProgressInterval time.Duration + NodeTimeout time.Duration + SpecTimeout time.Duration + GracePeriod time.Duration + + NodeIDWhereCleanupWasGenerated uint +} + +// Decoration Types +type focusType bool +type pendingType bool +type serialType bool +type orderedType bool +type honorsOrderedType bool +type suppressProgressReporting bool + +const Focus = focusType(true) +const Pending = pendingType(true) +const Serial = serialType(true) +const Ordered = orderedType(true) +const OncePerOrdered = honorsOrderedType(true) +const SuppressProgressReporting = suppressProgressReporting(true) + +type FlakeAttempts uint +type MustPassRepeatedly uint +type Offset uint +type Done chan<- interface{} // Deprecated Done Channel for asynchronous testing +type Labels []string +type PollProgressInterval time.Duration +type PollProgressAfter time.Duration +type NodeTimeout time.Duration +type SpecTimeout time.Duration +type GracePeriod time.Duration + +func UnionOfLabels(labels ...Labels) Labels { + out := Labels{} + seen := map[string]bool{} + for _, labelSet := range labels { + for _, label := range labelSet { + if !seen[label] { + seen[label] = true + out = append(out, label) + } + } + } + return out +} + +func PartitionDecorations(args ...interface{}) ([]interface{}, []interface{}) { + decorations := []interface{}{} + remainingArgs := []interface{}{} + for _, arg := range args { + if isDecoration(arg) { + decorations = append(decorations, arg) + } else { + remainingArgs = append(remainingArgs, arg) + } + } + return decorations, remainingArgs +} + +func isDecoration(arg interface{}) bool { + switch t := reflect.TypeOf(arg); { + case t == nil: + return false + case t == reflect.TypeOf(Offset(0)): + return true + case t == reflect.TypeOf(types.CodeLocation{}): + return true + case t == reflect.TypeOf(Focus): + return true + case t == reflect.TypeOf(Pending): + return true + case t == reflect.TypeOf(Serial): + return true + case t == reflect.TypeOf(Ordered): + return true + case t == reflect.TypeOf(OncePerOrdered): + return true + case t == reflect.TypeOf(SuppressProgressReporting): + return true + case t == reflect.TypeOf(FlakeAttempts(0)): + return true + case t == reflect.TypeOf(MustPassRepeatedly(0)): + return true + case t == reflect.TypeOf(Labels{}): + return true + case t == reflect.TypeOf(PollProgressInterval(0)): + return true + case t == reflect.TypeOf(PollProgressAfter(0)): + return true + case t == reflect.TypeOf(NodeTimeout(0)): + return true + case t == reflect.TypeOf(SpecTimeout(0)): + return true + case t == reflect.TypeOf(GracePeriod(0)): + return true + case t.Kind() == reflect.Slice && isSliceOfDecorations(arg): + return true + default: + return false + } +} + +func isSliceOfDecorations(slice interface{}) bool { + vSlice := reflect.ValueOf(slice) + if vSlice.Len() == 0 { + return false + } + for i := 0; i < vSlice.Len(); i++ { + if !isDecoration(vSlice.Index(i).Interface()) { + return false + } + } + return true +} + +var contextType = reflect.TypeOf(new(context.Context)).Elem() +var specContextType = reflect.TypeOf(new(SpecContext)).Elem() + +func NewNode(deprecationTracker *types.DeprecationTracker, nodeType types.NodeType, text string, args ...interface{}) (Node, []error) { + baseOffset := 2 + node := Node{ + ID: UniqueNodeID(), + NodeType: nodeType, + Text: text, + Labels: Labels{}, + CodeLocation: types.NewCodeLocation(baseOffset), + NestingLevel: -1, + PollProgressAfter: -1, + PollProgressInterval: -1, + GracePeriod: -1, + } + + errors := []error{} + appendError := func(err error) { + if err != nil { + errors = append(errors, err) + } + } + + args = unrollInterfaceSlice(args) + + remainingArgs := []interface{}{} + //First get the CodeLocation up-to-date + for _, arg := range args { + switch v := arg.(type) { + case Offset: + node.CodeLocation = types.NewCodeLocation(baseOffset + int(v)) + case types.CodeLocation: + node.CodeLocation = v + default: + remainingArgs = append(remainingArgs, arg) + } + } + + labelsSeen := map[string]bool{} + trackedFunctionError := false + args = remainingArgs + remainingArgs = []interface{}{} + //now process the rest of the args + for _, arg := range args { + switch t := reflect.TypeOf(arg); { + case t == reflect.TypeOf(float64(0)): + break //ignore deprecated timeouts + case t == reflect.TypeOf(Focus): + node.MarkedFocus = bool(arg.(focusType)) + if !nodeType.Is(types.NodeTypesForContainerAndIt) { + appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "Focus")) + } + case t == reflect.TypeOf(Pending): + node.MarkedPending = bool(arg.(pendingType)) + if !nodeType.Is(types.NodeTypesForContainerAndIt) { + appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "Pending")) + } + case t == reflect.TypeOf(Serial): + node.MarkedSerial = bool(arg.(serialType)) + if !nodeType.Is(types.NodeTypesForContainerAndIt) { + appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "Serial")) + } + case t == reflect.TypeOf(Ordered): + node.MarkedOrdered = bool(arg.(orderedType)) + if !nodeType.Is(types.NodeTypeContainer) { + appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "Ordered")) + } + case t == reflect.TypeOf(OncePerOrdered): + node.MarkedOncePerOrdered = bool(arg.(honorsOrderedType)) + if !nodeType.Is(types.NodeTypeBeforeEach | types.NodeTypeJustBeforeEach | types.NodeTypeAfterEach | types.NodeTypeJustAfterEach) { + appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "OncePerOrdered")) + } + case t == reflect.TypeOf(SuppressProgressReporting): + deprecationTracker.TrackDeprecation(types.Deprecations.SuppressProgressReporting()) + case t == reflect.TypeOf(FlakeAttempts(0)): + node.FlakeAttempts = int(arg.(FlakeAttempts)) + if !nodeType.Is(types.NodeTypesForContainerAndIt) { + appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "FlakeAttempts")) + } + case t == reflect.TypeOf(MustPassRepeatedly(0)): + node.MustPassRepeatedly = int(arg.(MustPassRepeatedly)) + if !nodeType.Is(types.NodeTypesForContainerAndIt) { + appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "MustPassRepeatedly")) + } + case t == reflect.TypeOf(PollProgressAfter(0)): + node.PollProgressAfter = time.Duration(arg.(PollProgressAfter)) + if nodeType.Is(types.NodeTypeContainer) { + appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "PollProgressAfter")) + } + case t == reflect.TypeOf(PollProgressInterval(0)): + node.PollProgressInterval = time.Duration(arg.(PollProgressInterval)) + if nodeType.Is(types.NodeTypeContainer) { + appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "PollProgressInterval")) + } + case t == reflect.TypeOf(NodeTimeout(0)): + node.NodeTimeout = time.Duration(arg.(NodeTimeout)) + if nodeType.Is(types.NodeTypeContainer) { + appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "NodeTimeout")) + } + case t == reflect.TypeOf(SpecTimeout(0)): + node.SpecTimeout = time.Duration(arg.(SpecTimeout)) + if !nodeType.Is(types.NodeTypeIt) { + appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "SpecTimeout")) + } + case t == reflect.TypeOf(GracePeriod(0)): + node.GracePeriod = time.Duration(arg.(GracePeriod)) + if nodeType.Is(types.NodeTypeContainer) { + appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "GracePeriod")) + } + case t == reflect.TypeOf(Labels{}): + if !nodeType.Is(types.NodeTypesForContainerAndIt) { + appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "Label")) + } + for _, label := range arg.(Labels) { + if !labelsSeen[label] { + labelsSeen[label] = true + label, err := types.ValidateAndCleanupLabel(label, node.CodeLocation) + node.Labels = append(node.Labels, label) + appendError(err) + } + } + case t.Kind() == reflect.Func: + if nodeType.Is(types.NodeTypeContainer) { + if node.Body != nil { + appendError(types.GinkgoErrors.MultipleBodyFunctions(node.CodeLocation, nodeType)) + trackedFunctionError = true + break + } + if t.NumOut() > 0 || t.NumIn() > 0 { + appendError(types.GinkgoErrors.InvalidBodyTypeForContainer(t, node.CodeLocation, nodeType)) + trackedFunctionError = true + break + } + body := arg.(func()) + node.Body = func(SpecContext) { body() } + } else if nodeType.Is(types.NodeTypeReportBeforeEach | types.NodeTypeReportAfterEach) { + if node.ReportEachBody == nil { + node.ReportEachBody = arg.(func(types.SpecReport)) + } else { + appendError(types.GinkgoErrors.MultipleBodyFunctions(node.CodeLocation, nodeType)) + trackedFunctionError = true + break + } + } else if nodeType.Is(types.NodeTypeReportAfterSuite) { + if node.ReportAfterSuiteBody == nil { + node.ReportAfterSuiteBody = arg.(func(types.Report)) + } else { + appendError(types.GinkgoErrors.MultipleBodyFunctions(node.CodeLocation, nodeType)) + trackedFunctionError = true + break + } + } else if nodeType.Is(types.NodeTypeSynchronizedBeforeSuite) { + if node.SynchronizedBeforeSuiteProc1Body != nil && node.SynchronizedBeforeSuiteAllProcsBody != nil { + appendError(types.GinkgoErrors.MultipleBodyFunctions(node.CodeLocation, nodeType)) + trackedFunctionError = true + break + } + if node.SynchronizedBeforeSuiteProc1Body == nil { + body, hasContext := extractSynchronizedBeforeSuiteProc1Body(arg) + if body == nil { + appendError(types.GinkgoErrors.InvalidBodyTypeForSynchronizedBeforeSuiteProc1(t, node.CodeLocation)) + trackedFunctionError = true + } + node.SynchronizedBeforeSuiteProc1Body, node.SynchronizedBeforeSuiteProc1BodyHasContext = body, hasContext + } else if node.SynchronizedBeforeSuiteAllProcsBody == nil { + body, hasContext := extractSynchronizedBeforeSuiteAllProcsBody(arg) + if body == nil { + appendError(types.GinkgoErrors.InvalidBodyTypeForSynchronizedBeforeSuiteAllProcs(t, node.CodeLocation)) + trackedFunctionError = true + } + node.SynchronizedBeforeSuiteAllProcsBody, node.SynchronizedBeforeSuiteAllProcsBodyHasContext = body, hasContext + } + } else if nodeType.Is(types.NodeTypeSynchronizedAfterSuite) { + if node.SynchronizedAfterSuiteAllProcsBody != nil && node.SynchronizedAfterSuiteProc1Body != nil { + appendError(types.GinkgoErrors.MultipleBodyFunctions(node.CodeLocation, nodeType)) + trackedFunctionError = true + break + } + body, hasContext := extractBodyFunction(deprecationTracker, node.CodeLocation, arg) + if body == nil { + appendError(types.GinkgoErrors.InvalidBodyType(t, node.CodeLocation, nodeType)) + trackedFunctionError = true + break + } + if node.SynchronizedAfterSuiteAllProcsBody == nil { + node.SynchronizedAfterSuiteAllProcsBody, node.SynchronizedAfterSuiteAllProcsBodyHasContext = body, hasContext + } else if node.SynchronizedAfterSuiteProc1Body == nil { + node.SynchronizedAfterSuiteProc1Body, node.SynchronizedAfterSuiteProc1BodyHasContext = body, hasContext + } + } else { + if node.Body != nil { + appendError(types.GinkgoErrors.MultipleBodyFunctions(node.CodeLocation, nodeType)) + trackedFunctionError = true + break + } + node.Body, node.HasContext = extractBodyFunction(deprecationTracker, node.CodeLocation, arg) + if node.Body == nil { + appendError(types.GinkgoErrors.InvalidBodyType(t, node.CodeLocation, nodeType)) + trackedFunctionError = true + break + } + } + default: + remainingArgs = append(remainingArgs, arg) + } + } + + //validations + if node.MarkedPending && node.MarkedFocus { + appendError(types.GinkgoErrors.InvalidDeclarationOfFocusedAndPending(node.CodeLocation, nodeType)) + } + + hasContext := node.HasContext || node.SynchronizedAfterSuiteProc1BodyHasContext || node.SynchronizedAfterSuiteAllProcsBodyHasContext || node.SynchronizedBeforeSuiteProc1BodyHasContext || node.SynchronizedBeforeSuiteAllProcsBodyHasContext + + if !hasContext && (node.NodeTimeout > 0 || node.SpecTimeout > 0 || node.GracePeriod > 0) && len(errors) == 0 { + appendError(types.GinkgoErrors.InvalidTimeoutOrGracePeriodForNonContextNode(node.CodeLocation, nodeType)) + } + + if !node.NodeType.Is(types.NodeTypeReportBeforeEach|types.NodeTypeReportAfterEach|types.NodeTypeSynchronizedBeforeSuite|types.NodeTypeSynchronizedAfterSuite|types.NodeTypeReportAfterSuite) && node.Body == nil && !node.MarkedPending && !trackedFunctionError { + appendError(types.GinkgoErrors.MissingBodyFunction(node.CodeLocation, nodeType)) + } + + if node.NodeType.Is(types.NodeTypeSynchronizedBeforeSuite) && !trackedFunctionError && (node.SynchronizedBeforeSuiteProc1Body == nil || node.SynchronizedBeforeSuiteAllProcsBody == nil) { + appendError(types.GinkgoErrors.MissingBodyFunction(node.CodeLocation, nodeType)) + } + + if node.NodeType.Is(types.NodeTypeSynchronizedAfterSuite) && !trackedFunctionError && (node.SynchronizedAfterSuiteProc1Body == nil || node.SynchronizedAfterSuiteAllProcsBody == nil) { + appendError(types.GinkgoErrors.MissingBodyFunction(node.CodeLocation, nodeType)) + } + + for _, arg := range remainingArgs { + appendError(types.GinkgoErrors.UnknownDecorator(node.CodeLocation, nodeType, arg)) + } + + if node.FlakeAttempts > 0 && node.MustPassRepeatedly > 0 { + appendError(types.GinkgoErrors.InvalidDeclarationOfFlakeAttemptsAndMustPassRepeatedly(node.CodeLocation, nodeType)) + } + + if len(errors) > 0 { + return Node{}, errors + } + + return node, errors +} + +var doneType = reflect.TypeOf(make(Done)) + +func extractBodyFunction(deprecationTracker *types.DeprecationTracker, cl types.CodeLocation, arg interface{}) (func(SpecContext), bool) { + t := reflect.TypeOf(arg) + if t.NumOut() > 0 || t.NumIn() > 1 { + return nil, false + } + if t.NumIn() == 1 { + if t.In(0) == doneType { + deprecationTracker.TrackDeprecation(types.Deprecations.Async(), cl) + deprecatedAsyncBody := arg.(func(Done)) + return func(SpecContext) { deprecatedAsyncBody(make(Done)) }, false + } else if t.In(0).Implements(specContextType) { + return arg.(func(SpecContext)), true + } else if t.In(0).Implements(contextType) { + body := arg.(func(context.Context)) + return func(c SpecContext) { body(c) }, true + } + + return nil, false + } + + body := arg.(func()) + return func(SpecContext) { body() }, false +} + +var byteType = reflect.TypeOf([]byte{}) + +func extractSynchronizedBeforeSuiteProc1Body(arg interface{}) (func(SpecContext) []byte, bool) { + t := reflect.TypeOf(arg) + v := reflect.ValueOf(arg) + + if t.NumOut() > 1 || t.NumIn() > 1 { + return nil, false + } else if t.NumOut() == 1 && t.Out(0) != byteType { + return nil, false + } else if t.NumIn() == 1 && !t.In(0).Implements(contextType) { + return nil, false + } + hasContext := t.NumIn() == 1 + + return func(c SpecContext) []byte { + var out []reflect.Value + if hasContext { + out = v.Call([]reflect.Value{reflect.ValueOf(c)}) + } else { + out = v.Call([]reflect.Value{}) + } + if len(out) == 1 { + return (out[0].Interface()).([]byte) + } else { + return []byte{} + } + }, hasContext +} + +func extractSynchronizedBeforeSuiteAllProcsBody(arg interface{}) (func(SpecContext, []byte), bool) { + t := reflect.TypeOf(arg) + v := reflect.ValueOf(arg) + hasContext, hasByte := false, false + + if t.NumOut() > 0 || t.NumIn() > 2 { + return nil, false + } else if t.NumIn() == 2 && t.In(0).Implements(contextType) && t.In(1) == byteType { + hasContext, hasByte = true, true + } else if t.NumIn() == 1 && t.In(0).Implements(contextType) { + hasContext = true + } else if t.NumIn() == 1 && t.In(0) == byteType { + hasByte = true + } else if t.NumIn() != 0 { + return nil, false + } + + return func(c SpecContext, b []byte) { + in := []reflect.Value{} + if hasContext { + in = append(in, reflect.ValueOf(c)) + } + if hasByte { + in = append(in, reflect.ValueOf(b)) + } + v.Call(in) + }, hasContext +} + +var errInterface = reflect.TypeOf((*error)(nil)).Elem() + +func NewCleanupNode(deprecationTracker *types.DeprecationTracker, fail func(string, types.CodeLocation), args ...interface{}) (Node, []error) { + decorations, remainingArgs := PartitionDecorations(args...) + baseOffset := 2 + cl := types.NewCodeLocation(baseOffset) + finalArgs := []interface{}{} + for _, arg := range decorations { + switch t := reflect.TypeOf(arg); { + case t == reflect.TypeOf(Offset(0)): + cl = types.NewCodeLocation(baseOffset + int(arg.(Offset))) + case t == reflect.TypeOf(types.CodeLocation{}): + cl = arg.(types.CodeLocation) + default: + finalArgs = append(finalArgs, arg) + } + } + finalArgs = append(finalArgs, cl) + + if len(remainingArgs) == 0 { + return Node{}, []error{types.GinkgoErrors.DeferCleanupInvalidFunction(cl)} + } + + callback := reflect.ValueOf(remainingArgs[0]) + if !(callback.Kind() == reflect.Func) { + return Node{}, []error{types.GinkgoErrors.DeferCleanupInvalidFunction(cl)} + } + + callArgs := []reflect.Value{} + for _, arg := range remainingArgs[1:] { + callArgs = append(callArgs, reflect.ValueOf(arg)) + } + + hasContext := false + t := callback.Type() + if t.NumIn() > 0 { + if t.In(0).Implements(specContextType) { + hasContext = true + } else if t.In(0).Implements(contextType) && (len(callArgs) == 0 || !callArgs[0].Type().Implements(contextType)) { + hasContext = true + } + } + + handleFailure := func(out []reflect.Value) { + if len(out) == 0 { + return + } + last := out[len(out)-1] + if last.Type().Implements(errInterface) && !last.IsNil() { + fail(fmt.Sprintf("DeferCleanup callback returned error: %v", last), cl) + } + } + + if hasContext { + finalArgs = append(finalArgs, func(c SpecContext) { + out := callback.Call(append([]reflect.Value{reflect.ValueOf(c)}, callArgs...)) + handleFailure(out) + }) + } else { + finalArgs = append(finalArgs, func() { + out := callback.Call(callArgs) + handleFailure(out) + }) + } + + return NewNode(deprecationTracker, types.NodeTypeCleanupInvalid, "", finalArgs...) +} + +func (n Node) IsZero() bool { + return n.ID == 0 +} + +/* Nodes */ +type Nodes []Node + +func (n Nodes) CopyAppend(nodes ...Node) Nodes { + numN := len(n) + out := make(Nodes, numN+len(nodes)) + for i, node := range n { + out[i] = node + } + for j, node := range nodes { + out[numN+j] = node + } + return out +} + +func (n Nodes) SplitAround(pivot Node) (Nodes, Nodes) { + pivotIdx := len(n) + for i := range n { + if n[i].ID == pivot.ID { + pivotIdx = i + break + } + } + left := n[:pivotIdx] + right := Nodes{} + if pivotIdx+1 < len(n) { + right = n[pivotIdx+1:] + } + + return left, right +} + +func (n Nodes) FirstNodeWithType(nodeTypes types.NodeType) Node { + for i := range n { + if n[i].NodeType.Is(nodeTypes) { + return n[i] + } + } + return Node{} +} + +func (n Nodes) WithType(nodeTypes types.NodeType) Nodes { + count := 0 + for i := range n { + if n[i].NodeType.Is(nodeTypes) { + count++ + } + } + + out, j := make(Nodes, count), 0 + for i := range n { + if n[i].NodeType.Is(nodeTypes) { + out[j] = n[i] + j++ + } + } + return out +} + +func (n Nodes) WithoutType(nodeTypes types.NodeType) Nodes { + count := 0 + for i := range n { + if !n[i].NodeType.Is(nodeTypes) { + count++ + } + } + + out, j := make(Nodes, count), 0 + for i := range n { + if !n[i].NodeType.Is(nodeTypes) { + out[j] = n[i] + j++ + } + } + return out +} + +func (n Nodes) WithoutNode(nodeToExclude Node) Nodes { + idxToExclude := len(n) + for i := range n { + if n[i].ID == nodeToExclude.ID { + idxToExclude = i + break + } + } + if idxToExclude == len(n) { + return n + } + out, j := make(Nodes, len(n)-1), 0 + for i := range n { + if i == idxToExclude { + continue + } + out[j] = n[i] + j++ + } + return out +} + +func (n Nodes) Filter(filter func(Node) bool) Nodes { + trufa, count := make([]bool, len(n)), 0 + for i := range n { + if filter(n[i]) { + trufa[i] = true + count += 1 + } + } + out, j := make(Nodes, count), 0 + for i := range n { + if trufa[i] { + out[j] = n[i] + j++ + } + } + return out +} + +func (n Nodes) FirstSatisfying(filter func(Node) bool) Node { + for i := range n { + if filter(n[i]) { + return n[i] + } + } + return Node{} +} + +func (n Nodes) WithinNestingLevel(deepestNestingLevel int) Nodes { + count := 0 + for i := range n { + if n[i].NestingLevel <= deepestNestingLevel { + count++ + } + } + out, j := make(Nodes, count), 0 + for i := range n { + if n[i].NestingLevel <= deepestNestingLevel { + out[j] = n[i] + j++ + } + } + return out +} + +func (n Nodes) SortedByDescendingNestingLevel() Nodes { + out := make(Nodes, len(n)) + copy(out, n) + sort.SliceStable(out, func(i int, j int) bool { + return out[i].NestingLevel > out[j].NestingLevel + }) + + return out +} + +func (n Nodes) SortedByAscendingNestingLevel() Nodes { + out := make(Nodes, len(n)) + copy(out, n) + sort.SliceStable(out, func(i int, j int) bool { + return out[i].NestingLevel < out[j].NestingLevel + }) + + return out +} + +func (n Nodes) FirstWithNestingLevel(level int) Node { + for i := range n { + if n[i].NestingLevel == level { + return n[i] + } + } + return Node{} +} + +func (n Nodes) Reverse() Nodes { + out := make(Nodes, len(n)) + for i := range n { + out[len(n)-1-i] = n[i] + } + return out +} + +func (n Nodes) Texts() []string { + out := make([]string, len(n)) + for i := range n { + out[i] = n[i].Text + } + return out +} + +func (n Nodes) Labels() [][]string { + out := make([][]string, len(n)) + for i := range n { + if n[i].Labels == nil { + out[i] = []string{} + } else { + out[i] = []string(n[i].Labels) + } + } + return out +} + +func (n Nodes) UnionOfLabels() []string { + out := []string{} + seen := map[string]bool{} + for i := range n { + for _, label := range n[i].Labels { + if !seen[label] { + seen[label] = true + out = append(out, label) + } + } + } + return out +} + +func (n Nodes) CodeLocations() []types.CodeLocation { + out := make([]types.CodeLocation, len(n)) + for i := range n { + out[i] = n[i].CodeLocation + } + return out +} + +func (n Nodes) BestTextFor(node Node) string { + if node.Text != "" { + return node.Text + } + parentNestingLevel := node.NestingLevel - 1 + for i := range n { + if n[i].Text != "" && n[i].NestingLevel == parentNestingLevel { + return n[i].Text + } + } + + return "" +} + +func (n Nodes) ContainsNodeID(id uint) bool { + for i := range n { + if n[i].ID == id { + return true + } + } + return false +} + +func (n Nodes) HasNodeMarkedPending() bool { + for i := range n { + if n[i].MarkedPending { + return true + } + } + return false +} + +func (n Nodes) HasNodeMarkedFocus() bool { + for i := range n { + if n[i].MarkedFocus { + return true + } + } + return false +} + +func (n Nodes) HasNodeMarkedSerial() bool { + for i := range n { + if n[i].MarkedSerial { + return true + } + } + return false +} + +func (n Nodes) FirstNodeMarkedOrdered() Node { + for i := range n { + if n[i].MarkedOrdered { + return n[i] + } + } + return Node{} +} + +func (n Nodes) GetMaxFlakeAttempts() int { + maxFlakeAttempts := 0 + for i := range n { + if n[i].FlakeAttempts > 0 { + maxFlakeAttempts = n[i].FlakeAttempts + } + } + return maxFlakeAttempts +} + +func (n Nodes) GetMaxMustPassRepeatedly() int { + maxMustPassRepeatedly := 0 + for i := range n { + if n[i].MustPassRepeatedly > 0 { + maxMustPassRepeatedly = n[i].MustPassRepeatedly + } + } + return maxMustPassRepeatedly +} + +func unrollInterfaceSlice(args interface{}) []interface{} { + v := reflect.ValueOf(args) + if v.Kind() != reflect.Slice { + return []interface{}{args} + } + out := []interface{}{} + for i := 0; i < v.Len(); i++ { + el := reflect.ValueOf(v.Index(i).Interface()) + if el.Kind() == reflect.Slice && el.Type() != reflect.TypeOf(Labels{}) { + out = append(out, unrollInterfaceSlice(el.Interface())...) + } else { + out = append(out, v.Index(i).Interface()) + } + } + return out +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/ordering.go b/vendor/github.com/onsi/ginkgo/v2/internal/ordering.go new file mode 100644 index 00000000000..161be820cce --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/ordering.go @@ -0,0 +1,121 @@ +package internal + +import ( + "math/rand" + "sort" + + "github.com/onsi/ginkgo/v2/types" +) + +type GroupedSpecIndices []SpecIndices +type SpecIndices []int + +func OrderSpecs(specs Specs, suiteConfig types.SuiteConfig) (GroupedSpecIndices, GroupedSpecIndices) { + /* + Ginkgo has sophisticated support for randomizing specs. Specs are guaranteed to have the same + order for a given seed across test runs. + + By default only top-level containers and specs are shuffled - this makes for a more intuitive debugging + experience - specs within a given container run in the order they appear in the file. + + Developers can set -randomizeAllSpecs to shuffle _all_ specs. + + In addition, spec containers can be marked as Ordered. Specs within an Ordered container are never shuffled. + + Finally, specs and spec containers can be marked as Serial. When running in parallel, serial specs run on Process #1 _after_ all other processes have finished. + */ + + // Seed a new random source based on thee configured random seed. + r := rand.New(rand.NewSource(suiteConfig.RandomSeed)) + + // first break things into execution groups + // a group represents a single unit of execution and is a collection of SpecIndices + // usually a group is just a single spec, however ordered containers must be preserved as a single group + executionGroupIDs := []uint{} + executionGroups := map[uint]SpecIndices{} + for idx, spec := range specs { + groupNode := spec.Nodes.FirstNodeMarkedOrdered() + if groupNode.IsZero() { + groupNode = spec.Nodes.FirstNodeWithType(types.NodeTypeIt) + } + executionGroups[groupNode.ID] = append(executionGroups[groupNode.ID], idx) + if len(executionGroups[groupNode.ID]) == 1 { + executionGroupIDs = append(executionGroupIDs, groupNode.ID) + } + } + + // now, we only shuffle all the execution groups if we're randomizing all specs, otherwise + // we shuffle outermost containers. so we need to form shufflable groupings of GroupIDs + shufflableGroupingIDs := []uint{} + shufflableGroupingIDToGroupIDs := map[uint][]uint{} + shufflableGroupingsIDToSortKeys := map[uint]string{} + + // for each execution group we're going to have to pick a node to represent how the + // execution group is grouped for shuffling: + nodeTypesToShuffle := types.NodeTypesForContainerAndIt + if suiteConfig.RandomizeAllSpecs { + nodeTypesToShuffle = types.NodeTypeIt + } + + //so, fo reach execution group: + for _, groupID := range executionGroupIDs { + // pick out a representative spec + representativeSpec := specs[executionGroups[groupID][0]] + + // and grab the node on the spec that will represent which shufflable group this execution group belongs tu + shufflableGroupingNode := representativeSpec.Nodes.FirstNodeWithType(nodeTypesToShuffle) + + //add the execution group to its shufflable group + shufflableGroupingIDToGroupIDs[shufflableGroupingNode.ID] = append(shufflableGroupingIDToGroupIDs[shufflableGroupingNode.ID], groupID) + + //and if it's the first one in + if len(shufflableGroupingIDToGroupIDs[shufflableGroupingNode.ID]) == 1 { + // record the shuffleable group ID + shufflableGroupingIDs = append(shufflableGroupingIDs, shufflableGroupingNode.ID) + // and record the sort key to use + shufflableGroupingsIDToSortKeys[shufflableGroupingNode.ID] = shufflableGroupingNode.CodeLocation.String() + } + } + + // now we sort the shufflable groups by the sort key. We use the shufflable group nodes code location and break ties using its node id + sort.SliceStable(shufflableGroupingIDs, func(i, j int) bool { + keyA := shufflableGroupingsIDToSortKeys[shufflableGroupingIDs[i]] + keyB := shufflableGroupingsIDToSortKeys[shufflableGroupingIDs[j]] + if keyA == keyB { + return shufflableGroupingIDs[i] < shufflableGroupingIDs[j] + } else { + return keyA < keyB + } + }) + + // now we permute the sorted shufflable grouping IDs and build the ordered Groups + orderedGroups := GroupedSpecIndices{} + permutation := r.Perm(len(shufflableGroupingIDs)) + for _, j := range permutation { + //let's get the execution group IDs for this shufflable group: + executionGroupIDsForJ := shufflableGroupingIDToGroupIDs[shufflableGroupingIDs[j]] + // and we'll add their associated specindices to the orderedGroups slice: + for _, executionGroupID := range executionGroupIDsForJ { + orderedGroups = append(orderedGroups, executionGroups[executionGroupID]) + } + } + + // If we're running in series, we're done. + if suiteConfig.ParallelTotal == 1 { + return orderedGroups, GroupedSpecIndices{} + } + + // We're running in parallel so we need to partition the ordered groups into a parallelizable set and a serialized set. + // The parallelizable groups will run across all Ginkgo processes... + // ...the serial groups will only run on Process #1 after all other processes have exited. + parallelizableGroups, serialGroups := GroupedSpecIndices{}, GroupedSpecIndices{} + for _, specIndices := range orderedGroups { + if specs[specIndices[0]].Nodes.HasNodeMarkedSerial() { + serialGroups = append(serialGroups, specIndices) + } else { + parallelizableGroups = append(parallelizableGroups, specIndices) + } + } + + return parallelizableGroups, serialGroups +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor.go b/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor.go new file mode 100644 index 00000000000..4a1c0946127 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor.go @@ -0,0 +1,250 @@ +package internal + +import ( + "bytes" + "io" + "os" + "time" +) + +const BAILOUT_TIME = 1 * time.Second +const BAILOUT_MESSAGE = `Ginkgo detected an issue while intercepting output. + +When running in parallel, Ginkgo captures stdout and stderr output +and attaches it to the running spec. It looks like that process is getting +stuck for this suite. + +This usually happens if you, or a library you are using, spin up an external +process and set cmd.Stdout = os.Stdout and/or cmd.Stderr = os.Stderr. This +causes the external process to keep Ginkgo's output interceptor pipe open and +causes output interception to hang. + +Ginkgo has detected this and shortcircuited the capture process. The specs +will continue running after this message however output from the external +process that caused this issue will not be captured. + +You have several options to fix this. In preferred order they are: + +1. Pass GinkgoWriter instead of os.Stdout or os.Stderr to your process. +2. Ensure your process exits before the current spec completes. If your +process is long-lived and must cross spec boundaries, this option won't +work for you. +3. Pause Ginkgo's output interceptor before starting your process and then +resume it after. Use PauseOutputInterception() and ResumeOutputInterception() +to do this. +4. Set --output-interceptor-mode=none when running your Ginkgo suite. This will +turn off all output interception but allow specs to run in parallel without this +issue. You may miss important output if you do this including output from Go's +race detector. + +More details on issue #851 - https://github.com/onsi/ginkgo/issues/851 +` + +/* +The OutputInterceptor is used by to +intercept and capture all stdin and stderr output during a test run. +*/ +type OutputInterceptor interface { + StartInterceptingOutput() + StartInterceptingOutputAndForwardTo(io.Writer) + StopInterceptingAndReturnOutput() string + + PauseIntercepting() + ResumeIntercepting() + + Shutdown() +} + +type NoopOutputInterceptor struct{} + +func (interceptor NoopOutputInterceptor) StartInterceptingOutput() {} +func (interceptor NoopOutputInterceptor) StartInterceptingOutputAndForwardTo(io.Writer) {} +func (interceptor NoopOutputInterceptor) StopInterceptingAndReturnOutput() string { return "" } +func (interceptor NoopOutputInterceptor) PauseIntercepting() {} +func (interceptor NoopOutputInterceptor) ResumeIntercepting() {} +func (interceptor NoopOutputInterceptor) Shutdown() {} + +type pipePair struct { + reader *os.File + writer *os.File +} + +func startPipeFactory(pipeChannel chan pipePair, shutdown chan interface{}) { + for { + //make the next pipe... + pair := pipePair{} + pair.reader, pair.writer, _ = os.Pipe() + select { + //...and provide it to the next consumer (they are responsible for closing the files) + case pipeChannel <- pair: + continue + //...or close the files if we were told to shutdown + case <-shutdown: + pair.reader.Close() + pair.writer.Close() + return + } + } +} + +type interceptorImplementation interface { + CreateStdoutStderrClones() (*os.File, *os.File) + ConnectPipeToStdoutStderr(*os.File) + RestoreStdoutStderrFromClones(*os.File, *os.File) + ShutdownClones(*os.File, *os.File) +} + +type genericOutputInterceptor struct { + intercepting bool + + stdoutClone *os.File + stderrClone *os.File + pipe pipePair + + shutdown chan interface{} + emergencyBailout chan interface{} + pipeChannel chan pipePair + interceptedContent chan string + + forwardTo io.Writer + accumulatedOutput string + + implementation interceptorImplementation +} + +func (interceptor *genericOutputInterceptor) StartInterceptingOutput() { + interceptor.StartInterceptingOutputAndForwardTo(io.Discard) +} + +func (interceptor *genericOutputInterceptor) StartInterceptingOutputAndForwardTo(w io.Writer) { + if interceptor.intercepting { + return + } + interceptor.accumulatedOutput = "" + interceptor.forwardTo = w + interceptor.ResumeIntercepting() +} + +func (interceptor *genericOutputInterceptor) StopInterceptingAndReturnOutput() string { + if interceptor.intercepting { + interceptor.PauseIntercepting() + } + return interceptor.accumulatedOutput +} + +func (interceptor *genericOutputInterceptor) ResumeIntercepting() { + if interceptor.intercepting { + return + } + interceptor.intercepting = true + if interceptor.stdoutClone == nil { + interceptor.stdoutClone, interceptor.stderrClone = interceptor.implementation.CreateStdoutStderrClones() + interceptor.shutdown = make(chan interface{}) + go startPipeFactory(interceptor.pipeChannel, interceptor.shutdown) + } + + // Now we make a pipe, we'll use this to redirect the input to the 1 and 2 file descriptors (this is how everything else in the world is string to log to stdout and stderr) + // we get the pipe from our pipe factory. it runs in the background so we can request the next pipe while the spec being intercepted is running + interceptor.pipe = <-interceptor.pipeChannel + + interceptor.emergencyBailout = make(chan interface{}) + + //Spin up a goroutine to copy data from the pipe into a buffer, this is how we capture any output the user is emitting + go func() { + buffer := &bytes.Buffer{} + destination := io.MultiWriter(buffer, interceptor.forwardTo) + copyFinished := make(chan interface{}) + reader := interceptor.pipe.reader + go func() { + io.Copy(destination, reader) + reader.Close() // close the read end of the pipe so we don't leak a file descriptor + close(copyFinished) + }() + select { + case <-copyFinished: + interceptor.interceptedContent <- buffer.String() + case <-interceptor.emergencyBailout: + interceptor.interceptedContent <- "" + } + }() + + interceptor.implementation.ConnectPipeToStdoutStderr(interceptor.pipe.writer) +} + +func (interceptor *genericOutputInterceptor) PauseIntercepting() { + if !interceptor.intercepting { + return + } + // first we have to close the write end of the pipe. To do this we have to close all file descriptors pointing + // to the write end. So that would be the pipewriter itself, and FD #1 and FD #2 if we've Dup2'd them + interceptor.pipe.writer.Close() // the pipewriter itself + + // we also need to stop intercepting. we do that by reconnecting the stdout and stderr file descriptions back to their respective #1 and #2 file descriptors; + // this also closes #1 and #2 before it points that their original stdout and stderr file descriptions + interceptor.implementation.RestoreStdoutStderrFromClones(interceptor.stdoutClone, interceptor.stderrClone) + + var content string + select { + case content = <-interceptor.interceptedContent: + case <-time.After(BAILOUT_TIME): + /* + By closing all the pipe writer's file descriptors associated with the pipe writer's file description the io.Copy reading from the reader + should eventually receive an EOF and exit. + + **However**, if the user has spun up an external process and passed in os.Stdout/os.Stderr to cmd.Stdout/cmd.Stderr then the external process + will have a file descriptor pointing to the pipe writer's file description and it will not close until the external process exits. + + That would leave us hanging here waiting for the io.Copy to close forever. Instead we invoke this emergency escape valve. This returns whatever + content we've got but leaves the io.Copy running. This ensures the external process can continue writing without hanging at the cost of leaking a goroutine + and file descriptor (those these will be cleaned up when the process exits). + + We tack on a message to notify the user that they've hit this edgecase and encourage them to address it. + */ + close(interceptor.emergencyBailout) + content = <-interceptor.interceptedContent + BAILOUT_MESSAGE + } + + interceptor.accumulatedOutput += content + interceptor.intercepting = false +} + +func (interceptor *genericOutputInterceptor) Shutdown() { + interceptor.PauseIntercepting() + + if interceptor.stdoutClone != nil { + close(interceptor.shutdown) + interceptor.implementation.ShutdownClones(interceptor.stdoutClone, interceptor.stderrClone) + interceptor.stdoutClone = nil + interceptor.stderrClone = nil + } +} + +/* This is used on windows builds but included here so it can be explicitly tested on unix systems too */ +func NewOSGlobalReassigningOutputInterceptor() OutputInterceptor { + return &genericOutputInterceptor{ + interceptedContent: make(chan string), + pipeChannel: make(chan pipePair), + shutdown: make(chan interface{}), + implementation: &osGlobalReassigningOutputInterceptorImpl{}, + } +} + +type osGlobalReassigningOutputInterceptorImpl struct{} + +func (impl *osGlobalReassigningOutputInterceptorImpl) CreateStdoutStderrClones() (*os.File, *os.File) { + return os.Stdout, os.Stderr +} + +func (impl *osGlobalReassigningOutputInterceptorImpl) ConnectPipeToStdoutStderr(pipeWriter *os.File) { + os.Stdout = pipeWriter + os.Stderr = pipeWriter +} + +func (impl *osGlobalReassigningOutputInterceptorImpl) RestoreStdoutStderrFromClones(stdoutClone *os.File, stderrClone *os.File) { + os.Stdout = stdoutClone + os.Stderr = stderrClone +} + +func (impl *osGlobalReassigningOutputInterceptorImpl) ShutdownClones(_ *os.File, _ *os.File) { + //noop +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_unix.go b/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_unix.go new file mode 100644 index 00000000000..f5ae15b8b5b --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_unix.go @@ -0,0 +1,62 @@ +//go:build freebsd || openbsd || netbsd || dragonfly || darwin || linux || solaris +// +build freebsd openbsd netbsd dragonfly darwin linux solaris + +package internal + +import ( + "os" + + "golang.org/x/sys/unix" +) + +func NewOutputInterceptor() OutputInterceptor { + return &genericOutputInterceptor{ + interceptedContent: make(chan string), + pipeChannel: make(chan pipePair), + shutdown: make(chan interface{}), + implementation: &dupSyscallOutputInterceptorImpl{}, + } +} + +type dupSyscallOutputInterceptorImpl struct{} + +func (impl *dupSyscallOutputInterceptorImpl) CreateStdoutStderrClones() (*os.File, *os.File) { + // To clone stdout and stderr we: + // First, create two clone file descriptors that point to the stdout and stderr file descriptions + stdoutCloneFD, _ := unix.Dup(1) + stderrCloneFD, _ := unix.Dup(2) + + // And then wrap the clone file descriptors in files. + // One benefit of this (that we don't use yet) is that we can actually write + // to these files to emit output to the console even though we're intercepting output + stdoutClone := os.NewFile(uintptr(stdoutCloneFD), "stdout-clone") + stderrClone := os.NewFile(uintptr(stderrCloneFD), "stderr-clone") + + //these clones remain alive throughout the lifecycle of the suite and don't need to be recreated + //this speeds things up a bit, actually. + return stdoutClone, stderrClone +} + +func (impl *dupSyscallOutputInterceptorImpl) ConnectPipeToStdoutStderr(pipeWriter *os.File) { + // To redirect output to our pipe we need to point the 1 and 2 file descriptors (which is how the world tries to log things) + // to the write end of the pipe. + // We do this with Dup2 (possibly Dup3 on some architectures) to have file descriptors 1 and 2 point to the same file description as the pipeWriter + // This effectively shunts data written to stdout and stderr to the write end of our pipe + unix.Dup2(int(pipeWriter.Fd()), 1) + unix.Dup2(int(pipeWriter.Fd()), 2) +} + +func (impl *dupSyscallOutputInterceptorImpl) RestoreStdoutStderrFromClones(stdoutClone *os.File, stderrClone *os.File) { + // To restore stdour/stderr from the clones we have the 1 and 2 file descriptors + // point to the original file descriptions that we saved off in the clones. + // This has the added benefit of closing the connection between these descriptors and the write end of the pipe + // which is important to cause the io.Copy on the pipe.Reader to end. + unix.Dup2(int(stdoutClone.Fd()), 1) + unix.Dup2(int(stderrClone.Fd()), 2) +} + +func (impl *dupSyscallOutputInterceptorImpl) ShutdownClones(stdoutClone *os.File, stderrClone *os.File) { + // We're done with the clones so we can close them to clean up after ourselves + stdoutClone.Close() + stderrClone.Close() +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_win.go b/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_win.go new file mode 100644 index 00000000000..30c2851a818 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_win.go @@ -0,0 +1,7 @@ +// +build windows + +package internal + +func NewOutputInterceptor() OutputInterceptor { + return NewOSGlobalReassigningOutputInterceptor() +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/client_server.go b/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/client_server.go new file mode 100644 index 00000000000..b417bf5b3fb --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/client_server.go @@ -0,0 +1,70 @@ +package parallel_support + +import ( + "fmt" + "io" + "os" + "time" + + "github.com/onsi/ginkgo/v2/reporters" + "github.com/onsi/ginkgo/v2/types" +) + +type BeforeSuiteState struct { + Data []byte + State types.SpecState +} + +type ParallelIndexCounter struct { + Index int +} + +var ErrorGone = fmt.Errorf("gone") +var ErrorFailed = fmt.Errorf("failed") +var ErrorEarly = fmt.Errorf("early") + +var POLLING_INTERVAL = 50 * time.Millisecond + +type Server interface { + Start() + Close() + Address() string + RegisterAlive(node int, alive func() bool) + GetSuiteDone() chan interface{} + GetOutputDestination() io.Writer + SetOutputDestination(io.Writer) +} + +type Client interface { + Connect() bool + Close() error + + PostSuiteWillBegin(report types.Report) error + PostDidRun(report types.SpecReport) error + PostSuiteDidEnd(report types.Report) error + PostSynchronizedBeforeSuiteCompleted(state types.SpecState, data []byte) error + BlockUntilSynchronizedBeforeSuiteData() (types.SpecState, []byte, error) + BlockUntilNonprimaryProcsHaveFinished() error + BlockUntilAggregatedNonprimaryProcsReport() (types.Report, error) + FetchNextCounter() (int, error) + PostAbort() error + ShouldAbort() bool + PostEmitProgressReport(report types.ProgressReport) error + Write(p []byte) (int, error) +} + +func NewServer(parallelTotal int, reporter reporters.Reporter) (Server, error) { + if os.Getenv("GINKGO_PARALLEL_PROTOCOL") == "HTTP" { + return newHttpServer(parallelTotal, reporter) + } else { + return newRPCServer(parallelTotal, reporter) + } +} + +func NewClient(serverHost string) Client { + if os.Getenv("GINKGO_PARALLEL_PROTOCOL") == "HTTP" { + return newHttpClient(serverHost) + } else { + return newRPCClient(serverHost) + } +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/http_client.go b/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/http_client.go new file mode 100644 index 00000000000..ad9932f2a9a --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/http_client.go @@ -0,0 +1,156 @@ +package parallel_support + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + "github.com/onsi/ginkgo/v2/types" +) + +type httpClient struct { + serverHost string +} + +func newHttpClient(serverHost string) *httpClient { + return &httpClient{ + serverHost: serverHost, + } +} + +func (client *httpClient) Connect() bool { + resp, err := http.Get(client.serverHost + "/up") + if err != nil { + return false + } + resp.Body.Close() + return resp.StatusCode == http.StatusOK +} + +func (client *httpClient) Close() error { + return nil +} + +func (client *httpClient) post(path string, data interface{}) error { + var body io.Reader + if data != nil { + encoded, err := json.Marshal(data) + if err != nil { + return err + } + body = bytes.NewBuffer(encoded) + } + resp, err := http.Post(client.serverHost+path, "application/json", body) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("received unexpected status code %d", resp.StatusCode) + } + return nil +} + +func (client *httpClient) poll(path string, data interface{}) error { + for { + resp, err := http.Get(client.serverHost + path) + if err != nil { + return err + } + if resp.StatusCode == http.StatusTooEarly { + resp.Body.Close() + time.Sleep(POLLING_INTERVAL) + continue + } + defer resp.Body.Close() + if resp.StatusCode == http.StatusGone { + return ErrorGone + } + if resp.StatusCode == http.StatusFailedDependency { + return ErrorFailed + } + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("received unexpected status code %d", resp.StatusCode) + } + if data != nil { + return json.NewDecoder(resp.Body).Decode(data) + } + return nil + } +} + +func (client *httpClient) PostSuiteWillBegin(report types.Report) error { + return client.post("/suite-will-begin", report) +} + +func (client *httpClient) PostDidRun(report types.SpecReport) error { + return client.post("/did-run", report) +} + +func (client *httpClient) PostSuiteDidEnd(report types.Report) error { + return client.post("/suite-did-end", report) +} + +func (client *httpClient) PostEmitProgressReport(report types.ProgressReport) error { + return client.post("/progress-report", report) +} + +func (client *httpClient) PostSynchronizedBeforeSuiteCompleted(state types.SpecState, data []byte) error { + beforeSuiteState := BeforeSuiteState{ + State: state, + Data: data, + } + return client.post("/before-suite-completed", beforeSuiteState) +} + +func (client *httpClient) BlockUntilSynchronizedBeforeSuiteData() (types.SpecState, []byte, error) { + var beforeSuiteState BeforeSuiteState + err := client.poll("/before-suite-state", &beforeSuiteState) + if err == ErrorGone { + return types.SpecStateInvalid, nil, types.GinkgoErrors.SynchronizedBeforeSuiteDisappearedOnProc1() + } + return beforeSuiteState.State, beforeSuiteState.Data, err +} + +func (client *httpClient) BlockUntilNonprimaryProcsHaveFinished() error { + return client.poll("/have-nonprimary-procs-finished", nil) +} + +func (client *httpClient) BlockUntilAggregatedNonprimaryProcsReport() (types.Report, error) { + var report types.Report + err := client.poll("/aggregated-nonprimary-procs-report", &report) + if err == ErrorGone { + return types.Report{}, types.GinkgoErrors.AggregatedReportUnavailableDueToNodeDisappearing() + } + return report, err +} + +func (client *httpClient) FetchNextCounter() (int, error) { + var counter ParallelIndexCounter + err := client.poll("/counter", &counter) + return counter.Index, err +} + +func (client *httpClient) PostAbort() error { + return client.post("/abort", nil) +} + +func (client *httpClient) ShouldAbort() bool { + err := client.poll("/abort", nil) + if err == ErrorGone { + return true + } + return false +} + +func (client *httpClient) Write(p []byte) (int, error) { + resp, err := http.Post(client.serverHost+"/emit-output", "text/plain;charset=UTF-8 ", bytes.NewReader(p)) + resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return 0, fmt.Errorf("failed to emit output") + } + return len(p), err +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/http_server.go b/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/http_server.go new file mode 100644 index 00000000000..fa3ac682a0f --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/http_server.go @@ -0,0 +1,223 @@ +/* + +The remote package provides the pieces to allow Ginkgo test suites to report to remote listeners. +This is used, primarily, to enable streaming parallel test output but has, in principal, broader applications (e.g. streaming test output to a browser). + +*/ + +package parallel_support + +import ( + "encoding/json" + "io" + "net" + "net/http" + + "github.com/onsi/ginkgo/v2/reporters" + "github.com/onsi/ginkgo/v2/types" +) + +/* +httpServer spins up on an automatically selected port and listens for communication from the forwarding reporter. +It then forwards that communication to attached reporters. +*/ +type httpServer struct { + listener net.Listener + handler *ServerHandler +} + +//Create a new server, automatically selecting a port +func newHttpServer(parallelTotal int, reporter reporters.Reporter) (*httpServer, error) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return nil, err + } + return &httpServer{ + listener: listener, + handler: newServerHandler(parallelTotal, reporter), + }, nil +} + +//Start the server. You don't need to `go s.Start()`, just `s.Start()` +func (server *httpServer) Start() { + httpServer := &http.Server{} + mux := http.NewServeMux() + httpServer.Handler = mux + + //streaming endpoints + mux.HandleFunc("/suite-will-begin", server.specSuiteWillBegin) + mux.HandleFunc("/did-run", server.didRun) + mux.HandleFunc("/suite-did-end", server.specSuiteDidEnd) + mux.HandleFunc("/emit-output", server.emitOutput) + mux.HandleFunc("/progress-report", server.emitProgressReport) + + //synchronization endpoints + mux.HandleFunc("/before-suite-completed", server.handleBeforeSuiteCompleted) + mux.HandleFunc("/before-suite-state", server.handleBeforeSuiteState) + mux.HandleFunc("/have-nonprimary-procs-finished", server.handleHaveNonprimaryProcsFinished) + mux.HandleFunc("/aggregated-nonprimary-procs-report", server.handleAggregatedNonprimaryProcsReport) + mux.HandleFunc("/counter", server.handleCounter) + mux.HandleFunc("/up", server.handleUp) + mux.HandleFunc("/abort", server.handleAbort) + + go httpServer.Serve(server.listener) +} + +//Stop the server +func (server *httpServer) Close() { + server.listener.Close() +} + +//The address the server can be reached it. Pass this into the `ForwardingReporter`. +func (server *httpServer) Address() string { + return "http://" + server.listener.Addr().String() +} + +func (server *httpServer) GetSuiteDone() chan interface{} { + return server.handler.done +} + +func (server *httpServer) GetOutputDestination() io.Writer { + return server.handler.outputDestination +} + +func (server *httpServer) SetOutputDestination(w io.Writer) { + server.handler.outputDestination = w +} + +func (server *httpServer) RegisterAlive(node int, alive func() bool) { + server.handler.registerAlive(node, alive) +} + +// +// Streaming Endpoints +// + +//The server will forward all received messages to Ginkgo reporters registered with `RegisterReporters` +func (server *httpServer) decode(writer http.ResponseWriter, request *http.Request, object interface{}) bool { + defer request.Body.Close() + if json.NewDecoder(request.Body).Decode(object) != nil { + writer.WriteHeader(http.StatusBadRequest) + return false + } + return true +} + +func (server *httpServer) handleError(err error, writer http.ResponseWriter) bool { + if err == nil { + return false + } + switch err { + case ErrorEarly: + writer.WriteHeader(http.StatusTooEarly) + case ErrorGone: + writer.WriteHeader(http.StatusGone) + case ErrorFailed: + writer.WriteHeader(http.StatusFailedDependency) + default: + writer.WriteHeader(http.StatusInternalServerError) + } + return true +} + +func (server *httpServer) specSuiteWillBegin(writer http.ResponseWriter, request *http.Request) { + var report types.Report + if !server.decode(writer, request, &report) { + return + } + + server.handleError(server.handler.SpecSuiteWillBegin(report, voidReceiver), writer) +} + +func (server *httpServer) didRun(writer http.ResponseWriter, request *http.Request) { + var report types.SpecReport + if !server.decode(writer, request, &report) { + return + } + + server.handleError(server.handler.DidRun(report, voidReceiver), writer) +} + +func (server *httpServer) specSuiteDidEnd(writer http.ResponseWriter, request *http.Request) { + var report types.Report + if !server.decode(writer, request, &report) { + return + } + server.handleError(server.handler.SpecSuiteDidEnd(report, voidReceiver), writer) +} + +func (server *httpServer) emitOutput(writer http.ResponseWriter, request *http.Request) { + output, err := io.ReadAll(request.Body) + if err != nil { + writer.WriteHeader(http.StatusInternalServerError) + return + } + var n int + server.handleError(server.handler.EmitOutput(output, &n), writer) +} + +func (server *httpServer) emitProgressReport(writer http.ResponseWriter, request *http.Request) { + var report types.ProgressReport + if !server.decode(writer, request, &report) { + return + } + server.handleError(server.handler.EmitProgressReport(report, voidReceiver), writer) +} + +func (server *httpServer) handleBeforeSuiteCompleted(writer http.ResponseWriter, request *http.Request) { + var beforeSuiteState BeforeSuiteState + if !server.decode(writer, request, &beforeSuiteState) { + return + } + + server.handleError(server.handler.BeforeSuiteCompleted(beforeSuiteState, voidReceiver), writer) +} + +func (server *httpServer) handleBeforeSuiteState(writer http.ResponseWriter, request *http.Request) { + var beforeSuiteState BeforeSuiteState + if server.handleError(server.handler.BeforeSuiteState(voidSender, &beforeSuiteState), writer) { + return + } + json.NewEncoder(writer).Encode(beforeSuiteState) +} + +func (server *httpServer) handleHaveNonprimaryProcsFinished(writer http.ResponseWriter, request *http.Request) { + if server.handleError(server.handler.HaveNonprimaryProcsFinished(voidSender, voidReceiver), writer) { + return + } + writer.WriteHeader(http.StatusOK) +} + +func (server *httpServer) handleAggregatedNonprimaryProcsReport(writer http.ResponseWriter, request *http.Request) { + var aggregatedReport types.Report + if server.handleError(server.handler.AggregatedNonprimaryProcsReport(voidSender, &aggregatedReport), writer) { + return + } + json.NewEncoder(writer).Encode(aggregatedReport) +} + +func (server *httpServer) handleCounter(writer http.ResponseWriter, request *http.Request) { + var n int + if server.handleError(server.handler.Counter(voidSender, &n), writer) { + return + } + json.NewEncoder(writer).Encode(ParallelIndexCounter{Index: n}) +} + +func (server *httpServer) handleUp(writer http.ResponseWriter, request *http.Request) { + writer.WriteHeader(http.StatusOK) +} + +func (server *httpServer) handleAbort(writer http.ResponseWriter, request *http.Request) { + if request.Method == "GET" { + var shouldAbort bool + server.handler.ShouldAbort(voidSender, &shouldAbort) + if shouldAbort { + writer.WriteHeader(http.StatusGone) + } else { + writer.WriteHeader(http.StatusOK) + } + } else { + server.handler.Abort(voidSender, voidReceiver) + } +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/rpc_client.go b/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/rpc_client.go new file mode 100644 index 00000000000..fe93cc2b9a8 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/rpc_client.go @@ -0,0 +1,123 @@ +package parallel_support + +import ( + "net/rpc" + "time" + + "github.com/onsi/ginkgo/v2/types" +) + +type rpcClient struct { + serverHost string + client *rpc.Client +} + +func newRPCClient(serverHost string) *rpcClient { + return &rpcClient{ + serverHost: serverHost, + } +} + +func (client *rpcClient) Connect() bool { + var err error + if client.client != nil { + return true + } + client.client, err = rpc.DialHTTPPath("tcp", client.serverHost, "/") + if err != nil { + client.client = nil + return false + } + return true +} + +func (client *rpcClient) Close() error { + return client.client.Close() +} + +func (client *rpcClient) poll(method string, data interface{}) error { + for { + err := client.client.Call(method, voidSender, data) + if err == nil { + return nil + } + switch err.Error() { + case ErrorEarly.Error(): + time.Sleep(POLLING_INTERVAL) + case ErrorGone.Error(): + return ErrorGone + case ErrorFailed.Error(): + return ErrorFailed + default: + return err + } + } +} + +func (client *rpcClient) PostSuiteWillBegin(report types.Report) error { + return client.client.Call("Server.SpecSuiteWillBegin", report, voidReceiver) +} + +func (client *rpcClient) PostDidRun(report types.SpecReport) error { + return client.client.Call("Server.DidRun", report, voidReceiver) +} + +func (client *rpcClient) PostSuiteDidEnd(report types.Report) error { + return client.client.Call("Server.SpecSuiteDidEnd", report, voidReceiver) +} + +func (client *rpcClient) Write(p []byte) (int, error) { + var n int + err := client.client.Call("Server.EmitOutput", p, &n) + return n, err +} + +func (client *rpcClient) PostEmitProgressReport(report types.ProgressReport) error { + return client.client.Call("Server.EmitProgressReport", report, voidReceiver) +} + +func (client *rpcClient) PostSynchronizedBeforeSuiteCompleted(state types.SpecState, data []byte) error { + beforeSuiteState := BeforeSuiteState{ + State: state, + Data: data, + } + return client.client.Call("Server.BeforeSuiteCompleted", beforeSuiteState, voidReceiver) +} + +func (client *rpcClient) BlockUntilSynchronizedBeforeSuiteData() (types.SpecState, []byte, error) { + var beforeSuiteState BeforeSuiteState + err := client.poll("Server.BeforeSuiteState", &beforeSuiteState) + if err == ErrorGone { + return types.SpecStateInvalid, nil, types.GinkgoErrors.SynchronizedBeforeSuiteDisappearedOnProc1() + } + return beforeSuiteState.State, beforeSuiteState.Data, err +} + +func (client *rpcClient) BlockUntilNonprimaryProcsHaveFinished() error { + return client.poll("Server.HaveNonprimaryProcsFinished", voidReceiver) +} + +func (client *rpcClient) BlockUntilAggregatedNonprimaryProcsReport() (types.Report, error) { + var report types.Report + err := client.poll("Server.AggregatedNonprimaryProcsReport", &report) + if err == ErrorGone { + return types.Report{}, types.GinkgoErrors.AggregatedReportUnavailableDueToNodeDisappearing() + } + return report, err +} + +func (client *rpcClient) FetchNextCounter() (int, error) { + var counter int + err := client.client.Call("Server.Counter", voidSender, &counter) + return counter, err +} + +func (client *rpcClient) PostAbort() error { + return client.client.Call("Server.Abort", voidSender, voidReceiver) +} + +func (client *rpcClient) ShouldAbort() bool { + var shouldAbort bool + client.client.Call("Server.ShouldAbort", voidSender, &shouldAbort) + return shouldAbort +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/rpc_server.go b/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/rpc_server.go new file mode 100644 index 00000000000..2620fd562d3 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/rpc_server.go @@ -0,0 +1,75 @@ +/* + +The remote package provides the pieces to allow Ginkgo test suites to report to remote listeners. +This is used, primarily, to enable streaming parallel test output but has, in principal, broader applications (e.g. streaming test output to a browser). + +*/ + +package parallel_support + +import ( + "io" + "net" + "net/http" + "net/rpc" + + "github.com/onsi/ginkgo/v2/reporters" +) + +/* +RPCServer spins up on an automatically selected port and listens for communication from the forwarding reporter. +It then forwards that communication to attached reporters. +*/ +type RPCServer struct { + listener net.Listener + handler *ServerHandler +} + +//Create a new server, automatically selecting a port +func newRPCServer(parallelTotal int, reporter reporters.Reporter) (*RPCServer, error) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return nil, err + } + return &RPCServer{ + listener: listener, + handler: newServerHandler(parallelTotal, reporter), + }, nil +} + +//Start the server. You don't need to `go s.Start()`, just `s.Start()` +func (server *RPCServer) Start() { + rpcServer := rpc.NewServer() + rpcServer.RegisterName("Server", server.handler) //register the handler's methods as the server + + httpServer := &http.Server{} + httpServer.Handler = rpcServer + + go httpServer.Serve(server.listener) +} + +//Stop the server +func (server *RPCServer) Close() { + server.listener.Close() +} + +//The address the server can be reached it. Pass this into the `ForwardingReporter`. +func (server *RPCServer) Address() string { + return server.listener.Addr().String() +} + +func (server *RPCServer) GetSuiteDone() chan interface{} { + return server.handler.done +} + +func (server *RPCServer) GetOutputDestination() io.Writer { + return server.handler.outputDestination +} + +func (server *RPCServer) SetOutputDestination(w io.Writer) { + server.handler.outputDestination = w +} + +func (server *RPCServer) RegisterAlive(node int, alive func() bool) { + server.handler.registerAlive(node, alive) +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/server_handler.go b/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/server_handler.go new file mode 100644 index 00000000000..7c6e67b9601 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/server_handler.go @@ -0,0 +1,209 @@ +package parallel_support + +import ( + "io" + "os" + "sync" + + "github.com/onsi/ginkgo/v2/reporters" + "github.com/onsi/ginkgo/v2/types" +) + +type Void struct{} + +var voidReceiver *Void = &Void{} +var voidSender Void + +// ServerHandler is an RPC-compatible handler that is shared between the http server and the rpc server. +// It handles all the business logic to avoid duplication between the two servers + +type ServerHandler struct { + done chan interface{} + outputDestination io.Writer + reporter reporters.Reporter + alives []func() bool + lock *sync.Mutex + beforeSuiteState BeforeSuiteState + parallelTotal int + counter int + counterLock *sync.Mutex + shouldAbort bool + + numSuiteDidBegins int + numSuiteDidEnds int + aggregatedReport types.Report + reportHoldingArea []types.SpecReport +} + +func newServerHandler(parallelTotal int, reporter reporters.Reporter) *ServerHandler { + return &ServerHandler{ + reporter: reporter, + lock: &sync.Mutex{}, + counterLock: &sync.Mutex{}, + alives: make([]func() bool, parallelTotal), + beforeSuiteState: BeforeSuiteState{Data: nil, State: types.SpecStateInvalid}, + parallelTotal: parallelTotal, + outputDestination: os.Stdout, + done: make(chan interface{}), + } +} + +func (handler *ServerHandler) SpecSuiteWillBegin(report types.Report, _ *Void) error { + handler.lock.Lock() + defer handler.lock.Unlock() + + handler.numSuiteDidBegins += 1 + + // all summaries are identical, so it's fine to simply emit the last one of these + if handler.numSuiteDidBegins == handler.parallelTotal { + handler.reporter.SuiteWillBegin(report) + + for _, summary := range handler.reportHoldingArea { + handler.reporter.WillRun(summary) + handler.reporter.DidRun(summary) + } + + handler.reportHoldingArea = nil + } + + return nil +} + +func (handler *ServerHandler) DidRun(report types.SpecReport, _ *Void) error { + handler.lock.Lock() + defer handler.lock.Unlock() + + if handler.numSuiteDidBegins == handler.parallelTotal { + handler.reporter.WillRun(report) + handler.reporter.DidRun(report) + } else { + handler.reportHoldingArea = append(handler.reportHoldingArea, report) + } + + return nil +} + +func (handler *ServerHandler) SpecSuiteDidEnd(report types.Report, _ *Void) error { + handler.lock.Lock() + defer handler.lock.Unlock() + + handler.numSuiteDidEnds += 1 + if handler.numSuiteDidEnds == 1 { + handler.aggregatedReport = report + } else { + handler.aggregatedReport = handler.aggregatedReport.Add(report) + } + + if handler.numSuiteDidEnds == handler.parallelTotal { + handler.reporter.SuiteDidEnd(handler.aggregatedReport) + close(handler.done) + } + + return nil +} + +func (handler *ServerHandler) EmitOutput(output []byte, n *int) error { + var err error + *n, err = handler.outputDestination.Write(output) + return err +} + +func (handler *ServerHandler) EmitProgressReport(report types.ProgressReport, _ *Void) error { + handler.lock.Lock() + defer handler.lock.Unlock() + handler.reporter.EmitProgressReport(report) + return nil +} + +func (handler *ServerHandler) registerAlive(proc int, alive func() bool) { + handler.lock.Lock() + defer handler.lock.Unlock() + handler.alives[proc-1] = alive +} + +func (handler *ServerHandler) procIsAlive(proc int) bool { + handler.lock.Lock() + defer handler.lock.Unlock() + alive := handler.alives[proc-1] + if alive == nil { + return true + } + return alive() +} + +func (handler *ServerHandler) haveNonprimaryProcsFinished() bool { + for i := 2; i <= handler.parallelTotal; i++ { + if handler.procIsAlive(i) { + return false + } + } + return true +} + +func (handler *ServerHandler) BeforeSuiteCompleted(beforeSuiteState BeforeSuiteState, _ *Void) error { + handler.lock.Lock() + defer handler.lock.Unlock() + handler.beforeSuiteState = beforeSuiteState + + return nil +} + +func (handler *ServerHandler) BeforeSuiteState(_ Void, beforeSuiteState *BeforeSuiteState) error { + proc1IsAlive := handler.procIsAlive(1) + handler.lock.Lock() + defer handler.lock.Unlock() + if handler.beforeSuiteState.State == types.SpecStateInvalid { + if proc1IsAlive { + return ErrorEarly + } else { + return ErrorGone + } + } + *beforeSuiteState = handler.beforeSuiteState + return nil +} + +func (handler *ServerHandler) HaveNonprimaryProcsFinished(_ Void, _ *Void) error { + if handler.haveNonprimaryProcsFinished() { + return nil + } else { + return ErrorEarly + } +} + +func (handler *ServerHandler) AggregatedNonprimaryProcsReport(_ Void, report *types.Report) error { + if handler.haveNonprimaryProcsFinished() { + handler.lock.Lock() + defer handler.lock.Unlock() + if handler.numSuiteDidEnds == handler.parallelTotal-1 { + *report = handler.aggregatedReport + return nil + } else { + return ErrorGone + } + } else { + return ErrorEarly + } +} + +func (handler *ServerHandler) Counter(_ Void, counter *int) error { + handler.counterLock.Lock() + defer handler.counterLock.Unlock() + *counter = handler.counter + handler.counter++ + return nil +} + +func (handler *ServerHandler) Abort(_ Void, _ *Void) error { + handler.lock.Lock() + defer handler.lock.Unlock() + handler.shouldAbort = true + return nil +} + +func (handler *ServerHandler) ShouldAbort(_ Void, shouldAbort *bool) error { + handler.lock.Lock() + defer handler.lock.Unlock() + *shouldAbort = handler.shouldAbort + return nil +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/progress_report.go b/vendor/github.com/onsi/ginkgo/v2/internal/progress_report.go new file mode 100644 index 00000000000..7dd3ca963e3 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/progress_report.go @@ -0,0 +1,288 @@ +package internal + +import ( + "bufio" + "bytes" + "context" + "fmt" + "io" + "os" + "os/signal" + "path/filepath" + "runtime" + "strconv" + "strings" + "time" + + "github.com/onsi/ginkgo/v2/types" +) + +var _SOURCE_CACHE = map[string][]string{} + +type ProgressSignalRegistrar func(func()) context.CancelFunc + +func RegisterForProgressSignal(handler func()) context.CancelFunc { + signalChannel := make(chan os.Signal, 1) + if len(PROGRESS_SIGNALS) > 0 { + signal.Notify(signalChannel, PROGRESS_SIGNALS...) + } + ctx, cancel := context.WithCancel(context.Background()) + go func() { + for { + select { + case <-signalChannel: + handler() + case <-ctx.Done(): + signal.Stop(signalChannel) + return + } + } + }() + + return cancel +} + +type ProgressStepCursor struct { + Text string + CodeLocation types.CodeLocation + StartTime time.Time +} + +func NewProgressReport(isRunningInParallel bool, report types.SpecReport, currentNode Node, currentNodeStartTime time.Time, currentStep types.SpecEvent, gwOutput string, timelineLocation types.TimelineLocation, additionalReports []string, sourceRoots []string, includeAll bool) (types.ProgressReport, error) { + pr := types.ProgressReport{ + ParallelProcess: report.ParallelProcess, + RunningInParallel: isRunningInParallel, + ContainerHierarchyTexts: report.ContainerHierarchyTexts, + LeafNodeText: report.LeafNodeText, + LeafNodeLocation: report.LeafNodeLocation, + SpecStartTime: report.StartTime, + + CurrentNodeType: currentNode.NodeType, + CurrentNodeText: currentNode.Text, + CurrentNodeLocation: currentNode.CodeLocation, + CurrentNodeStartTime: currentNodeStartTime, + + CurrentStepText: currentStep.Message, + CurrentStepLocation: currentStep.CodeLocation, + CurrentStepStartTime: currentStep.TimelineLocation.Time, + + AdditionalReports: additionalReports, + + CapturedGinkgoWriterOutput: gwOutput, + TimelineLocation: timelineLocation, + } + + goroutines, err := extractRunningGoroutines() + if err != nil { + return pr, err + } + pr.Goroutines = goroutines + + // now we want to try to find goroutines of interest. these will be goroutines that have any function calls with code in packagesOfInterest: + packagesOfInterest := map[string]bool{} + packageFromFilename := func(filename string) string { + return filepath.Dir(filename) + } + addPackageFor := func(filename string) { + if filename != "" { + packagesOfInterest[packageFromFilename(filename)] = true + } + } + isPackageOfInterest := func(filename string) bool { + stackPackage := packageFromFilename(filename) + for packageOfInterest := range packagesOfInterest { + if strings.HasPrefix(stackPackage, packageOfInterest) { + return true + } + } + return false + } + for _, location := range report.ContainerHierarchyLocations { + addPackageFor(location.FileName) + } + addPackageFor(report.LeafNodeLocation.FileName) + addPackageFor(currentNode.CodeLocation.FileName) + addPackageFor(currentStep.CodeLocation.FileName) + + //First, we find the SpecGoroutine - this will be the goroutine that includes `runNode` + specGoRoutineIdx := -1 + runNodeFunctionCallIdx := -1 +OUTER: + for goroutineIdx, goroutine := range pr.Goroutines { + for functionCallIdx, functionCall := range goroutine.Stack { + if strings.Contains(functionCall.Function, "ginkgo/v2/internal.(*Suite).runNode.func") { + specGoRoutineIdx = goroutineIdx + runNodeFunctionCallIdx = functionCallIdx + break OUTER + } + } + } + + //Now, we find the first non-Ginkgo function call + if specGoRoutineIdx > -1 { + for runNodeFunctionCallIdx >= 0 { + fn := goroutines[specGoRoutineIdx].Stack[runNodeFunctionCallIdx].Function + file := goroutines[specGoRoutineIdx].Stack[runNodeFunctionCallIdx].Filename + // these are all things that could potentially happen from within ginkgo + if strings.Contains(fn, "ginkgo/v2/internal") || strings.Contains(fn, "reflect.Value") || strings.Contains(file, "ginkgo/table_dsl") || strings.Contains(file, "ginkgo/core_dsl") { + runNodeFunctionCallIdx-- + continue + } + if strings.Contains(goroutines[specGoRoutineIdx].Stack[runNodeFunctionCallIdx].Function, "ginkgo/table_dsl") { + + } + //found it! lets add its package of interest + addPackageFor(goroutines[specGoRoutineIdx].Stack[runNodeFunctionCallIdx].Filename) + break + } + } + + ginkgoEntryPointIdx := -1 +OUTER_GINKGO_ENTRY_POINT: + for goroutineIdx, goroutine := range pr.Goroutines { + for _, functionCall := range goroutine.Stack { + if strings.Contains(functionCall.Function, "ginkgo/v2.RunSpecs") { + ginkgoEntryPointIdx = goroutineIdx + break OUTER_GINKGO_ENTRY_POINT + } + } + } + + // Now we go through all goroutines and highlight any lines with packages in `packagesOfInterest` + // Any goroutines with highlighted lines end up in the HighlightGoRoutines + for goroutineIdx, goroutine := range pr.Goroutines { + if goroutineIdx == ginkgoEntryPointIdx { + continue + } + if goroutineIdx == specGoRoutineIdx { + pr.Goroutines[goroutineIdx].IsSpecGoroutine = true + } + for functionCallIdx, functionCall := range goroutine.Stack { + if isPackageOfInterest(functionCall.Filename) { + goroutine.Stack[functionCallIdx].Highlight = true + goroutine.Stack[functionCallIdx].Source, goroutine.Stack[functionCallIdx].SourceHighlight = fetchSource(functionCall.Filename, functionCall.Line, 2, sourceRoots) + } + } + } + + if !includeAll { + goroutines := []types.Goroutine{pr.SpecGoroutine()} + goroutines = append(goroutines, pr.HighlightedGoroutines()...) + pr.Goroutines = goroutines + } + + return pr, nil +} + +func extractRunningGoroutines() ([]types.Goroutine, error) { + var stack []byte + for size := 64 * 1024; ; size *= 2 { + stack = make([]byte, size) + if n := runtime.Stack(stack, true); n < size { + stack = stack[:n] + break + } + } + + r := bufio.NewReader(bytes.NewReader(stack)) + out := []types.Goroutine{} + idx := -1 + for { + line, err := r.ReadString('\n') + if err == io.EOF { + break + } + + line = strings.TrimSuffix(line, "\n") + + //skip blank lines + if line == "" { + continue + } + + //parse headers for new goroutine frames + if strings.HasPrefix(line, "goroutine") { + out = append(out, types.Goroutine{}) + idx = len(out) - 1 + + line = strings.TrimPrefix(line, "goroutine ") + line = strings.TrimSuffix(line, ":") + fields := strings.SplitN(line, " ", 2) + if len(fields) != 2 { + return nil, types.GinkgoErrors.FailedToParseStackTrace(fmt.Sprintf("Invalid goroutine frame header: %s", line)) + } + out[idx].ID, err = strconv.ParseUint(fields[0], 10, 64) + if err != nil { + return nil, types.GinkgoErrors.FailedToParseStackTrace(fmt.Sprintf("Invalid goroutine ID: %s", fields[1])) + } + + out[idx].State = strings.TrimSuffix(strings.TrimPrefix(fields[1], "["), "]") + continue + } + + //if we are here we must be at a function call entry in the stack + functionCall := types.FunctionCall{ + Function: strings.TrimPrefix(line, "created by "), // no need to track 'created by' + } + + line, err = r.ReadString('\n') + line = strings.TrimSuffix(line, "\n") + if err == io.EOF { + return nil, types.GinkgoErrors.FailedToParseStackTrace(fmt.Sprintf("Invalid function call: %s -- missing file name and line number", functionCall.Function)) + } + line = strings.TrimLeft(line, " \t") + fields := strings.SplitN(line, ":", 2) + if len(fields) != 2 { + return nil, types.GinkgoErrors.FailedToParseStackTrace(fmt.Sprintf("Invalid filename nad line number: %s", line)) + } + functionCall.Filename = fields[0] + line = strings.Split(fields[1], " ")[0] + lineNumber, err := strconv.ParseInt(line, 10, 64) + functionCall.Line = int(lineNumber) + if err != nil { + return nil, types.GinkgoErrors.FailedToParseStackTrace(fmt.Sprintf("Invalid function call line number: %s\n%s", line, err.Error())) + } + out[idx].Stack = append(out[idx].Stack, functionCall) + } + + return out, nil +} + +func fetchSource(filename string, lineNumber int, span int, configuredSourceRoots []string) ([]string, int) { + if filename == "" { + return []string{}, 0 + } + + var lines []string + var ok bool + if lines, ok = _SOURCE_CACHE[filename]; !ok { + sourceRoots := []string{""} + sourceRoots = append(sourceRoots, configuredSourceRoots...) + var data []byte + var err error + var found bool + for _, root := range sourceRoots { + data, err = os.ReadFile(filepath.Join(root, filename)) + if err == nil { + found = true + break + } + } + if !found { + return []string{}, 0 + } + lines = strings.Split(string(data), "\n") + _SOURCE_CACHE[filename] = lines + } + + startIndex := lineNumber - span - 1 + endIndex := startIndex + span + span + 1 + if startIndex < 0 { + startIndex = 0 + } + if endIndex > len(lines) { + endIndex = len(lines) + } + highlightIndex := lineNumber - 1 - startIndex + return lines[startIndex:endIndex], highlightIndex +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/progress_report_bsd.go b/vendor/github.com/onsi/ginkgo/v2/internal/progress_report_bsd.go new file mode 100644 index 00000000000..61e0ed30667 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/progress_report_bsd.go @@ -0,0 +1,11 @@ +//go:build freebsd || openbsd || netbsd || darwin || dragonfly +// +build freebsd openbsd netbsd darwin dragonfly + +package internal + +import ( + "os" + "syscall" +) + +var PROGRESS_SIGNALS = []os.Signal{syscall.SIGINFO, syscall.SIGUSR1} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/progress_report_unix.go b/vendor/github.com/onsi/ginkgo/v2/internal/progress_report_unix.go new file mode 100644 index 00000000000..ad30de459da --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/progress_report_unix.go @@ -0,0 +1,11 @@ +//go:build linux || solaris +// +build linux solaris + +package internal + +import ( + "os" + "syscall" +) + +var PROGRESS_SIGNALS = []os.Signal{syscall.SIGUSR1} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/progress_report_win.go b/vendor/github.com/onsi/ginkgo/v2/internal/progress_report_win.go new file mode 100644 index 00000000000..0eca2516adb --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/progress_report_win.go @@ -0,0 +1,8 @@ +//go:build windows +// +build windows + +package internal + +import "os" + +var PROGRESS_SIGNALS = []os.Signal{} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/report_entry.go b/vendor/github.com/onsi/ginkgo/v2/internal/report_entry.go new file mode 100644 index 00000000000..cc351a39bd6 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/report_entry.go @@ -0,0 +1,39 @@ +package internal + +import ( + "time" + + "github.com/onsi/ginkgo/v2/types" +) + +type ReportEntry = types.ReportEntry + +func NewReportEntry(name string, cl types.CodeLocation, args ...interface{}) (ReportEntry, error) { + out := ReportEntry{ + Visibility: types.ReportEntryVisibilityAlways, + Name: name, + Location: cl, + Time: time.Now(), + } + var didSetValue = false + for _, arg := range args { + switch x := arg.(type) { + case types.ReportEntryVisibility: + out.Visibility = x + case types.CodeLocation: + out.Location = x + case Offset: + out.Location = types.NewCodeLocation(2 + int(x)) + case time.Time: + out.Time = x + default: + if didSetValue { + return ReportEntry{}, types.GinkgoErrors.TooManyReportEntryValues(out.Location, arg) + } + out.Value = types.WrapEntryValue(arg) + didSetValue = true + } + } + + return out, nil +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/spec.go b/vendor/github.com/onsi/ginkgo/v2/internal/spec.go new file mode 100644 index 00000000000..7c4ee5bb7f1 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/spec.go @@ -0,0 +1,87 @@ +package internal + +import ( + "strings" + "time" + + "github.com/onsi/ginkgo/v2/types" +) + +type Spec struct { + Nodes Nodes + Skip bool +} + +func (s Spec) SubjectID() uint { + return s.Nodes.FirstNodeWithType(types.NodeTypeIt).ID +} + +func (s Spec) Text() string { + texts := []string{} + for i := range s.Nodes { + if s.Nodes[i].Text != "" { + texts = append(texts, s.Nodes[i].Text) + } + } + return strings.Join(texts, " ") +} + +func (s Spec) FirstNodeWithType(nodeTypes types.NodeType) Node { + return s.Nodes.FirstNodeWithType(nodeTypes) +} + +func (s Spec) FlakeAttempts() int { + flakeAttempts := 0 + for i := range s.Nodes { + if s.Nodes[i].FlakeAttempts > 0 { + flakeAttempts = s.Nodes[i].FlakeAttempts + } + } + + return flakeAttempts +} + +func (s Spec) MustPassRepeatedly() int { + mustPassRepeatedly := 0 + for i := range s.Nodes { + if s.Nodes[i].MustPassRepeatedly > 0 { + mustPassRepeatedly = s.Nodes[i].MustPassRepeatedly + } + } + + return mustPassRepeatedly +} + +func (s Spec) SpecTimeout() time.Duration { + return s.FirstNodeWithType(types.NodeTypeIt).SpecTimeout +} + +type Specs []Spec + +func (s Specs) HasAnySpecsMarkedPending() bool { + for i := range s { + if s[i].Nodes.HasNodeMarkedPending() { + return true + } + } + + return false +} + +func (s Specs) CountWithoutSkip() int { + n := 0 + for i := range s { + if !s[i].Skip { + n += 1 + } + } + return n +} + +func (s Specs) AtIndices(indices SpecIndices) Specs { + out := make(Specs, len(indices)) + for i, idx := range indices { + out[i] = s[idx] + } + return out +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/spec_context.go b/vendor/github.com/onsi/ginkgo/v2/internal/spec_context.go new file mode 100644 index 00000000000..8f569dd3597 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/spec_context.go @@ -0,0 +1,90 @@ +package internal + +import ( + "context" + "sort" + "sync" + + "github.com/onsi/ginkgo/v2/types" +) + +type SpecContext interface { + context.Context + + SpecReport() types.SpecReport + AttachProgressReporter(func() string) func() +} + +type specContext struct { + context.Context + + cancel context.CancelFunc + lock *sync.Mutex + progressReporters map[int]func() string + prCounter int + + suite *Suite +} + +/* +SpecContext includes a reference to `suite` and embeds itself in itself as a "GINKGO_SPEC_CONTEXT" value. This allows users to create child Contexts without having down-stream consumers (e.g. Gomega) lose access to the SpecContext and its methods. This allows us to build extensions on top of Ginkgo that simply take an all-encompassing context. + +Note that while SpecContext is used to enforce deadlines by Ginkgo it is not configured as a context.WithDeadline. Instead, Ginkgo owns responsibility for cancelling the context when the deadline elapses. + +This is because Ginkgo needs finer control over when the context is canceled. Specifically, Ginkgo needs to generate a ProgressReport before it cancels the context to ensure progress is captured where the spec is currently running. The only way to avoid a race here is to manually control the cancellation. +*/ +func NewSpecContext(suite *Suite) *specContext { + ctx, cancel := context.WithCancel(context.Background()) + sc := &specContext{ + cancel: cancel, + suite: suite, + lock: &sync.Mutex{}, + prCounter: 0, + progressReporters: map[int]func() string{}, + } + ctx = context.WithValue(ctx, "GINKGO_SPEC_CONTEXT", sc) //yes, yes, the go docs say don't use a string for a key... but we'd rather avoid a circular dependency between Gomega and Ginkgo + sc.Context = ctx //thank goodness for garbage collectors that can handle circular dependencies + + return sc +} + +func (sc *specContext) SpecReport() types.SpecReport { + return sc.suite.CurrentSpecReport() +} + +func (sc *specContext) AttachProgressReporter(reporter func() string) func() { + sc.lock.Lock() + defer sc.lock.Unlock() + sc.prCounter += 1 + prCounter := sc.prCounter + sc.progressReporters[prCounter] = reporter + + return func() { + sc.lock.Lock() + defer sc.lock.Unlock() + delete(sc.progressReporters, prCounter) + } +} + +func (sc *specContext) QueryProgressReporters() []string { + sc.lock.Lock() + keys := []int{} + for key := range sc.progressReporters { + keys = append(keys, key) + } + sort.Ints(keys) + reporters := []func() string{} + for _, key := range keys { + reporters = append(reporters, sc.progressReporters[key]) + } + sc.lock.Unlock() + + if len(reporters) == 0 { + return nil + } + out := []string{} + for _, reporter := range reporters { + out = append(out, reporter()) + } + return out +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/suite.go b/vendor/github.com/onsi/ginkgo/v2/internal/suite.go new file mode 100644 index 00000000000..d3671ebcfb0 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/suite.go @@ -0,0 +1,966 @@ +package internal + +import ( + "fmt" + "sync" + "time" + + "github.com/onsi/ginkgo/v2/internal/interrupt_handler" + "github.com/onsi/ginkgo/v2/internal/parallel_support" + "github.com/onsi/ginkgo/v2/reporters" + "github.com/onsi/ginkgo/v2/types" +) + +type Phase uint + +const ( + PhaseBuildTopLevel Phase = iota + PhaseBuildTree + PhaseRun +) + +type Suite struct { + tree *TreeNode + topLevelContainers Nodes + + phase Phase + + suiteNodes Nodes + cleanupNodes Nodes + + failer *Failer + reporter reporters.Reporter + writer WriterInterface + outputInterceptor OutputInterceptor + interruptHandler interrupt_handler.InterruptHandlerInterface + config types.SuiteConfig + deadline time.Time + + skipAll bool + report types.Report + currentSpecReport types.SpecReport + currentNode Node + currentNodeStartTime time.Time + + currentSpecContext *specContext + + currentByStep types.SpecEvent + timelineOrder int + + /* + We don't need to lock around all operations. Just those that *could* happen concurrently. + + Suite, generally, only runs one node at a time - and so the possibiity for races is small. In fact, the presence of a race usually indicates the user has launched a goroutine that has leaked past the node it was launched in. + + However, there are some operations that can happen concurrently: + + - AddReportEntry and CurrentSpecReport can be accessed at any point by the user - including in goroutines that outlive the node intentionally (see, e.g. #1020). They both form a self-contained read-write pair and so a lock in them is sufficent. + - generateProgressReport can be invoked at any point in time by an interrupt or a progres poll. Moreover, it requires access to currentSpecReport, currentNode, currentNodeStartTime, and progressStepCursor. To make it threadsafe we need to lock around generateProgressReport when we read those variables _and_ everywhere those variables are *written*. In general we don't need to worry about all possible field writes to these variables as what `generateProgressReport` does with these variables is fairly selective (hence the name of the lock). Specifically, we dont' need to lock around state and failure message changes on `currentSpecReport` - just the setting of the variable itself. + */ + selectiveLock *sync.Mutex + + client parallel_support.Client +} + +func NewSuite() *Suite { + return &Suite{ + tree: &TreeNode{}, + phase: PhaseBuildTopLevel, + + selectiveLock: &sync.Mutex{}, + } +} + +func (suite *Suite) BuildTree() error { + // During PhaseBuildTopLevel, the top level containers are stored in suite.topLevelCotainers and entered + // We now enter PhaseBuildTree where these top level containers are entered and added to the spec tree + suite.phase = PhaseBuildTree + for _, topLevelContainer := range suite.topLevelContainers { + err := suite.PushNode(topLevelContainer) + if err != nil { + return err + } + } + return nil +} + +func (suite *Suite) Run(description string, suiteLabels Labels, suitePath string, failer *Failer, reporter reporters.Reporter, writer WriterInterface, outputInterceptor OutputInterceptor, interruptHandler interrupt_handler.InterruptHandlerInterface, client parallel_support.Client, progressSignalRegistrar ProgressSignalRegistrar, suiteConfig types.SuiteConfig) (bool, bool) { + if suite.phase != PhaseBuildTree { + panic("cannot run before building the tree = call suite.BuildTree() first") + } + ApplyNestedFocusPolicyToTree(suite.tree) + specs := GenerateSpecsFromTreeRoot(suite.tree) + specs, hasProgrammaticFocus := ApplyFocusToSpecs(specs, description, suiteLabels, suiteConfig) + + suite.phase = PhaseRun + suite.client = client + suite.failer = failer + suite.reporter = reporter + suite.writer = writer + suite.outputInterceptor = outputInterceptor + suite.interruptHandler = interruptHandler + suite.config = suiteConfig + + if suite.config.Timeout > 0 { + suite.deadline = time.Now().Add(suite.config.Timeout) + } + + cancelProgressHandler := progressSignalRegistrar(suite.handleProgressSignal) + + success := suite.runSpecs(description, suiteLabels, suitePath, hasProgrammaticFocus, specs) + + cancelProgressHandler() + + return success, hasProgrammaticFocus +} + +func (suite *Suite) InRunPhase() bool { + return suite.phase == PhaseRun +} + +/* + Tree Construction methods + + PushNode is used during PhaseBuildTopLevel and PhaseBuildTree +*/ + +func (suite *Suite) PushNode(node Node) error { + if node.NodeType.Is(types.NodeTypeCleanupInvalid | types.NodeTypeCleanupAfterEach | types.NodeTypeCleanupAfterAll | types.NodeTypeCleanupAfterSuite) { + return suite.pushCleanupNode(node) + } + + if node.NodeType.Is(types.NodeTypeBeforeSuite | types.NodeTypeAfterSuite | types.NodeTypeSynchronizedBeforeSuite | types.NodeTypeSynchronizedAfterSuite | types.NodeTypeReportAfterSuite) { + return suite.pushSuiteNode(node) + } + + if suite.phase == PhaseRun { + return types.GinkgoErrors.PushingNodeInRunPhase(node.NodeType, node.CodeLocation) + } + + if node.MarkedSerial { + firstOrderedNode := suite.tree.AncestorNodeChain().FirstNodeMarkedOrdered() + if !firstOrderedNode.IsZero() && !firstOrderedNode.MarkedSerial { + return types.GinkgoErrors.InvalidSerialNodeInNonSerialOrderedContainer(node.CodeLocation, node.NodeType) + } + } + + if node.NodeType.Is(types.NodeTypeBeforeAll | types.NodeTypeAfterAll) { + firstOrderedNode := suite.tree.AncestorNodeChain().FirstNodeMarkedOrdered() + if firstOrderedNode.IsZero() { + return types.GinkgoErrors.SetupNodeNotInOrderedContainer(node.CodeLocation, node.NodeType) + } + } + + if node.NodeType == types.NodeTypeContainer { + // During PhaseBuildTopLevel we only track the top level containers without entering them + // We only enter the top level container nodes during PhaseBuildTree + // + // This ensures the tree is only constructed after `go spec` has called `flag.Parse()` and gives + // the user an opportunity to load suiteConfiguration information in the `TestX` go spec hook just before `RunSpecs` + // is invoked. This makes the lifecycle easier to reason about and solves issues like #693. + if suite.phase == PhaseBuildTopLevel { + suite.topLevelContainers = append(suite.topLevelContainers, node) + return nil + } + if suite.phase == PhaseBuildTree { + parentTree := suite.tree + suite.tree = &TreeNode{Node: node} + parentTree.AppendChild(suite.tree) + err := func() (err error) { + defer func() { + if e := recover(); e != nil { + err = types.GinkgoErrors.CaughtPanicDuringABuildPhase(e, node.CodeLocation) + } + }() + node.Body(nil) + return err + }() + suite.tree = parentTree + return err + } + } else { + suite.tree.AppendChild(&TreeNode{Node: node}) + return nil + } + + return nil +} + +func (suite *Suite) pushSuiteNode(node Node) error { + if suite.phase == PhaseBuildTree { + return types.GinkgoErrors.SuiteNodeInNestedContext(node.NodeType, node.CodeLocation) + } + + if suite.phase == PhaseRun { + return types.GinkgoErrors.SuiteNodeDuringRunPhase(node.NodeType, node.CodeLocation) + } + + switch node.NodeType { + case types.NodeTypeBeforeSuite, types.NodeTypeSynchronizedBeforeSuite: + existingBefores := suite.suiteNodes.WithType(types.NodeTypeBeforeSuite | types.NodeTypeSynchronizedBeforeSuite) + if len(existingBefores) > 0 { + return types.GinkgoErrors.MultipleBeforeSuiteNodes(node.NodeType, node.CodeLocation, existingBefores[0].NodeType, existingBefores[0].CodeLocation) + } + case types.NodeTypeAfterSuite, types.NodeTypeSynchronizedAfterSuite: + existingAfters := suite.suiteNodes.WithType(types.NodeTypeAfterSuite | types.NodeTypeSynchronizedAfterSuite) + if len(existingAfters) > 0 { + return types.GinkgoErrors.MultipleAfterSuiteNodes(node.NodeType, node.CodeLocation, existingAfters[0].NodeType, existingAfters[0].CodeLocation) + } + } + + suite.suiteNodes = append(suite.suiteNodes, node) + return nil +} + +func (suite *Suite) pushCleanupNode(node Node) error { + if suite.phase != PhaseRun || suite.currentNode.IsZero() { + return types.GinkgoErrors.PushingCleanupNodeDuringTreeConstruction(node.CodeLocation) + } + + switch suite.currentNode.NodeType { + case types.NodeTypeBeforeSuite, types.NodeTypeSynchronizedBeforeSuite, types.NodeTypeAfterSuite, types.NodeTypeSynchronizedAfterSuite: + node.NodeType = types.NodeTypeCleanupAfterSuite + case types.NodeTypeBeforeAll, types.NodeTypeAfterAll: + node.NodeType = types.NodeTypeCleanupAfterAll + case types.NodeTypeReportBeforeEach, types.NodeTypeReportAfterEach, types.NodeTypeReportAfterSuite: + return types.GinkgoErrors.PushingCleanupInReportingNode(node.CodeLocation, suite.currentNode.NodeType) + case types.NodeTypeCleanupInvalid, types.NodeTypeCleanupAfterEach, types.NodeTypeCleanupAfterAll, types.NodeTypeCleanupAfterSuite: + return types.GinkgoErrors.PushingCleanupInCleanupNode(node.CodeLocation) + default: + node.NodeType = types.NodeTypeCleanupAfterEach + } + + node.NodeIDWhereCleanupWasGenerated = suite.currentNode.ID + node.NestingLevel = suite.currentNode.NestingLevel + suite.cleanupNodes = append(suite.cleanupNodes, node) + + return nil +} + +func (suite *Suite) generateTimelineLocation() types.TimelineLocation { + suite.selectiveLock.Lock() + defer suite.selectiveLock.Unlock() + + suite.timelineOrder += 1 + return types.TimelineLocation{ + Offset: len(suite.currentSpecReport.CapturedGinkgoWriterOutput) + suite.writer.Len(), + Order: suite.timelineOrder, + Time: time.Now(), + } +} + +func (suite *Suite) handleSpecEvent(event types.SpecEvent) types.SpecEvent { + event.TimelineLocation = suite.generateTimelineLocation() + suite.selectiveLock.Lock() + suite.currentSpecReport.SpecEvents = append(suite.currentSpecReport.SpecEvents, event) + suite.selectiveLock.Unlock() + suite.reporter.EmitSpecEvent(event) + return event +} + +func (suite *Suite) handleSpecEventEnd(eventType types.SpecEventType, startEvent types.SpecEvent) { + event := startEvent + event.SpecEventType = eventType + event.TimelineLocation = suite.generateTimelineLocation() + event.Duration = event.TimelineLocation.Time.Sub(startEvent.TimelineLocation.Time) + suite.selectiveLock.Lock() + suite.currentSpecReport.SpecEvents = append(suite.currentSpecReport.SpecEvents, event) + suite.selectiveLock.Unlock() + suite.reporter.EmitSpecEvent(event) +} + +func (suite *Suite) By(text string, callback ...func()) error { + cl := types.NewCodeLocation(2) + if suite.phase != PhaseRun { + return types.GinkgoErrors.ByNotDuringRunPhase(cl) + } + + event := suite.handleSpecEvent(types.SpecEvent{ + SpecEventType: types.SpecEventByStart, + CodeLocation: cl, + Message: text, + }) + suite.selectiveLock.Lock() + suite.currentByStep = event + suite.selectiveLock.Unlock() + + if len(callback) == 1 { + defer func() { + suite.selectiveLock.Lock() + suite.currentByStep = types.SpecEvent{} + suite.selectiveLock.Unlock() + suite.handleSpecEventEnd(types.SpecEventByEnd, event) + }() + callback[0]() + } else if len(callback) > 1 { + panic("just one callback per By, please") + } + return nil +} + +/* +Spec Running methods - used during PhaseRun +*/ +func (suite *Suite) CurrentSpecReport() types.SpecReport { + suite.selectiveLock.Lock() + defer suite.selectiveLock.Unlock() + report := suite.currentSpecReport + if suite.writer != nil { + report.CapturedGinkgoWriterOutput = string(suite.writer.Bytes()) + } + report.ReportEntries = make([]ReportEntry, len(report.ReportEntries)) + copy(report.ReportEntries, suite.currentSpecReport.ReportEntries) + return report +} + +func (suite *Suite) AddReportEntry(entry ReportEntry) error { + if suite.phase != PhaseRun { + return types.GinkgoErrors.AddReportEntryNotDuringRunPhase(entry.Location) + } + entry.TimelineLocation = suite.generateTimelineLocation() + entry.Time = entry.TimelineLocation.Time + suite.selectiveLock.Lock() + suite.currentSpecReport.ReportEntries = append(suite.currentSpecReport.ReportEntries, entry) + suite.selectiveLock.Unlock() + suite.reporter.EmitReportEntry(entry) + return nil +} + +func (suite *Suite) generateProgressReport(fullReport bool) types.ProgressReport { + timelineLocation := suite.generateTimelineLocation() + suite.selectiveLock.Lock() + defer suite.selectiveLock.Unlock() + + var additionalReports []string + if suite.currentSpecContext != nil { + additionalReports = suite.currentSpecContext.QueryProgressReporters() + } + gwOutput := suite.currentSpecReport.CapturedGinkgoWriterOutput + string(suite.writer.Bytes()) + pr, err := NewProgressReport(suite.isRunningInParallel(), suite.currentSpecReport, suite.currentNode, suite.currentNodeStartTime, suite.currentByStep, gwOutput, timelineLocation, additionalReports, suite.config.SourceRoots, fullReport) + + if err != nil { + fmt.Printf("{{red}}Failed to generate progress report:{{/}}\n%s\n", err.Error()) + } + return pr +} + +func (suite *Suite) handleProgressSignal() { + report := suite.generateProgressReport(false) + report.Message = "{{bold}}You've requested a progress report:{{/}}" + suite.emitProgressReport(report) +} + +func (suite *Suite) emitProgressReport(report types.ProgressReport) { + suite.selectiveLock.Lock() + suite.currentSpecReport.ProgressReports = append(suite.currentSpecReport.ProgressReports, report.WithoutCapturedGinkgoWriterOutput()) + suite.selectiveLock.Unlock() + + suite.reporter.EmitProgressReport(report) + if suite.isRunningInParallel() { + err := suite.client.PostEmitProgressReport(report) + if err != nil { + fmt.Println(err.Error()) + } + } +} + +func (suite *Suite) isRunningInParallel() bool { + return suite.config.ParallelTotal > 1 +} + +func (suite *Suite) processCurrentSpecReport() { + suite.reporter.DidRun(suite.currentSpecReport) + if suite.isRunningInParallel() { + suite.client.PostDidRun(suite.currentSpecReport) + } + suite.report.SpecReports = append(suite.report.SpecReports, suite.currentSpecReport) + + if suite.currentSpecReport.State.Is(types.SpecStateFailureStates) { + suite.report.SuiteSucceeded = false + if suite.config.FailFast || suite.currentSpecReport.State.Is(types.SpecStateAborted) { + suite.skipAll = true + if suite.isRunningInParallel() { + suite.client.PostAbort() + } + } + } +} + +func (suite *Suite) runSpecs(description string, suiteLabels Labels, suitePath string, hasProgrammaticFocus bool, specs Specs) bool { + numSpecsThatWillBeRun := specs.CountWithoutSkip() + + suite.report = types.Report{ + SuitePath: suitePath, + SuiteDescription: description, + SuiteLabels: suiteLabels, + SuiteConfig: suite.config, + SuiteHasProgrammaticFocus: hasProgrammaticFocus, + PreRunStats: types.PreRunStats{ + TotalSpecs: len(specs), + SpecsThatWillRun: numSpecsThatWillBeRun, + }, + StartTime: time.Now(), + } + + suite.reporter.SuiteWillBegin(suite.report) + if suite.isRunningInParallel() { + suite.client.PostSuiteWillBegin(suite.report) + } + + suite.report.SuiteSucceeded = true + suite.runBeforeSuite(numSpecsThatWillBeRun) + + if suite.report.SuiteSucceeded { + groupedSpecIndices, serialGroupedSpecIndices := OrderSpecs(specs, suite.config) + nextIndex := MakeIncrementingIndexCounter() + if suite.isRunningInParallel() { + nextIndex = suite.client.FetchNextCounter + } + + for { + groupedSpecIdx, err := nextIndex() + if err != nil { + suite.report.SpecialSuiteFailureReasons = append(suite.report.SpecialSuiteFailureReasons, fmt.Sprintf("Failed to iterate over specs:\n%s", err.Error())) + suite.report.SuiteSucceeded = false + break + } + + if groupedSpecIdx >= len(groupedSpecIndices) { + if suite.config.ParallelProcess == 1 && len(serialGroupedSpecIndices) > 0 { + groupedSpecIndices, serialGroupedSpecIndices, nextIndex = serialGroupedSpecIndices, GroupedSpecIndices{}, MakeIncrementingIndexCounter() + suite.client.BlockUntilNonprimaryProcsHaveFinished() + continue + } + break + } + + // the complexity for running groups of specs is very high because of Ordered containers and FlakeAttempts + // we encapsulate that complexity in the notion of a Group that can run + // Group is really just an extension of suite so it gets passed a suite and has access to all its internals + // Note that group is stateful and intended for single use! + newGroup(suite).run(specs.AtIndices(groupedSpecIndices[groupedSpecIdx])) + } + + if specs.HasAnySpecsMarkedPending() && suite.config.FailOnPending { + suite.report.SpecialSuiteFailureReasons = append(suite.report.SpecialSuiteFailureReasons, "Detected pending specs and --fail-on-pending is set") + suite.report.SuiteSucceeded = false + } + } + + suite.runAfterSuiteCleanup(numSpecsThatWillBeRun) + + interruptStatus := suite.interruptHandler.Status() + if interruptStatus.Interrupted() { + suite.report.SpecialSuiteFailureReasons = append(suite.report.SpecialSuiteFailureReasons, interruptStatus.Cause.String()) + suite.report.SuiteSucceeded = false + } + suite.report.EndTime = time.Now() + suite.report.RunTime = suite.report.EndTime.Sub(suite.report.StartTime) + if !suite.deadline.IsZero() && suite.report.EndTime.After(suite.deadline) { + suite.report.SpecialSuiteFailureReasons = append(suite.report.SpecialSuiteFailureReasons, "Suite Timeout Elapsed") + suite.report.SuiteSucceeded = false + } + + if suite.config.ParallelProcess == 1 { + suite.runReportAfterSuite() + } + suite.reporter.SuiteDidEnd(suite.report) + if suite.isRunningInParallel() { + suite.client.PostSuiteDidEnd(suite.report) + } + + return suite.report.SuiteSucceeded +} + +func (suite *Suite) runBeforeSuite(numSpecsThatWillBeRun int) { + beforeSuiteNode := suite.suiteNodes.FirstNodeWithType(types.NodeTypeBeforeSuite | types.NodeTypeSynchronizedBeforeSuite) + if !beforeSuiteNode.IsZero() && numSpecsThatWillBeRun > 0 { + suite.selectiveLock.Lock() + suite.currentSpecReport = types.SpecReport{ + LeafNodeType: beforeSuiteNode.NodeType, + LeafNodeLocation: beforeSuiteNode.CodeLocation, + ParallelProcess: suite.config.ParallelProcess, + RunningInParallel: suite.isRunningInParallel(), + } + suite.selectiveLock.Unlock() + + suite.reporter.WillRun(suite.currentSpecReport) + suite.runSuiteNode(beforeSuiteNode) + if suite.currentSpecReport.State.Is(types.SpecStateSkipped) { + suite.report.SpecialSuiteFailureReasons = append(suite.report.SpecialSuiteFailureReasons, "Suite skipped in BeforeSuite") + suite.skipAll = true + } + suite.processCurrentSpecReport() + } +} + +func (suite *Suite) runAfterSuiteCleanup(numSpecsThatWillBeRun int) { + afterSuiteNode := suite.suiteNodes.FirstNodeWithType(types.NodeTypeAfterSuite | types.NodeTypeSynchronizedAfterSuite) + if !afterSuiteNode.IsZero() && numSpecsThatWillBeRun > 0 { + suite.selectiveLock.Lock() + suite.currentSpecReport = types.SpecReport{ + LeafNodeType: afterSuiteNode.NodeType, + LeafNodeLocation: afterSuiteNode.CodeLocation, + ParallelProcess: suite.config.ParallelProcess, + RunningInParallel: suite.isRunningInParallel(), + } + suite.selectiveLock.Unlock() + + suite.reporter.WillRun(suite.currentSpecReport) + suite.runSuiteNode(afterSuiteNode) + suite.processCurrentSpecReport() + } + + afterSuiteCleanup := suite.cleanupNodes.WithType(types.NodeTypeCleanupAfterSuite).Reverse() + if len(afterSuiteCleanup) > 0 { + for _, cleanupNode := range afterSuiteCleanup { + suite.selectiveLock.Lock() + suite.currentSpecReport = types.SpecReport{ + LeafNodeType: cleanupNode.NodeType, + LeafNodeLocation: cleanupNode.CodeLocation, + ParallelProcess: suite.config.ParallelProcess, + RunningInParallel: suite.isRunningInParallel(), + } + suite.selectiveLock.Unlock() + + suite.reporter.WillRun(suite.currentSpecReport) + suite.runSuiteNode(cleanupNode) + suite.processCurrentSpecReport() + } + } +} + +func (suite *Suite) runReportAfterSuite() { + for _, node := range suite.suiteNodes.WithType(types.NodeTypeReportAfterSuite) { + suite.selectiveLock.Lock() + suite.currentSpecReport = types.SpecReport{ + LeafNodeType: node.NodeType, + LeafNodeLocation: node.CodeLocation, + LeafNodeText: node.Text, + ParallelProcess: suite.config.ParallelProcess, + RunningInParallel: suite.isRunningInParallel(), + } + suite.selectiveLock.Unlock() + + suite.reporter.WillRun(suite.currentSpecReport) + suite.runReportAfterSuiteNode(node, suite.report) + suite.processCurrentSpecReport() + } +} + +func (suite *Suite) reportEach(spec Spec, nodeType types.NodeType) { + nodes := spec.Nodes.WithType(nodeType) + if nodeType == types.NodeTypeReportAfterEach { + nodes = nodes.SortedByDescendingNestingLevel() + } + if nodeType == types.NodeTypeReportBeforeEach { + nodes = nodes.SortedByAscendingNestingLevel() + } + if len(nodes) == 0 { + return + } + + for i := range nodes { + suite.writer.Truncate() + suite.outputInterceptor.StartInterceptingOutput() + report := suite.currentSpecReport + nodes[i].Body = func(SpecContext) { + nodes[i].ReportEachBody(report) + } + state, failure := suite.runNode(nodes[i], time.Time{}, spec.Nodes.BestTextFor(nodes[i])) + + // If the spec is not in a failure state (i.e. it's Passed/Skipped/Pending) and the reporter has failed, override the state. + // Also, if the reporter is every aborted - always override the state to propagate the abort + if (!suite.currentSpecReport.State.Is(types.SpecStateFailureStates) && state.Is(types.SpecStateFailureStates)) || state.Is(types.SpecStateAborted) { + suite.currentSpecReport.State = state + suite.currentSpecReport.Failure = failure + } + suite.currentSpecReport.CapturedGinkgoWriterOutput += string(suite.writer.Bytes()) + suite.currentSpecReport.CapturedStdOutErr += suite.outputInterceptor.StopInterceptingAndReturnOutput() + } +} + +func (suite *Suite) runSuiteNode(node Node) { + if suite.config.DryRun { + suite.currentSpecReport.State = types.SpecStatePassed + return + } + + suite.writer.Truncate() + suite.outputInterceptor.StartInterceptingOutput() + suite.currentSpecReport.StartTime = time.Now() + + var err error + switch node.NodeType { + case types.NodeTypeBeforeSuite, types.NodeTypeAfterSuite: + suite.currentSpecReport.State, suite.currentSpecReport.Failure = suite.runNode(node, time.Time{}, "") + case types.NodeTypeCleanupAfterSuite: + if suite.config.ParallelTotal > 1 && suite.config.ParallelProcess == 1 { + err = suite.client.BlockUntilNonprimaryProcsHaveFinished() + } + if err == nil { + suite.currentSpecReport.State, suite.currentSpecReport.Failure = suite.runNode(node, time.Time{}, "") + } + case types.NodeTypeSynchronizedBeforeSuite: + var data []byte + var runAllProcs bool + if suite.config.ParallelProcess == 1 { + if suite.config.ParallelTotal > 1 { + suite.outputInterceptor.StopInterceptingAndReturnOutput() + suite.outputInterceptor.StartInterceptingOutputAndForwardTo(suite.client) + } + node.Body = func(c SpecContext) { data = node.SynchronizedBeforeSuiteProc1Body(c) } + node.HasContext = node.SynchronizedBeforeSuiteProc1BodyHasContext + suite.currentSpecReport.State, suite.currentSpecReport.Failure = suite.runNode(node, time.Time{}, "") + if suite.config.ParallelTotal > 1 { + suite.currentSpecReport.CapturedStdOutErr += suite.outputInterceptor.StopInterceptingAndReturnOutput() + suite.outputInterceptor.StartInterceptingOutput() + if suite.currentSpecReport.State.Is(types.SpecStatePassed) { + err = suite.client.PostSynchronizedBeforeSuiteCompleted(types.SpecStatePassed, data) + } else { + err = suite.client.PostSynchronizedBeforeSuiteCompleted(suite.currentSpecReport.State, nil) + } + } + runAllProcs = suite.currentSpecReport.State.Is(types.SpecStatePassed) && err == nil + } else { + var proc1State types.SpecState + proc1State, data, err = suite.client.BlockUntilSynchronizedBeforeSuiteData() + switch proc1State { + case types.SpecStatePassed: + runAllProcs = true + case types.SpecStateFailed, types.SpecStatePanicked, types.SpecStateTimedout: + err = types.GinkgoErrors.SynchronizedBeforeSuiteFailedOnProc1() + case types.SpecStateInterrupted, types.SpecStateAborted, types.SpecStateSkipped: + suite.currentSpecReport.State = proc1State + } + } + if runAllProcs { + node.Body = func(c SpecContext) { node.SynchronizedBeforeSuiteAllProcsBody(c, data) } + node.HasContext = node.SynchronizedBeforeSuiteAllProcsBodyHasContext + suite.currentSpecReport.State, suite.currentSpecReport.Failure = suite.runNode(node, time.Time{}, "") + } + case types.NodeTypeSynchronizedAfterSuite: + node.Body = node.SynchronizedAfterSuiteAllProcsBody + node.HasContext = node.SynchronizedAfterSuiteAllProcsBodyHasContext + suite.currentSpecReport.State, suite.currentSpecReport.Failure = suite.runNode(node, time.Time{}, "") + if suite.config.ParallelProcess == 1 { + if suite.config.ParallelTotal > 1 { + err = suite.client.BlockUntilNonprimaryProcsHaveFinished() + } + if err == nil { + if suite.config.ParallelTotal > 1 { + suite.currentSpecReport.CapturedStdOutErr += suite.outputInterceptor.StopInterceptingAndReturnOutput() + suite.outputInterceptor.StartInterceptingOutputAndForwardTo(suite.client) + } + + node.Body = node.SynchronizedAfterSuiteProc1Body + node.HasContext = node.SynchronizedAfterSuiteProc1BodyHasContext + state, failure := suite.runNode(node, time.Time{}, "") + if suite.currentSpecReport.State.Is(types.SpecStatePassed) { + suite.currentSpecReport.State, suite.currentSpecReport.Failure = state, failure + } + } + } + } + + if err != nil && !suite.currentSpecReport.State.Is(types.SpecStateFailureStates) { + suite.currentSpecReport.State, suite.currentSpecReport.Failure = types.SpecStateFailed, suite.failureForLeafNodeWithMessage(node, err.Error()) + suite.reporter.EmitFailure(suite.currentSpecReport.State, suite.currentSpecReport.Failure) + } + + suite.currentSpecReport.EndTime = time.Now() + suite.currentSpecReport.RunTime = suite.currentSpecReport.EndTime.Sub(suite.currentSpecReport.StartTime) + suite.currentSpecReport.CapturedGinkgoWriterOutput = string(suite.writer.Bytes()) + suite.currentSpecReport.CapturedStdOutErr += suite.outputInterceptor.StopInterceptingAndReturnOutput() + + return +} + +func (suite *Suite) runReportAfterSuiteNode(node Node, report types.Report) { + suite.writer.Truncate() + suite.outputInterceptor.StartInterceptingOutput() + suite.currentSpecReport.StartTime = time.Now() + + if suite.config.ParallelTotal > 1 { + aggregatedReport, err := suite.client.BlockUntilAggregatedNonprimaryProcsReport() + if err != nil { + suite.currentSpecReport.State, suite.currentSpecReport.Failure = types.SpecStateFailed, suite.failureForLeafNodeWithMessage(node, err.Error()) + suite.reporter.EmitFailure(suite.currentSpecReport.State, suite.currentSpecReport.Failure) + return + } + report = report.Add(aggregatedReport) + } + + node.Body = func(SpecContext) { node.ReportAfterSuiteBody(report) } + suite.currentSpecReport.State, suite.currentSpecReport.Failure = suite.runNode(node, time.Time{}, "") + + suite.currentSpecReport.EndTime = time.Now() + suite.currentSpecReport.RunTime = suite.currentSpecReport.EndTime.Sub(suite.currentSpecReport.StartTime) + suite.currentSpecReport.CapturedGinkgoWriterOutput = string(suite.writer.Bytes()) + suite.currentSpecReport.CapturedStdOutErr = suite.outputInterceptor.StopInterceptingAndReturnOutput() + + return +} + +func (suite *Suite) runNode(node Node, specDeadline time.Time, text string) (types.SpecState, types.Failure) { + if node.NodeType.Is(types.NodeTypeCleanupAfterEach | types.NodeTypeCleanupAfterAll | types.NodeTypeCleanupAfterSuite) { + suite.cleanupNodes = suite.cleanupNodes.WithoutNode(node) + } + + interruptStatus := suite.interruptHandler.Status() + if interruptStatus.Level == interrupt_handler.InterruptLevelBailOut { + return types.SpecStateSkipped, types.Failure{} + } + if interruptStatus.Level == interrupt_handler.InterruptLevelReportOnly && !node.NodeType.Is(types.NodeTypesAllowedDuringReportInterrupt) { + return types.SpecStateSkipped, types.Failure{} + } + if interruptStatus.Level == interrupt_handler.InterruptLevelCleanupAndReport && !node.NodeType.Is(types.NodeTypesAllowedDuringReportInterrupt|types.NodeTypesAllowedDuringCleanupInterrupt) { + return types.SpecStateSkipped, types.Failure{} + } + + suite.selectiveLock.Lock() + suite.currentNode = node + suite.currentNodeStartTime = time.Now() + suite.currentByStep = types.SpecEvent{} + suite.selectiveLock.Unlock() + defer func() { + suite.selectiveLock.Lock() + suite.currentNode = Node{} + suite.currentNodeStartTime = time.Time{} + suite.selectiveLock.Unlock() + }() + + if text == "" { + text = "TOP-LEVEL" + } + event := suite.handleSpecEvent(types.SpecEvent{ + SpecEventType: types.SpecEventNodeStart, + NodeType: node.NodeType, + Message: text, + CodeLocation: node.CodeLocation, + }) + defer func() { + suite.handleSpecEventEnd(types.SpecEventNodeEnd, event) + }() + + var failure types.Failure + failure.FailureNodeType, failure.FailureNodeLocation = node.NodeType, node.CodeLocation + if node.NodeType.Is(types.NodeTypeIt) || node.NodeType.Is(types.NodeTypesForSuiteLevelNodes) { + failure.FailureNodeContext = types.FailureNodeIsLeafNode + } else if node.NestingLevel <= 0 { + failure.FailureNodeContext = types.FailureNodeAtTopLevel + } else { + failure.FailureNodeContext, failure.FailureNodeContainerIndex = types.FailureNodeInContainer, node.NestingLevel-1 + } + var outcome types.SpecState + + gracePeriod := suite.config.GracePeriod + if node.GracePeriod >= 0 { + gracePeriod = node.GracePeriod + } + + now := time.Now() + deadline := suite.deadline + timeoutInPlay := "suite" + if deadline.IsZero() || (!specDeadline.IsZero() && specDeadline.Before(deadline)) { + deadline = specDeadline + timeoutInPlay = "spec" + } + if node.NodeTimeout > 0 && (deadline.IsZero() || deadline.Sub(now) > node.NodeTimeout) { + deadline = now.Add(node.NodeTimeout) + timeoutInPlay = "node" + } + if (!deadline.IsZero() && deadline.Before(now)) || interruptStatus.Interrupted() { + //we're out of time already. let's wait for a NodeTimeout if we have it, or GracePeriod if we don't + if node.NodeTimeout > 0 { + deadline = now.Add(node.NodeTimeout) + timeoutInPlay = "node" + } else { + deadline = now.Add(gracePeriod) + timeoutInPlay = "grace period" + } + } + + if !node.HasContext { + // this maps onto the pre-context behavior: + // - an interrupted node exits immediately. with this, context-less nodes that are in a spec with a SpecTimeout and/or are interrupted by other means will simply exit immediately after the timeout/interrupt + // - clean up nodes have up to GracePeriod (formerly hard-coded at 30s) to complete before they are interrupted + gracePeriod = 0 + } + + sc := NewSpecContext(suite) + defer sc.cancel() + + suite.selectiveLock.Lock() + suite.currentSpecContext = sc + suite.selectiveLock.Unlock() + + var deadlineChannel <-chan time.Time + if !deadline.IsZero() { + deadlineChannel = time.After(deadline.Sub(now)) + } + var gracePeriodChannel <-chan time.Time + + outcomeC := make(chan types.SpecState) + failureC := make(chan types.Failure) + + go func() { + finished := false + defer func() { + if e := recover(); e != nil || !finished { + suite.failer.Panic(types.NewCodeLocationWithStackTrace(2), e) + } + + outcomeFromRun, failureFromRun := suite.failer.Drain() + failureFromRun.TimelineLocation = suite.generateTimelineLocation() + outcomeC <- outcomeFromRun + failureC <- failureFromRun + }() + + node.Body(sc) + finished = true + }() + + // progress polling timer and channel + var emitProgressNow <-chan time.Time + var progressPoller *time.Timer + var pollProgressAfter, pollProgressInterval = suite.config.PollProgressAfter, suite.config.PollProgressInterval + if node.PollProgressAfter >= 0 { + pollProgressAfter = node.PollProgressAfter + } + if node.PollProgressInterval >= 0 { + pollProgressInterval = node.PollProgressInterval + } + if pollProgressAfter > 0 { + progressPoller = time.NewTimer(pollProgressAfter) + emitProgressNow = progressPoller.C + defer progressPoller.Stop() + } + + // now we wait for an outcome, an interrupt, a timeout, or a progress poll + for { + select { + case outcomeFromRun := <-outcomeC: + failureFromRun := <-failureC + if outcome.Is(types.SpecStateInterrupted | types.SpecStateTimedout) { + // we've already been interrupted/timed out. we just managed to actually exit + // before the grace period elapsed + // if we have a failure message we attach it as an additional failure + if outcomeFromRun != types.SpecStatePassed { + additionalFailure := types.AdditionalFailure{ + State: outcomeFromRun, + Failure: failure, //we make a copy - this will include all the configuration set up above... + } + //...and then we update the failure with the details from failureFromRun + additionalFailure.Failure.Location, additionalFailure.Failure.ForwardedPanic, additionalFailure.Failure.TimelineLocation = failureFromRun.Location, failureFromRun.ForwardedPanic, failureFromRun.TimelineLocation + additionalFailure.Failure.ProgressReport = types.ProgressReport{} + if outcome == types.SpecStateTimedout { + additionalFailure.Failure.Message = fmt.Sprintf("A %s timeout occurred and then the following failure was recorded in the timedout node before it exited:\n%s", timeoutInPlay, failureFromRun.Message) + } else { + additionalFailure.Failure.Message = fmt.Sprintf("An interrupt occurred and then the following failure was recorded in the interrupted node before it exited:\n%s", failureFromRun.Message) + } + suite.reporter.EmitFailure(additionalFailure.State, additionalFailure.Failure) + failure.AdditionalFailure = &additionalFailure + } + return outcome, failure + } + if outcomeFromRun.Is(types.SpecStatePassed) { + return outcomeFromRun, types.Failure{} + } else { + failure.Message, failure.Location, failure.ForwardedPanic, failure.TimelineLocation = failureFromRun.Message, failureFromRun.Location, failureFromRun.ForwardedPanic, failureFromRun.TimelineLocation + suite.reporter.EmitFailure(outcomeFromRun, failure) + return outcomeFromRun, failure + } + case <-gracePeriodChannel: + if node.HasContext && outcome.Is(types.SpecStateTimedout) { + report := suite.generateProgressReport(false) + report.Message = "{{bold}}{{orange}}A running node failed to exit in time{{/}}\nGinkgo is moving on but a node has timed out and failed to exit before its grace period elapsed. The node has now leaked and is running in the background.\nHere's a current progress report:" + suite.emitProgressReport(report) + } + return outcome, failure + case <-deadlineChannel: + // we're out of time - the outcome is a timeout and we capture the failure and progress report + outcome = types.SpecStateTimedout + failure.Message, failure.Location, failure.TimelineLocation = fmt.Sprintf("A %s timeout occurred", timeoutInPlay), node.CodeLocation, suite.generateTimelineLocation() + failure.ProgressReport = suite.generateProgressReport(false).WithoutCapturedGinkgoWriterOutput() + failure.ProgressReport.Message = fmt.Sprintf("{{bold}}This is the Progress Report generated when the %s timeout occurred:{{/}}", timeoutInPlay) + deadlineChannel = nil + suite.reporter.EmitFailure(outcome, failure) + + // tell the spec to stop. it's important we generate the progress report first to make sure we capture where + // the spec is actually stuck + sc.cancel() + //and now we wait for the grace period + gracePeriodChannel = time.After(gracePeriod) + case <-interruptStatus.Channel: + interruptStatus = suite.interruptHandler.Status() + deadlineChannel = nil // don't worry about deadlines, time's up now + + failureTimelineLocation := suite.generateTimelineLocation() + progressReport := suite.generateProgressReport(true) + + if outcome == types.SpecStateInvalid { + outcome = types.SpecStateInterrupted + failure.Message, failure.Location, failure.TimelineLocation = interruptStatus.Message(), node.CodeLocation, failureTimelineLocation + if interruptStatus.ShouldIncludeProgressReport() { + failure.ProgressReport = progressReport.WithoutCapturedGinkgoWriterOutput() + failure.ProgressReport.Message = "{{bold}}This is the Progress Report generated when the interrupt was received:{{/}}" + } + suite.reporter.EmitFailure(outcome, failure) + } + + progressReport = progressReport.WithoutOtherGoroutines() + sc.cancel() + + if interruptStatus.Level == interrupt_handler.InterruptLevelBailOut { + if interruptStatus.ShouldIncludeProgressReport() { + progressReport.Message = fmt.Sprintf("{{bold}}{{orange}}%s{{/}}\n{{bold}}{{red}}Final interrupt received{{/}}; Ginkgo will not run any cleanup or reporting nodes and will terminate as soon as possible.\nHere's a current progress report:", interruptStatus.Message()) + suite.emitProgressReport(progressReport) + } + return outcome, failure + } + if interruptStatus.ShouldIncludeProgressReport() { + if interruptStatus.Level == interrupt_handler.InterruptLevelCleanupAndReport { + progressReport.Message = fmt.Sprintf("{{bold}}{{orange}}%s{{/}}\nFirst interrupt received; Ginkgo will run any cleanup and reporting nodes but will skip all remaining specs. {{bold}}Interrupt again to skip cleanup{{/}}.\nHere's a current progress report:", interruptStatus.Message()) + } else if interruptStatus.Level == interrupt_handler.InterruptLevelReportOnly { + progressReport.Message = fmt.Sprintf("{{bold}}{{orange}}%s{{/}}\nSecond interrupt received; Ginkgo will run any reporting nodes but will skip all remaining specs and cleanup nodes. {{bold}}Interrupt again to bail immediately{{/}}.\nHere's a current progress report:", interruptStatus.Message()) + } + suite.emitProgressReport(progressReport) + } + + if gracePeriodChannel == nil { + // we haven't given grace yet... so let's + gracePeriodChannel = time.After(gracePeriod) + } else { + // we've already given grace. time's up. now. + return outcome, failure + } + case <-emitProgressNow: + report := suite.generateProgressReport(false) + report.Message = "{{bold}}Automatically polling progress:{{/}}" + suite.emitProgressReport(report) + if pollProgressInterval > 0 { + progressPoller.Reset(pollProgressInterval) + } + } + } +} + +// TODO: search for usages and consider if reporter.EmitFailure() is necessary +func (suite *Suite) failureForLeafNodeWithMessage(node Node, message string) types.Failure { + return types.Failure{ + Message: message, + Location: node.CodeLocation, + TimelineLocation: suite.generateTimelineLocation(), + FailureNodeContext: types.FailureNodeIsLeafNode, + FailureNodeType: node.NodeType, + FailureNodeLocation: node.CodeLocation, + } +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/testingtproxy/testing_t_proxy.go b/vendor/github.com/onsi/ginkgo/v2/internal/testingtproxy/testing_t_proxy.go new file mode 100644 index 00000000000..2f42b2642b5 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/testingtproxy/testing_t_proxy.go @@ -0,0 +1,128 @@ +package testingtproxy + +import ( + "fmt" + "io" + "os" + + "github.com/onsi/ginkgo/v2/internal" + "github.com/onsi/ginkgo/v2/types" +) + +type failFunc func(message string, callerSkip ...int) +type skipFunc func(message string, callerSkip ...int) +type cleanupFunc func(args ...interface{}) +type reportFunc func() types.SpecReport + +func New(writer io.Writer, fail failFunc, skip skipFunc, cleanup cleanupFunc, report reportFunc, offset int) *ginkgoTestingTProxy { + return &ginkgoTestingTProxy{ + fail: fail, + offset: offset, + writer: writer, + skip: skip, + cleanup: cleanup, + report: report, + } +} + +type ginkgoTestingTProxy struct { + fail failFunc + skip skipFunc + cleanup cleanupFunc + report reportFunc + offset int + writer io.Writer +} + +func (t *ginkgoTestingTProxy) Cleanup(f func()) { + t.cleanup(f, internal.Offset(1)) +} + +func (t *ginkgoTestingTProxy) Setenv(key, value string) { + originalValue, exists := os.LookupEnv(key) + if exists { + t.cleanup(os.Setenv, key, originalValue, internal.Offset(1)) + } else { + t.cleanup(os.Unsetenv, key, internal.Offset(1)) + } + + err := os.Setenv(key, value) + if err != nil { + t.fail(fmt.Sprintf("Failed to set environment variable: %v", err), 1) + } +} + +func (t *ginkgoTestingTProxy) Error(args ...interface{}) { + t.fail(fmt.Sprintln(args...), t.offset) +} + +func (t *ginkgoTestingTProxy) Errorf(format string, args ...interface{}) { + t.fail(fmt.Sprintf(format, args...), t.offset) +} + +func (t *ginkgoTestingTProxy) Fail() { + t.fail("failed", t.offset) +} + +func (t *ginkgoTestingTProxy) FailNow() { + t.fail("failed", t.offset) +} + +func (t *ginkgoTestingTProxy) Failed() bool { + return t.report().Failed() +} + +func (t *ginkgoTestingTProxy) Fatal(args ...interface{}) { + t.fail(fmt.Sprintln(args...), t.offset) +} + +func (t *ginkgoTestingTProxy) Fatalf(format string, args ...interface{}) { + t.fail(fmt.Sprintf(format, args...), t.offset) +} + +func (t *ginkgoTestingTProxy) Helper() { + // No-op +} + +func (t *ginkgoTestingTProxy) Log(args ...interface{}) { + fmt.Fprintln(t.writer, args...) +} + +func (t *ginkgoTestingTProxy) Logf(format string, args ...interface{}) { + t.Log(fmt.Sprintf(format, args...)) +} + +func (t *ginkgoTestingTProxy) Name() string { + return t.report().FullText() +} + +func (t *ginkgoTestingTProxy) Parallel() { + // No-op +} + +func (t *ginkgoTestingTProxy) Skip(args ...interface{}) { + t.skip(fmt.Sprintln(args...), t.offset) +} + +func (t *ginkgoTestingTProxy) SkipNow() { + t.skip("skip", t.offset) +} + +func (t *ginkgoTestingTProxy) Skipf(format string, args ...interface{}) { + t.skip(fmt.Sprintf(format, args...), t.offset) +} + +func (t *ginkgoTestingTProxy) Skipped() bool { + return t.report().State.Is(types.SpecStateSkipped) +} + +func (t *ginkgoTestingTProxy) TempDir() string { + tmpDir, err := os.MkdirTemp("", "ginkgo") + if err != nil { + t.fail(fmt.Sprintf("Failed to create temporary directory: %v", err), 1) + return "" + } + t.cleanup(os.RemoveAll, tmpDir) + + return tmpDir +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/tree.go b/vendor/github.com/onsi/ginkgo/v2/internal/tree.go new file mode 100644 index 00000000000..f9d1eeb8f8e --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/tree.go @@ -0,0 +1,77 @@ +package internal + +import "github.com/onsi/ginkgo/v2/types" + +type TreeNode struct { + Node Node + Parent *TreeNode + Children TreeNodes +} + +func (tn *TreeNode) AppendChild(child *TreeNode) { + tn.Children = append(tn.Children, child) + child.Parent = tn +} + +func (tn *TreeNode) AncestorNodeChain() Nodes { + if tn.Parent == nil || tn.Parent.Node.IsZero() { + return Nodes{tn.Node} + } + return append(tn.Parent.AncestorNodeChain(), tn.Node) +} + +type TreeNodes []*TreeNode + +func (tn TreeNodes) Nodes() Nodes { + out := make(Nodes, len(tn)) + for i := range tn { + out[i] = tn[i].Node + } + return out +} + +func (tn TreeNodes) WithID(id uint) *TreeNode { + for i := range tn { + if tn[i].Node.ID == id { + return tn[i] + } + } + + return nil +} + +func GenerateSpecsFromTreeRoot(tree *TreeNode) Specs { + var walkTree func(nestingLevel int, lNodes Nodes, rNodes Nodes, trees TreeNodes) Specs + walkTree = func(nestingLevel int, lNodes Nodes, rNodes Nodes, trees TreeNodes) Specs { + tests := Specs{} + + nodes := make(Nodes, len(trees)) + for i := range trees { + nodes[i] = trees[i].Node + nodes[i].NestingLevel = nestingLevel + } + + for i := range nodes { + if !nodes[i].NodeType.Is(types.NodeTypesForContainerAndIt) { + continue + } + leftNodes, rightNodes := nodes.SplitAround(nodes[i]) + leftNodes = leftNodes.WithoutType(types.NodeTypesForContainerAndIt) + rightNodes = rightNodes.WithoutType(types.NodeTypesForContainerAndIt) + + leftNodes = lNodes.CopyAppend(leftNodes...) + rightNodes = rightNodes.CopyAppend(rNodes...) + + if nodes[i].NodeType.Is(types.NodeTypeIt) { + tests = append(tests, Spec{Nodes: leftNodes.CopyAppend(nodes[i]).CopyAppend(rightNodes...)}) + } else { + treeNode := trees.WithID(nodes[i].ID) + tests = append(tests, walkTree(nestingLevel+1, leftNodes.CopyAppend(nodes[i]), rightNodes, treeNode.Children)...) + } + } + + return tests + } + + return walkTree(0, Nodes{}, Nodes{}, tree.Children) +} diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/writer.go b/vendor/github.com/onsi/ginkgo/v2/internal/writer.go new file mode 100644 index 00000000000..28a45b0faaa --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/internal/writer.go @@ -0,0 +1,140 @@ +package internal + +import ( + "bytes" + "fmt" + "io" + "sync" + + "github.com/go-logr/logr" + "github.com/go-logr/logr/funcr" +) + +type WriterMode uint + +const ( + WriterModeStreamAndBuffer WriterMode = iota + WriterModeBufferOnly +) + +type WriterInterface interface { + io.Writer + + Truncate() + Bytes() []byte + Len() int +} + +// Writer implements WriterInterface and GinkgoWriterInterface +type Writer struct { + buffer *bytes.Buffer + outWriter io.Writer + lock *sync.Mutex + mode WriterMode + + streamIndent []byte + indentNext bool + + teeWriters []io.Writer +} + +func NewWriter(outWriter io.Writer) *Writer { + return &Writer{ + buffer: &bytes.Buffer{}, + lock: &sync.Mutex{}, + outWriter: outWriter, + mode: WriterModeStreamAndBuffer, + streamIndent: []byte(" "), + indentNext: true, + } +} + +func (w *Writer) SetMode(mode WriterMode) { + w.lock.Lock() + defer w.lock.Unlock() + w.mode = mode +} + +func (w *Writer) Len() int { + w.lock.Lock() + defer w.lock.Unlock() + return w.buffer.Len() +} + +var newline = []byte("\n") + +func (w *Writer) Write(b []byte) (n int, err error) { + w.lock.Lock() + defer w.lock.Unlock() + + for _, teeWriter := range w.teeWriters { + teeWriter.Write(b) + } + + if w.mode == WriterModeStreamAndBuffer { + line, remaining, found := []byte{}, b, false + for len(remaining) > 0 { + line, remaining, found = bytes.Cut(remaining, newline) + if len(line) > 0 { + if w.indentNext { + w.outWriter.Write(w.streamIndent) + w.indentNext = false + } + w.outWriter.Write(line) + } + if found { + w.outWriter.Write(newline) + w.indentNext = true + } + } + } + return w.buffer.Write(b) +} + +func (w *Writer) Truncate() { + w.lock.Lock() + defer w.lock.Unlock() + w.buffer.Reset() +} + +func (w *Writer) Bytes() []byte { + w.lock.Lock() + defer w.lock.Unlock() + b := w.buffer.Bytes() + copied := make([]byte, len(b)) + copy(copied, b) + return copied +} + +// GinkgoWriterInterface +func (w *Writer) TeeTo(writer io.Writer) { + w.lock.Lock() + defer w.lock.Unlock() + + w.teeWriters = append(w.teeWriters, writer) +} + +func (w *Writer) ClearTeeWriters() { + w.lock.Lock() + defer w.lock.Unlock() + + w.teeWriters = []io.Writer{} +} + +func (w *Writer) Print(a ...interface{}) { + fmt.Fprint(w, a...) +} + +func (w *Writer) Printf(format string, a ...interface{}) { + fmt.Fprintf(w, format, a...) +} + +func (w *Writer) Println(a ...interface{}) { + fmt.Fprintln(w, a...) +} + +func GinkgoLogrFunc(writer *Writer) logr.Logger { + return funcr.New(func(prefix, args string) { + writer.Printf("%s", args) + }, funcr.Options{}) +} diff --git a/vendor/github.com/onsi/ginkgo/v2/reporters/default_reporter.go b/vendor/github.com/onsi/ginkgo/v2/reporters/default_reporter.go new file mode 100644 index 00000000000..b4a0a226afc --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/reporters/default_reporter.go @@ -0,0 +1,750 @@ +/* +Ginkgo's Default Reporter + +A number of command line flags are available to tweak Ginkgo's default output. + +These are documented [here](http://onsi.github.io/ginkgo/#running_tests) +*/ +package reporters + +import ( + "fmt" + "io" + "runtime" + "strings" + "time" + + "github.com/onsi/ginkgo/v2/formatter" + "github.com/onsi/ginkgo/v2/types" +) + +type DefaultReporter struct { + conf types.ReporterConfig + writer io.Writer + + // managing the emission stream + lastChar string + lastEmissionWasDelimiter bool + + // rendering + specDenoter string + retryDenoter string + formatter formatter.Formatter + + runningInParallel bool +} + +func NewDefaultReporterUnderTest(conf types.ReporterConfig, writer io.Writer) *DefaultReporter { + reporter := NewDefaultReporter(conf, writer) + reporter.formatter = formatter.New(formatter.ColorModePassthrough) + + return reporter +} + +func NewDefaultReporter(conf types.ReporterConfig, writer io.Writer) *DefaultReporter { + reporter := &DefaultReporter{ + conf: conf, + writer: writer, + + lastChar: "\n", + lastEmissionWasDelimiter: false, + + specDenoter: "•", + retryDenoter: "↺", + formatter: formatter.NewWithNoColorBool(conf.NoColor), + } + if runtime.GOOS == "windows" { + reporter.specDenoter = "+" + reporter.retryDenoter = "R" + } + + return reporter +} + +/* The Reporter Interface */ + +func (r *DefaultReporter) SuiteWillBegin(report types.Report) { + if r.conf.Verbosity().Is(types.VerbosityLevelSuccinct) { + r.emit(r.f("[%d] {{bold}}%s{{/}} ", report.SuiteConfig.RandomSeed, report.SuiteDescription)) + if len(report.SuiteLabels) > 0 { + r.emit(r.f("{{coral}}[%s]{{/}} ", strings.Join(report.SuiteLabels, ", "))) + } + r.emit(r.f("- %d/%d specs ", report.PreRunStats.SpecsThatWillRun, report.PreRunStats.TotalSpecs)) + if report.SuiteConfig.ParallelTotal > 1 { + r.emit(r.f("- %d procs ", report.SuiteConfig.ParallelTotal)) + } + } else { + banner := r.f("Running Suite: %s - %s", report.SuiteDescription, report.SuitePath) + r.emitBlock(banner) + bannerWidth := len(banner) + if len(report.SuiteLabels) > 0 { + labels := strings.Join(report.SuiteLabels, ", ") + r.emitBlock(r.f("{{coral}}[%s]{{/}} ", labels)) + if len(labels)+2 > bannerWidth { + bannerWidth = len(labels) + 2 + } + } + r.emitBlock(strings.Repeat("=", bannerWidth)) + + out := r.f("Random Seed: {{bold}}%d{{/}}", report.SuiteConfig.RandomSeed) + if report.SuiteConfig.RandomizeAllSpecs { + out += r.f(" - will randomize all specs") + } + r.emitBlock(out) + r.emit("\n") + r.emitBlock(r.f("Will run {{bold}}%d{{/}} of {{bold}}%d{{/}} specs", report.PreRunStats.SpecsThatWillRun, report.PreRunStats.TotalSpecs)) + if report.SuiteConfig.ParallelTotal > 1 { + r.emitBlock(r.f("Running in parallel across {{bold}}%d{{/}} processes", report.SuiteConfig.ParallelTotal)) + } + } +} + +func (r *DefaultReporter) SuiteDidEnd(report types.Report) { + failures := report.SpecReports.WithState(types.SpecStateFailureStates) + if len(failures) > 0 { + r.emitBlock("\n") + if len(failures) > 1 { + r.emitBlock(r.f("{{red}}{{bold}}Summarizing %d Failures:{{/}}", len(failures))) + } else { + r.emitBlock(r.f("{{red}}{{bold}}Summarizing 1 Failure:{{/}}")) + } + for _, specReport := range failures { + highlightColor, heading := "{{red}}", "[FAIL]" + switch specReport.State { + case types.SpecStatePanicked: + highlightColor, heading = "{{magenta}}", "[PANICKED!]" + case types.SpecStateAborted: + highlightColor, heading = "{{coral}}", "[ABORTED]" + case types.SpecStateTimedout: + highlightColor, heading = "{{orange}}", "[TIMEDOUT]" + case types.SpecStateInterrupted: + highlightColor, heading = "{{orange}}", "[INTERRUPTED]" + } + locationBlock := r.codeLocationBlock(specReport, highlightColor, false, true) + r.emitBlock(r.fi(1, highlightColor+"%s{{/}} %s", heading, locationBlock)) + } + } + + //summarize the suite + if r.conf.Verbosity().Is(types.VerbosityLevelSuccinct) && report.SuiteSucceeded { + r.emit(r.f(" {{green}}SUCCESS!{{/}} %s ", report.RunTime)) + return + } + + r.emitBlock("\n") + color, status := "{{green}}{{bold}}", "SUCCESS!" + if !report.SuiteSucceeded { + color, status = "{{red}}{{bold}}", "FAIL!" + } + + specs := report.SpecReports.WithLeafNodeType(types.NodeTypeIt) //exclude any suite setup nodes + r.emitBlock(r.f(color+"Ran %d of %d Specs in %.3f seconds{{/}}", + specs.CountWithState(types.SpecStatePassed)+specs.CountWithState(types.SpecStateFailureStates), + report.PreRunStats.TotalSpecs, + report.RunTime.Seconds()), + ) + + switch len(report.SpecialSuiteFailureReasons) { + case 0: + r.emit(r.f(color+"%s{{/}} -- ", status)) + case 1: + r.emit(r.f(color+"%s - %s{{/}} -- ", status, report.SpecialSuiteFailureReasons[0])) + default: + r.emitBlock(r.f(color+"%s - %s{{/}}\n", status, strings.Join(report.SpecialSuiteFailureReasons, ", "))) + } + + if len(specs) == 0 && report.SpecReports.WithLeafNodeType(types.NodeTypeBeforeSuite|types.NodeTypeSynchronizedBeforeSuite).CountWithState(types.SpecStateFailureStates) > 0 { + r.emit(r.f("{{cyan}}{{bold}}A BeforeSuite node failed so all tests were skipped.{{/}}\n")) + } else { + r.emit(r.f("{{green}}{{bold}}%d Passed{{/}} | ", specs.CountWithState(types.SpecStatePassed))) + r.emit(r.f("{{red}}{{bold}}%d Failed{{/}} | ", specs.CountWithState(types.SpecStateFailureStates))) + if specs.CountOfFlakedSpecs() > 0 { + r.emit(r.f("{{light-yellow}}{{bold}}%d Flaked{{/}} | ", specs.CountOfFlakedSpecs())) + } + if specs.CountOfRepeatedSpecs() > 0 { + r.emit(r.f("{{light-yellow}}{{bold}}%d Repeated{{/}} | ", specs.CountOfRepeatedSpecs())) + } + r.emit(r.f("{{yellow}}{{bold}}%d Pending{{/}} | ", specs.CountWithState(types.SpecStatePending))) + r.emit(r.f("{{cyan}}{{bold}}%d Skipped{{/}}\n", specs.CountWithState(types.SpecStateSkipped))) + } +} + +func (r *DefaultReporter) WillRun(report types.SpecReport) { + v := r.conf.Verbosity() + if v.LT(types.VerbosityLevelVerbose) || report.State.Is(types.SpecStatePending|types.SpecStateSkipped) || report.RunningInParallel { + return + } + + r.emitDelimiter(0) + r.emitBlock(r.f(r.codeLocationBlock(report, "{{/}}", v.Is(types.VerbosityLevelVeryVerbose), false))) +} + +func (r *DefaultReporter) DidRun(report types.SpecReport) { + v := r.conf.Verbosity() + inParallel := report.RunningInParallel + + header := r.specDenoter + if report.LeafNodeType.Is(types.NodeTypesForSuiteLevelNodes) { + header = fmt.Sprintf("[%s]", report.LeafNodeType) + } + highlightColor := r.highlightColorForState(report.State) + + // have we already been streaming the timeline? + timelineHasBeenStreaming := v.GTE(types.VerbosityLevelVerbose) && !inParallel + + // should we show the timeline? + var timeline types.Timeline + showTimeline := !timelineHasBeenStreaming && (v.GTE(types.VerbosityLevelVerbose) || report.Failed()) + if showTimeline { + timeline = report.Timeline().WithoutHiddenReportEntries() + keepVeryVerboseSpecEvents := v.Is(types.VerbosityLevelVeryVerbose) || + (v.Is(types.VerbosityLevelVerbose) && r.conf.ShowNodeEvents) || + (report.Failed() && r.conf.ShowNodeEvents) + if !keepVeryVerboseSpecEvents { + timeline = timeline.WithoutVeryVerboseSpecEvents() + } + if len(timeline) == 0 && report.CapturedGinkgoWriterOutput == "" { + // the timeline is completely empty - don't show it + showTimeline = false + } + if v.LT(types.VerbosityLevelVeryVerbose) && report.CapturedGinkgoWriterOutput == "" && len(timeline) > 0 { + //if we aren't -vv and the timeline only has a single failure, don't show it as it will appear at the end of the report + failure, isFailure := timeline[0].(types.Failure) + if isFailure && (len(timeline) == 1 || (len(timeline) == 2 && failure.AdditionalFailure != nil)) { + showTimeline = false + } + } + } + + // should we have a separate section for always-visible reports? + showSeparateVisibilityAlwaysReportsSection := !timelineHasBeenStreaming && !showTimeline && report.ReportEntries.HasVisibility(types.ReportEntryVisibilityAlways) + + // should we have a separate section for captured stdout/stderr + showSeparateStdSection := inParallel && (report.CapturedStdOutErr != "") + + // given all that - do we have any actual content to show? or are we a single denoter in a stream? + reportHasContent := v.GTE(types.VerbosityLevelVerbose) || showTimeline || showSeparateVisibilityAlwaysReportsSection || showSeparateStdSection || report.Failed() + + // should we show a runtime? + includeRuntime := !report.State.Is(types.SpecStateSkipped|types.SpecStatePending) || (report.State.Is(types.SpecStateSkipped) && report.Failure.Message != "") + + // should we show the codelocation block? + showCodeLocation := !timelineHasBeenStreaming || !report.State.Is(types.SpecStatePassed) + + switch report.State { + case types.SpecStatePassed: + if report.LeafNodeType.Is(types.NodeTypesForSuiteLevelNodes) && !reportHasContent { + return + } + if report.LeafNodeType.Is(types.NodeTypesForSuiteLevelNodes) { + header = fmt.Sprintf("%s PASSED", header) + } + if report.NumAttempts > 1 && report.MaxFlakeAttempts > 1 { + header, reportHasContent = fmt.Sprintf("%s [FLAKEY TEST - TOOK %d ATTEMPTS TO PASS]", r.retryDenoter, report.NumAttempts), true + } + case types.SpecStatePending: + header = "P" + if v.GT(types.VerbosityLevelSuccinct) { + header, reportHasContent = "P [PENDING]", true + } + case types.SpecStateSkipped: + header = "S" + if v.GTE(types.VerbosityLevelVerbose) { + header = "S [SKIPPED]" + } + default: + header = fmt.Sprintf("%s [%s]", header, r.humanReadableState(report.State)) + if report.MaxMustPassRepeatedly > 1 { + header = fmt.Sprintf("%s DURING REPETITION #%d", header, report.NumAttempts) + } + } + + // If we have no content to show, jsut emit the header and return + if !reportHasContent { + r.emit(r.f(highlightColor + header + "{{/}}")) + return + } + + if includeRuntime { + header = r.f("%s [%.3f seconds]", header, report.RunTime.Seconds()) + } + + // Emit header + if !timelineHasBeenStreaming { + r.emitDelimiter(0) + } + r.emitBlock(r.f(highlightColor + header + "{{/}}")) + if showCodeLocation { + r.emitBlock(r.codeLocationBlock(report, highlightColor, v.Is(types.VerbosityLevelVeryVerbose), false)) + } + + //Emit Stdout/Stderr Output + if showSeparateStdSection { + r.emitBlock("\n") + r.emitBlock(r.fi(1, "{{gray}}Captured StdOut/StdErr Output >>{{/}}")) + r.emitBlock(r.fi(1, "%s", report.CapturedStdOutErr)) + r.emitBlock(r.fi(1, "{{gray}}<< Captured StdOut/StdErr Output{{/}}")) + } + + if showSeparateVisibilityAlwaysReportsSection { + r.emitBlock("\n") + r.emitBlock(r.fi(1, "{{gray}}Report Entries >>{{/}}")) + for _, entry := range report.ReportEntries.WithVisibility(types.ReportEntryVisibilityAlways) { + r.emitReportEntry(1, entry) + } + r.emitBlock(r.fi(1, "{{gray}}<< Report Entries{{/}}")) + } + + if showTimeline { + r.emitBlock("\n") + r.emitBlock(r.fi(1, "{{gray}}Timeline >>{{/}}")) + r.emitTimeline(1, report, timeline) + r.emitBlock(r.fi(1, "{{gray}}<< Timeline{{/}}")) + } + + // Emit Failure Message + if !report.Failure.IsZero() && !v.Is(types.VerbosityLevelVeryVerbose) { + r.emitBlock("\n") + r.emitFailure(1, report.State, report.Failure, true) + if len(report.AdditionalFailures) > 0 { + r.emitBlock(r.fi(1, "\nThere were {{bold}}{{red}}additional failures{{/}} detected. To view them in detail run {{bold}}ginkgo -vv{{/}}")) + } + } + + r.emitDelimiter(0) +} + +func (r *DefaultReporter) highlightColorForState(state types.SpecState) string { + switch state { + case types.SpecStatePassed: + return "{{green}}" + case types.SpecStatePending: + return "{{yellow}}" + case types.SpecStateSkipped: + return "{{cyan}}" + case types.SpecStateFailed: + return "{{red}}" + case types.SpecStateTimedout: + return "{{orange}}" + case types.SpecStatePanicked: + return "{{magenta}}" + case types.SpecStateInterrupted: + return "{{orange}}" + case types.SpecStateAborted: + return "{{coral}}" + default: + return "{{gray}}" + } +} + +func (r *DefaultReporter) humanReadableState(state types.SpecState) string { + return strings.ToUpper(state.String()) +} + +func (r *DefaultReporter) emitTimeline(indent uint, report types.SpecReport, timeline types.Timeline) { + isVeryVerbose := r.conf.Verbosity().Is(types.VerbosityLevelVeryVerbose) + gw := report.CapturedGinkgoWriterOutput + cursor := 0 + for _, entry := range timeline { + tl := entry.GetTimelineLocation() + if tl.Offset < len(gw) { + r.emit(r.fi(indent, "%s", gw[cursor:tl.Offset])) + cursor = tl.Offset + } else if cursor < len(gw) { + r.emit(r.fi(indent, "%s", gw[cursor:])) + cursor = len(gw) + } + switch x := entry.(type) { + case types.Failure: + if isVeryVerbose { + r.emitFailure(indent, report.State, x, false) + } else { + r.emitShortFailure(indent, report.State, x) + } + case types.AdditionalFailure: + if isVeryVerbose { + r.emitFailure(indent, x.State, x.Failure, true) + } else { + r.emitShortFailure(indent, x.State, x.Failure) + } + case types.ReportEntry: + r.emitReportEntry(indent, x) + case types.ProgressReport: + r.emitProgressReport(indent, false, x) + case types.SpecEvent: + if isVeryVerbose || !x.IsOnlyVisibleAtVeryVerbose() || r.conf.ShowNodeEvents { + r.emitSpecEvent(indent, x, isVeryVerbose) + } + } + } + if cursor < len(gw) { + r.emit(r.fi(indent, "%s", gw[cursor:])) + } +} + +func (r *DefaultReporter) EmitFailure(state types.SpecState, failure types.Failure) { + if r.conf.Verbosity().Is(types.VerbosityLevelVerbose) { + r.emitShortFailure(1, state, failure) + } else if r.conf.Verbosity().Is(types.VerbosityLevelVeryVerbose) { + r.emitFailure(1, state, failure, true) + } +} + +func (r *DefaultReporter) emitShortFailure(indent uint, state types.SpecState, failure types.Failure) { + r.emitBlock(r.fi(indent, r.highlightColorForState(state)+"[%s]{{/}} in [%s] - %s {{gray}}@ %s{{/}}", + r.humanReadableState(state), + failure.FailureNodeType, + failure.Location, + failure.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT), + )) +} + +func (r *DefaultReporter) emitFailure(indent uint, state types.SpecState, failure types.Failure, includeAdditionalFailure bool) { + highlightColor := r.highlightColorForState(state) + r.emitBlock(r.fi(indent, highlightColor+"[%s] %s{{/}}", r.humanReadableState(state), failure.Message)) + r.emitBlock(r.fi(indent, highlightColor+"In {{bold}}[%s]{{/}}"+highlightColor+" at: {{bold}}%s{{/}} {{gray}}@ %s{{/}}\n", failure.FailureNodeType, failure.Location, failure.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT))) + if failure.ForwardedPanic != "" { + r.emitBlock("\n") + r.emitBlock(r.fi(indent, highlightColor+"%s{{/}}", failure.ForwardedPanic)) + } + + if r.conf.FullTrace || failure.ForwardedPanic != "" { + r.emitBlock("\n") + r.emitBlock(r.fi(indent, highlightColor+"Full Stack Trace{{/}}")) + r.emitBlock(r.fi(indent+1, "%s", failure.Location.FullStackTrace)) + } + + if !failure.ProgressReport.IsZero() { + r.emitBlock("\n") + r.emitProgressReport(indent, false, failure.ProgressReport) + } + + if failure.AdditionalFailure != nil && includeAdditionalFailure { + r.emitBlock("\n") + r.emitFailure(indent, failure.AdditionalFailure.State, failure.AdditionalFailure.Failure, true) + } +} + +func (r *DefaultReporter) EmitProgressReport(report types.ProgressReport) { + r.emitDelimiter(1) + + if report.RunningInParallel { + r.emit(r.fi(1, "{{coral}}Progress Report for Ginkgo Process #{{bold}}%d{{/}}\n", report.ParallelProcess)) + } + shouldEmitGW := report.RunningInParallel || r.conf.Verbosity().LT(types.VerbosityLevelVerbose) + r.emitProgressReport(1, shouldEmitGW, report) + r.emitDelimiter(1) +} + +func (r *DefaultReporter) emitProgressReport(indent uint, emitGinkgoWriterOutput bool, report types.ProgressReport) { + if report.Message != "" { + r.emitBlock(r.fi(indent, report.Message+"\n")) + indent += 1 + } + if report.LeafNodeText != "" { + subjectIndent := indent + if len(report.ContainerHierarchyTexts) > 0 { + r.emit(r.fi(indent, r.cycleJoin(report.ContainerHierarchyTexts, " "))) + r.emit(" ") + subjectIndent = 0 + } + r.emit(r.fi(subjectIndent, "{{bold}}{{orange}}%s{{/}} (Spec Runtime: %s)\n", report.LeafNodeText, report.Time().Sub(report.SpecStartTime).Round(time.Millisecond))) + r.emit(r.fi(indent+1, "{{gray}}%s{{/}}\n", report.LeafNodeLocation)) + indent += 1 + } + if report.CurrentNodeType != types.NodeTypeInvalid { + r.emit(r.fi(indent, "In {{bold}}{{orange}}[%s]{{/}}", report.CurrentNodeType)) + if report.CurrentNodeText != "" && !report.CurrentNodeType.Is(types.NodeTypeIt) { + r.emit(r.f(" {{bold}}{{orange}}%s{{/}}", report.CurrentNodeText)) + } + + r.emit(r.f(" (Node Runtime: %s)\n", report.Time().Sub(report.CurrentNodeStartTime).Round(time.Millisecond))) + r.emit(r.fi(indent+1, "{{gray}}%s{{/}}\n", report.CurrentNodeLocation)) + indent += 1 + } + if report.CurrentStepText != "" { + r.emit(r.fi(indent, "At {{bold}}{{orange}}[By Step] %s{{/}} (Step Runtime: %s)\n", report.CurrentStepText, report.Time().Sub(report.CurrentStepStartTime).Round(time.Millisecond))) + r.emit(r.fi(indent+1, "{{gray}}%s{{/}}\n", report.CurrentStepLocation)) + indent += 1 + } + + if indent > 0 { + indent -= 1 + } + + if emitGinkgoWriterOutput && report.CapturedGinkgoWriterOutput != "" { + r.emit("\n") + r.emitBlock(r.fi(indent, "{{gray}}Begin Captured GinkgoWriter Output >>{{/}}")) + limit, lines := 10, strings.Split(report.CapturedGinkgoWriterOutput, "\n") + if len(lines) <= limit { + r.emitBlock(r.fi(indent+1, "%s", report.CapturedGinkgoWriterOutput)) + } else { + r.emitBlock(r.fi(indent+1, "{{gray}}...{{/}}")) + for _, line := range lines[len(lines)-limit-1:] { + r.emitBlock(r.fi(indent+1, "%s", line)) + } + } + r.emitBlock(r.fi(indent, "{{gray}}<< End Captured GinkgoWriter Output{{/}}")) + } + + if !report.SpecGoroutine().IsZero() { + r.emit("\n") + r.emit(r.fi(indent, "{{bold}}{{underline}}Spec Goroutine{{/}}\n")) + r.emitGoroutines(indent, report.SpecGoroutine()) + } + + if len(report.AdditionalReports) > 0 { + r.emit("\n") + r.emitBlock(r.fi(indent, "{{gray}}Begin Additional Progress Reports >>{{/}}")) + for i, additionalReport := range report.AdditionalReports { + r.emit(r.fi(indent+1, additionalReport)) + if i < len(report.AdditionalReports)-1 { + r.emitBlock(r.fi(indent+1, "{{gray}}%s{{/}}", strings.Repeat("-", 10))) + } + } + r.emitBlock(r.fi(indent, "{{gray}}<< End Additional Progress Reports{{/}}")) + } + + highlightedGoroutines := report.HighlightedGoroutines() + if len(highlightedGoroutines) > 0 { + r.emit("\n") + r.emit(r.fi(indent, "{{bold}}{{underline}}Goroutines of Interest{{/}}\n")) + r.emitGoroutines(indent, highlightedGoroutines...) + } + + otherGoroutines := report.OtherGoroutines() + if len(otherGoroutines) > 0 { + r.emit("\n") + r.emit(r.fi(indent, "{{gray}}{{bold}}{{underline}}Other Goroutines{{/}}\n")) + r.emitGoroutines(indent, otherGoroutines...) + } +} + +func (r *DefaultReporter) EmitReportEntry(entry types.ReportEntry) { + if r.conf.Verbosity().LT(types.VerbosityLevelVerbose) || entry.Visibility == types.ReportEntryVisibilityNever { + return + } + r.emitReportEntry(1, entry) +} + +func (r *DefaultReporter) emitReportEntry(indent uint, entry types.ReportEntry) { + r.emitBlock(r.fi(indent, "{{bold}}"+entry.Name+"{{gray}} - %s @ %s{{/}}", entry.Location, entry.Time.Format(types.GINKGO_TIME_FORMAT))) + if representation := entry.StringRepresentation(); representation != "" { + r.emitBlock(r.fi(indent+1, representation)) + } +} + +func (r *DefaultReporter) EmitSpecEvent(event types.SpecEvent) { + v := r.conf.Verbosity() + if v.Is(types.VerbosityLevelVeryVerbose) || (v.Is(types.VerbosityLevelVerbose) && (r.conf.ShowNodeEvents || !event.IsOnlyVisibleAtVeryVerbose())) { + r.emitSpecEvent(1, event, r.conf.Verbosity().Is(types.VerbosityLevelVeryVerbose)) + } +} + +func (r *DefaultReporter) emitSpecEvent(indent uint, event types.SpecEvent, includeLocation bool) { + location := "" + if includeLocation { + location = fmt.Sprintf("- %s ", event.CodeLocation.String()) + } + switch event.SpecEventType { + case types.SpecEventInvalid: + return + case types.SpecEventByStart: + r.emitBlock(r.fi(indent, "{{bold}}STEP:{{/}} %s {{gray}}%s@ %s{{/}}", event.Message, location, event.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT))) + case types.SpecEventByEnd: + r.emitBlock(r.fi(indent, "{{bold}}END STEP:{{/}} %s {{gray}}%s@ %s (%s){{/}}", event.Message, location, event.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT), event.Duration.Round(time.Millisecond))) + case types.SpecEventNodeStart: + r.emitBlock(r.fi(indent, "> Enter {{bold}}[%s]{{/}} %s {{gray}}%s@ %s{{/}}", event.NodeType.String(), event.Message, location, event.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT))) + case types.SpecEventNodeEnd: + r.emitBlock(r.fi(indent, "< Exit {{bold}}[%s]{{/}} %s {{gray}}%s@ %s (%s){{/}}", event.NodeType.String(), event.Message, location, event.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT), event.Duration.Round(time.Millisecond))) + case types.SpecEventSpecRepeat: + r.emitBlock(r.fi(indent, "\n{{bold}}Attempt #%d {{green}}Passed{{/}}{{bold}}. Repeating %s{{/}} {{gray}}@ %s{{/}}\n\n", event.Attempt, r.retryDenoter, event.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT))) + case types.SpecEventSpecRetry: + r.emitBlock(r.fi(indent, "\n{{bold}}Attempt #%d {{red}}Failed{{/}}{{bold}}. Retrying %s{{/}} {{gray}}@ %s{{/}}\n\n", event.Attempt, r.retryDenoter, event.TimelineLocation.Time.Format(types.GINKGO_TIME_FORMAT))) + } +} + +func (r *DefaultReporter) emitGoroutines(indent uint, goroutines ...types.Goroutine) { + for idx, g := range goroutines { + color := "{{gray}}" + if g.HasHighlights() { + color = "{{orange}}" + } + r.emit(r.fi(indent, color+"goroutine %d [%s]{{/}}\n", g.ID, g.State)) + for _, fc := range g.Stack { + if fc.Highlight { + r.emit(r.fi(indent, color+"{{bold}}> %s{{/}}\n", fc.Function)) + r.emit(r.fi(indent+2, color+"{{bold}}%s:%d{{/}}\n", fc.Filename, fc.Line)) + r.emitSource(indent+3, fc) + } else { + r.emit(r.fi(indent+1, "{{gray}}%s{{/}}\n", fc.Function)) + r.emit(r.fi(indent+2, "{{gray}}%s:%d{{/}}\n", fc.Filename, fc.Line)) + } + } + + if idx+1 < len(goroutines) { + r.emit("\n") + } + } +} + +func (r *DefaultReporter) emitSource(indent uint, fc types.FunctionCall) { + lines := fc.Source + if len(lines) == 0 { + return + } + + lTrim := 100000 + for _, line := range lines { + lTrimLine := len(line) - len(strings.TrimLeft(line, " \t")) + if lTrimLine < lTrim && len(line) > 0 { + lTrim = lTrimLine + } + } + if lTrim == 100000 { + lTrim = 0 + } + + for idx, line := range lines { + if len(line) > lTrim { + line = line[lTrim:] + } + if idx == fc.SourceHighlight { + r.emit(r.fi(indent, "{{bold}}{{orange}}> %s{{/}}\n", line)) + } else { + r.emit(r.fi(indent, "| %s\n", line)) + } + } +} + +/* Emitting to the writer */ +func (r *DefaultReporter) emit(s string) { + if len(s) > 0 { + r.lastChar = s[len(s)-1:] + r.lastEmissionWasDelimiter = false + r.writer.Write([]byte(s)) + } +} + +func (r *DefaultReporter) emitBlock(s string) { + if len(s) > 0 { + if r.lastChar != "\n" { + r.emit("\n") + } + r.emit(s) + if r.lastChar != "\n" { + r.emit("\n") + } + } +} + +func (r *DefaultReporter) emitDelimiter(indent uint) { + if r.lastEmissionWasDelimiter { + return + } + r.emitBlock(r.fi(indent, "{{gray}}%s{{/}}", strings.Repeat("-", 30))) + r.lastEmissionWasDelimiter = true +} + +/* Rendering text */ +func (r *DefaultReporter) f(format string, args ...interface{}) string { + return r.formatter.F(format, args...) +} + +func (r *DefaultReporter) fi(indentation uint, format string, args ...interface{}) string { + return r.formatter.Fi(indentation, format, args...) +} + +func (r *DefaultReporter) cycleJoin(elements []string, joiner string) string { + return r.formatter.CycleJoin(elements, joiner, []string{"{{/}}", "{{gray}}"}) +} + +func (r *DefaultReporter) codeLocationBlock(report types.SpecReport, highlightColor string, veryVerbose bool, usePreciseFailureLocation bool) string { + texts, locations, labels := []string{}, []types.CodeLocation{}, [][]string{} + texts, locations, labels = append(texts, report.ContainerHierarchyTexts...), append(locations, report.ContainerHierarchyLocations...), append(labels, report.ContainerHierarchyLabels...) + + if report.LeafNodeType.Is(types.NodeTypesForSuiteLevelNodes) { + texts = append(texts, r.f("[%s] %s", report.LeafNodeType, report.LeafNodeText)) + } else { + texts = append(texts, r.f(report.LeafNodeText)) + } + labels = append(labels, report.LeafNodeLabels) + locations = append(locations, report.LeafNodeLocation) + + failureLocation := report.Failure.FailureNodeLocation + if usePreciseFailureLocation { + failureLocation = report.Failure.Location + } + + highlightIndex := -1 + switch report.Failure.FailureNodeContext { + case types.FailureNodeAtTopLevel: + texts = append([]string{fmt.Sprintf("TOP-LEVEL [%s]", report.Failure.FailureNodeType)}, texts...) + locations = append([]types.CodeLocation{failureLocation}, locations...) + labels = append([][]string{{}}, labels...) + highlightIndex = 0 + case types.FailureNodeInContainer: + i := report.Failure.FailureNodeContainerIndex + texts[i] = fmt.Sprintf("%s [%s]", texts[i], report.Failure.FailureNodeType) + locations[i] = failureLocation + highlightIndex = i + case types.FailureNodeIsLeafNode: + i := len(texts) - 1 + texts[i] = fmt.Sprintf("[%s] %s", report.LeafNodeType, report.LeafNodeText) + locations[i] = failureLocation + highlightIndex = i + default: + //there is no failure, so we highlight the leaf ndoe + highlightIndex = len(texts) - 1 + } + + out := "" + if veryVerbose { + for i := range texts { + if i == highlightIndex { + out += r.fi(uint(i), highlightColor+"{{bold}}%s{{/}}", texts[i]) + } else { + out += r.fi(uint(i), "%s", texts[i]) + } + if len(labels[i]) > 0 { + out += r.f(" {{coral}}[%s]{{/}}", strings.Join(labels[i], ", ")) + } + out += "\n" + out += r.fi(uint(i), "{{gray}}%s{{/}}\n", locations[i]) + } + } else { + for i := range texts { + style := "{{/}}" + if i%2 == 1 { + style = "{{gray}}" + } + if i == highlightIndex { + style = highlightColor + "{{bold}}" + } + out += r.f(style+"%s", texts[i]) + if i < len(texts)-1 { + out += " " + } else { + out += r.f("{{/}}") + } + } + flattenedLabels := report.Labels() + if len(flattenedLabels) > 0 { + out += r.f(" {{coral}}[%s]{{/}}", strings.Join(flattenedLabels, ", ")) + } + out += "\n" + if usePreciseFailureLocation { + out += r.f("{{gray}}%s{{/}}", failureLocation) + } else { + leafLocation := locations[len(locations)-1] + if (report.Failure.FailureNodeLocation != types.CodeLocation{}) && (report.Failure.FailureNodeLocation != leafLocation) { + out += r.fi(1, highlightColor+"[%s]{{/}} {{gray}}%s{{/}}\n", report.Failure.FailureNodeType, report.Failure.FailureNodeLocation) + out += r.fi(1, "{{gray}}[%s] %s{{/}}", report.LeafNodeType, leafLocation) + } else { + out += r.f("{{gray}}%s{{/}}", leafLocation) + } + } + + } + return out +} diff --git a/vendor/github.com/onsi/ginkgo/v2/reporters/deprecated_reporter.go b/vendor/github.com/onsi/ginkgo/v2/reporters/deprecated_reporter.go new file mode 100644 index 00000000000..613072ebf1c --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/reporters/deprecated_reporter.go @@ -0,0 +1,149 @@ +package reporters + +import ( + "github.com/onsi/ginkgo/v2/config" + "github.com/onsi/ginkgo/v2/types" +) + +// Deprecated: DeprecatedReporter was how Ginkgo V1 provided support for CustomReporters +// this has been removed in V2. +// Please read the documentation at: +// https://onsi.github.io/ginkgo/MIGRATING_TO_V2#removed-custom-reporters +// for Ginkgo's new behavior and for a migration path. +type DeprecatedReporter interface { + SuiteWillBegin(config config.GinkgoConfigType, summary *types.SuiteSummary) + BeforeSuiteDidRun(setupSummary *types.SetupSummary) + SpecWillRun(specSummary *types.SpecSummary) + SpecDidComplete(specSummary *types.SpecSummary) + AfterSuiteDidRun(setupSummary *types.SetupSummary) + SuiteDidEnd(summary *types.SuiteSummary) +} + +// ReportViaDeprecatedReporter takes a V1 custom reporter and a V2 report and +// calls the custom reporter's methods with appropriately transformed data from the V2 report. +// +// ReportViaDeprecatedReporter should be called in a `ReportAfterSuite()` +// +// Deprecated: ReportViaDeprecatedReporter method exists to help developer bridge between deprecated V1 functionality and the new +// reporting support in V2. It will be removed in a future minor version of Ginkgo. +func ReportViaDeprecatedReporter(reporter DeprecatedReporter, report types.Report) { + conf := config.DeprecatedGinkgoConfigType{ + RandomSeed: report.SuiteConfig.RandomSeed, + RandomizeAllSpecs: report.SuiteConfig.RandomizeAllSpecs, + FocusStrings: report.SuiteConfig.FocusStrings, + SkipStrings: report.SuiteConfig.SkipStrings, + FailOnPending: report.SuiteConfig.FailOnPending, + FailFast: report.SuiteConfig.FailFast, + FlakeAttempts: report.SuiteConfig.FlakeAttempts, + EmitSpecProgress: false, + DryRun: report.SuiteConfig.DryRun, + ParallelNode: report.SuiteConfig.ParallelProcess, + ParallelTotal: report.SuiteConfig.ParallelTotal, + SyncHost: report.SuiteConfig.ParallelHost, + StreamHost: report.SuiteConfig.ParallelHost, + } + + summary := &types.DeprecatedSuiteSummary{ + SuiteDescription: report.SuiteDescription, + SuiteID: report.SuitePath, + + NumberOfSpecsBeforeParallelization: report.PreRunStats.TotalSpecs, + NumberOfTotalSpecs: report.PreRunStats.TotalSpecs, + NumberOfSpecsThatWillBeRun: report.PreRunStats.SpecsThatWillRun, + } + + reporter.SuiteWillBegin(conf, summary) + + for _, spec := range report.SpecReports { + switch spec.LeafNodeType { + case types.NodeTypeBeforeSuite, types.NodeTypeSynchronizedBeforeSuite: + setupSummary := &types.DeprecatedSetupSummary{ + ComponentType: spec.LeafNodeType, + CodeLocation: spec.LeafNodeLocation, + State: spec.State, + RunTime: spec.RunTime, + Failure: failureFor(spec), + CapturedOutput: spec.CombinedOutput(), + SuiteID: report.SuitePath, + } + reporter.BeforeSuiteDidRun(setupSummary) + case types.NodeTypeAfterSuite, types.NodeTypeSynchronizedAfterSuite: + setupSummary := &types.DeprecatedSetupSummary{ + ComponentType: spec.LeafNodeType, + CodeLocation: spec.LeafNodeLocation, + State: spec.State, + RunTime: spec.RunTime, + Failure: failureFor(spec), + CapturedOutput: spec.CombinedOutput(), + SuiteID: report.SuitePath, + } + reporter.AfterSuiteDidRun(setupSummary) + case types.NodeTypeIt: + componentTexts, componentCodeLocations := []string{}, []types.CodeLocation{} + componentTexts = append(componentTexts, spec.ContainerHierarchyTexts...) + componentCodeLocations = append(componentCodeLocations, spec.ContainerHierarchyLocations...) + componentTexts = append(componentTexts, spec.LeafNodeText) + componentCodeLocations = append(componentCodeLocations, spec.LeafNodeLocation) + + specSummary := &types.DeprecatedSpecSummary{ + ComponentTexts: componentTexts, + ComponentCodeLocations: componentCodeLocations, + State: spec.State, + RunTime: spec.RunTime, + Failure: failureFor(spec), + NumberOfSamples: spec.NumAttempts, + CapturedOutput: spec.CombinedOutput(), + SuiteID: report.SuitePath, + } + reporter.SpecWillRun(specSummary) + reporter.SpecDidComplete(specSummary) + + switch spec.State { + case types.SpecStatePending: + summary.NumberOfPendingSpecs += 1 + case types.SpecStateSkipped: + summary.NumberOfSkippedSpecs += 1 + case types.SpecStateFailed, types.SpecStatePanicked, types.SpecStateInterrupted: + summary.NumberOfFailedSpecs += 1 + case types.SpecStatePassed: + summary.NumberOfPassedSpecs += 1 + if spec.NumAttempts > 1 { + summary.NumberOfFlakedSpecs += 1 + } + } + } + } + + summary.SuiteSucceeded = report.SuiteSucceeded + summary.RunTime = report.RunTime + + reporter.SuiteDidEnd(summary) +} + +func failureFor(spec types.SpecReport) types.DeprecatedSpecFailure { + if spec.Failure.IsZero() { + return types.DeprecatedSpecFailure{} + } + + index := 0 + switch spec.Failure.FailureNodeContext { + case types.FailureNodeInContainer: + index = spec.Failure.FailureNodeContainerIndex + case types.FailureNodeAtTopLevel: + index = -1 + case types.FailureNodeIsLeafNode: + index = len(spec.ContainerHierarchyTexts) - 1 + if spec.LeafNodeText != "" { + index += 1 + } + } + + return types.DeprecatedSpecFailure{ + Message: spec.Failure.Message, + Location: spec.Failure.Location, + ForwardedPanic: spec.Failure.ForwardedPanic, + ComponentIndex: index, + ComponentType: spec.Failure.FailureNodeType, + ComponentCodeLocation: spec.Failure.FailureNodeLocation, + } +} diff --git a/vendor/github.com/onsi/ginkgo/v2/reporters/json_report.go b/vendor/github.com/onsi/ginkgo/v2/reporters/json_report.go new file mode 100644 index 00000000000..7f96c450fe9 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/reporters/json_report.go @@ -0,0 +1,60 @@ +package reporters + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/onsi/ginkgo/v2/types" +) + +//GenerateJSONReport produces a JSON-formatted report at the passed in destination +func GenerateJSONReport(report types.Report, destination string) error { + f, err := os.Create(destination) + if err != nil { + return err + } + enc := json.NewEncoder(f) + enc.SetIndent("", " ") + err = enc.Encode([]types.Report{ + report, + }) + if err != nil { + return err + } + return f.Close() +} + +//MergeJSONReports produces a single JSON-formatted report at the passed in destination by merging the JSON-formatted reports provided in sources +//It skips over reports that fail to decode but reports on them via the returned messages []string +func MergeAndCleanupJSONReports(sources []string, destination string) ([]string, error) { + messages := []string{} + allReports := []types.Report{} + for _, source := range sources { + reports := []types.Report{} + data, err := os.ReadFile(source) + if err != nil { + messages = append(messages, fmt.Sprintf("Could not open %s:\n%s", source, err.Error())) + continue + } + err = json.Unmarshal(data, &reports) + if err != nil { + messages = append(messages, fmt.Sprintf("Could not decode %s:\n%s", source, err.Error())) + continue + } + os.Remove(source) + allReports = append(allReports, reports...) + } + + f, err := os.Create(destination) + if err != nil { + return messages, err + } + enc := json.NewEncoder(f) + enc.SetIndent("", " ") + err = enc.Encode(allReports) + if err != nil { + return messages, err + } + return messages, f.Close() +} diff --git a/vendor/github.com/onsi/ginkgo/v2/reporters/junit_report.go b/vendor/github.com/onsi/ginkgo/v2/reporters/junit_report.go new file mode 100644 index 00000000000..576962e8f2c --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/reporters/junit_report.go @@ -0,0 +1,349 @@ +/* + +JUnit XML Reporter for Ginkgo + +For usage instructions: http://onsi.github.io/ginkgo/#generating_junit_xml_output + +The schema used for the generated JUnit xml file was adapted from https://llg.cubic.org/docs/junit/ + +*/ + +package reporters + +import ( + "encoding/xml" + "fmt" + "os" + "strings" + + "github.com/onsi/ginkgo/v2/config" + "github.com/onsi/ginkgo/v2/types" +) + +type JunitReportConfig struct { + // Spec States for which no timeline should be emitted for system-err + // set this to types.SpecStatePassed|types.SpecStateSkipped|types.SpecStatePending to only match failing specs + OmitTimelinesForSpecState types.SpecState + + // Enable OmitFailureMessageAttr to prevent failure messages appearing in the "message" attribute of the Failure and Error tags + OmitFailureMessageAttr bool + + //Enable OmitCapturedStdOutErr to prevent captured stdout/stderr appearing in system-out + OmitCapturedStdOutErr bool +} + +type JUnitTestSuites struct { + XMLName xml.Name `xml:"testsuites"` + // Tests maps onto the total number of specs in all test suites (this includes any suite nodes such as BeforeSuite) + Tests int `xml:"tests,attr"` + // Disabled maps onto specs that are pending and/or skipped + Disabled int `xml:"disabled,attr"` + // Errors maps onto specs that panicked or were interrupted + Errors int `xml:"errors,attr"` + // Failures maps onto specs that failed + Failures int `xml:"failures,attr"` + // Time is the time in seconds to execute all test suites + Time float64 `xml:"time,attr"` + + //The set of all test suites + TestSuites []JUnitTestSuite `xml:"testsuite"` +} + +type JUnitTestSuite struct { + // Name maps onto the description of the test suite - maps onto Report.SuiteDescription + Name string `xml:"name,attr"` + // Package maps onto the absolute path to the test suite - maps onto Report.SuitePath + Package string `xml:"package,attr"` + // Tests maps onto the total number of specs in the test suite (this includes any suite nodes such as BeforeSuite) + Tests int `xml:"tests,attr"` + // Disabled maps onto specs that are pending + Disabled int `xml:"disabled,attr"` + // Skiped maps onto specs that are skipped + Skipped int `xml:"skipped,attr"` + // Errors maps onto specs that panicked or were interrupted + Errors int `xml:"errors,attr"` + // Failures maps onto specs that failed + Failures int `xml:"failures,attr"` + // Time is the time in seconds to execute all the test suite - maps onto Report.RunTime + Time float64 `xml:"time,attr"` + // Timestamp is the ISO 8601 formatted start-time of the suite - maps onto Report.StartTime + Timestamp string `xml:"timestamp,attr"` + + //Properties captures the information stored in the rest of the Report type (including SuiteConfig) as key-value pairs + Properties JUnitProperties `xml:"properties"` + + //TestCases capture the individual specs + TestCases []JUnitTestCase `xml:"testcase"` +} + +type JUnitProperties struct { + Properties []JUnitProperty `xml:"property"` +} + +func (jup JUnitProperties) WithName(name string) string { + for _, property := range jup.Properties { + if property.Name == name { + return property.Value + } + } + return "" +} + +type JUnitProperty struct { + Name string `xml:"name,attr"` + Value string `xml:"value,attr"` +} + +type JUnitTestCase struct { + // Name maps onto the full text of the spec - equivalent to "[SpecReport.LeafNodeType] SpecReport.FullText()" + Name string `xml:"name,attr"` + // Classname maps onto the name of the test suite - equivalent to Report.SuiteDescription + Classname string `xml:"classname,attr"` + // Status maps onto the string representation of SpecReport.State + Status string `xml:"status,attr"` + // Time is the time in seconds to execute the spec - maps onto SpecReport.RunTime + Time float64 `xml:"time,attr"` + //Skipped is populated with a message if the test was skipped or pending + Skipped *JUnitSkipped `xml:"skipped,omitempty"` + //Error is populated if the test panicked or was interrupted + Error *JUnitError `xml:"error,omitempty"` + //Failure is populated if the test failed + Failure *JUnitFailure `xml:"failure,omitempty"` + //SystemOut maps onto any captured stdout/stderr output - maps onto SpecReport.CapturedStdOutErr + SystemOut string `xml:"system-out,omitempty"` + //SystemOut maps onto any captured GinkgoWriter output - maps onto SpecReport.CapturedGinkgoWriterOutput + SystemErr string `xml:"system-err,omitempty"` +} + +type JUnitSkipped struct { + // Message maps onto "pending" if the test was marked pending, "skipped" if the test was marked skipped, and "skipped - REASON" if the user called Skip(REASON) + Message string `xml:"message,attr"` +} + +type JUnitError struct { + //Message maps onto the panic/exception thrown - equivalent to SpecReport.Failure.ForwardedPanic - or to "interrupted" + Message string `xml:"message,attr"` + //Type is one of "panicked" or "interrupted" + Type string `xml:"type,attr"` + //Description maps onto the captured stack trace for a panic, or the failure message for an interrupt which will include the dump of running goroutines + Description string `xml:",chardata"` +} + +type JUnitFailure struct { + //Message maps onto the failure message - equivalent to SpecReport.Failure.Message + Message string `xml:"message,attr"` + //Type is "failed" + Type string `xml:"type,attr"` + //Description maps onto the location and stack trace of the failure + Description string `xml:",chardata"` +} + +func GenerateJUnitReport(report types.Report, dst string) error { + return GenerateJUnitReportWithConfig(report, dst, JunitReportConfig{}) +} + +func GenerateJUnitReportWithConfig(report types.Report, dst string, config JunitReportConfig) error { + suite := JUnitTestSuite{ + Name: report.SuiteDescription, + Package: report.SuitePath, + Time: report.RunTime.Seconds(), + Timestamp: report.StartTime.Format("2006-01-02T15:04:05"), + Properties: JUnitProperties{ + Properties: []JUnitProperty{ + {"SuiteSucceeded", fmt.Sprintf("%t", report.SuiteSucceeded)}, + {"SuiteHasProgrammaticFocus", fmt.Sprintf("%t", report.SuiteHasProgrammaticFocus)}, + {"SpecialSuiteFailureReason", strings.Join(report.SpecialSuiteFailureReasons, ",")}, + {"SuiteLabels", fmt.Sprintf("[%s]", strings.Join(report.SuiteLabels, ","))}, + {"RandomSeed", fmt.Sprintf("%d", report.SuiteConfig.RandomSeed)}, + {"RandomizeAllSpecs", fmt.Sprintf("%t", report.SuiteConfig.RandomizeAllSpecs)}, + {"LabelFilter", report.SuiteConfig.LabelFilter}, + {"FocusStrings", strings.Join(report.SuiteConfig.FocusStrings, ",")}, + {"SkipStrings", strings.Join(report.SuiteConfig.SkipStrings, ",")}, + {"FocusFiles", strings.Join(report.SuiteConfig.FocusFiles, ";")}, + {"SkipFiles", strings.Join(report.SuiteConfig.SkipFiles, ";")}, + {"FailOnPending", fmt.Sprintf("%t", report.SuiteConfig.FailOnPending)}, + {"FailFast", fmt.Sprintf("%t", report.SuiteConfig.FailFast)}, + {"FlakeAttempts", fmt.Sprintf("%d", report.SuiteConfig.FlakeAttempts)}, + {"DryRun", fmt.Sprintf("%t", report.SuiteConfig.DryRun)}, + {"ParallelTotal", fmt.Sprintf("%d", report.SuiteConfig.ParallelTotal)}, + {"OutputInterceptorMode", report.SuiteConfig.OutputInterceptorMode}, + }, + }, + } + for _, spec := range report.SpecReports { + name := fmt.Sprintf("[%s]", spec.LeafNodeType) + if spec.FullText() != "" { + name = name + " " + spec.FullText() + } + labels := spec.Labels() + if len(labels) > 0 { + name = name + " [" + strings.Join(labels, ", ") + "]" + } + + test := JUnitTestCase{ + Name: name, + Classname: report.SuiteDescription, + Status: spec.State.String(), + Time: spec.RunTime.Seconds(), + } + if !spec.State.Is(config.OmitTimelinesForSpecState) { + test.SystemErr = systemErrForUnstructuredReporters(spec) + } + if !config.OmitCapturedStdOutErr { + test.SystemOut = systemOutForUnstructuredReporters(spec) + } + suite.Tests += 1 + + switch spec.State { + case types.SpecStateSkipped: + message := "skipped" + if spec.Failure.Message != "" { + message += " - " + spec.Failure.Message + } + test.Skipped = &JUnitSkipped{Message: message} + suite.Skipped += 1 + case types.SpecStatePending: + test.Skipped = &JUnitSkipped{Message: "pending"} + suite.Disabled += 1 + case types.SpecStateFailed: + test.Failure = &JUnitFailure{ + Message: spec.Failure.Message, + Type: "failed", + Description: failureDescriptionForUnstructuredReporters(spec), + } + if config.OmitFailureMessageAttr { + test.Failure.Message = "" + } + suite.Failures += 1 + case types.SpecStateTimedout: + test.Failure = &JUnitFailure{ + Message: spec.Failure.Message, + Type: "timedout", + Description: failureDescriptionForUnstructuredReporters(spec), + } + if config.OmitFailureMessageAttr { + test.Failure.Message = "" + } + suite.Failures += 1 + case types.SpecStateInterrupted: + test.Error = &JUnitError{ + Message: spec.Failure.Message, + Type: "interrupted", + Description: failureDescriptionForUnstructuredReporters(spec), + } + if config.OmitFailureMessageAttr { + test.Error.Message = "" + } + suite.Errors += 1 + case types.SpecStateAborted: + test.Failure = &JUnitFailure{ + Message: spec.Failure.Message, + Type: "aborted", + Description: failureDescriptionForUnstructuredReporters(spec), + } + if config.OmitFailureMessageAttr { + test.Failure.Message = "" + } + suite.Errors += 1 + case types.SpecStatePanicked: + test.Error = &JUnitError{ + Message: spec.Failure.ForwardedPanic, + Type: "panicked", + Description: failureDescriptionForUnstructuredReporters(spec), + } + if config.OmitFailureMessageAttr { + test.Error.Message = "" + } + suite.Errors += 1 + } + + suite.TestCases = append(suite.TestCases, test) + } + + junitReport := JUnitTestSuites{ + Tests: suite.Tests, + Disabled: suite.Disabled + suite.Skipped, + Errors: suite.Errors, + Failures: suite.Failures, + Time: suite.Time, + TestSuites: []JUnitTestSuite{suite}, + } + + f, err := os.Create(dst) + if err != nil { + return err + } + f.WriteString(xml.Header) + encoder := xml.NewEncoder(f) + encoder.Indent(" ", " ") + encoder.Encode(junitReport) + + return f.Close() +} + +func MergeAndCleanupJUnitReports(sources []string, dst string) ([]string, error) { + messages := []string{} + mergedReport := JUnitTestSuites{} + for _, source := range sources { + report := JUnitTestSuites{} + f, err := os.Open(source) + if err != nil { + messages = append(messages, fmt.Sprintf("Could not open %s:\n%s", source, err.Error())) + continue + } + err = xml.NewDecoder(f).Decode(&report) + if err != nil { + messages = append(messages, fmt.Sprintf("Could not decode %s:\n%s", source, err.Error())) + continue + } + os.Remove(source) + + mergedReport.Tests += report.Tests + mergedReport.Disabled += report.Disabled + mergedReport.Errors += report.Errors + mergedReport.Failures += report.Failures + mergedReport.Time += report.Time + mergedReport.TestSuites = append(mergedReport.TestSuites, report.TestSuites...) + } + + f, err := os.Create(dst) + if err != nil { + return messages, err + } + f.WriteString(xml.Header) + encoder := xml.NewEncoder(f) + encoder.Indent(" ", " ") + encoder.Encode(mergedReport) + + return messages, f.Close() +} + +func failureDescriptionForUnstructuredReporters(spec types.SpecReport) string { + out := &strings.Builder{} + NewDefaultReporter(types.ReporterConfig{NoColor: true, VeryVerbose: true}, out).emitFailure(0, spec.State, spec.Failure, true) + if len(spec.AdditionalFailures) > 0 { + out.WriteString("\nThere were additional failures detected after the initial failure. These are visible in the timeline\n") + } + return out.String() +} + +func systemErrForUnstructuredReporters(spec types.SpecReport) string { + out := &strings.Builder{} + NewDefaultReporter(types.ReporterConfig{NoColor: true, VeryVerbose: true}, out).emitTimeline(0, spec, spec.Timeline()) + return out.String() +} + +func systemOutForUnstructuredReporters(spec types.SpecReport) string { + return spec.CapturedStdOutErr +} + +// Deprecated JUnitReporter (so folks can still compile their suites) +type JUnitReporter struct{} + +func NewJUnitReporter(_ string) *JUnitReporter { return &JUnitReporter{} } +func (reporter *JUnitReporter) SuiteWillBegin(_ config.GinkgoConfigType, _ *types.SuiteSummary) {} +func (reporter *JUnitReporter) BeforeSuiteDidRun(_ *types.SetupSummary) {} +func (reporter *JUnitReporter) SpecWillRun(_ *types.SpecSummary) {} +func (reporter *JUnitReporter) SpecDidComplete(_ *types.SpecSummary) {} +func (reporter *JUnitReporter) AfterSuiteDidRun(_ *types.SetupSummary) {} +func (reporter *JUnitReporter) SuiteDidEnd(_ *types.SuiteSummary) {} diff --git a/vendor/github.com/onsi/ginkgo/v2/reporters/reporter.go b/vendor/github.com/onsi/ginkgo/v2/reporters/reporter.go new file mode 100644 index 00000000000..5e726c464ef --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/reporters/reporter.go @@ -0,0 +1,29 @@ +package reporters + +import ( + "github.com/onsi/ginkgo/v2/types" +) + +type Reporter interface { + SuiteWillBegin(report types.Report) + WillRun(report types.SpecReport) + DidRun(report types.SpecReport) + SuiteDidEnd(report types.Report) + + //Timeline emission + EmitFailure(state types.SpecState, failure types.Failure) + EmitProgressReport(progressReport types.ProgressReport) + EmitReportEntry(entry types.ReportEntry) + EmitSpecEvent(event types.SpecEvent) +} + +type NoopReporter struct{} + +func (n NoopReporter) SuiteWillBegin(report types.Report) {} +func (n NoopReporter) WillRun(report types.SpecReport) {} +func (n NoopReporter) DidRun(report types.SpecReport) {} +func (n NoopReporter) SuiteDidEnd(report types.Report) {} +func (n NoopReporter) EmitFailure(state types.SpecState, failure types.Failure) {} +func (n NoopReporter) EmitProgressReport(progressReport types.ProgressReport) {} +func (n NoopReporter) EmitReportEntry(entry types.ReportEntry) {} +func (n NoopReporter) EmitSpecEvent(event types.SpecEvent) {} diff --git a/vendor/github.com/onsi/ginkgo/v2/reporters/teamcity_report.go b/vendor/github.com/onsi/ginkgo/v2/reporters/teamcity_report.go new file mode 100644 index 00000000000..c1863496dc7 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/reporters/teamcity_report.go @@ -0,0 +1,101 @@ +/* + +TeamCity Reporter for Ginkgo + +Makes use of TeamCity's support for Service Messages +http://confluence.jetbrains.com/display/TCD7/Build+Script+Interaction+with+TeamCity#BuildScriptInteractionwithTeamCity-ReportingTests +*/ + +package reporters + +import ( + "fmt" + "os" + "strings" + + "github.com/onsi/ginkgo/v2/types" +) + +func tcEscape(s string) string { + s = strings.ReplaceAll(s, "|", "||") + s = strings.ReplaceAll(s, "'", "|'") + s = strings.ReplaceAll(s, "\n", "|n") + s = strings.ReplaceAll(s, "\r", "|r") + s = strings.ReplaceAll(s, "[", "|[") + s = strings.ReplaceAll(s, "]", "|]") + return s +} + +func GenerateTeamcityReport(report types.Report, dst string) error { + f, err := os.Create(dst) + if err != nil { + return err + } + + name := report.SuiteDescription + labels := report.SuiteLabels + if len(labels) > 0 { + name = name + " [" + strings.Join(labels, ", ") + "]" + } + fmt.Fprintf(f, "##teamcity[testSuiteStarted name='%s']\n", tcEscape(name)) + for _, spec := range report.SpecReports { + name := fmt.Sprintf("[%s]", spec.LeafNodeType) + if spec.FullText() != "" { + name = name + " " + spec.FullText() + } + labels := spec.Labels() + if len(labels) > 0 { + name = name + " [" + strings.Join(labels, ", ") + "]" + } + + name = tcEscape(name) + fmt.Fprintf(f, "##teamcity[testStarted name='%s']\n", name) + switch spec.State { + case types.SpecStatePending: + fmt.Fprintf(f, "##teamcity[testIgnored name='%s' message='pending']\n", name) + case types.SpecStateSkipped: + message := "skipped" + if spec.Failure.Message != "" { + message += " - " + spec.Failure.Message + } + fmt.Fprintf(f, "##teamcity[testIgnored name='%s' message='%s']\n", name, tcEscape(message)) + case types.SpecStateFailed: + details := failureDescriptionForUnstructuredReporters(spec) + fmt.Fprintf(f, "##teamcity[testFailed name='%s' message='failed - %s' details='%s']\n", name, tcEscape(spec.Failure.Message), tcEscape(details)) + case types.SpecStatePanicked: + details := failureDescriptionForUnstructuredReporters(spec) + fmt.Fprintf(f, "##teamcity[testFailed name='%s' message='panicked - %s' details='%s']\n", name, tcEscape(spec.Failure.ForwardedPanic), tcEscape(details)) + case types.SpecStateTimedout: + details := failureDescriptionForUnstructuredReporters(spec) + fmt.Fprintf(f, "##teamcity[testFailed name='%s' message='timedout - %s' details='%s']\n", name, tcEscape(spec.Failure.Message), tcEscape(details)) + case types.SpecStateInterrupted: + details := failureDescriptionForUnstructuredReporters(spec) + fmt.Fprintf(f, "##teamcity[testFailed name='%s' message='interrupted - %s' details='%s']\n", name, tcEscape(spec.Failure.Message), tcEscape(details)) + case types.SpecStateAborted: + details := failureDescriptionForUnstructuredReporters(spec) + fmt.Fprintf(f, "##teamcity[testFailed name='%s' message='aborted - %s' details='%s']\n", name, tcEscape(spec.Failure.Message), tcEscape(details)) + } + + fmt.Fprintf(f, "##teamcity[testStdOut name='%s' out='%s']\n", name, tcEscape(systemOutForUnstructuredReporters(spec))) + fmt.Fprintf(f, "##teamcity[testStdErr name='%s' out='%s']\n", name, tcEscape(systemErrForUnstructuredReporters(spec))) + fmt.Fprintf(f, "##teamcity[testFinished name='%s' duration='%d']\n", name, int(spec.RunTime.Seconds()*1000.0)) + } + fmt.Fprintf(f, "##teamcity[testSuiteFinished name='%s']\n", tcEscape(report.SuiteDescription)) + + return f.Close() +} + +func MergeAndCleanupTeamcityReports(sources []string, dst string) ([]string, error) { + messages := []string{} + merged := []byte{} + for _, source := range sources { + data, err := os.ReadFile(source) + if err != nil { + messages = append(messages, fmt.Sprintf("Could not open %s:\n%s", source, err.Error())) + continue + } + os.Remove(source) + merged = append(merged, data...) + } + return messages, os.WriteFile(dst, merged, 0666) +} diff --git a/vendor/github.com/onsi/ginkgo/v2/reporting_dsl.go b/vendor/github.com/onsi/ginkgo/v2/reporting_dsl.go new file mode 100644 index 00000000000..afc151b1311 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/reporting_dsl.go @@ -0,0 +1,162 @@ +package ginkgo + +import ( + "fmt" + "strings" + + "github.com/onsi/ginkgo/v2/internal" + "github.com/onsi/ginkgo/v2/internal/global" + "github.com/onsi/ginkgo/v2/reporters" + "github.com/onsi/ginkgo/v2/types" +) + +/* +Report represents the report for a Suite. +It is documented here: https://pkg.go.dev/github.com/onsi/ginkgo/v2/types#Report +*/ +type Report = types.Report + +/* +Report represents the report for a Spec. +It is documented here: https://pkg.go.dev/github.com/onsi/ginkgo/v2/types#SpecReport +*/ +type SpecReport = types.SpecReport + +/* +CurrentSpecReport returns information about the current running spec. +The returned object is a types.SpecReport which includes helper methods +to make extracting information about the spec easier. + +You can learn more about SpecReport here: https://pkg.go.dev/github.com/onsi/ginkgo/types#SpecReport +You can learn more about CurrentSpecReport() here: https://onsi.github.io/ginkgo/#getting-a-report-for-the-current-spec +*/ +func CurrentSpecReport() SpecReport { + return global.Suite.CurrentSpecReport() +} + +/* + ReportEntryVisibility governs the visibility of ReportEntries in Ginkgo's console reporter + +- ReportEntryVisibilityAlways: the default behavior - the ReportEntry is always emitted. +- ReportEntryVisibilityFailureOrVerbose: the ReportEntry is only emitted if the spec fails or if the tests are run with -v (similar to GinkgoWriters behavior). +- ReportEntryVisibilityNever: the ReportEntry is never emitted though it appears in any generated machine-readable reports (e.g. by setting `--json-report`). + +You can learn more about Report Entries here: https://onsi.github.io/ginkgo/#attaching-data-to-reports +*/ +type ReportEntryVisibility = types.ReportEntryVisibility + +const ReportEntryVisibilityAlways, ReportEntryVisibilityFailureOrVerbose, ReportEntryVisibilityNever = types.ReportEntryVisibilityAlways, types.ReportEntryVisibilityFailureOrVerbose, types.ReportEntryVisibilityNever + +/* +AddReportEntry generates and adds a new ReportEntry to the current spec's SpecReport. +It can take any of the following arguments: + - A single arbitrary object to attach as the Value of the ReportEntry. This object will be included in any generated reports and will be emitted to the console when the report is emitted. + - A ReportEntryVisibility enum to control the visibility of the ReportEntry + - An Offset or CodeLocation decoration to control the reported location of the ReportEntry + +If the Value object implements `fmt.Stringer`, it's `String()` representation is used when emitting to the console. + +AddReportEntry() must be called within a Subject or Setup node - not in a Container node. + +You can learn more about Report Entries here: https://onsi.github.io/ginkgo/#attaching-data-to-reports +*/ +func AddReportEntry(name string, args ...interface{}) { + cl := types.NewCodeLocation(1) + reportEntry, err := internal.NewReportEntry(name, cl, args...) + if err != nil { + Fail(fmt.Sprintf("Failed to generate Report Entry:\n%s", err.Error()), 1) + } + err = global.Suite.AddReportEntry(reportEntry) + if err != nil { + Fail(fmt.Sprintf("Failed to add Report Entry:\n%s", err.Error()), 1) + } +} + +/* +ReportBeforeEach nodes are run for each spec, even if the spec is skipped or pending. ReportBeforeEach nodes take a function that +receives a SpecReport. They are called before the spec starts. + +You cannot nest any other Ginkgo nodes within a ReportBeforeEach node's closure. +You can learn more about ReportBeforeEach here: https://onsi.github.io/ginkgo/#generating-reports-programmatically +*/ +func ReportBeforeEach(body func(SpecReport), args ...interface{}) bool { + combinedArgs := []interface{}{body} + combinedArgs = append(combinedArgs, args...) + + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeReportBeforeEach, "", combinedArgs...)) +} + +/* +ReportAfterEach nodes are run for each spec, even if the spec is skipped or pending. ReportAfterEach nodes take a function that +receives a SpecReport. They are called after the spec has completed and receive the final report for the spec. + +You cannot nest any other Ginkgo nodes within a ReportAfterEach node's closure. +You can learn more about ReportAfterEach here: https://onsi.github.io/ginkgo/#generating-reports-programmatically +*/ +func ReportAfterEach(body func(SpecReport), args ...interface{}) bool { + combinedArgs := []interface{}{body} + combinedArgs = append(combinedArgs, args...) + + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeReportAfterEach, "", combinedArgs...)) +} + +/* +ReportAfterSuite nodes are run at the end of the suite. ReportAfterSuite nodes take a function that receives a suite Report. + +They are called at the end of the suite, after all specs have run and any AfterSuite or SynchronizedAfterSuite nodes, and are passed in the final report for the suite. +ReportAftersuite nodes must be created at the top-level (i.e. not nested in a Context/Describe/When node) + +When running in parallel, Ginkgo ensures that only one of the parallel nodes runs the ReportAfterSuite and that it is passed a report that is aggregated across +all parallel nodes + +In addition to using ReportAfterSuite to programmatically generate suite reports, you can also generate JSON, JUnit, and Teamcity formatted reports using the --json-report, --junit-report, and --teamcity-report ginkgo CLI flags. + +You cannot nest any other Ginkgo nodes within a ReportAfterSuite node's closure. +You can learn more about ReportAfterSuite here: https://onsi.github.io/ginkgo/#generating-reports-programmatically +You can learn more about Ginkgo's reporting infrastructure, including generating reports with the CLI here: https://onsi.github.io/ginkgo/#generating-machine-readable-reports +*/ +func ReportAfterSuite(text string, body func(Report), args ...interface{}) bool { + combinedArgs := []interface{}{body} + combinedArgs = append(combinedArgs, args...) + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeReportAfterSuite, text, combinedArgs...)) +} + +func registerReportAfterSuiteNodeForAutogeneratedReports(reporterConfig types.ReporterConfig) { + body := func(report Report) { + if reporterConfig.JSONReport != "" { + err := reporters.GenerateJSONReport(report, reporterConfig.JSONReport) + if err != nil { + Fail(fmt.Sprintf("Failed to generate JSON report:\n%s", err.Error())) + } + } + if reporterConfig.JUnitReport != "" { + err := reporters.GenerateJUnitReport(report, reporterConfig.JUnitReport) + if err != nil { + Fail(fmt.Sprintf("Failed to generate JUnit report:\n%s", err.Error())) + } + } + if reporterConfig.TeamcityReport != "" { + err := reporters.GenerateTeamcityReport(report, reporterConfig.TeamcityReport) + if err != nil { + Fail(fmt.Sprintf("Failed to generate Teamcity report:\n%s", err.Error())) + } + } + } + + flags := []string{} + if reporterConfig.JSONReport != "" { + flags = append(flags, "--json-report") + } + if reporterConfig.JUnitReport != "" { + flags = append(flags, "--junit-report") + } + if reporterConfig.TeamcityReport != "" { + flags = append(flags, "--teamcity-report") + } + pushNode(internal.NewNode( + deprecationTracker, types.NodeTypeReportAfterSuite, + fmt.Sprintf("Autogenerated ReportAfterSuite for %s", strings.Join(flags, " ")), + body, + types.NewCustomCodeLocation("autogenerated by Ginkgo"), + )) +} diff --git a/vendor/github.com/onsi/ginkgo/v2/table_dsl.go b/vendor/github.com/onsi/ginkgo/v2/table_dsl.go new file mode 100644 index 00000000000..68367446297 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/table_dsl.go @@ -0,0 +1,302 @@ +package ginkgo + +import ( + "context" + "fmt" + "reflect" + "strings" + + "github.com/onsi/ginkgo/v2/internal" + "github.com/onsi/ginkgo/v2/types" +) + +/* +The EntryDescription decorator allows you to pass a format string to DescribeTable() and Entry(). This format string is used to generate entry names via: + + fmt.Sprintf(formatString, parameters...) + +where parameters are the parameters passed into the entry. + +When passed into an Entry the EntryDescription is used to generate the name or that entry. When passed to DescribeTable, the EntryDescription is used to generate the names for any entries that have `nil` descriptions. + +You can learn more about generating EntryDescriptions here: https://onsi.github.io/ginkgo/#generating-entry-descriptions +*/ +type EntryDescription string + +func (ed EntryDescription) render(args ...interface{}) string { + return fmt.Sprintf(string(ed), args...) +} + +/* +DescribeTable describes a table-driven spec. + +For example: + + DescribeTable("a simple table", + func(x int, y int, expected bool) { + Ω(x > y).Should(Equal(expected)) + }, + Entry("x > y", 1, 0, true), + Entry("x == y", 0, 0, false), + Entry("x < y", 0, 1, false), + ) + +You can learn more about DescribeTable here: https://onsi.github.io/ginkgo/#table-specs +And can explore some Table patterns here: https://onsi.github.io/ginkgo/#table-specs-patterns +*/ +func DescribeTable(description string, args ...interface{}) bool { + generateTable(description, args...) + return true +} + +/* +You can focus a table with `FDescribeTable`. This is equivalent to `FDescribe`. +*/ +func FDescribeTable(description string, args ...interface{}) bool { + args = append(args, internal.Focus) + generateTable(description, args...) + return true +} + +/* +You can mark a table as pending with `PDescribeTable`. This is equivalent to `PDescribe`. +*/ +func PDescribeTable(description string, args ...interface{}) bool { + args = append(args, internal.Pending) + generateTable(description, args...) + return true +} + +/* +You can mark a table as pending with `XDescribeTable`. This is equivalent to `XDescribe`. +*/ +var XDescribeTable = PDescribeTable + +/* +TableEntry represents an entry in a table test. You generally use the `Entry` constructor. +*/ +type TableEntry struct { + description interface{} + decorations []interface{} + parameters []interface{} + codeLocation types.CodeLocation +} + +/* +Entry constructs a TableEntry. + +The first argument is a description. This can be a string, a function that accepts the parameters passed to the TableEntry and returns a string, an EntryDescription format string, or nil. If nil is provided then the name of the Entry is derived using the table-level entry description. +Subsequent arguments accept any Ginkgo decorators. These are filtered out and the remaining arguments are passed into the Spec function associated with the table. + +Each Entry ends up generating an individual Ginkgo It. The body of the it is the Table Body function with the Entry parameters passed in. + +If you want to generate interruptible specs simply write a Table function that accepts a SpecContext as its first argument. You can then decorate individual Entrys with the NodeTimeout and SpecTimeout decorators. + +You can learn more about Entry here: https://onsi.github.io/ginkgo/#table-specs +*/ +func Entry(description interface{}, args ...interface{}) TableEntry { + decorations, parameters := internal.PartitionDecorations(args...) + return TableEntry{description: description, decorations: decorations, parameters: parameters, codeLocation: types.NewCodeLocation(1)} +} + +/* +You can focus a particular entry with FEntry. This is equivalent to FIt. +*/ +func FEntry(description interface{}, args ...interface{}) TableEntry { + decorations, parameters := internal.PartitionDecorations(args...) + decorations = append(decorations, internal.Focus) + return TableEntry{description: description, decorations: decorations, parameters: parameters, codeLocation: types.NewCodeLocation(1)} +} + +/* +You can mark a particular entry as pending with PEntry. This is equivalent to PIt. +*/ +func PEntry(description interface{}, args ...interface{}) TableEntry { + decorations, parameters := internal.PartitionDecorations(args...) + decorations = append(decorations, internal.Pending) + return TableEntry{description: description, decorations: decorations, parameters: parameters, codeLocation: types.NewCodeLocation(1)} +} + +/* +You can mark a particular entry as pending with XEntry. This is equivalent to XIt. +*/ +var XEntry = PEntry + +var contextType = reflect.TypeOf(new(context.Context)).Elem() +var specContextType = reflect.TypeOf(new(SpecContext)).Elem() + +func generateTable(description string, args ...interface{}) { + cl := types.NewCodeLocation(2) + containerNodeArgs := []interface{}{cl} + + entries := []TableEntry{} + var itBody interface{} + var itBodyType reflect.Type + + var tableLevelEntryDescription interface{} + tableLevelEntryDescription = func(args ...interface{}) string { + out := []string{} + for _, arg := range args { + out = append(out, fmt.Sprint(arg)) + } + return "Entry: " + strings.Join(out, ", ") + } + + if len(args) == 1 { + exitIfErr(types.GinkgoErrors.MissingParametersForTableFunction(cl)) + } + + for i, arg := range args { + switch t := reflect.TypeOf(arg); { + case t == nil: + exitIfErr(types.GinkgoErrors.IncorrectParameterTypeForTable(i, "nil", cl)) + case t == reflect.TypeOf(TableEntry{}): + entries = append(entries, arg.(TableEntry)) + case t == reflect.TypeOf([]TableEntry{}): + entries = append(entries, arg.([]TableEntry)...) + case t == reflect.TypeOf(EntryDescription("")): + tableLevelEntryDescription = arg.(EntryDescription).render + case t.Kind() == reflect.Func && t.NumOut() == 1 && t.Out(0) == reflect.TypeOf(""): + tableLevelEntryDescription = arg + case t.Kind() == reflect.Func: + if itBody != nil { + exitIfErr(types.GinkgoErrors.MultipleEntryBodyFunctionsForTable(cl)) + } + itBody = arg + itBodyType = reflect.TypeOf(itBody) + default: + containerNodeArgs = append(containerNodeArgs, arg) + } + } + + containerNodeArgs = append(containerNodeArgs, func() { + for _, entry := range entries { + var err error + entry := entry + var description string + switch t := reflect.TypeOf(entry.description); { + case t == nil: + err = validateParameters(tableLevelEntryDescription, entry.parameters, "Entry Description function", entry.codeLocation, false) + if err == nil { + description = invokeFunction(tableLevelEntryDescription, entry.parameters)[0].String() + } + case t == reflect.TypeOf(EntryDescription("")): + description = entry.description.(EntryDescription).render(entry.parameters...) + case t == reflect.TypeOf(""): + description = entry.description.(string) + case t.Kind() == reflect.Func && t.NumOut() == 1 && t.Out(0) == reflect.TypeOf(""): + err = validateParameters(entry.description, entry.parameters, "Entry Description function", entry.codeLocation, false) + if err == nil { + description = invokeFunction(entry.description, entry.parameters)[0].String() + } + default: + err = types.GinkgoErrors.InvalidEntryDescription(entry.codeLocation) + } + + itNodeArgs := []interface{}{entry.codeLocation} + itNodeArgs = append(itNodeArgs, entry.decorations...) + + hasContext := false + if itBodyType.NumIn() > 0. { + if itBodyType.In(0).Implements(specContextType) { + hasContext = true + } else if itBodyType.In(0).Implements(contextType) && (len(entry.parameters) == 0 || !reflect.TypeOf(entry.parameters[0]).Implements(contextType)) { + hasContext = true + } + } + + if err == nil { + err = validateParameters(itBody, entry.parameters, "Table Body function", entry.codeLocation, hasContext) + } + + if hasContext { + itNodeArgs = append(itNodeArgs, func(c SpecContext) { + if err != nil { + panic(err) + } + invokeFunction(itBody, append([]interface{}{c}, entry.parameters...)) + }) + } else { + itNodeArgs = append(itNodeArgs, func() { + if err != nil { + panic(err) + } + invokeFunction(itBody, entry.parameters) + }) + } + + pushNode(internal.NewNode(deprecationTracker, types.NodeTypeIt, description, itNodeArgs...)) + } + }) + + pushNode(internal.NewNode(deprecationTracker, types.NodeTypeContainer, description, containerNodeArgs...)) +} + +func invokeFunction(function interface{}, parameters []interface{}) []reflect.Value { + inValues := make([]reflect.Value, len(parameters)) + + funcType := reflect.TypeOf(function) + limit := funcType.NumIn() + if funcType.IsVariadic() { + limit = limit - 1 + } + + for i := 0; i < limit && i < len(parameters); i++ { + inValues[i] = computeValue(parameters[i], funcType.In(i)) + } + + if funcType.IsVariadic() { + variadicType := funcType.In(limit).Elem() + for i := limit; i < len(parameters); i++ { + inValues[i] = computeValue(parameters[i], variadicType) + } + } + + return reflect.ValueOf(function).Call(inValues) +} + +func validateParameters(function interface{}, parameters []interface{}, kind string, cl types.CodeLocation, hasContext bool) error { + funcType := reflect.TypeOf(function) + limit := funcType.NumIn() + offset := 0 + if hasContext { + limit = limit - 1 + offset = 1 + } + if funcType.IsVariadic() { + limit = limit - 1 + } + if len(parameters) < limit { + return types.GinkgoErrors.TooFewParametersToTableFunction(limit, len(parameters), kind, cl) + } + if len(parameters) > limit && !funcType.IsVariadic() { + return types.GinkgoErrors.TooManyParametersToTableFunction(limit, len(parameters), kind, cl) + } + var i = 0 + for ; i < limit; i++ { + actual := reflect.TypeOf(parameters[i]) + expected := funcType.In(i + offset) + if !(actual == nil) && !actual.AssignableTo(expected) { + return types.GinkgoErrors.IncorrectParameterTypeToTableFunction(i+1, expected, actual, kind, cl) + } + } + if funcType.IsVariadic() { + expected := funcType.In(limit + offset).Elem() + for ; i < len(parameters); i++ { + actual := reflect.TypeOf(parameters[i]) + if !(actual == nil) && !actual.AssignableTo(expected) { + return types.GinkgoErrors.IncorrectVariadicParameterTypeToTableFunction(expected, actual, kind, cl) + } + } + } + + return nil +} + +func computeValue(parameter interface{}, t reflect.Type) reflect.Value { + if parameter == nil { + return reflect.Zero(t) + } else { + return reflect.ValueOf(parameter) + } +} diff --git a/vendor/github.com/onsi/ginkgo/v2/types/code_location.go b/vendor/github.com/onsi/ginkgo/v2/types/code_location.go new file mode 100644 index 00000000000..12910918343 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/types/code_location.go @@ -0,0 +1,92 @@ +package types + +import ( + "fmt" + "os" + "regexp" + "runtime" + "runtime/debug" + "strings" +) + +type CodeLocation struct { + FileName string `json:",omitempty"` + LineNumber int `json:",omitempty"` + FullStackTrace string `json:",omitempty"` + CustomMessage string `json:",omitempty"` +} + +func (codeLocation CodeLocation) String() string { + if codeLocation.CustomMessage != "" { + return codeLocation.CustomMessage + } + return fmt.Sprintf("%s:%d", codeLocation.FileName, codeLocation.LineNumber) +} + +func (codeLocation CodeLocation) ContentsOfLine() string { + if codeLocation.CustomMessage != "" { + return "" + } + contents, err := os.ReadFile(codeLocation.FileName) + if err != nil { + return "" + } + lines := strings.Split(string(contents), "\n") + if len(lines) < codeLocation.LineNumber { + return "" + } + return lines[codeLocation.LineNumber-1] +} + +func NewCustomCodeLocation(message string) CodeLocation { + return CodeLocation{ + CustomMessage: message, + } +} + +func NewCodeLocation(skip int) CodeLocation { + _, file, line, _ := runtime.Caller(skip + 1) + return CodeLocation{FileName: file, LineNumber: line} +} + +func NewCodeLocationWithStackTrace(skip int) CodeLocation { + _, file, line, _ := runtime.Caller(skip + 1) + stackTrace := PruneStack(string(debug.Stack()), skip+1) + return CodeLocation{FileName: file, LineNumber: line, FullStackTrace: stackTrace} +} + +// PruneStack removes references to functions that are internal to Ginkgo +// and the Go runtime from a stack string and a certain number of stack entries +// at the beginning of the stack. The stack string has the format +// as returned by runtime/debug.Stack. The leading goroutine information is +// optional and always removed if present. Beware that runtime/debug.Stack +// adds itself as first entry, so typically skip must be >= 1 to remove that +// entry. +func PruneStack(fullStackTrace string, skip int) string { + stack := strings.Split(fullStackTrace, "\n") + // Ensure that the even entries are the method names and the + // odd entries the source code information. + if len(stack) > 0 && strings.HasPrefix(stack[0], "goroutine ") { + // Ignore "goroutine 29 [running]:" line. + stack = stack[1:] + } + // The "+1" is for skipping over the initial entry, which is + // runtime/debug.Stack() itself. + if len(stack) > 2*(skip+1) { + stack = stack[2*(skip+1):] + } + prunedStack := []string{} + if os.Getenv("GINKGO_PRUNE_STACK") == "FALSE" { + prunedStack = stack + } else { + re := regexp.MustCompile(`\/ginkgo\/|\/pkg\/testing\/|\/pkg\/runtime\/`) + for i := 0; i < len(stack)/2; i++ { + // We filter out based on the source code file name. + if !re.Match([]byte(stack[i*2+1])) { + prunedStack = append(prunedStack, stack[i*2]) + prunedStack = append(prunedStack, stack[i*2+1]) + } + } + } + return strings.Join(prunedStack, "\n") +} diff --git a/vendor/github.com/onsi/ginkgo/v2/types/config.go b/vendor/github.com/onsi/ginkgo/v2/types/config.go new file mode 100644 index 00000000000..84ee937905a --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/types/config.go @@ -0,0 +1,739 @@ +/* +Ginkgo accepts a number of configuration options. +These are documented [here](http://onsi.github.io/ginkgo/#the-ginkgo-cli) +*/ + +package types + +import ( + "flag" + "os" + "runtime" + "strconv" + "strings" + "time" +) + +// Configuration controlling how an individual test suite is run +type SuiteConfig struct { + RandomSeed int64 + RandomizeAllSpecs bool + FocusStrings []string + SkipStrings []string + FocusFiles []string + SkipFiles []string + LabelFilter string + FailOnPending bool + FailFast bool + FlakeAttempts int + DryRun bool + PollProgressAfter time.Duration + PollProgressInterval time.Duration + Timeout time.Duration + OutputInterceptorMode string + SourceRoots []string + GracePeriod time.Duration + + ParallelProcess int + ParallelTotal int + ParallelHost string +} + +func NewDefaultSuiteConfig() SuiteConfig { + return SuiteConfig{ + RandomSeed: time.Now().Unix(), + Timeout: time.Hour, + ParallelProcess: 1, + ParallelTotal: 1, + GracePeriod: 30 * time.Second, + } +} + +type VerbosityLevel uint + +const ( + VerbosityLevelSuccinct VerbosityLevel = iota + VerbosityLevelNormal + VerbosityLevelVerbose + VerbosityLevelVeryVerbose +) + +func (vl VerbosityLevel) GT(comp VerbosityLevel) bool { + return vl > comp +} + +func (vl VerbosityLevel) GTE(comp VerbosityLevel) bool { + return vl >= comp +} + +func (vl VerbosityLevel) Is(comp VerbosityLevel) bool { + return vl == comp +} + +func (vl VerbosityLevel) LTE(comp VerbosityLevel) bool { + return vl <= comp +} + +func (vl VerbosityLevel) LT(comp VerbosityLevel) bool { + return vl < comp +} + +// Configuration for Ginkgo's reporter +type ReporterConfig struct { + NoColor bool + Succinct bool + Verbose bool + VeryVerbose bool + FullTrace bool + ShowNodeEvents bool + + JSONReport string + JUnitReport string + TeamcityReport string +} + +func (rc ReporterConfig) Verbosity() VerbosityLevel { + if rc.Succinct { + return VerbosityLevelSuccinct + } else if rc.Verbose { + return VerbosityLevelVerbose + } else if rc.VeryVerbose { + return VerbosityLevelVeryVerbose + } + return VerbosityLevelNormal +} + +func (rc ReporterConfig) WillGenerateReport() bool { + return rc.JSONReport != "" || rc.JUnitReport != "" || rc.TeamcityReport != "" +} + +func NewDefaultReporterConfig() ReporterConfig { + return ReporterConfig{} +} + +// Configuration for the Ginkgo CLI +type CLIConfig struct { + //for build, run, and watch + Recurse bool + SkipPackage string + RequireSuite bool + NumCompilers int + + //for run and watch only + Procs int + Parallel bool + AfterRunHook string + OutputDir string + KeepSeparateCoverprofiles bool + KeepSeparateReports bool + + //for run only + KeepGoing bool + UntilItFails bool + Repeat int + RandomizeSuites bool + + //for watch only + Depth int + WatchRegExp string +} + +func NewDefaultCLIConfig() CLIConfig { + return CLIConfig{ + Depth: 1, + WatchRegExp: `\.go$`, + } +} + +func (g CLIConfig) ComputedProcs() int { + if g.Procs > 0 { + return g.Procs + } + + n := 1 + if g.Parallel { + n = runtime.NumCPU() + if n > 4 { + n = n - 1 + } + } + return n +} + +func (g CLIConfig) ComputedNumCompilers() int { + if g.NumCompilers > 0 { + return g.NumCompilers + } + + return runtime.NumCPU() +} + +// Configuration for the Ginkgo CLI capturing available go flags +// A subset of Go flags are exposed by Ginkgo. Some are available at compile time (e.g. ginkgo build) and others only at run time (e.g. ginkgo run - which has both build and run time flags). +// More details can be found at: +// https://docs.google.com/spreadsheets/d/1zkp-DS4hU4sAJl5eHh1UmgwxCPQhf3s5a8fbiOI8tJU/ +type GoFlagsConfig struct { + //build-time flags for code-and-performance analysis + Race bool + Cover bool + CoverMode string + CoverPkg string + Vet string + + //run-time flags for code-and-performance analysis + BlockProfile string + BlockProfileRate int + CoverProfile string + CPUProfile string + MemProfile string + MemProfileRate int + MutexProfile string + MutexProfileFraction int + Trace string + + //build-time flags for building + A bool + ASMFlags string + BuildMode string + Compiler string + GCCGoFlags string + GCFlags string + InstallSuffix string + LDFlags string + LinkShared bool + Mod string + N bool + ModFile string + ModCacheRW bool + MSan bool + PkgDir string + Tags string + TrimPath bool + ToolExec string + Work bool + X bool +} + +func NewDefaultGoFlagsConfig() GoFlagsConfig { + return GoFlagsConfig{} +} + +func (g GoFlagsConfig) BinaryMustBePreserved() bool { + return g.BlockProfile != "" || g.CPUProfile != "" || g.MemProfile != "" || g.MutexProfile != "" +} + +// Configuration that were deprecated in 2.0 +type deprecatedConfig struct { + DebugParallel bool + NoisySkippings bool + NoisyPendings bool + RegexScansFilePath bool + SlowSpecThresholdWithFLoatUnits float64 + Stream bool + Notify bool + EmitSpecProgress bool + SlowSpecThreshold time.Duration + AlwaysEmitGinkgoWriter bool +} + +// Flags + +// Flags sections used by both the CLI and the Ginkgo test process +var FlagSections = GinkgoFlagSections{ + {Key: "multiple-suites", Style: "{{dark-green}}", Heading: "Running Multiple Test Suites"}, + {Key: "order", Style: "{{green}}", Heading: "Controlling Test Order"}, + {Key: "parallel", Style: "{{yellow}}", Heading: "Controlling Test Parallelism"}, + {Key: "low-level-parallel", Style: "{{yellow}}", Heading: "Controlling Test Parallelism", + Description: "These are set by the Ginkgo CLI, {{red}}{{bold}}do not set them manually{{/}} via go test.\nUse ginkgo -p or ginkgo -procs=N instead."}, + {Key: "filter", Style: "{{cyan}}", Heading: "Filtering Tests"}, + {Key: "failure", Style: "{{red}}", Heading: "Failure Handling"}, + {Key: "output", Style: "{{magenta}}", Heading: "Controlling Output Formatting"}, + {Key: "code-and-coverage-analysis", Style: "{{orange}}", Heading: "Code and Coverage Analysis"}, + {Key: "performance-analysis", Style: "{{coral}}", Heading: "Performance Analysis"}, + {Key: "debug", Style: "{{blue}}", Heading: "Debugging Tests", + Description: "In addition to these flags, Ginkgo supports a few debugging environment variables. To change the parallel server protocol set {{blue}}GINKGO_PARALLEL_PROTOCOL{{/}} to {{bold}}HTTP{{/}}. To avoid pruning callstacks set {{blue}}GINKGO_PRUNE_STACK{{/}} to {{bold}}FALSE{{/}}."}, + {Key: "watch", Style: "{{light-yellow}}", Heading: "Controlling Ginkgo Watch"}, + {Key: "misc", Style: "{{light-gray}}", Heading: "Miscellaneous"}, + {Key: "go-build", Style: "{{light-gray}}", Heading: "Go Build Flags", Succinct: true, + Description: "These flags are inherited from go build. Run {{bold}}ginkgo help build{{/}} for more detailed flag documentation."}, +} + +// SuiteConfigFlags provides flags for the Ginkgo test process, and CLI +var SuiteConfigFlags = GinkgoFlags{ + {KeyPath: "S.RandomSeed", Name: "seed", SectionKey: "order", UsageDefaultValue: "randomly generated by Ginkgo", + Usage: "The seed used to randomize the spec suite."}, + {KeyPath: "S.RandomizeAllSpecs", Name: "randomize-all", SectionKey: "order", DeprecatedName: "randomizeAllSpecs", DeprecatedDocLink: "changed-command-line-flags", + Usage: "If set, ginkgo will randomize all specs together. By default, ginkgo only randomizes the top level Describe, Context and When containers."}, + + {KeyPath: "S.FailOnPending", Name: "fail-on-pending", SectionKey: "failure", DeprecatedName: "failOnPending", DeprecatedDocLink: "changed-command-line-flags", + Usage: "If set, ginkgo will mark the test suite as failed if any specs are pending."}, + {KeyPath: "S.FailFast", Name: "fail-fast", SectionKey: "failure", DeprecatedName: "failFast", DeprecatedDocLink: "changed-command-line-flags", + Usage: "If set, ginkgo will stop running a test suite after a failure occurs."}, + {KeyPath: "S.FlakeAttempts", Name: "flake-attempts", SectionKey: "failure", UsageDefaultValue: "0 - failed tests are not retried", DeprecatedName: "flakeAttempts", DeprecatedDocLink: "changed-command-line-flags", + Usage: "Make up to this many attempts to run each spec. If any of the attempts succeed, the suite will not be failed."}, + + {KeyPath: "S.DryRun", Name: "dry-run", SectionKey: "debug", DeprecatedName: "dryRun", DeprecatedDocLink: "changed-command-line-flags", + Usage: "If set, ginkgo will walk the test hierarchy without actually running anything. Best paired with -v."}, + {KeyPath: "S.PollProgressAfter", Name: "poll-progress-after", SectionKey: "debug", UsageDefaultValue: "0", + Usage: "Emit node progress reports periodically if node hasn't completed after this duration."}, + {KeyPath: "S.PollProgressInterval", Name: "poll-progress-interval", SectionKey: "debug", UsageDefaultValue: "10s", + Usage: "The rate at which to emit node progress reports after poll-progress-after has elapsed."}, + {KeyPath: "S.SourceRoots", Name: "source-root", SectionKey: "debug", + Usage: "The location to look for source code when generating progress reports. You can pass multiple --source-root flags."}, + {KeyPath: "S.Timeout", Name: "timeout", SectionKey: "debug", UsageDefaultValue: "1h", + Usage: "Test suite fails if it does not complete within the specified timeout."}, + {KeyPath: "S.GracePeriod", Name: "grace-period", SectionKey: "debug", UsageDefaultValue: "30s", + Usage: "When interrupted, Ginkgo will wait for GracePeriod for the current running node to exit before moving on to the next one."}, + {KeyPath: "S.OutputInterceptorMode", Name: "output-interceptor-mode", SectionKey: "debug", UsageArgument: "dup, swap, or none", + Usage: "If set, ginkgo will use the specified output interception strategy when running in parallel. Defaults to dup on unix and swap on windows."}, + + {KeyPath: "S.LabelFilter", Name: "label-filter", SectionKey: "filter", UsageArgument: "expression", + Usage: "If set, ginkgo will only run specs with labels that match the label-filter. The passed-in expression can include boolean operations (!, &&, ||, ','), groupings via '()', and regular expressions '/regexp/'. e.g. '(cat || dog) && !fruit'"}, + {KeyPath: "S.FocusStrings", Name: "focus", SectionKey: "filter", + Usage: "If set, ginkgo will only run specs that match this regular expression. Can be specified multiple times, values are ORed."}, + {KeyPath: "S.SkipStrings", Name: "skip", SectionKey: "filter", + Usage: "If set, ginkgo will only run specs that do not match this regular expression. Can be specified multiple times, values are ORed."}, + {KeyPath: "S.FocusFiles", Name: "focus-file", SectionKey: "filter", UsageArgument: "file (regexp) | file:line | file:lineA-lineB | file:line,line,line", + Usage: "If set, ginkgo will only run specs in matching files. Can be specified multiple times, values are ORed."}, + {KeyPath: "S.SkipFiles", Name: "skip-file", SectionKey: "filter", UsageArgument: "file (regexp) | file:line | file:lineA-lineB | file:line,line,line", + Usage: "If set, ginkgo will skip specs in matching files. Can be specified multiple times, values are ORed."}, + + {KeyPath: "D.RegexScansFilePath", DeprecatedName: "regexScansFilePath", DeprecatedDocLink: "removed--regexscansfilepath", DeprecatedVersion: "2.0.0"}, + {KeyPath: "D.DebugParallel", DeprecatedName: "debug", DeprecatedDocLink: "removed--debug", DeprecatedVersion: "2.0.0"}, + {KeyPath: "D.EmitSpecProgress", DeprecatedName: "progress", SectionKey: "debug", + DeprecatedVersion: "2.5.0", Usage: ". The funcitonality provided by --progress was confusing and is no longer needed. Use --show-node-events instead to see node entry and exit events included in the timeline of failed and verbose specs. Or you can run with -vv to always see all node events. Lastly, --poll-progress-after and the PollProgressAfter decorator now provide a better mechanism for debugging specs that tend to get stuck."}, +} + +// ParallelConfigFlags provides flags for the Ginkgo test process (not the CLI) +var ParallelConfigFlags = GinkgoFlags{ + {KeyPath: "S.ParallelProcess", Name: "parallel.process", SectionKey: "low-level-parallel", UsageDefaultValue: "1", + Usage: "This worker process's (one-indexed) process number. For running specs in parallel."}, + {KeyPath: "S.ParallelTotal", Name: "parallel.total", SectionKey: "low-level-parallel", UsageDefaultValue: "1", + Usage: "The total number of worker processes. For running specs in parallel."}, + {KeyPath: "S.ParallelHost", Name: "parallel.host", SectionKey: "low-level-parallel", UsageDefaultValue: "set by Ginkgo CLI", + Usage: "The address for the server that will synchronize the processes."}, +} + +// ReporterConfigFlags provides flags for the Ginkgo test process, and CLI +var ReporterConfigFlags = GinkgoFlags{ + {KeyPath: "R.NoColor", Name: "no-color", SectionKey: "output", DeprecatedName: "noColor", DeprecatedDocLink: "changed-command-line-flags", + Usage: "If set, suppress color output in default reporter."}, + {KeyPath: "R.Verbose", Name: "v", SectionKey: "output", + Usage: "If set, emits more output including GinkgoWriter contents."}, + {KeyPath: "R.VeryVerbose", Name: "vv", SectionKey: "output", + Usage: "If set, emits with maximal verbosity - includes skipped and pending tests."}, + {KeyPath: "R.Succinct", Name: "succinct", SectionKey: "output", + Usage: "If set, default reporter prints out a very succinct report"}, + {KeyPath: "R.FullTrace", Name: "trace", SectionKey: "output", + Usage: "If set, default reporter prints out the full stack trace when a failure occurs"}, + {KeyPath: "R.ShowNodeEvents", Name: "show-node-events", SectionKey: "output", + Usage: "If set, default reporter prints node > Enter and < Exit events when specs fail"}, + + {KeyPath: "R.JSONReport", Name: "json-report", UsageArgument: "filename.json", SectionKey: "output", + Usage: "If set, Ginkgo will generate a JSON-formatted test report at the specified location."}, + {KeyPath: "R.JUnitReport", Name: "junit-report", UsageArgument: "filename.xml", SectionKey: "output", DeprecatedName: "reportFile", DeprecatedDocLink: "improved-reporting-infrastructure", + Usage: "If set, Ginkgo will generate a conformant junit test report in the specified file."}, + {KeyPath: "R.TeamcityReport", Name: "teamcity-report", UsageArgument: "filename", SectionKey: "output", + Usage: "If set, Ginkgo will generate a Teamcity-formatted test report at the specified location."}, + + {KeyPath: "D.SlowSpecThresholdWithFLoatUnits", DeprecatedName: "slowSpecThreshold", DeprecatedDocLink: "changed--slowspecthreshold", + Usage: "use --slow-spec-threshold instead and pass in a duration string (e.g. '5s', not '5.0')"}, + {KeyPath: "D.NoisyPendings", DeprecatedName: "noisyPendings", DeprecatedDocLink: "removed--noisypendings-and--noisyskippings", DeprecatedVersion: "2.0.0"}, + {KeyPath: "D.NoisySkippings", DeprecatedName: "noisySkippings", DeprecatedDocLink: "removed--noisypendings-and--noisyskippings", DeprecatedVersion: "2.0.0"}, + {KeyPath: "D.SlowSpecThreshold", DeprecatedName: "slow-spec-threshold", SectionKey: "output", Usage: "--slow-spec-threshold has been deprecated and will be removed in a future version of Ginkgo. This feature has proved to be more noisy than useful. You can use --poll-progress-after, instead, to get more actionable feedback about potentially slow specs and understand where they might be getting stuck.", DeprecatedVersion: "2.5.0"}, + {KeyPath: "D.AlwaysEmitGinkgoWriter", DeprecatedName: "always-emit-ginkgo-writer", SectionKey: "output", Usage: " - use -v instead, or one of Ginkgo's machine-readable report formats to get GinkgoWriter output for passing specs."}, +} + +// BuildTestSuiteFlagSet attaches to the CommandLine flagset and provides flags for the Ginkgo test process +func BuildTestSuiteFlagSet(suiteConfig *SuiteConfig, reporterConfig *ReporterConfig) (GinkgoFlagSet, error) { + flags := SuiteConfigFlags.CopyAppend(ParallelConfigFlags...).CopyAppend(ReporterConfigFlags...) + flags = flags.WithPrefix("ginkgo") + bindings := map[string]interface{}{ + "S": suiteConfig, + "R": reporterConfig, + "D": &deprecatedConfig{}, + } + extraGoFlagsSection := GinkgoFlagSection{Style: "{{gray}}", Heading: "Go test flags"} + + return NewAttachedGinkgoFlagSet(flag.CommandLine, flags, bindings, FlagSections, extraGoFlagsSection) +} + +// VetConfig validates that the Ginkgo test process' configuration is sound +func VetConfig(flagSet GinkgoFlagSet, suiteConfig SuiteConfig, reporterConfig ReporterConfig) []error { + errors := []error{} + + if flagSet.WasSet("count") || flagSet.WasSet("test.count") { + flag := flagSet.Lookup("count") + if flag == nil { + flag = flagSet.Lookup("test.count") + } + count, err := strconv.Atoi(flag.Value.String()) + if err != nil || count != 1 { + errors = append(errors, GinkgoErrors.InvalidGoFlagCount()) + } + } + + if flagSet.WasSet("parallel") || flagSet.WasSet("test.parallel") { + errors = append(errors, GinkgoErrors.InvalidGoFlagParallel()) + } + + if suiteConfig.ParallelTotal < 1 { + errors = append(errors, GinkgoErrors.InvalidParallelTotalConfiguration()) + } + + if suiteConfig.ParallelProcess > suiteConfig.ParallelTotal || suiteConfig.ParallelProcess < 1 { + errors = append(errors, GinkgoErrors.InvalidParallelProcessConfiguration()) + } + + if suiteConfig.ParallelTotal > 1 && suiteConfig.ParallelHost == "" { + errors = append(errors, GinkgoErrors.MissingParallelHostConfiguration()) + } + + if suiteConfig.DryRun && suiteConfig.ParallelTotal > 1 { + errors = append(errors, GinkgoErrors.DryRunInParallelConfiguration()) + } + + if suiteConfig.GracePeriod <= 0 { + errors = append(errors, GinkgoErrors.GracePeriodCannotBeZero()) + } + + if len(suiteConfig.FocusFiles) > 0 { + _, err := ParseFileFilters(suiteConfig.FocusFiles) + if err != nil { + errors = append(errors, err) + } + } + + if len(suiteConfig.SkipFiles) > 0 { + _, err := ParseFileFilters(suiteConfig.SkipFiles) + if err != nil { + errors = append(errors, err) + } + } + + if suiteConfig.LabelFilter != "" { + _, err := ParseLabelFilter(suiteConfig.LabelFilter) + if err != nil { + errors = append(errors, err) + } + } + + switch strings.ToLower(suiteConfig.OutputInterceptorMode) { + case "", "dup", "swap", "none": + default: + errors = append(errors, GinkgoErrors.InvalidOutputInterceptorModeConfiguration(suiteConfig.OutputInterceptorMode)) + } + + numVerbosity := 0 + for _, v := range []bool{reporterConfig.Succinct, reporterConfig.Verbose, reporterConfig.VeryVerbose} { + if v { + numVerbosity++ + } + } + if numVerbosity > 1 { + errors = append(errors, GinkgoErrors.ConflictingVerbosityConfiguration()) + } + + return errors +} + +// GinkgoCLISharedFlags provides flags shared by the Ginkgo CLI's build, watch, and run commands +var GinkgoCLISharedFlags = GinkgoFlags{ + {KeyPath: "C.Recurse", Name: "r", SectionKey: "multiple-suites", + Usage: "If set, ginkgo finds and runs test suites under the current directory recursively."}, + {KeyPath: "C.SkipPackage", Name: "skip-package", SectionKey: "multiple-suites", DeprecatedName: "skipPackage", DeprecatedDocLink: "changed-command-line-flags", + UsageArgument: "comma-separated list of packages", + Usage: "A comma-separated list of package names to be skipped. If any part of the package's path matches, that package is ignored."}, + {KeyPath: "C.RequireSuite", Name: "require-suite", SectionKey: "failure", DeprecatedName: "requireSuite", DeprecatedDocLink: "changed-command-line-flags", + Usage: "If set, Ginkgo fails if there are ginkgo tests in a directory but no invocation of RunSpecs."}, + {KeyPath: "C.NumCompilers", Name: "compilers", SectionKey: "multiple-suites", UsageDefaultValue: "0 (will autodetect)", + Usage: "When running multiple packages, the number of concurrent compilations to perform."}, +} + +// GinkgoCLIRunAndWatchFlags provides flags shared by the Ginkgo CLI's build and watch commands (but not run) +var GinkgoCLIRunAndWatchFlags = GinkgoFlags{ + {KeyPath: "C.Procs", Name: "procs", SectionKey: "parallel", UsageDefaultValue: "1 (run in series)", + Usage: "The number of parallel test nodes to run."}, + {KeyPath: "C.Procs", Name: "nodes", SectionKey: "parallel", UsageDefaultValue: "1 (run in series)", + Usage: "--nodes is an alias for --procs"}, + {KeyPath: "C.Parallel", Name: "p", SectionKey: "parallel", + Usage: "If set, ginkgo will run in parallel with an auto-detected number of nodes."}, + {KeyPath: "C.AfterRunHook", Name: "after-run-hook", SectionKey: "misc", DeprecatedName: "afterSuiteHook", DeprecatedDocLink: "changed-command-line-flags", + Usage: "Command to run when a test suite completes."}, + {KeyPath: "C.OutputDir", Name: "output-dir", SectionKey: "output", UsageArgument: "directory", DeprecatedName: "outputdir", DeprecatedDocLink: "improved-profiling-support", + Usage: "A location to place all generated profiles and reports."}, + {KeyPath: "C.KeepSeparateCoverprofiles", Name: "keep-separate-coverprofiles", SectionKey: "code-and-coverage-analysis", + Usage: "If set, Ginkgo does not merge coverprofiles into one monolithic coverprofile. The coverprofiles will remain in their respective package directories or in -output-dir if set."}, + {KeyPath: "C.KeepSeparateReports", Name: "keep-separate-reports", SectionKey: "output", + Usage: "If set, Ginkgo does not merge per-suite reports (e.g. -json-report) into one monolithic report for the entire testrun. The reports will remain in their respective package directories or in -output-dir if set."}, + + {KeyPath: "D.Stream", DeprecatedName: "stream", DeprecatedDocLink: "removed--stream", DeprecatedVersion: "2.0.0"}, + {KeyPath: "D.Notify", DeprecatedName: "notify", DeprecatedDocLink: "removed--notify", DeprecatedVersion: "2.0.0"}, +} + +// GinkgoCLIRunFlags provides flags for Ginkgo CLI's run command that aren't shared by any other commands +var GinkgoCLIRunFlags = GinkgoFlags{ + {KeyPath: "C.KeepGoing", Name: "keep-going", SectionKey: "multiple-suites", DeprecatedName: "keepGoing", DeprecatedDocLink: "changed-command-line-flags", + Usage: "If set, failures from earlier test suites do not prevent later test suites from running."}, + {KeyPath: "C.UntilItFails", Name: "until-it-fails", SectionKey: "debug", DeprecatedName: "untilItFails", DeprecatedDocLink: "changed-command-line-flags", + Usage: "If set, ginkgo will keep rerunning test suites until a failure occurs."}, + {KeyPath: "C.Repeat", Name: "repeat", SectionKey: "debug", UsageArgument: "n", UsageDefaultValue: "0 - i.e. no repetition, run only once", + Usage: "The number of times to re-run a test-suite. Useful for debugging flaky tests. If set to N the suite will be run N+1 times and will be required to pass each time."}, + {KeyPath: "C.RandomizeSuites", Name: "randomize-suites", SectionKey: "order", DeprecatedName: "randomizeSuites", DeprecatedDocLink: "changed-command-line-flags", + Usage: "If set, ginkgo will randomize the order in which test suites run."}, +} + +// GinkgoCLIRunFlags provides flags for Ginkgo CLI's watch command that aren't shared by any other commands +var GinkgoCLIWatchFlags = GinkgoFlags{ + {KeyPath: "C.Depth", Name: "depth", SectionKey: "watch", + Usage: "Ginkgo will watch dependencies down to this depth in the dependency tree."}, + {KeyPath: "C.WatchRegExp", Name: "watch-regexp", SectionKey: "watch", DeprecatedName: "watchRegExp", DeprecatedDocLink: "changed-command-line-flags", + UsageArgument: "Regular Expression", + UsageDefaultValue: `\.go$`, + Usage: "Only files matching this regular expression will be watched for changes."}, +} + +// GoBuildFlags provides flags for the Ginkgo CLI build, run, and watch commands that capture go's build-time flags. These are passed to go test -c by the ginkgo CLI +var GoBuildFlags = GinkgoFlags{ + {KeyPath: "Go.Race", Name: "race", SectionKey: "code-and-coverage-analysis", + Usage: "enable data race detection. Supported only on linux/amd64, freebsd/amd64, darwin/amd64, windows/amd64, linux/ppc64le and linux/arm64 (only for 48-bit VMA)."}, + {KeyPath: "Go.Vet", Name: "vet", UsageArgument: "list", SectionKey: "code-and-coverage-analysis", + Usage: `Configure the invocation of "go vet" during "go test" to use the comma-separated list of vet checks. If list is empty, "go test" runs "go vet" with a curated list of checks believed to be always worth addressing. If list is "off", "go test" does not run "go vet" at all. Available checks can be found by running 'go doc cmd/vet'`}, + {KeyPath: "Go.Cover", Name: "cover", SectionKey: "code-and-coverage-analysis", + Usage: "Enable coverage analysis. Note that because coverage works by annotating the source code before compilation, compilation and test failures with coverage enabled may report line numbers that don't correspond to the original sources."}, + {KeyPath: "Go.CoverMode", Name: "covermode", UsageArgument: "set,count,atomic", SectionKey: "code-and-coverage-analysis", + Usage: `Set the mode for coverage analysis for the package[s] being tested. 'set': does this statement run? 'count': how many times does this statement run? 'atomic': like count, but correct in multithreaded tests and more expensive (must use atomic with -race). Sets -cover`}, + {KeyPath: "Go.CoverPkg", Name: "coverpkg", UsageArgument: "pattern1,pattern2,pattern3", SectionKey: "code-and-coverage-analysis", + Usage: "Apply coverage analysis in each test to packages matching the patterns. The default is for each test to analyze only the package being tested. See 'go help packages' for a description of package patterns. Sets -cover."}, + + {KeyPath: "Go.A", Name: "a", SectionKey: "go-build", + Usage: "force rebuilding of packages that are already up-to-date."}, + {KeyPath: "Go.ASMFlags", Name: "asmflags", UsageArgument: "'[pattern=]arg list'", SectionKey: "go-build", + Usage: "arguments to pass on each go tool asm invocation."}, + {KeyPath: "Go.BuildMode", Name: "buildmode", UsageArgument: "mode", SectionKey: "go-build", + Usage: "build mode to use. See 'go help buildmode' for more."}, + {KeyPath: "Go.Compiler", Name: "compiler", UsageArgument: "name", SectionKey: "go-build", + Usage: "name of compiler to use, as in runtime.Compiler (gccgo or gc)."}, + {KeyPath: "Go.GCCGoFlags", Name: "gccgoflags", UsageArgument: "'[pattern=]arg list'", SectionKey: "go-build", + Usage: "arguments to pass on each gccgo compiler/linker invocation."}, + {KeyPath: "Go.GCFlags", Name: "gcflags", UsageArgument: "'[pattern=]arg list'", SectionKey: "go-build", + Usage: "arguments to pass on each go tool compile invocation."}, + {KeyPath: "Go.InstallSuffix", Name: "installsuffix", SectionKey: "go-build", + Usage: "a suffix to use in the name of the package installation directory, in order to keep output separate from default builds. If using the -race flag, the install suffix is automatically set to raceor, if set explicitly, has _race appended to it. Likewise for the -msan flag. Using a -buildmode option that requires non-default compile flags has a similar effect."}, + {KeyPath: "Go.LDFlags", Name: "ldflags", UsageArgument: "'[pattern=]arg list'", SectionKey: "go-build", + Usage: "arguments to pass on each go tool link invocation."}, + {KeyPath: "Go.LinkShared", Name: "linkshared", SectionKey: "go-build", + Usage: "build code that will be linked against shared libraries previously created with -buildmode=shared."}, + {KeyPath: "Go.Mod", Name: "mod", UsageArgument: "mode (readonly, vendor, or mod)", SectionKey: "go-build", + Usage: "module download mode to use: readonly, vendor, or mod. See 'go help modules' for more."}, + {KeyPath: "Go.ModCacheRW", Name: "modcacherw", SectionKey: "go-build", + Usage: "leave newly-created directories in the module cache read-write instead of making them read-only."}, + {KeyPath: "Go.ModFile", Name: "modfile", UsageArgument: "file", SectionKey: "go-build", + Usage: `in module aware mode, read (and possibly write) an alternate go.mod file instead of the one in the module root directory. A file named go.mod must still be present in order to determine the module root directory, but it is not accessed. When -modfile is specified, an alternate go.sum file is also used: its path is derived from the -modfile flag by trimming the ".mod" extension and appending ".sum".`}, + {KeyPath: "Go.MSan", Name: "msan", SectionKey: "go-build", + Usage: "enable interoperation with memory sanitizer. Supported only on linux/amd64, linux/arm64 and only with Clang/LLVM as the host C compiler. On linux/arm64, pie build mode will be used."}, + {KeyPath: "Go.N", Name: "n", SectionKey: "go-build", + Usage: "print the commands but do not run them."}, + {KeyPath: "Go.PkgDir", Name: "pkgdir", UsageArgument: "dir", SectionKey: "go-build", + Usage: "install and load all packages from dir instead of the usual locations. For example, when building with a non-standard configuration, use -pkgdir to keep generated packages in a separate location."}, + {KeyPath: "Go.Tags", Name: "tags", UsageArgument: "tag,list", SectionKey: "go-build", + Usage: "a comma-separated list of build tags to consider satisfied during the build. For more information about build tags, see the description of build constraints in the documentation for the go/build package. (Earlier versions of Go used a space-separated list, and that form is deprecated but still recognized.)"}, + {KeyPath: "Go.TrimPath", Name: "trimpath", SectionKey: "go-build", + Usage: `remove all file system paths from the resulting executable. Instead of absolute file system paths, the recorded file names will begin with either "go" (for the standard library), or a module path@version (when using modules), or a plain import path (when using GOPATH).`}, + {KeyPath: "Go.ToolExec", Name: "toolexec", UsageArgument: "'cmd args'", SectionKey: "go-build", + Usage: "a program to use to invoke toolchain programs like vet and asm. For example, instead of running asm, the go command will run cmd args /path/to/asm '."}, + {KeyPath: "Go.Work", Name: "work", SectionKey: "go-build", + Usage: "print the name of the temporary work directory and do not delete it when exiting."}, + {KeyPath: "Go.X", Name: "x", SectionKey: "go-build", + Usage: "print the commands."}, +} + +// GoRunFlags provides flags for the Ginkgo CLI run, and watch commands that capture go's run-time flags. These are passed to the compiled test binary by the ginkgo CLI +var GoRunFlags = GinkgoFlags{ + {KeyPath: "Go.CoverProfile", Name: "coverprofile", UsageArgument: "file", SectionKey: "code-and-coverage-analysis", + Usage: `Write a coverage profile to the file after all tests have passed. Sets -cover.`}, + {KeyPath: "Go.BlockProfile", Name: "blockprofile", UsageArgument: "file", SectionKey: "performance-analysis", + Usage: `Write a goroutine blocking profile to the specified file when all tests are complete. Preserves test binary.`}, + {KeyPath: "Go.BlockProfileRate", Name: "blockprofilerate", UsageArgument: "rate", SectionKey: "performance-analysis", + Usage: `Control the detail provided in goroutine blocking profiles by calling runtime.SetBlockProfileRate with rate. See 'go doc runtime.SetBlockProfileRate'. The profiler aims to sample, on average, one blocking event every n nanoseconds the program spends blocked. By default, if -test.blockprofile is set without this flag, all blocking events are recorded, equivalent to -test.blockprofilerate=1.`}, + {KeyPath: "Go.CPUProfile", Name: "cpuprofile", UsageArgument: "file", SectionKey: "performance-analysis", + Usage: `Write a CPU profile to the specified file before exiting. Preserves test binary.`}, + {KeyPath: "Go.MemProfile", Name: "memprofile", UsageArgument: "file", SectionKey: "performance-analysis", + Usage: `Write an allocation profile to the file after all tests have passed. Preserves test binary.`}, + {KeyPath: "Go.MemProfileRate", Name: "memprofilerate", UsageArgument: "rate", SectionKey: "performance-analysis", + Usage: `Enable more precise (and expensive) memory allocation profiles by setting runtime.MemProfileRate. See 'go doc runtime.MemProfileRate'. To profile all memory allocations, use -test.memprofilerate=1.`}, + {KeyPath: "Go.MutexProfile", Name: "mutexprofile", UsageArgument: "file", SectionKey: "performance-analysis", + Usage: `Write a mutex contention profile to the specified file when all tests are complete. Preserves test binary.`}, + {KeyPath: "Go.MutexProfileFraction", Name: "mutexprofilefraction", UsageArgument: "n", SectionKey: "performance-analysis", + Usage: `if >= 0, calls runtime.SetMutexProfileFraction() Sample 1 in n stack traces of goroutines holding a contended mutex.`}, + {KeyPath: "Go.Trace", Name: "execution-trace", UsageArgument: "file", ExportAs: "trace", SectionKey: "performance-analysis", + Usage: `Write an execution trace to the specified file before exiting.`}, +} + +// VetAndInitializeCLIAndGoConfig validates that the Ginkgo CLI's configuration is sound +// It returns a potentially mutated copy of the config that rationalizes the configuration to ensure consistency for downstream consumers +func VetAndInitializeCLIAndGoConfig(cliConfig CLIConfig, goFlagsConfig GoFlagsConfig) (CLIConfig, GoFlagsConfig, []error) { + errors := []error{} + + if cliConfig.Repeat > 0 && cliConfig.UntilItFails { + errors = append(errors, GinkgoErrors.BothRepeatAndUntilItFails()) + } + + //initialize the output directory + if cliConfig.OutputDir != "" { + err := os.MkdirAll(cliConfig.OutputDir, 0777) + if err != nil { + errors = append(errors, err) + } + } + + //ensure cover mode is configured appropriately + if goFlagsConfig.CoverMode != "" || goFlagsConfig.CoverPkg != "" || goFlagsConfig.CoverProfile != "" { + goFlagsConfig.Cover = true + } + if goFlagsConfig.Cover && goFlagsConfig.CoverProfile == "" { + goFlagsConfig.CoverProfile = "coverprofile.out" + } + + return cliConfig, goFlagsConfig, errors +} + +// GenerateGoTestCompileArgs is used by the Ginkgo CLI to generate command line arguments to pass to the go test -c command when compiling the test +func GenerateGoTestCompileArgs(goFlagsConfig GoFlagsConfig, destination string, packageToBuild string) ([]string, error) { + // if the user has set the CoverProfile run-time flag make sure to set the build-time cover flag to make sure + // the built test binary can generate a coverprofile + if goFlagsConfig.CoverProfile != "" { + goFlagsConfig.Cover = true + } + + args := []string{"test", "-c", "-o", destination, packageToBuild} + goArgs, err := GenerateFlagArgs( + GoBuildFlags, + map[string]interface{}{ + "Go": &goFlagsConfig, + }, + ) + + if err != nil { + return []string{}, err + } + args = append(args, goArgs...) + return args, nil +} + +// GenerateGinkgoTestRunArgs is used by the Ginkgo CLI to generate command line arguments to pass to the compiled Ginkgo test binary +func GenerateGinkgoTestRunArgs(suiteConfig SuiteConfig, reporterConfig ReporterConfig, goFlagsConfig GoFlagsConfig) ([]string, error) { + var flags GinkgoFlags + flags = SuiteConfigFlags.WithPrefix("ginkgo") + flags = flags.CopyAppend(ParallelConfigFlags.WithPrefix("ginkgo")...) + flags = flags.CopyAppend(ReporterConfigFlags.WithPrefix("ginkgo")...) + flags = flags.CopyAppend(GoRunFlags.WithPrefix("test")...) + bindings := map[string]interface{}{ + "S": &suiteConfig, + "R": &reporterConfig, + "Go": &goFlagsConfig, + } + + return GenerateFlagArgs(flags, bindings) +} + +// GenerateGoTestRunArgs is used by the Ginkgo CLI to generate command line arguments to pass to the compiled non-Ginkgo test binary +func GenerateGoTestRunArgs(goFlagsConfig GoFlagsConfig) ([]string, error) { + flags := GoRunFlags.WithPrefix("test") + bindings := map[string]interface{}{ + "Go": &goFlagsConfig, + } + + args, err := GenerateFlagArgs(flags, bindings) + if err != nil { + return args, err + } + args = append(args, "--test.v") + return args, nil +} + +// BuildRunCommandFlagSet builds the FlagSet for the `ginkgo run` command +func BuildRunCommandFlagSet(suiteConfig *SuiteConfig, reporterConfig *ReporterConfig, cliConfig *CLIConfig, goFlagsConfig *GoFlagsConfig) (GinkgoFlagSet, error) { + flags := SuiteConfigFlags + flags = flags.CopyAppend(ReporterConfigFlags...) + flags = flags.CopyAppend(GinkgoCLISharedFlags...) + flags = flags.CopyAppend(GinkgoCLIRunAndWatchFlags...) + flags = flags.CopyAppend(GinkgoCLIRunFlags...) + flags = flags.CopyAppend(GoBuildFlags...) + flags = flags.CopyAppend(GoRunFlags...) + + bindings := map[string]interface{}{ + "S": suiteConfig, + "R": reporterConfig, + "C": cliConfig, + "Go": goFlagsConfig, + "D": &deprecatedConfig{}, + } + + return NewGinkgoFlagSet(flags, bindings, FlagSections) +} + +// BuildWatchCommandFlagSet builds the FlagSet for the `ginkgo watch` command +func BuildWatchCommandFlagSet(suiteConfig *SuiteConfig, reporterConfig *ReporterConfig, cliConfig *CLIConfig, goFlagsConfig *GoFlagsConfig) (GinkgoFlagSet, error) { + flags := SuiteConfigFlags + flags = flags.CopyAppend(ReporterConfigFlags...) + flags = flags.CopyAppend(GinkgoCLISharedFlags...) + flags = flags.CopyAppend(GinkgoCLIRunAndWatchFlags...) + flags = flags.CopyAppend(GinkgoCLIWatchFlags...) + flags = flags.CopyAppend(GoBuildFlags...) + flags = flags.CopyAppend(GoRunFlags...) + + bindings := map[string]interface{}{ + "S": suiteConfig, + "R": reporterConfig, + "C": cliConfig, + "Go": goFlagsConfig, + "D": &deprecatedConfig{}, + } + + return NewGinkgoFlagSet(flags, bindings, FlagSections) +} + +// BuildBuildCommandFlagSet builds the FlagSet for the `ginkgo build` command +func BuildBuildCommandFlagSet(cliConfig *CLIConfig, goFlagsConfig *GoFlagsConfig) (GinkgoFlagSet, error) { + flags := GinkgoCLISharedFlags + flags = flags.CopyAppend(GoBuildFlags...) + + bindings := map[string]interface{}{ + "C": cliConfig, + "Go": goFlagsConfig, + "D": &deprecatedConfig{}, + } + + flagSections := make(GinkgoFlagSections, len(FlagSections)) + copy(flagSections, FlagSections) + for i := range flagSections { + if flagSections[i].Key == "multiple-suites" { + flagSections[i].Heading = "Building Multiple Suites" + } + if flagSections[i].Key == "go-build" { + flagSections[i] = GinkgoFlagSection{Key: "go-build", Style: "{{/}}", Heading: "Go Build Flags", + Description: "These flags are inherited from go build."} + } + } + + return NewGinkgoFlagSet(flags, bindings, flagSections) +} + +func BuildLabelsCommandFlagSet(cliConfig *CLIConfig) (GinkgoFlagSet, error) { + flags := GinkgoCLISharedFlags.SubsetWithNames("r", "skip-package") + + bindings := map[string]interface{}{ + "C": cliConfig, + } + + flagSections := make(GinkgoFlagSections, len(FlagSections)) + copy(flagSections, FlagSections) + for i := range flagSections { + if flagSections[i].Key == "multiple-suites" { + flagSections[i].Heading = "Fetching Labels from Multiple Suites" + } + } + + return NewGinkgoFlagSet(flags, bindings, flagSections) +} diff --git a/vendor/github.com/onsi/ginkgo/v2/types/deprecated_types.go b/vendor/github.com/onsi/ginkgo/v2/types/deprecated_types.go new file mode 100644 index 00000000000..17922304b63 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/types/deprecated_types.go @@ -0,0 +1,141 @@ +package types + +import ( + "strconv" + "time" +) + +/* + A set of deprecations to make the transition from v1 to v2 easier for users who have written custom reporters. +*/ + +type SuiteSummary = DeprecatedSuiteSummary +type SetupSummary = DeprecatedSetupSummary +type SpecSummary = DeprecatedSpecSummary +type SpecMeasurement = DeprecatedSpecMeasurement +type SpecComponentType = NodeType +type SpecFailure = DeprecatedSpecFailure + +var ( + SpecComponentTypeInvalid = NodeTypeInvalid + SpecComponentTypeContainer = NodeTypeContainer + SpecComponentTypeIt = NodeTypeIt + SpecComponentTypeBeforeEach = NodeTypeBeforeEach + SpecComponentTypeJustBeforeEach = NodeTypeJustBeforeEach + SpecComponentTypeAfterEach = NodeTypeAfterEach + SpecComponentTypeJustAfterEach = NodeTypeJustAfterEach + SpecComponentTypeBeforeSuite = NodeTypeBeforeSuite + SpecComponentTypeSynchronizedBeforeSuite = NodeTypeSynchronizedBeforeSuite + SpecComponentTypeAfterSuite = NodeTypeAfterSuite + SpecComponentTypeSynchronizedAfterSuite = NodeTypeSynchronizedAfterSuite +) + +type DeprecatedSuiteSummary struct { + SuiteDescription string + SuiteSucceeded bool + SuiteID string + + NumberOfSpecsBeforeParallelization int + NumberOfTotalSpecs int + NumberOfSpecsThatWillBeRun int + NumberOfPendingSpecs int + NumberOfSkippedSpecs int + NumberOfPassedSpecs int + NumberOfFailedSpecs int + NumberOfFlakedSpecs int + RunTime time.Duration +} + +type DeprecatedSetupSummary struct { + ComponentType SpecComponentType + CodeLocation CodeLocation + + State SpecState + RunTime time.Duration + Failure SpecFailure + + CapturedOutput string + SuiteID string +} + +type DeprecatedSpecSummary struct { + ComponentTexts []string + ComponentCodeLocations []CodeLocation + + State SpecState + RunTime time.Duration + Failure SpecFailure + IsMeasurement bool + NumberOfSamples int + Measurements map[string]*DeprecatedSpecMeasurement + + CapturedOutput string + SuiteID string +} + +func (s DeprecatedSpecSummary) HasFailureState() bool { + return s.State.Is(SpecStateFailureStates) +} + +func (s DeprecatedSpecSummary) TimedOut() bool { + return false +} + +func (s DeprecatedSpecSummary) Panicked() bool { + return s.State == SpecStatePanicked +} + +func (s DeprecatedSpecSummary) Failed() bool { + return s.State == SpecStateFailed +} + +func (s DeprecatedSpecSummary) Passed() bool { + return s.State == SpecStatePassed +} + +func (s DeprecatedSpecSummary) Skipped() bool { + return s.State == SpecStateSkipped +} + +func (s DeprecatedSpecSummary) Pending() bool { + return s.State == SpecStatePending +} + +type DeprecatedSpecFailure struct { + Message string + Location CodeLocation + ForwardedPanic string + + ComponentIndex int + ComponentType SpecComponentType + ComponentCodeLocation CodeLocation +} + +type DeprecatedSpecMeasurement struct { + Name string + Info interface{} + Order int + + Results []float64 + + Smallest float64 + Largest float64 + Average float64 + StdDeviation float64 + + SmallestLabel string + LargestLabel string + AverageLabel string + Units string + Precision int +} + +func (s DeprecatedSpecMeasurement) PrecisionFmt() string { + if s.Precision == 0 { + return "%f" + } + + str := strconv.Itoa(s.Precision) + + return "%." + str + "f" +} diff --git a/vendor/github.com/onsi/ginkgo/v2/types/deprecation_support.go b/vendor/github.com/onsi/ginkgo/v2/types/deprecation_support.go new file mode 100644 index 00000000000..f267bdefd51 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/types/deprecation_support.go @@ -0,0 +1,177 @@ +package types + +import ( + "os" + "strconv" + "strings" + "sync" + "unicode" + + "github.com/onsi/ginkgo/v2/formatter" +) + +type Deprecation struct { + Message string + DocLink string + Version string +} + +type deprecations struct{} + +var Deprecations = deprecations{} + +func (d deprecations) CustomReporter() Deprecation { + return Deprecation{ + Message: "Support for custom reporters has been removed in V2. Please read the documentation linked to below for Ginkgo's new behavior and for a migration path:", + DocLink: "removed-custom-reporters", + Version: "1.16.0", + } +} + +func (d deprecations) Async() Deprecation { + return Deprecation{ + Message: "You are passing a Done channel to a test node to test asynchronous behavior. This is deprecated in Ginkgo V2. Your test will run synchronously and the timeout will be ignored.", + DocLink: "removed-async-testing", + Version: "1.16.0", + } +} + +func (d deprecations) Measure() Deprecation { + return Deprecation{ + Message: "Measure is deprecated and will be removed in Ginkgo V2. Please migrate to gomega/gmeasure.", + DocLink: "removed-measure", + Version: "1.16.3", + } +} + +func (d deprecations) ParallelNode() Deprecation { + return Deprecation{ + Message: "GinkgoParallelNode is deprecated and will be removed in Ginkgo V2. Please use GinkgoParallelProcess instead.", + DocLink: "renamed-ginkgoparallelnode", + Version: "1.16.4", + } +} + +func (d deprecations) CurrentGinkgoTestDescription() Deprecation { + return Deprecation{ + Message: "CurrentGinkgoTestDescription() is deprecated in Ginkgo V2. Use CurrentSpecReport() instead.", + DocLink: "changed-currentginkgotestdescription", + Version: "1.16.0", + } +} + +func (d deprecations) Convert() Deprecation { + return Deprecation{ + Message: "The convert command is deprecated in Ginkgo V2", + DocLink: "removed-ginkgo-convert", + Version: "1.16.0", + } +} + +func (d deprecations) Blur() Deprecation { + return Deprecation{ + Message: "The blur command is deprecated in Ginkgo V2. Use 'ginkgo unfocus' instead.", + Version: "1.16.0", + } +} + +func (d deprecations) Nodot() Deprecation { + return Deprecation{ + Message: "The nodot command is deprecated in Ginkgo V2. Please either dot-import Ginkgo or use the package identifier in your code to references objects and types provided by Ginkgo and Gomega.", + DocLink: "removed-ginkgo-nodot", + Version: "1.16.0", + } +} + +func (d deprecations) SuppressProgressReporting() Deprecation { + return Deprecation{ + Message: "Improvements to how reporters emit timeline information means that SuppressProgressReporting is no longer necessary and has been deprecated.", + Version: "2.5.0", + } +} + +type DeprecationTracker struct { + deprecations map[Deprecation][]CodeLocation + lock *sync.Mutex +} + +func NewDeprecationTracker() *DeprecationTracker { + return &DeprecationTracker{ + deprecations: map[Deprecation][]CodeLocation{}, + lock: &sync.Mutex{}, + } +} + +func (d *DeprecationTracker) TrackDeprecation(deprecation Deprecation, cl ...CodeLocation) { + ackVersion := os.Getenv("ACK_GINKGO_DEPRECATIONS") + if deprecation.Version != "" && ackVersion != "" { + ack := ParseSemVer(ackVersion) + version := ParseSemVer(deprecation.Version) + if ack.GreaterThanOrEqualTo(version) { + return + } + } + + d.lock.Lock() + defer d.lock.Unlock() + if len(cl) == 1 { + d.deprecations[deprecation] = append(d.deprecations[deprecation], cl[0]) + } else { + d.deprecations[deprecation] = []CodeLocation{} + } +} + +func (d *DeprecationTracker) DidTrackDeprecations() bool { + d.lock.Lock() + defer d.lock.Unlock() + return len(d.deprecations) > 0 +} + +func (d *DeprecationTracker) DeprecationsReport() string { + d.lock.Lock() + defer d.lock.Unlock() + out := formatter.F("{{light-yellow}}You're using deprecated Ginkgo functionality:{{/}}\n") + out += formatter.F("{{light-yellow}}============================================={{/}}\n") + for deprecation, locations := range d.deprecations { + out += formatter.Fi(1, "{{yellow}}"+deprecation.Message+"{{/}}\n") + if deprecation.DocLink != "" { + out += formatter.Fi(1, "{{bold}}Learn more at:{{/}} {{cyan}}{{underline}}https://onsi.github.io/ginkgo/MIGRATING_TO_V2#%s{{/}}\n", deprecation.DocLink) + } + for _, location := range locations { + out += formatter.Fi(2, "{{gray}}%s{{/}}\n", location) + } + } + out += formatter.F("\n{{gray}}To silence deprecations that can be silenced set the following environment variable:{{/}}\n") + out += formatter.Fi(1, "{{gray}}ACK_GINKGO_DEPRECATIONS=%s{{/}}\n", VERSION) + return out +} + +type SemVer struct { + Major int + Minor int + Patch int +} + +func (s SemVer) GreaterThanOrEqualTo(o SemVer) bool { + return (s.Major > o.Major) || + (s.Major == o.Major && s.Minor > o.Minor) || + (s.Major == o.Major && s.Minor == o.Minor && s.Patch >= o.Patch) +} + +func ParseSemVer(semver string) SemVer { + out := SemVer{} + semver = strings.TrimFunc(semver, func(r rune) bool { + return !(unicode.IsNumber(r) || r == '.') + }) + components := strings.Split(semver, ".") + if len(components) > 0 { + out.Major, _ = strconv.Atoi(components[0]) + } + if len(components) > 1 { + out.Minor, _ = strconv.Atoi(components[1]) + } + if len(components) > 2 { + out.Patch, _ = strconv.Atoi(components[2]) + } + return out +} diff --git a/vendor/github.com/onsi/ginkgo/v2/types/enum_support.go b/vendor/github.com/onsi/ginkgo/v2/types/enum_support.go new file mode 100644 index 00000000000..1d96ae02800 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/types/enum_support.go @@ -0,0 +1,43 @@ +package types + +import "encoding/json" + +type EnumSupport struct { + toString map[uint]string + toEnum map[string]uint + maxEnum uint +} + +func NewEnumSupport(toString map[uint]string) EnumSupport { + toEnum, maxEnum := map[string]uint{}, uint(0) + for k, v := range toString { + toEnum[v] = k + if maxEnum < k { + maxEnum = k + } + } + return EnumSupport{toString: toString, toEnum: toEnum, maxEnum: maxEnum} +} + +func (es EnumSupport) String(e uint) string { + if e > es.maxEnum { + return es.toString[0] + } + return es.toString[e] +} + +func (es EnumSupport) UnmarshJSON(b []byte) (uint, error) { + var dec string + if err := json.Unmarshal(b, &dec); err != nil { + return 0, err + } + out := es.toEnum[dec] // if we miss we get 0 which is what we want anyway + return out, nil +} + +func (es EnumSupport) MarshJSON(e uint) ([]byte, error) { + if e == 0 || e > es.maxEnum { + return json.Marshal(nil) + } + return json.Marshal(es.toString[e]) +} diff --git a/vendor/github.com/onsi/ginkgo/v2/types/errors.go b/vendor/github.com/onsi/ginkgo/v2/types/errors.go new file mode 100644 index 00000000000..b7ed5a21ebb --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/types/errors.go @@ -0,0 +1,621 @@ +package types + +import ( + "fmt" + "reflect" + "strings" + + "github.com/onsi/ginkgo/v2/formatter" +) + +type GinkgoError struct { + Heading string + Message string + DocLink string + CodeLocation CodeLocation +} + +func (g GinkgoError) Error() string { + out := formatter.F("{{bold}}{{red}}%s{{/}}\n", g.Heading) + if (g.CodeLocation != CodeLocation{}) { + contentsOfLine := strings.TrimLeft(g.CodeLocation.ContentsOfLine(), "\t ") + if contentsOfLine != "" { + out += formatter.F("{{light-gray}}%s{{/}}\n", contentsOfLine) + } + out += formatter.F("{{gray}}%s{{/}}\n", g.CodeLocation) + } + if g.Message != "" { + out += formatter.Fiw(1, formatter.COLS, g.Message) + out += "\n\n" + } + if g.DocLink != "" { + out += formatter.Fiw(1, formatter.COLS, "{{bold}}Learn more at:{{/}} {{cyan}}{{underline}}http://onsi.github.io/ginkgo/#%s{{/}}\n", g.DocLink) + } + + return out +} + +type ginkgoErrors struct{} + +var GinkgoErrors = ginkgoErrors{} + +func (g ginkgoErrors) UncaughtGinkgoPanic(cl CodeLocation) error { + return GinkgoError{ + Heading: "Your Test Panicked", + Message: `When you, or your assertion library, calls Ginkgo's Fail(), +Ginkgo panics to prevent subsequent assertions from running. + +Normally Ginkgo rescues this panic so you shouldn't see it. + +However, if you make an assertion in a goroutine, Ginkgo can't capture the panic. +To circumvent this, you should call + + defer GinkgoRecover() + +at the top of the goroutine that caused this panic. + +Alternatively, you may have made an assertion outside of a Ginkgo +leaf node (e.g. in a container node or some out-of-band function) - please move your assertion to +an appropriate Ginkgo node (e.g. a BeforeSuite, BeforeEach, It, etc...).`, + DocLink: "mental-model-how-ginkgo-handles-failure", + CodeLocation: cl, + } +} + +func (g ginkgoErrors) RerunningSuite() error { + return GinkgoError{ + Heading: "Rerunning Suite", + Message: formatter.F(`It looks like you are calling RunSpecs more than once. Ginkgo does not support rerunning suites. If you want to rerun a suite try {{bold}}ginkgo --repeat=N{{/}} or {{bold}}ginkgo --until-it-fails{{/}}`), + DocLink: "repeating-spec-runs-and-managing-flaky-specs", + } +} + +/* Tree construction errors */ + +func (g ginkgoErrors) PushingNodeInRunPhase(nodeType NodeType, cl CodeLocation) error { + return GinkgoError{ + Heading: "Ginkgo detected an issue with your spec structure", + Message: formatter.F( + `It looks like you are trying to add a {{bold}}[%s]{{/}} node +to the Ginkgo spec tree in a leaf node {{bold}}after{{/}} the specs started running. + +To enable randomization and parallelization Ginkgo requires the spec tree +to be fully constructed up front. In practice, this means that you can +only create nodes like {{bold}}[%s]{{/}} at the top-level or within the +body of a {{bold}}Describe{{/}}, {{bold}}Context{{/}}, or {{bold}}When{{/}}.`, nodeType, nodeType), + CodeLocation: cl, + DocLink: "mental-model-how-ginkgo-traverses-the-spec-hierarchy", + } +} + +func (g ginkgoErrors) CaughtPanicDuringABuildPhase(caughtPanic interface{}, cl CodeLocation) error { + return GinkgoError{ + Heading: "Assertion or Panic detected during tree construction", + Message: formatter.F( + `Ginkgo detected a panic while constructing the spec tree. +You may be trying to make an assertion in the body of a container node +(i.e. {{bold}}Describe{{/}}, {{bold}}Context{{/}}, or {{bold}}When{{/}}). + +Please ensure all assertions are inside leaf nodes such as {{bold}}BeforeEach{{/}}, +{{bold}}It{{/}}, etc. + +{{bold}}Here's the content of the panic that was caught:{{/}} +%v`, caughtPanic), + CodeLocation: cl, + DocLink: "no-assertions-in-container-nodes", + } +} + +func (g ginkgoErrors) SuiteNodeInNestedContext(nodeType NodeType, cl CodeLocation) error { + docLink := "suite-setup-and-cleanup-beforesuite-and-aftersuite" + if nodeType.Is(NodeTypeReportAfterSuite) { + docLink = "reporting-nodes---reportaftersuite" + } + + return GinkgoError{ + Heading: "Ginkgo detected an issue with your spec structure", + Message: formatter.F( + `It looks like you are trying to add a {{bold}}[%s]{{/}} node within a container node. + +{{bold}}%s{{/}} can only be called at the top level.`, nodeType, nodeType), + CodeLocation: cl, + DocLink: docLink, + } +} + +func (g ginkgoErrors) SuiteNodeDuringRunPhase(nodeType NodeType, cl CodeLocation) error { + docLink := "suite-setup-and-cleanup-beforesuite-and-aftersuite" + if nodeType.Is(NodeTypeReportAfterSuite) { + docLink = "reporting-nodes---reportaftersuite" + } + + return GinkgoError{ + Heading: "Ginkgo detected an issue with your spec structure", + Message: formatter.F( + `It looks like you are trying to add a {{bold}}[%s]{{/}} node within a leaf node after the spec started running. + +{{bold}}%s{{/}} can only be called at the top level.`, nodeType, nodeType), + CodeLocation: cl, + DocLink: docLink, + } +} + +func (g ginkgoErrors) MultipleBeforeSuiteNodes(nodeType NodeType, cl CodeLocation, earlierNodeType NodeType, earlierCodeLocation CodeLocation) error { + return ginkgoErrorMultipleSuiteNodes("setup", nodeType, cl, earlierNodeType, earlierCodeLocation) +} + +func (g ginkgoErrors) MultipleAfterSuiteNodes(nodeType NodeType, cl CodeLocation, earlierNodeType NodeType, earlierCodeLocation CodeLocation) error { + return ginkgoErrorMultipleSuiteNodes("teardown", nodeType, cl, earlierNodeType, earlierCodeLocation) +} + +func ginkgoErrorMultipleSuiteNodes(setupOrTeardown string, nodeType NodeType, cl CodeLocation, earlierNodeType NodeType, earlierCodeLocation CodeLocation) error { + return GinkgoError{ + Heading: "Ginkgo detected an issue with your spec structure", + Message: formatter.F( + `It looks like you are trying to add a {{bold}}[%s]{{/}} node but +you already have a {{bold}}[%s]{{/}} node defined at: {{gray}}%s{{/}}. + +Ginkgo only allows you to define one suite %s node.`, nodeType, earlierNodeType, earlierCodeLocation, setupOrTeardown), + CodeLocation: cl, + DocLink: "suite-setup-and-cleanup-beforesuite-and-aftersuite", + } +} + +/* Decorator errors */ +func (g ginkgoErrors) InvalidDecoratorForNodeType(cl CodeLocation, nodeType NodeType, decorator string) error { + return GinkgoError{ + Heading: "Invalid Decorator", + Message: formatter.F(`[%s] node cannot be passed a(n) '%s' decorator`, nodeType, decorator), + CodeLocation: cl, + DocLink: "node-decorators-overview", + } +} + +func (g ginkgoErrors) InvalidDeclarationOfFocusedAndPending(cl CodeLocation, nodeType NodeType) error { + return GinkgoError{ + Heading: "Invalid Combination of Decorators: Focused and Pending", + Message: formatter.F(`[%s] node was decorated with both Focus and Pending. At most one is allowed.`, nodeType), + CodeLocation: cl, + DocLink: "node-decorators-overview", + } +} + +func (g ginkgoErrors) InvalidDeclarationOfFlakeAttemptsAndMustPassRepeatedly(cl CodeLocation, nodeType NodeType) error { + return GinkgoError{ + Heading: "Invalid Combination of Decorators: FlakeAttempts and MustPassRepeatedly", + Message: formatter.F(`[%s] node was decorated with both FlakeAttempts and MustPassRepeatedly. At most one is allowed.`, nodeType), + CodeLocation: cl, + DocLink: "node-decorators-overview", + } +} + +func (g ginkgoErrors) UnknownDecorator(cl CodeLocation, nodeType NodeType, decorator interface{}) error { + return GinkgoError{ + Heading: "Unknown Decorator", + Message: formatter.F(`[%s] node was passed an unknown decorator: '%#v'`, nodeType, decorator), + CodeLocation: cl, + DocLink: "node-decorators-overview", + } +} + +func (g ginkgoErrors) InvalidBodyTypeForContainer(t reflect.Type, cl CodeLocation, nodeType NodeType) error { + return GinkgoError{ + Heading: "Invalid Function", + Message: formatter.F(`[%s] node must be passed {{bold}}func(){{/}} - i.e. functions that take nothing and return nothing. You passed {{bold}}%s{{/}} instead.`, nodeType, t), + CodeLocation: cl, + DocLink: "node-decorators-overview", + } +} + +func (g ginkgoErrors) InvalidBodyType(t reflect.Type, cl CodeLocation, nodeType NodeType) error { + mustGet := "{{bold}}func(){{/}}, {{bold}}func(ctx SpecContext){{/}}, or {{bold}}func(ctx context.Context){{/}}" + if nodeType.Is(NodeTypeContainer) { + mustGet = "{{bold}}func(){{/}}" + } + return GinkgoError{ + Heading: "Invalid Function", + Message: formatter.F(`[%s] node must be passed `+mustGet+`. +You passed {{bold}}%s{{/}} instead.`, nodeType, t), + CodeLocation: cl, + DocLink: "node-decorators-overview", + } +} + +func (g ginkgoErrors) InvalidBodyTypeForSynchronizedBeforeSuiteProc1(t reflect.Type, cl CodeLocation) error { + mustGet := "{{bold}}func() []byte{{/}}, {{bold}}func(ctx SpecContext) []byte{{/}}, or {{bold}}func(ctx context.Context) []byte{{/}}, {{bold}}func(){{/}}, {{bold}}func(ctx SpecContext){{/}}, or {{bold}}func(ctx context.Context){{/}}" + return GinkgoError{ + Heading: "Invalid Function", + Message: formatter.F(`[SynchronizedBeforeSuite] node must be passed `+mustGet+` for its first function. +You passed {{bold}}%s{{/}} instead.`, t), + CodeLocation: cl, + DocLink: "node-decorators-overview", + } +} + +func (g ginkgoErrors) InvalidBodyTypeForSynchronizedBeforeSuiteAllProcs(t reflect.Type, cl CodeLocation) error { + mustGet := "{{bold}}func(){{/}}, {{bold}}func(ctx SpecContext){{/}}, or {{bold}}func(ctx context.Context){{/}}, {{bold}}func([]byte){{/}}, {{bold}}func(ctx SpecContext, []byte){{/}}, or {{bold}}func(ctx context.Context, []byte){{/}}" + return GinkgoError{ + Heading: "Invalid Function", + Message: formatter.F(`[SynchronizedBeforeSuite] node must be passed `+mustGet+` for its second function. +You passed {{bold}}%s{{/}} instead.`, t), + CodeLocation: cl, + DocLink: "node-decorators-overview", + } +} + +func (g ginkgoErrors) MultipleBodyFunctions(cl CodeLocation, nodeType NodeType) error { + return GinkgoError{ + Heading: "Multiple Functions", + Message: formatter.F(`[%s] node must be passed a single function - but more than one was passed in.`, nodeType), + CodeLocation: cl, + DocLink: "node-decorators-overview", + } +} + +func (g ginkgoErrors) MissingBodyFunction(cl CodeLocation, nodeType NodeType) error { + return GinkgoError{ + Heading: "Missing Functions", + Message: formatter.F(`[%s] node must be passed a single function - but none was passed in.`, nodeType), + CodeLocation: cl, + DocLink: "node-decorators-overview", + } +} + +func (g ginkgoErrors) InvalidTimeoutOrGracePeriodForNonContextNode(cl CodeLocation, nodeType NodeType) error { + return GinkgoError{ + Heading: "Invalid NodeTimeout SpecTimeout, or GracePeriod", + Message: formatter.F(`[%s] was passed NodeTimeout, SpecTimeout, or GracePeriod but does not have a callback that accepts a {{bold}}SpecContext{{/}} or {{bold}}context.Context{{/}}. You must accept a context to enable timeouts and grace periods`, nodeType), + CodeLocation: cl, + DocLink: "spec-timeouts-and-interruptible-nodes", + } +} + +func (g ginkgoErrors) InvalidTimeoutOrGracePeriodForNonContextCleanupNode(cl CodeLocation) error { + return GinkgoError{ + Heading: "Invalid NodeTimeout SpecTimeout, or GracePeriod", + Message: formatter.F(`[DeferCleanup] was passed NodeTimeout or GracePeriod but does not have a callback that accepts a {{bold}}SpecContext{{/}} or {{bold}}context.Context{{/}}. You must accept a context to enable timeouts and grace periods`), + CodeLocation: cl, + DocLink: "spec-timeouts-and-interruptible-nodes", + } +} + +/* Ordered Container errors */ +func (g ginkgoErrors) InvalidSerialNodeInNonSerialOrderedContainer(cl CodeLocation, nodeType NodeType) error { + return GinkgoError{ + Heading: "Invalid Serial Node in Non-Serial Ordered Container", + Message: formatter.F(`[%s] node was decorated with Serial but occurs in an Ordered container that is not marked Serial. Move the Serial decorator to the outer-most Ordered container to mark all ordered specs within the container as serial.`, nodeType), + CodeLocation: cl, + DocLink: "node-decorators-overview", + } +} + +func (g ginkgoErrors) SetupNodeNotInOrderedContainer(cl CodeLocation, nodeType NodeType) error { + return GinkgoError{ + Heading: "Setup Node not in Ordered Container", + Message: fmt.Sprintf("[%s] setup nodes must appear inside an Ordered container. They cannot be nested within other containers, even containers in an ordered container.", nodeType), + CodeLocation: cl, + DocLink: "ordered-containers", + } +} + +/* DeferCleanup errors */ +func (g ginkgoErrors) DeferCleanupInvalidFunction(cl CodeLocation) error { + return GinkgoError{ + Heading: "DeferCleanup requires a valid function", + Message: "You must pass DeferCleanup a function to invoke. This function must return zero or one values - if it does return, it must return an error. The function can take arbitrarily many arguments and you should provide these to DeferCleanup to pass along to the function.", + CodeLocation: cl, + DocLink: "cleaning-up-our-cleanup-code-defercleanup", + } +} + +func (g ginkgoErrors) PushingCleanupNodeDuringTreeConstruction(cl CodeLocation) error { + return GinkgoError{ + Heading: "DeferCleanup must be called inside a setup or subject node", + Message: "You must call DeferCleanup inside a setup node (e.g. BeforeEach, BeforeSuite, AfterAll...) or a subject node (i.e. It). You can't call DeferCleanup at the top-level or in a container node - use the After* family of setup nodes instead.", + CodeLocation: cl, + DocLink: "cleaning-up-our-cleanup-code-defercleanup", + } +} + +func (g ginkgoErrors) PushingCleanupInReportingNode(cl CodeLocation, nodeType NodeType) error { + return GinkgoError{ + Heading: fmt.Sprintf("DeferCleanup cannot be called in %s", nodeType), + Message: "Please inline your cleanup code - Ginkgo won't run cleanup code after a ReportAfterEach or ReportAfterSuite.", + CodeLocation: cl, + DocLink: "cleaning-up-our-cleanup-code-defercleanup", + } +} + +func (g ginkgoErrors) PushingCleanupInCleanupNode(cl CodeLocation) error { + return GinkgoError{ + Heading: "DeferCleanup cannot be called in a DeferCleanup callback", + Message: "Please inline your cleanup code - Ginkgo doesn't let you call DeferCleanup from within DeferCleanup", + CodeLocation: cl, + DocLink: "cleaning-up-our-cleanup-code-defercleanup", + } +} + +/* ReportEntry errors */ +func (g ginkgoErrors) TooManyReportEntryValues(cl CodeLocation, arg interface{}) error { + return GinkgoError{ + Heading: "Too Many ReportEntry Values", + Message: formatter.F(`{{bold}}AddGinkgoReport{{/}} can only be given one value. Got unexpected value: %#v`, arg), + CodeLocation: cl, + DocLink: "attaching-data-to-reports", + } +} + +func (g ginkgoErrors) AddReportEntryNotDuringRunPhase(cl CodeLocation) error { + return GinkgoError{ + Heading: "Ginkgo detected an issue with your spec structure", + Message: formatter.F(`It looks like you are calling {{bold}}AddGinkgoReport{{/}} outside of a running spec. Make sure you call {{bold}}AddGinkgoReport{{/}} inside a runnable node such as It or BeforeEach and not inside the body of a container such as Describe or Context.`), + CodeLocation: cl, + DocLink: "attaching-data-to-reports", + } +} + +/* By errors */ +func (g ginkgoErrors) ByNotDuringRunPhase(cl CodeLocation) error { + return GinkgoError{ + Heading: "Ginkgo detected an issue with your spec structure", + Message: formatter.F(`It looks like you are calling {{bold}}By{{/}} outside of a running spec. Make sure you call {{bold}}By{{/}} inside a runnable node such as It or BeforeEach and not inside the body of a container such as Describe or Context.`), + CodeLocation: cl, + DocLink: "documenting-complex-specs-by", + } +} + +/* FileFilter and SkipFilter errors */ +func (g ginkgoErrors) InvalidFileFilter(filter string) error { + return GinkgoError{ + Heading: "Invalid File Filter", + Message: fmt.Sprintf(`The provided file filter: "%s" is invalid. File filters must have the format "file", "file:lines" where "file" is a regular expression that will match against the file path and lines is a comma-separated list of integers (e.g. file:1,5,7) or line-ranges (e.g. file:1-3,5-9) or both (e.g. file:1,5-9)`, filter), + DocLink: "filtering-specs", + } +} + +func (g ginkgoErrors) InvalidFileFilterRegularExpression(filter string, err error) error { + return GinkgoError{ + Heading: "Invalid File Filter Regular Expression", + Message: fmt.Sprintf(`The provided file filter: "%s" included an invalid regular expression. regexp.Compile error: %s`, filter, err), + DocLink: "filtering-specs", + } +} + +/* Label Errors */ +func (g ginkgoErrors) SyntaxErrorParsingLabelFilter(input string, location int, error string) error { + var message string + if location >= 0 { + for i, r := range input { + if i == location { + message += "{{red}}{{bold}}{{underline}}" + } + message += string(r) + if i == location { + message += "{{/}}" + } + } + } else { + message = input + } + message += "\n" + error + return GinkgoError{ + Heading: "Syntax Error Parsing Label Filter", + Message: message, + DocLink: "spec-labels", + } +} + +func (g ginkgoErrors) InvalidLabel(label string, cl CodeLocation) error { + return GinkgoError{ + Heading: "Invalid Label", + Message: fmt.Sprintf("'%s' is an invalid label. Labels cannot contain of the following characters: '&|!,()/'", label), + CodeLocation: cl, + DocLink: "spec-labels", + } +} + +func (g ginkgoErrors) InvalidEmptyLabel(cl CodeLocation) error { + return GinkgoError{ + Heading: "Invalid Empty Label", + Message: "Labels cannot be empty", + CodeLocation: cl, + DocLink: "spec-labels", + } +} + +/* Table errors */ +func (g ginkgoErrors) MultipleEntryBodyFunctionsForTable(cl CodeLocation) error { + return GinkgoError{ + Heading: "DescribeTable passed multiple functions", + Message: "It looks like you are passing multiple functions into DescribeTable. Only one function can be passed in. This function will be called for each Entry in the table.", + CodeLocation: cl, + DocLink: "table-specs", + } +} + +func (g ginkgoErrors) InvalidEntryDescription(cl CodeLocation) error { + return GinkgoError{ + Heading: "Invalid Entry description", + Message: "Entry description functions must be a string, a function that accepts the entry parameters and returns a string, or nil.", + CodeLocation: cl, + DocLink: "table-specs", + } +} + +func (g ginkgoErrors) MissingParametersForTableFunction(cl CodeLocation) error { + return GinkgoError{ + Heading: fmt.Sprintf("No parameters have been passed to the Table Function"), + Message: fmt.Sprintf("The Table Function expected at least 1 parameter"), + CodeLocation: cl, + DocLink: "table-specs", + } +} + +func (g ginkgoErrors) IncorrectParameterTypeForTable(i int, name string, cl CodeLocation) error { + return GinkgoError{ + Heading: "DescribeTable passed incorrect parameter type", + Message: fmt.Sprintf("Parameter #%d passed to DescribeTable is of incorrect type <%s>", i, name), + CodeLocation: cl, + DocLink: "table-specs", + } +} + +func (g ginkgoErrors) TooFewParametersToTableFunction(expected, actual int, kind string, cl CodeLocation) error { + return GinkgoError{ + Heading: fmt.Sprintf("Too few parameters passed in to %s", kind), + Message: fmt.Sprintf("The %s expected %d parameters but you passed in %d", kind, expected, actual), + CodeLocation: cl, + DocLink: "table-specs", + } +} + +func (g ginkgoErrors) TooManyParametersToTableFunction(expected, actual int, kind string, cl CodeLocation) error { + return GinkgoError{ + Heading: fmt.Sprintf("Too many parameters passed in to %s", kind), + Message: fmt.Sprintf("The %s expected %d parameters but you passed in %d", kind, expected, actual), + CodeLocation: cl, + DocLink: "table-specs", + } +} + +func (g ginkgoErrors) IncorrectParameterTypeToTableFunction(i int, expected, actual reflect.Type, kind string, cl CodeLocation) error { + return GinkgoError{ + Heading: fmt.Sprintf("Incorrect parameters type passed to %s", kind), + Message: fmt.Sprintf("The %s expected parameter #%d to be of type <%s> but you passed in <%s>", kind, i, expected, actual), + CodeLocation: cl, + DocLink: "table-specs", + } +} + +func (g ginkgoErrors) IncorrectVariadicParameterTypeToTableFunction(expected, actual reflect.Type, kind string, cl CodeLocation) error { + return GinkgoError{ + Heading: fmt.Sprintf("Incorrect parameters type passed to %s", kind), + Message: fmt.Sprintf("The %s expected its variadic parameters to be of type <%s> but you passed in <%s>", kind, expected, actual), + CodeLocation: cl, + DocLink: "table-specs", + } +} + +/* Parallel Synchronization errors */ + +func (g ginkgoErrors) AggregatedReportUnavailableDueToNodeDisappearing() error { + return GinkgoError{ + Heading: "Test Report unavailable because a Ginkgo parallel process disappeared", + Message: "The aggregated report could not be fetched for a ReportAfterSuite node. A Ginkgo parallel process disappeared before it could finish reporting.", + } +} + +func (g ginkgoErrors) SynchronizedBeforeSuiteFailedOnProc1() error { + return GinkgoError{ + Heading: "SynchronizedBeforeSuite failed on Ginkgo parallel process #1", + Message: "The first SynchronizedBeforeSuite function running on Ginkgo parallel process #1 failed. This suite will now abort.", + } +} + +func (g ginkgoErrors) SynchronizedBeforeSuiteDisappearedOnProc1() error { + return GinkgoError{ + Heading: "Process #1 disappeared before SynchronizedBeforeSuite could report back", + Message: "Ginkgo parallel process #1 disappeared before the first SynchronizedBeforeSuite function completed. This suite will now abort.", + } +} + +/* Configuration errors */ + +func (g ginkgoErrors) UnknownTypePassedToRunSpecs(value interface{}) error { + return GinkgoError{ + Heading: "Unknown Type passed to RunSpecs", + Message: fmt.Sprintf("RunSpecs() accepts labels, and configuration of type types.SuiteConfig and/or types.ReporterConfig.\n You passed in: %v", value), + } +} + +var sharedParallelErrorMessage = "It looks like you are trying to run specs in parallel with go test.\nThis is unsupported and you should use the ginkgo CLI instead." + +func (g ginkgoErrors) InvalidParallelTotalConfiguration() error { + return GinkgoError{ + Heading: "-ginkgo.parallel.total must be >= 1", + Message: sharedParallelErrorMessage, + DocLink: "spec-parallelization", + } +} + +func (g ginkgoErrors) InvalidParallelProcessConfiguration() error { + return GinkgoError{ + Heading: "-ginkgo.parallel.process is one-indexed and must be <= ginkgo.parallel.total", + Message: sharedParallelErrorMessage, + DocLink: "spec-parallelization", + } +} + +func (g ginkgoErrors) MissingParallelHostConfiguration() error { + return GinkgoError{ + Heading: "-ginkgo.parallel.host is missing", + Message: sharedParallelErrorMessage, + DocLink: "spec-parallelization", + } +} + +func (g ginkgoErrors) UnreachableParallelHost(host string) error { + return GinkgoError{ + Heading: "Could not reach ginkgo.parallel.host:" + host, + Message: sharedParallelErrorMessage, + DocLink: "spec-parallelization", + } +} + +func (g ginkgoErrors) DryRunInParallelConfiguration() error { + return GinkgoError{ + Heading: "Ginkgo only performs -dryRun in serial mode.", + Message: "Please try running ginkgo -dryRun again, but without -p or -procs to ensure the suite is running in series.", + } +} + +func (g ginkgoErrors) GracePeriodCannotBeZero() error { + return GinkgoError{ + Heading: "Ginkgo requires a positive --grace-period.", + Message: "Please set --grace-period to a positive duration. The default is 30s.", + } +} + +func (g ginkgoErrors) ConflictingVerbosityConfiguration() error { + return GinkgoError{ + Heading: "Conflicting reporter verbosity settings.", + Message: "You can't set more than one of -v, -vv and --succinct. Please pick one!", + } +} + +func (g ginkgoErrors) InvalidOutputInterceptorModeConfiguration(value string) error { + return GinkgoError{ + Heading: fmt.Sprintf("Invalid value '%s' for --output-interceptor-mode.", value), + Message: "You must choose one of 'dup', 'swap', or 'none'.", + } +} + +func (g ginkgoErrors) InvalidGoFlagCount() error { + return GinkgoError{ + Heading: "Use of go test -count", + Message: "Ginkgo does not support using go test -count to rerun suites. Only -count=1 is allowed. To repeat suite runs, please use the ginkgo cli and `ginkgo -until-it-fails` or `ginkgo -repeat=N`.", + } +} + +func (g ginkgoErrors) InvalidGoFlagParallel() error { + return GinkgoError{ + Heading: "Use of go test -parallel", + Message: "Go test's implementation of parallelization does not actually parallelize Ginkgo specs. Please use the ginkgo cli and `ginkgo -p` or `ginkgo -procs=N` instead.", + } +} + +func (g ginkgoErrors) BothRepeatAndUntilItFails() error { + return GinkgoError{ + Heading: "--repeat and --until-it-fails are both set", + Message: "--until-it-fails directs Ginkgo to rerun specs indefinitely until they fail. --repeat directs Ginkgo to rerun specs a set number of times. You can't set both... which would you like?", + } +} + +/* Stack-Trace parsing errors */ + +func (g ginkgoErrors) FailedToParseStackTrace(message string) error { + return GinkgoError{ + Heading: "Failed to Parse Stack Trace", + Message: message, + } +} diff --git a/vendor/github.com/onsi/ginkgo/v2/types/file_filter.go b/vendor/github.com/onsi/ginkgo/v2/types/file_filter.go new file mode 100644 index 00000000000..cc21df71ec8 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/types/file_filter.go @@ -0,0 +1,106 @@ +package types + +import ( + "regexp" + "strconv" + "strings" +) + +func ParseFileFilters(filters []string) (FileFilters, error) { + ffs := FileFilters{} + for _, filter := range filters { + ff := FileFilter{} + if filter == "" { + return nil, GinkgoErrors.InvalidFileFilter(filter) + } + components := strings.Split(filter, ":") + if !(len(components) == 1 || len(components) == 2) { + return nil, GinkgoErrors.InvalidFileFilter(filter) + } + + var err error + ff.Filename, err = regexp.Compile(components[0]) + if err != nil { + return nil, err + } + if len(components) == 2 { + lineFilters := strings.Split(components[1], ",") + for _, lineFilter := range lineFilters { + components := strings.Split(lineFilter, "-") + if len(components) == 1 { + line, err := strconv.Atoi(strings.TrimSpace(components[0])) + if err != nil { + return nil, GinkgoErrors.InvalidFileFilter(filter) + } + ff.LineFilters = append(ff.LineFilters, LineFilter{line, line + 1}) + } else if len(components) == 2 { + line1, err := strconv.Atoi(strings.TrimSpace(components[0])) + if err != nil { + return nil, GinkgoErrors.InvalidFileFilter(filter) + } + line2, err := strconv.Atoi(strings.TrimSpace(components[1])) + if err != nil { + return nil, GinkgoErrors.InvalidFileFilter(filter) + } + ff.LineFilters = append(ff.LineFilters, LineFilter{line1, line2}) + } else { + return nil, GinkgoErrors.InvalidFileFilter(filter) + } + } + } + ffs = append(ffs, ff) + } + return ffs, nil +} + +type FileFilter struct { + Filename *regexp.Regexp + LineFilters LineFilters +} + +func (f FileFilter) Matches(locations []CodeLocation) bool { + for _, location := range locations { + if f.Filename.MatchString(location.FileName) && + f.LineFilters.Matches(location.LineNumber) { + return true + } + + } + return false +} + +type FileFilters []FileFilter + +func (ffs FileFilters) Matches(locations []CodeLocation) bool { + for _, ff := range ffs { + if ff.Matches(locations) { + return true + } + } + + return false +} + +type LineFilter struct { + Min int + Max int +} + +func (lf LineFilter) Matches(line int) bool { + return lf.Min <= line && line < lf.Max +} + +type LineFilters []LineFilter + +func (lfs LineFilters) Matches(line int) bool { + if len(lfs) == 0 { + return true + } + + for _, lf := range lfs { + if lf.Matches(line) { + return true + } + } + return false +} diff --git a/vendor/github.com/onsi/ginkgo/v2/types/flags.go b/vendor/github.com/onsi/ginkgo/v2/types/flags.go new file mode 100644 index 00000000000..9186ae873d0 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/types/flags.go @@ -0,0 +1,489 @@ +package types + +import ( + "flag" + "fmt" + "io" + "reflect" + "strings" + "time" + + "github.com/onsi/ginkgo/v2/formatter" +) + +type GinkgoFlag struct { + Name string + KeyPath string + SectionKey string + + Usage string + UsageArgument string + UsageDefaultValue string + + DeprecatedName string + DeprecatedDocLink string + DeprecatedVersion string + + ExportAs string +} + +type GinkgoFlags []GinkgoFlag + +func (f GinkgoFlags) CopyAppend(flags ...GinkgoFlag) GinkgoFlags { + out := GinkgoFlags{} + out = append(out, f...) + out = append(out, flags...) + return out +} + +func (f GinkgoFlags) WithPrefix(prefix string) GinkgoFlags { + if prefix == "" { + return f + } + out := GinkgoFlags{} + for _, flag := range f { + if flag.Name != "" { + flag.Name = prefix + "." + flag.Name + } + if flag.DeprecatedName != "" { + flag.DeprecatedName = prefix + "." + flag.DeprecatedName + } + if flag.ExportAs != "" { + flag.ExportAs = prefix + "." + flag.ExportAs + } + out = append(out, flag) + } + return out +} + +func (f GinkgoFlags) SubsetWithNames(names ...string) GinkgoFlags { + out := GinkgoFlags{} + for _, flag := range f { + for _, name := range names { + if flag.Name == name { + out = append(out, flag) + break + } + } + } + return out +} + +type GinkgoFlagSection struct { + Key string + Style string + Succinct bool + Heading string + Description string +} + +type GinkgoFlagSections []GinkgoFlagSection + +func (gfs GinkgoFlagSections) Lookup(key string) (GinkgoFlagSection, bool) { + for _, section := range gfs { + if section.Key == key { + return section, true + } + } + + return GinkgoFlagSection{}, false +} + +type GinkgoFlagSet struct { + flags GinkgoFlags + bindings interface{} + + sections GinkgoFlagSections + extraGoFlagsSection GinkgoFlagSection + + flagSet *flag.FlagSet +} + +// Call NewGinkgoFlagSet to create GinkgoFlagSet that creates and binds to it's own *flag.FlagSet +func NewGinkgoFlagSet(flags GinkgoFlags, bindings interface{}, sections GinkgoFlagSections) (GinkgoFlagSet, error) { + return bindFlagSet(GinkgoFlagSet{ + flags: flags, + bindings: bindings, + sections: sections, + }, nil) +} + +// Call NewGinkgoFlagSet to create GinkgoFlagSet that extends an existing *flag.FlagSet +func NewAttachedGinkgoFlagSet(flagSet *flag.FlagSet, flags GinkgoFlags, bindings interface{}, sections GinkgoFlagSections, extraGoFlagsSection GinkgoFlagSection) (GinkgoFlagSet, error) { + return bindFlagSet(GinkgoFlagSet{ + flags: flags, + bindings: bindings, + sections: sections, + extraGoFlagsSection: extraGoFlagsSection, + }, flagSet) +} + +func bindFlagSet(f GinkgoFlagSet, flagSet *flag.FlagSet) (GinkgoFlagSet, error) { + if flagSet == nil { + f.flagSet = flag.NewFlagSet("", flag.ContinueOnError) + //suppress all output as Ginkgo is responsible for formatting usage + f.flagSet.SetOutput(io.Discard) + } else { + f.flagSet = flagSet + //we're piggybacking on an existing flagset (typically go test) so we have limited control + //on user feedback + f.flagSet.Usage = f.substituteUsage + } + + for _, flag := range f.flags { + name := flag.Name + + deprecatedUsage := "[DEPRECATED]" + deprecatedName := flag.DeprecatedName + if name != "" { + deprecatedUsage = fmt.Sprintf("[DEPRECATED] use --%s instead", name) + } else if flag.Usage != "" { + deprecatedUsage += " " + flag.Usage + } + + value, ok := valueAtKeyPath(f.bindings, flag.KeyPath) + if !ok { + return GinkgoFlagSet{}, fmt.Errorf("could not load KeyPath: %s", flag.KeyPath) + } + + iface, addr := value.Interface(), value.Addr().Interface() + + switch value.Type() { + case reflect.TypeOf(string("")): + if name != "" { + f.flagSet.StringVar(addr.(*string), name, iface.(string), flag.Usage) + } + if deprecatedName != "" { + f.flagSet.StringVar(addr.(*string), deprecatedName, iface.(string), deprecatedUsage) + } + case reflect.TypeOf(int64(0)): + if name != "" { + f.flagSet.Int64Var(addr.(*int64), name, iface.(int64), flag.Usage) + } + if deprecatedName != "" { + f.flagSet.Int64Var(addr.(*int64), deprecatedName, iface.(int64), deprecatedUsage) + } + case reflect.TypeOf(float64(0)): + if name != "" { + f.flagSet.Float64Var(addr.(*float64), name, iface.(float64), flag.Usage) + } + if deprecatedName != "" { + f.flagSet.Float64Var(addr.(*float64), deprecatedName, iface.(float64), deprecatedUsage) + } + case reflect.TypeOf(int(0)): + if name != "" { + f.flagSet.IntVar(addr.(*int), name, iface.(int), flag.Usage) + } + if deprecatedName != "" { + f.flagSet.IntVar(addr.(*int), deprecatedName, iface.(int), deprecatedUsage) + } + case reflect.TypeOf(bool(true)): + if name != "" { + f.flagSet.BoolVar(addr.(*bool), name, iface.(bool), flag.Usage) + } + if deprecatedName != "" { + f.flagSet.BoolVar(addr.(*bool), deprecatedName, iface.(bool), deprecatedUsage) + } + case reflect.TypeOf(time.Duration(0)): + if name != "" { + f.flagSet.DurationVar(addr.(*time.Duration), name, iface.(time.Duration), flag.Usage) + } + if deprecatedName != "" { + f.flagSet.DurationVar(addr.(*time.Duration), deprecatedName, iface.(time.Duration), deprecatedUsage) + } + + case reflect.TypeOf([]string{}): + if name != "" { + f.flagSet.Var(stringSliceVar{value}, name, flag.Usage) + } + if deprecatedName != "" { + f.flagSet.Var(stringSliceVar{value}, deprecatedName, deprecatedUsage) + } + default: + return GinkgoFlagSet{}, fmt.Errorf("unsupported type %T", iface) + } + } + + return f, nil +} + +func (f GinkgoFlagSet) IsZero() bool { + return f.flagSet == nil +} + +func (f GinkgoFlagSet) WasSet(name string) bool { + found := false + f.flagSet.Visit(func(f *flag.Flag) { + if f.Name == name { + found = true + } + }) + + return found +} + +func (f GinkgoFlagSet) Lookup(name string) *flag.Flag { + return f.flagSet.Lookup(name) +} + +func (f GinkgoFlagSet) Parse(args []string) ([]string, error) { + if f.IsZero() { + return args, nil + } + err := f.flagSet.Parse(args) + if err != nil { + return []string{}, err + } + return f.flagSet.Args(), nil +} + +func (f GinkgoFlagSet) ValidateDeprecations(deprecationTracker *DeprecationTracker) { + if f.IsZero() { + return + } + f.flagSet.Visit(func(flag *flag.Flag) { + for _, ginkgoFlag := range f.flags { + if ginkgoFlag.DeprecatedName != "" && strings.HasSuffix(flag.Name, ginkgoFlag.DeprecatedName) { + message := fmt.Sprintf("--%s is deprecated", ginkgoFlag.DeprecatedName) + if ginkgoFlag.Name != "" { + message = fmt.Sprintf("--%s is deprecated, use --%s instead", ginkgoFlag.DeprecatedName, ginkgoFlag.Name) + } else if ginkgoFlag.Usage != "" { + message += " " + ginkgoFlag.Usage + } + + deprecationTracker.TrackDeprecation(Deprecation{ + Message: message, + DocLink: ginkgoFlag.DeprecatedDocLink, + Version: ginkgoFlag.DeprecatedVersion, + }) + } + } + }) +} + +func (f GinkgoFlagSet) Usage() string { + if f.IsZero() { + return "" + } + groupedFlags := map[GinkgoFlagSection]GinkgoFlags{} + ungroupedFlags := GinkgoFlags{} + managedFlags := map[string]bool{} + extraGoFlags := []*flag.Flag{} + + for _, flag := range f.flags { + managedFlags[flag.Name] = true + managedFlags[flag.DeprecatedName] = true + + if flag.Name == "" { + continue + } + + section, ok := f.sections.Lookup(flag.SectionKey) + if ok { + groupedFlags[section] = append(groupedFlags[section], flag) + } else { + ungroupedFlags = append(ungroupedFlags, flag) + } + } + + f.flagSet.VisitAll(func(flag *flag.Flag) { + if !managedFlags[flag.Name] { + extraGoFlags = append(extraGoFlags, flag) + } + }) + + out := "" + for _, section := range f.sections { + flags := groupedFlags[section] + if len(flags) == 0 { + continue + } + out += f.usageForSection(section) + if section.Succinct { + succinctFlags := []string{} + for _, flag := range flags { + if flag.Name != "" { + succinctFlags = append(succinctFlags, fmt.Sprintf("--%s", flag.Name)) + } + } + out += formatter.Fiw(1, formatter.COLS, section.Style+strings.Join(succinctFlags, ", ")+"{{/}}\n") + } else { + for _, flag := range flags { + out += f.usageForFlag(flag, section.Style) + } + } + out += "\n" + } + if len(ungroupedFlags) > 0 { + for _, flag := range ungroupedFlags { + out += f.usageForFlag(flag, "") + } + out += "\n" + } + if len(extraGoFlags) > 0 { + out += f.usageForSection(f.extraGoFlagsSection) + for _, goFlag := range extraGoFlags { + out += f.usageForGoFlag(goFlag) + } + } + + return out +} + +func (f GinkgoFlagSet) substituteUsage() { + fmt.Fprintln(f.flagSet.Output(), f.Usage()) +} + +func valueAtKeyPath(root interface{}, keyPath string) (reflect.Value, bool) { + if len(keyPath) == 0 { + return reflect.Value{}, false + } + + val := reflect.ValueOf(root) + components := strings.Split(keyPath, ".") + for _, component := range components { + val = reflect.Indirect(val) + switch val.Kind() { + case reflect.Map: + val = val.MapIndex(reflect.ValueOf(component)) + if val.Kind() == reflect.Interface { + val = reflect.ValueOf(val.Interface()) + } + case reflect.Struct: + val = val.FieldByName(component) + default: + return reflect.Value{}, false + } + if (val == reflect.Value{}) { + return reflect.Value{}, false + } + } + + return val, true +} + +func (f GinkgoFlagSet) usageForSection(section GinkgoFlagSection) string { + out := formatter.F(section.Style + "{{bold}}{{underline}}" + section.Heading + "{{/}}\n") + if section.Description != "" { + out += formatter.Fiw(0, formatter.COLS, section.Description+"\n") + } + return out +} + +func (f GinkgoFlagSet) usageForFlag(flag GinkgoFlag, style string) string { + argument := flag.UsageArgument + defValue := flag.UsageDefaultValue + if argument == "" { + value, _ := valueAtKeyPath(f.bindings, flag.KeyPath) + switch value.Type() { + case reflect.TypeOf(string("")): + argument = "string" + case reflect.TypeOf(int64(0)), reflect.TypeOf(int(0)): + argument = "int" + case reflect.TypeOf(time.Duration(0)): + argument = "duration" + case reflect.TypeOf(float64(0)): + argument = "float" + case reflect.TypeOf([]string{}): + argument = "string" + } + } + if argument != "" { + argument = "[" + argument + "] " + } + if defValue != "" { + defValue = fmt.Sprintf("(default: %s)", defValue) + } + hyphens := "--" + if len(flag.Name) == 1 { + hyphens = "-" + } + + out := formatter.Fi(1, style+"%s%s{{/}} %s{{gray}}%s{{/}}\n", hyphens, flag.Name, argument, defValue) + out += formatter.Fiw(2, formatter.COLS, "{{light-gray}}%s{{/}}\n", flag.Usage) + return out +} + +func (f GinkgoFlagSet) usageForGoFlag(goFlag *flag.Flag) string { + //Taken directly from the flag package + out := fmt.Sprintf(" -%s", goFlag.Name) + name, usage := flag.UnquoteUsage(goFlag) + if len(name) > 0 { + out += " " + name + } + if len(out) <= 4 { + out += "\t" + } else { + out += "\n \t" + } + out += strings.ReplaceAll(usage, "\n", "\n \t") + out += "\n" + return out +} + +type stringSliceVar struct { + slice reflect.Value +} + +func (ssv stringSliceVar) String() string { return "" } +func (ssv stringSliceVar) Set(s string) error { + ssv.slice.Set(reflect.AppendSlice(ssv.slice, reflect.ValueOf([]string{s}))) + return nil +} + +//given a set of GinkgoFlags and bindings, generate flag arguments suitable to be passed to an application with that set of flags configured. +func GenerateFlagArgs(flags GinkgoFlags, bindings interface{}) ([]string, error) { + result := []string{} + for _, flag := range flags { + name := flag.ExportAs + if name == "" { + name = flag.Name + } + if name == "" { + continue + } + + value, ok := valueAtKeyPath(bindings, flag.KeyPath) + if !ok { + return []string{}, fmt.Errorf("could not load KeyPath: %s", flag.KeyPath) + } + + iface := value.Interface() + switch value.Type() { + case reflect.TypeOf(string("")): + if iface.(string) != "" { + result = append(result, fmt.Sprintf("--%s=%s", name, iface)) + } + case reflect.TypeOf(int64(0)): + if iface.(int64) != 0 { + result = append(result, fmt.Sprintf("--%s=%d", name, iface)) + } + case reflect.TypeOf(float64(0)): + if iface.(float64) != 0 { + result = append(result, fmt.Sprintf("--%s=%f", name, iface)) + } + case reflect.TypeOf(int(0)): + if iface.(int) != 0 { + result = append(result, fmt.Sprintf("--%s=%d", name, iface)) + } + case reflect.TypeOf(bool(true)): + if iface.(bool) { + result = append(result, fmt.Sprintf("--%s", name)) + } + case reflect.TypeOf(time.Duration(0)): + if iface.(time.Duration) != time.Duration(0) { + result = append(result, fmt.Sprintf("--%s=%s", name, iface)) + } + + case reflect.TypeOf([]string{}): + strings := iface.([]string) + for _, s := range strings { + result = append(result, fmt.Sprintf("--%s=%s", name, s)) + } + default: + return []string{}, fmt.Errorf("unsupported type %T", iface) + } + } + + return result, nil +} diff --git a/vendor/github.com/onsi/ginkgo/v2/types/label_filter.go b/vendor/github.com/onsi/ginkgo/v2/types/label_filter.go new file mode 100644 index 00000000000..0403f9e6319 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/types/label_filter.go @@ -0,0 +1,347 @@ +package types + +import ( + "fmt" + "regexp" + "strings" +) + +var DEBUG_LABEL_FILTER_PARSING = false + +type LabelFilter func([]string) bool + +func matchLabelAction(label string) LabelFilter { + expected := strings.ToLower(label) + return func(labels []string) bool { + for i := range labels { + if strings.ToLower(labels[i]) == expected { + return true + } + } + return false + } +} + +func matchLabelRegexAction(regex *regexp.Regexp) LabelFilter { + return func(labels []string) bool { + for i := range labels { + if regex.MatchString(labels[i]) { + return true + } + } + return false + } +} + +func notAction(filter LabelFilter) LabelFilter { + return func(labels []string) bool { return !filter(labels) } +} + +func andAction(a, b LabelFilter) LabelFilter { + return func(labels []string) bool { return a(labels) && b(labels) } +} + +func orAction(a, b LabelFilter) LabelFilter { + return func(labels []string) bool { return a(labels) || b(labels) } +} + +type lfToken uint + +const ( + lfTokenInvalid lfToken = iota + + lfTokenRoot + lfTokenOpenGroup + lfTokenCloseGroup + lfTokenNot + lfTokenAnd + lfTokenOr + lfTokenRegexp + lfTokenLabel + lfTokenEOF +) + +func (l lfToken) Precedence() int { + switch l { + case lfTokenRoot, lfTokenOpenGroup: + return 0 + case lfTokenOr: + return 1 + case lfTokenAnd: + return 2 + case lfTokenNot: + return 3 + } + return -1 +} + +func (l lfToken) String() string { + switch l { + case lfTokenRoot: + return "ROOT" + case lfTokenOpenGroup: + return "(" + case lfTokenCloseGroup: + return ")" + case lfTokenNot: + return "!" + case lfTokenAnd: + return "&&" + case lfTokenOr: + return "||" + case lfTokenRegexp: + return "/regexp/" + case lfTokenLabel: + return "label" + case lfTokenEOF: + return "EOF" + } + return "INVALID" +} + +type treeNode struct { + token lfToken + location int + value string + + parent *treeNode + leftNode *treeNode + rightNode *treeNode +} + +func (tn *treeNode) setRightNode(node *treeNode) { + tn.rightNode = node + node.parent = tn +} + +func (tn *treeNode) setLeftNode(node *treeNode) { + tn.leftNode = node + node.parent = tn +} + +func (tn *treeNode) firstAncestorWithPrecedenceLEQ(precedence int) *treeNode { + if tn.token.Precedence() <= precedence { + return tn + } + return tn.parent.firstAncestorWithPrecedenceLEQ(precedence) +} + +func (tn *treeNode) firstUnmatchedOpenNode() *treeNode { + if tn.token == lfTokenOpenGroup { + return tn + } + if tn.parent == nil { + return nil + } + return tn.parent.firstUnmatchedOpenNode() +} + +func (tn *treeNode) constructLabelFilter(input string) (LabelFilter, error) { + switch tn.token { + case lfTokenOpenGroup: + return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, tn.location, "Mismatched '(' - could not find matching ')'.") + case lfTokenLabel: + return matchLabelAction(tn.value), nil + case lfTokenRegexp: + re, err := regexp.Compile(tn.value) + if err != nil { + return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, tn.location, fmt.Sprintf("RegExp compilation error: %s", err)) + } + return matchLabelRegexAction(re), nil + } + + if tn.rightNode == nil { + return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, -1, "Unexpected EOF.") + } + rightLF, err := tn.rightNode.constructLabelFilter(input) + if err != nil { + return nil, err + } + + switch tn.token { + case lfTokenRoot, lfTokenCloseGroup: + return rightLF, nil + case lfTokenNot: + return notAction(rightLF), nil + } + + if tn.leftNode == nil { + return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, tn.location, fmt.Sprintf("Malformed tree - '%s' is missing left operand.", tn.token)) + } + leftLF, err := tn.leftNode.constructLabelFilter(input) + if err != nil { + return nil, err + } + + switch tn.token { + case lfTokenAnd: + return andAction(leftLF, rightLF), nil + case lfTokenOr: + return orAction(leftLF, rightLF), nil + } + + return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, tn.location, fmt.Sprintf("Invalid token '%s'.", tn.token)) +} + +func (tn *treeNode) tokenString() string { + out := fmt.Sprintf("<%s", tn.token) + if tn.value != "" { + out += " | " + tn.value + } + out += ">" + return out +} + +func (tn *treeNode) toString(indent int) string { + out := tn.tokenString() + "\n" + if tn.leftNode != nil { + out += fmt.Sprintf("%s |_(L)_%s", strings.Repeat(" ", indent), tn.leftNode.toString(indent+1)) + } + if tn.rightNode != nil { + out += fmt.Sprintf("%s |_(R)_%s", strings.Repeat(" ", indent), tn.rightNode.toString(indent+1)) + } + return out +} + +func tokenize(input string) func() (*treeNode, error) { + runes, i := []rune(input), 0 + + peekIs := func(r rune) bool { + if i+1 < len(runes) { + return runes[i+1] == r + } + return false + } + + consumeUntil := func(cutset string) (string, int) { + j := i + for ; j < len(runes); j++ { + if strings.IndexRune(cutset, runes[j]) >= 0 { + break + } + } + return string(runes[i:j]), j - i + } + + return func() (*treeNode, error) { + for i < len(runes) && runes[i] == ' ' { + i += 1 + } + + if i >= len(runes) { + return &treeNode{token: lfTokenEOF}, nil + } + + node := &treeNode{location: i} + switch runes[i] { + case '&': + if !peekIs('&') { + return &treeNode{}, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, i, "Invalid token '&'. Did you mean '&&'?") + } + i += 2 + node.token = lfTokenAnd + case '|': + if !peekIs('|') { + return &treeNode{}, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, i, "Invalid token '|'. Did you mean '||'?") + } + i += 2 + node.token = lfTokenOr + case '!': + i += 1 + node.token = lfTokenNot + case ',': + i += 1 + node.token = lfTokenOr + case '(': + i += 1 + node.token = lfTokenOpenGroup + case ')': + i += 1 + node.token = lfTokenCloseGroup + case '/': + i += 1 + value, n := consumeUntil("/") + i += n + 1 + node.token, node.value = lfTokenRegexp, value + default: + value, n := consumeUntil("&|!,()/") + i += n + node.token, node.value = lfTokenLabel, strings.TrimSpace(value) + } + return node, nil + } +} + +func ParseLabelFilter(input string) (LabelFilter, error) { + if DEBUG_LABEL_FILTER_PARSING { + fmt.Println("\n==============") + fmt.Println("Input: ", input) + fmt.Print("Tokens: ") + } + nextToken := tokenize(input) + + root := &treeNode{token: lfTokenRoot} + current := root +LOOP: + for { + node, err := nextToken() + if err != nil { + return nil, err + } + + if DEBUG_LABEL_FILTER_PARSING { + fmt.Print(node.tokenString() + " ") + } + + switch node.token { + case lfTokenEOF: + break LOOP + case lfTokenLabel, lfTokenRegexp: + if current.rightNode != nil { + return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, node.location, "Found two adjacent labels. You need an operator between them.") + } + current.setRightNode(node) + case lfTokenNot, lfTokenOpenGroup: + if current.rightNode != nil { + return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, node.location, fmt.Sprintf("Invalid token '%s'.", node.token)) + } + current.setRightNode(node) + current = node + case lfTokenAnd, lfTokenOr: + if current.rightNode == nil { + return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, node.location, fmt.Sprintf("Operator '%s' missing left hand operand.", node.token)) + } + nodeToStealFrom := current.firstAncestorWithPrecedenceLEQ(node.token.Precedence()) + node.setLeftNode(nodeToStealFrom.rightNode) + nodeToStealFrom.setRightNode(node) + current = node + case lfTokenCloseGroup: + firstUnmatchedOpenNode := current.firstUnmatchedOpenNode() + if firstUnmatchedOpenNode == nil { + return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, node.location, "Mismatched ')' - could not find matching '('.") + } + if firstUnmatchedOpenNode == current && current.rightNode == nil { + return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, node.location, "Found empty '()' group.") + } + firstUnmatchedOpenNode.token = lfTokenCloseGroup //signify the group is now closed + current = firstUnmatchedOpenNode.parent + default: + return nil, GinkgoErrors.SyntaxErrorParsingLabelFilter(input, node.location, fmt.Sprintf("Unknown token '%s'.", node.token)) + } + } + if DEBUG_LABEL_FILTER_PARSING { + fmt.Printf("\n Tree:\n%s", root.toString(0)) + } + return root.constructLabelFilter(input) +} + +func ValidateAndCleanupLabel(label string, cl CodeLocation) (string, error) { + out := strings.TrimSpace(label) + if out == "" { + return "", GinkgoErrors.InvalidEmptyLabel(cl) + } + if strings.ContainsAny(out, "&|!,()/") { + return "", GinkgoErrors.InvalidLabel(label, cl) + } + return out, nil +} diff --git a/vendor/github.com/onsi/ginkgo/v2/types/report_entry.go b/vendor/github.com/onsi/ginkgo/v2/types/report_entry.go new file mode 100644 index 00000000000..7b1524b52e8 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/types/report_entry.go @@ -0,0 +1,190 @@ +package types + +import ( + "encoding/json" + "fmt" + "time" +) + +// ReportEntryValue wraps a report entry's value ensuring it can be encoded and decoded safely into reports +// and across the network connection when running in parallel +type ReportEntryValue struct { + raw interface{} //unexported to prevent gob from freaking out about unregistered structs + AsJSON string + Representation string +} + +func WrapEntryValue(value interface{}) ReportEntryValue { + return ReportEntryValue{ + raw: value, + } +} + +func (rev ReportEntryValue) GetRawValue() interface{} { + return rev.raw +} + +func (rev ReportEntryValue) String() string { + if rev.raw == nil { + return "" + } + if colorableStringer, ok := rev.raw.(ColorableStringer); ok { + return colorableStringer.ColorableString() + } + + if stringer, ok := rev.raw.(fmt.Stringer); ok { + return stringer.String() + } + if rev.Representation != "" { + return rev.Representation + } + return fmt.Sprintf("%+v", rev.raw) +} + +func (rev ReportEntryValue) MarshalJSON() ([]byte, error) { + //All this to capture the representation at encoding-time, not creating time + //This way users can Report on pointers and get their final values at reporting-time + out := struct { + AsJSON string + Representation string + }{ + Representation: rev.String(), + } + asJSON, err := json.Marshal(rev.raw) + if err != nil { + return nil, err + } + out.AsJSON = string(asJSON) + + return json.Marshal(out) +} + +func (rev *ReportEntryValue) UnmarshalJSON(data []byte) error { + in := struct { + AsJSON string + Representation string + }{} + err := json.Unmarshal(data, &in) + if err != nil { + return err + } + rev.AsJSON = in.AsJSON + rev.Representation = in.Representation + return json.Unmarshal([]byte(in.AsJSON), &(rev.raw)) +} + +func (rev ReportEntryValue) GobEncode() ([]byte, error) { + return rev.MarshalJSON() +} + +func (rev *ReportEntryValue) GobDecode(data []byte) error { + return rev.UnmarshalJSON(data) +} + +// ReportEntry captures information attached to `SpecReport` via `AddReportEntry` +type ReportEntry struct { + // Visibility captures the visibility policy for this ReportEntry + Visibility ReportEntryVisibility + // Location captures the location of the AddReportEntry call + Location CodeLocation + + Time time.Time //need this for backwards compatibility + TimelineLocation TimelineLocation + + // Name captures the name of this report + Name string + // Value captures the (optional) object passed into AddReportEntry - this can be + // anything the user wants. The value passed to AddReportEntry is wrapped in a ReportEntryValue to make + // encoding/decoding the value easier. To access the raw value call entry.GetRawValue() + Value ReportEntryValue +} + +// ColorableStringer is an interface that ReportEntry values can satisfy. If they do then ColorableString() is used to generate their representation. +type ColorableStringer interface { + ColorableString() string +} + +// StringRepresentation() returns the string representation of the value associated with the ReportEntry -- +// if value is nil, empty string is returned +// if value is a `ColorableStringer` then `Value.ColorableString()` is returned +// if value is a `fmt.Stringer` then `Value.String()` is returned +// otherwise the value is formatted with "%+v" +func (entry ReportEntry) StringRepresentation() string { + return entry.Value.String() +} + +// GetRawValue returns the Value object that was passed to AddReportEntry +// If called in-process this will be the same object that was passed into AddReportEntry. +// If used from a rehydrated JSON file _or_ in a ReportAfterSuite when running in parallel this will be +// a JSON-decoded {}interface. If you want to reconstitute your original object you can decode the entry.Value.AsJSON +// field yourself. +func (entry ReportEntry) GetRawValue() interface{} { + return entry.Value.GetRawValue() +} + +func (entry ReportEntry) GetTimelineLocation() TimelineLocation { + return entry.TimelineLocation +} + +type ReportEntries []ReportEntry + +func (re ReportEntries) HasVisibility(visibilities ...ReportEntryVisibility) bool { + for _, entry := range re { + if entry.Visibility.Is(visibilities...) { + return true + } + } + return false +} + +func (re ReportEntries) WithVisibility(visibilities ...ReportEntryVisibility) ReportEntries { + out := ReportEntries{} + + for _, entry := range re { + if entry.Visibility.Is(visibilities...) { + out = append(out, entry) + } + } + + return out +} + +// ReportEntryVisibility governs the visibility of ReportEntries in Ginkgo's console reporter +type ReportEntryVisibility uint + +const ( + // Always print out this ReportEntry + ReportEntryVisibilityAlways ReportEntryVisibility = iota + // Only print out this ReportEntry if the spec fails or if the test is run with -v + ReportEntryVisibilityFailureOrVerbose + // Never print out this ReportEntry (note that ReportEntrys are always encoded in machine readable reports (e.g. JSON, JUnit, etc.)) + ReportEntryVisibilityNever +) + +var revEnumSupport = NewEnumSupport(map[uint]string{ + uint(ReportEntryVisibilityAlways): "always", + uint(ReportEntryVisibilityFailureOrVerbose): "failure-or-verbose", + uint(ReportEntryVisibilityNever): "never", +}) + +func (rev ReportEntryVisibility) String() string { + return revEnumSupport.String(uint(rev)) +} +func (rev *ReportEntryVisibility) UnmarshalJSON(b []byte) error { + out, err := revEnumSupport.UnmarshJSON(b) + *rev = ReportEntryVisibility(out) + return err +} +func (rev ReportEntryVisibility) MarshalJSON() ([]byte, error) { + return revEnumSupport.MarshJSON(uint(rev)) +} + +func (v ReportEntryVisibility) Is(visibilities ...ReportEntryVisibility) bool { + for _, visibility := range visibilities { + if v == visibility { + return true + } + } + + return false +} diff --git a/vendor/github.com/onsi/ginkgo/v2/types/types.go b/vendor/github.com/onsi/ginkgo/v2/types/types.go new file mode 100644 index 00000000000..a388f70c09e --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/types/types.go @@ -0,0 +1,910 @@ +package types + +import ( + "encoding/json" + "fmt" + "sort" + "strings" + "time" +) + +const GINKGO_FOCUS_EXIT_CODE = 197 +const GINKGO_TIME_FORMAT = "01/02/06 15:04:05.999" + +// Report captures information about a Ginkgo test run +type Report struct { + //SuitePath captures the absolute path to the test suite + SuitePath string + + //SuiteDescription captures the description string passed to the DSL's RunSpecs() function + SuiteDescription string + + //SuiteLabels captures any labels attached to the suite by the DSL's RunSpecs() function + SuiteLabels []string + + //SuiteSucceeded captures the success or failure status of the test run + //If true, the test run is considered successful. + //If false, the test run is considered unsuccessful + SuiteSucceeded bool + + //SuiteHasProgrammaticFocus captures whether the test suite has a test or set of tests that are programmatically focused + //(i.e an `FIt` or an `FDescribe` + SuiteHasProgrammaticFocus bool + + //SpecialSuiteFailureReasons may contain special failure reasons + //For example, a test suite might be considered "failed" even if none of the individual specs + //have a failure state. For example, if the user has configured --fail-on-pending the test suite + //will have failed if there are pending tests even though all non-pending tests may have passed. In such + //cases, Ginkgo populates SpecialSuiteFailureReasons with a clear message indicating the reason for the failure. + //SpecialSuiteFailureReasons is also populated if the test suite is interrupted by the user. + //Since multiple special failure reasons can occur, this field is a slice. + SpecialSuiteFailureReasons []string + + //PreRunStats contains a set of stats captured before the test run begins. This is primarily used + //by Ginkgo's reporter to tell the user how many specs are in the current suite (PreRunStats.TotalSpecs) + //and how many it intends to run (PreRunStats.SpecsThatWillRun) after applying any relevant focus or skip filters. + PreRunStats PreRunStats + + //StartTime and EndTime capture the start and end time of the test run + StartTime time.Time + EndTime time.Time + + //RunTime captures the duration of the test run + RunTime time.Duration + + //SuiteConfig captures the Ginkgo configuration governing this test run + //SuiteConfig includes information necessary for reproducing an identical test run, + //such as the random seed and any filters applied during the test run + SuiteConfig SuiteConfig + + //SpecReports is a list of all SpecReports generated by this test run + SpecReports SpecReports +} + +// PreRunStats contains a set of stats captured before the test run begins. This is primarily used +// by Ginkgo's reporter to tell the user how many specs are in the current suite (PreRunStats.TotalSpecs) +// and how many it intends to run (PreRunStats.SpecsThatWillRun) after applying any relevant focus or skip filters. +type PreRunStats struct { + TotalSpecs int + SpecsThatWillRun int +} + +// Add is used by Ginkgo's parallel aggregation mechanisms to combine test run reports form individual parallel processes +// to form a complete final report. +func (report Report) Add(other Report) Report { + report.SuiteSucceeded = report.SuiteSucceeded && other.SuiteSucceeded + + if other.StartTime.Before(report.StartTime) { + report.StartTime = other.StartTime + } + + if other.EndTime.After(report.EndTime) { + report.EndTime = other.EndTime + } + + specialSuiteFailureReasons := []string{} + reasonsLookup := map[string]bool{} + for _, reasons := range [][]string{report.SpecialSuiteFailureReasons, other.SpecialSuiteFailureReasons} { + for _, reason := range reasons { + if !reasonsLookup[reason] { + reasonsLookup[reason] = true + specialSuiteFailureReasons = append(specialSuiteFailureReasons, reason) + } + } + } + report.SpecialSuiteFailureReasons = specialSuiteFailureReasons + report.RunTime = report.EndTime.Sub(report.StartTime) + + reports := make(SpecReports, len(report.SpecReports)+len(other.SpecReports)) + for i := range report.SpecReports { + reports[i] = report.SpecReports[i] + } + offset := len(report.SpecReports) + for i := range other.SpecReports { + reports[i+offset] = other.SpecReports[i] + } + + report.SpecReports = reports + return report +} + +// SpecReport captures information about a Ginkgo spec. +type SpecReport struct { + // ContainerHierarchyTexts is a slice containing the text strings of + // all Describe/Context/When containers in this spec's hierarchy. + ContainerHierarchyTexts []string + + // ContainerHierarchyLocations is a slice containing the CodeLocations of + // all Describe/Context/When containers in this spec's hierarchy. + ContainerHierarchyLocations []CodeLocation + + // ContainerHierarchyLabels is a slice containing the labels of + // all Describe/Context/When containers in this spec's hierarchy + ContainerHierarchyLabels [][]string + + // LeafNodeType, LeadNodeLocation, LeafNodeLabels and LeafNodeText capture the NodeType, CodeLocation, and text + // of the Ginkgo node being tested (typically an NodeTypeIt node, though this can also be + // one of the NodeTypesForSuiteLevelNodes node types) + LeafNodeType NodeType + LeafNodeLocation CodeLocation + LeafNodeLabels []string + LeafNodeText string + + // State captures whether the spec has passed, failed, etc. + State SpecState + + // IsSerial captures whether the spec has the Serial decorator + IsSerial bool + + // IsInOrderedContainer captures whether the spec appears in an Ordered container + IsInOrderedContainer bool + + // StartTime and EndTime capture the start and end time of the spec + StartTime time.Time + EndTime time.Time + + // RunTime captures the duration of the spec + RunTime time.Duration + + // ParallelProcess captures the parallel process that this spec ran on + ParallelProcess int + + // RunningInParallel captures whether this spec is part of a suite that ran in parallel + RunningInParallel bool + + //Failure is populated if a spec has failed, panicked, been interrupted, or skipped by the user (e.g. calling Skip()) + //It includes detailed information about the Failure + Failure Failure + + // NumAttempts captures the number of times this Spec was run. + // Flakey specs can be retried with ginkgo --flake-attempts=N or the use of the FlakeAttempts decorator. + // Repeated specs can be retried with the use of the MustPassRepeatedly decorator + NumAttempts int + + // MaxFlakeAttempts captures whether the spec has been retried with ginkgo --flake-attempts=N or the use of the FlakeAttempts decorator. + MaxFlakeAttempts int + + // MaxMustPassRepeatedly captures whether the spec has the MustPassRepeatedly decorator + MaxMustPassRepeatedly int + + // CapturedGinkgoWriterOutput contains text printed to the GinkgoWriter + CapturedGinkgoWriterOutput string + + // CapturedStdOutErr contains text printed to stdout/stderr (when running in parallel) + // This is always empty when running in series or calling CurrentSpecReport() + // It is used internally by Ginkgo's reporter + CapturedStdOutErr string + + // ReportEntries contains any reports added via `AddReportEntry` + ReportEntries ReportEntries + + // ProgressReports contains any progress reports generated during this spec. These can either be manually triggered, or automatically generated by Ginkgo via the PollProgressAfter() decorator + ProgressReports []ProgressReport + + // AdditionalFailures contains any failures that occurred after the initial spec failure. These typically occur in cleanup nodes after the initial failure and are only emitted when running in verbose mode. + AdditionalFailures []AdditionalFailure + + // SpecEvents capture additional events that occur during the spec run + SpecEvents SpecEvents +} + +func (report SpecReport) MarshalJSON() ([]byte, error) { + //All this to avoid emitting an empty Failure struct in the JSON + out := struct { + ContainerHierarchyTexts []string + ContainerHierarchyLocations []CodeLocation + ContainerHierarchyLabels [][]string + LeafNodeType NodeType + LeafNodeLocation CodeLocation + LeafNodeLabels []string + LeafNodeText string + State SpecState + StartTime time.Time + EndTime time.Time + RunTime time.Duration + ParallelProcess int + Failure *Failure `json:",omitempty"` + NumAttempts int + MaxFlakeAttempts int + MaxMustPassRepeatedly int + CapturedGinkgoWriterOutput string `json:",omitempty"` + CapturedStdOutErr string `json:",omitempty"` + ReportEntries ReportEntries `json:",omitempty"` + ProgressReports []ProgressReport `json:",omitempty"` + AdditionalFailures []AdditionalFailure `json:",omitempty"` + SpecEvents SpecEvents `json:",omitempty"` + }{ + ContainerHierarchyTexts: report.ContainerHierarchyTexts, + ContainerHierarchyLocations: report.ContainerHierarchyLocations, + ContainerHierarchyLabels: report.ContainerHierarchyLabels, + LeafNodeType: report.LeafNodeType, + LeafNodeLocation: report.LeafNodeLocation, + LeafNodeLabels: report.LeafNodeLabels, + LeafNodeText: report.LeafNodeText, + State: report.State, + StartTime: report.StartTime, + EndTime: report.EndTime, + RunTime: report.RunTime, + ParallelProcess: report.ParallelProcess, + Failure: nil, + ReportEntries: nil, + NumAttempts: report.NumAttempts, + MaxFlakeAttempts: report.MaxFlakeAttempts, + MaxMustPassRepeatedly: report.MaxMustPassRepeatedly, + CapturedGinkgoWriterOutput: report.CapturedGinkgoWriterOutput, + CapturedStdOutErr: report.CapturedStdOutErr, + } + + if !report.Failure.IsZero() { + out.Failure = &(report.Failure) + } + if len(report.ReportEntries) > 0 { + out.ReportEntries = report.ReportEntries + } + if len(report.ProgressReports) > 0 { + out.ProgressReports = report.ProgressReports + } + if len(report.AdditionalFailures) > 0 { + out.AdditionalFailures = report.AdditionalFailures + } + if len(report.SpecEvents) > 0 { + out.SpecEvents = report.SpecEvents + } + + return json.Marshal(out) +} + +// CombinedOutput returns a single string representation of both CapturedStdOutErr and CapturedGinkgoWriterOutput +// Note that both are empty when using CurrentSpecReport() so CurrentSpecReport().CombinedOutput() will always be empty. +// CombinedOutput() is used internally by Ginkgo's reporter. +func (report SpecReport) CombinedOutput() string { + if report.CapturedStdOutErr == "" { + return report.CapturedGinkgoWriterOutput + } + if report.CapturedGinkgoWriterOutput == "" { + return report.CapturedStdOutErr + } + return report.CapturedStdOutErr + "\n" + report.CapturedGinkgoWriterOutput +} + +// Failed returns true if report.State is one of the SpecStateFailureStates +// (SpecStateFailed, SpecStatePanicked, SpecStateinterrupted, SpecStateAborted) +func (report SpecReport) Failed() bool { + return report.State.Is(SpecStateFailureStates) +} + +// FullText returns a concatenation of all the report.ContainerHierarchyTexts and report.LeafNodeText +func (report SpecReport) FullText() string { + texts := []string{} + texts = append(texts, report.ContainerHierarchyTexts...) + if report.LeafNodeText != "" { + texts = append(texts, report.LeafNodeText) + } + return strings.Join(texts, " ") +} + +// Labels returns a deduped set of all the spec's Labels. +func (report SpecReport) Labels() []string { + out := []string{} + seen := map[string]bool{} + for _, labels := range report.ContainerHierarchyLabels { + for _, label := range labels { + if !seen[label] { + seen[label] = true + out = append(out, label) + } + } + } + for _, label := range report.LeafNodeLabels { + if !seen[label] { + seen[label] = true + out = append(out, label) + } + } + + return out +} + +// MatchesLabelFilter returns true if the spec satisfies the passed in label filter query +func (report SpecReport) MatchesLabelFilter(query string) (bool, error) { + filter, err := ParseLabelFilter(query) + if err != nil { + return false, err + } + return filter(report.Labels()), nil +} + +// FileName() returns the name of the file containing the spec +func (report SpecReport) FileName() string { + return report.LeafNodeLocation.FileName +} + +// LineNumber() returns the line number of the leaf node +func (report SpecReport) LineNumber() int { + return report.LeafNodeLocation.LineNumber +} + +// FailureMessage() returns the failure message (or empty string if the test hasn't failed) +func (report SpecReport) FailureMessage() string { + return report.Failure.Message +} + +// FailureLocation() returns the location of the failure (or an empty CodeLocation if the test hasn't failed) +func (report SpecReport) FailureLocation() CodeLocation { + return report.Failure.Location +} + +// Timeline() returns a timeline view of the report +func (report SpecReport) Timeline() Timeline { + timeline := Timeline{} + if !report.Failure.IsZero() { + timeline = append(timeline, report.Failure) + if report.Failure.AdditionalFailure != nil { + timeline = append(timeline, *(report.Failure.AdditionalFailure)) + } + } + for _, additionalFailure := range report.AdditionalFailures { + timeline = append(timeline, additionalFailure) + } + for _, reportEntry := range report.ReportEntries { + timeline = append(timeline, reportEntry) + } + for _, progressReport := range report.ProgressReports { + timeline = append(timeline, progressReport) + } + for _, specEvent := range report.SpecEvents { + timeline = append(timeline, specEvent) + } + sort.Sort(timeline) + return timeline +} + +type SpecReports []SpecReport + +// WithLeafNodeType returns the subset of SpecReports with LeafNodeType matching one of the requested NodeTypes +func (reports SpecReports) WithLeafNodeType(nodeTypes NodeType) SpecReports { + count := 0 + for i := range reports { + if reports[i].LeafNodeType.Is(nodeTypes) { + count++ + } + } + + out := make(SpecReports, count) + j := 0 + for i := range reports { + if reports[i].LeafNodeType.Is(nodeTypes) { + out[j] = reports[i] + j++ + } + } + return out +} + +// WithState returns the subset of SpecReports with State matching one of the requested SpecStates +func (reports SpecReports) WithState(states SpecState) SpecReports { + count := 0 + for i := range reports { + if reports[i].State.Is(states) { + count++ + } + } + + out, j := make(SpecReports, count), 0 + for i := range reports { + if reports[i].State.Is(states) { + out[j] = reports[i] + j++ + } + } + return out +} + +// CountWithState returns the number of SpecReports with State matching one of the requested SpecStates +func (reports SpecReports) CountWithState(states SpecState) int { + n := 0 + for i := range reports { + if reports[i].State.Is(states) { + n += 1 + } + } + return n +} + +// If the Spec passes, CountOfFlakedSpecs returns the number of SpecReports that failed after multiple attempts. +func (reports SpecReports) CountOfFlakedSpecs() int { + n := 0 + for i := range reports { + if reports[i].MaxFlakeAttempts > 1 && reports[i].State.Is(SpecStatePassed) && reports[i].NumAttempts > 1 { + n += 1 + } + } + return n +} + +// If the Spec fails, CountOfRepeatedSpecs returns the number of SpecReports that passed after multiple attempts +func (reports SpecReports) CountOfRepeatedSpecs() int { + n := 0 + for i := range reports { + if reports[i].MaxMustPassRepeatedly > 1 && reports[i].State.Is(SpecStateFailureStates) && reports[i].NumAttempts > 1 { + n += 1 + } + } + return n +} + +// TimelineLocation captures the location of an event in the spec's timeline +type TimelineLocation struct { + //Offset is the offset (in bytes) of the event relative to the GinkgoWriter stream + Offset int `json:",omitempty"` + + //Order is the order of the event with respect to other events. The absolute value of Order + //is irrelevant. All that matters is that an event with a lower Order occurs before ane vent with a higher Order + Order int `json:",omitempty"` + + Time time.Time +} + +// TimelineEvent represent an event on the timeline +// consumers of Timeline will need to check the concrete type of each entry to determine how to handle it +type TimelineEvent interface { + GetTimelineLocation() TimelineLocation +} + +type Timeline []TimelineEvent + +func (t Timeline) Len() int { return len(t) } +func (t Timeline) Less(i, j int) bool { + return t[i].GetTimelineLocation().Order < t[j].GetTimelineLocation().Order +} +func (t Timeline) Swap(i, j int) { t[i], t[j] = t[j], t[i] } +func (t Timeline) WithoutHiddenReportEntries() Timeline { + out := Timeline{} + for _, event := range t { + if reportEntry, isReportEntry := event.(ReportEntry); isReportEntry && reportEntry.Visibility == ReportEntryVisibilityNever { + continue + } + out = append(out, event) + } + return out +} + +func (t Timeline) WithoutVeryVerboseSpecEvents() Timeline { + out := Timeline{} + for _, event := range t { + if specEvent, isSpecEvent := event.(SpecEvent); isSpecEvent && specEvent.IsOnlyVisibleAtVeryVerbose() { + continue + } + out = append(out, event) + } + return out +} + +// Failure captures failure information for an individual test +type Failure struct { + // Message - the failure message passed into Fail(...). When using a matcher library + // like Gomega, this will contain the failure message generated by Gomega. + // + // Message is also populated if the user has called Skip(...). + Message string + + // Location - the CodeLocation where the failure occurred + // This CodeLocation will include a fully-populated StackTrace + Location CodeLocation + + TimelineLocation TimelineLocation + + // ForwardedPanic - if the failure represents a captured panic (i.e. Summary.State == SpecStatePanicked) + // then ForwardedPanic will be populated with a string representation of the captured panic. + ForwardedPanic string `json:",omitempty"` + + // FailureNodeContext - one of three contexts describing the node in which the failure occurred: + // FailureNodeIsLeafNode means the failure occurred in the leaf node of the associated SpecReport. None of the other FailureNode fields will be populated + // FailureNodeAtTopLevel means the failure occurred in a non-leaf node that is defined at the top-level of the spec (i.e. not in a container). FailureNodeType and FailureNodeLocation will be populated. + // FailureNodeInContainer means the failure occurred in a non-leaf node that is defined within a container. FailureNodeType, FailureNodeLocation, and FailureNodeContainerIndex will be populated. + // + // FailureNodeType will contain the NodeType of the node in which the failure occurred. + // FailureNodeLocation will contain the CodeLocation of the node in which the failure occurred. + // If populated, FailureNodeContainerIndex will be the index into SpecReport.ContainerHierarchyTexts and SpecReport.ContainerHierarchyLocations that represents the parent container of the node in which the failure occurred. + FailureNodeContext FailureNodeContext `json:",omitempty"` + + FailureNodeType NodeType `json:",omitempty"` + + FailureNodeLocation CodeLocation `json:",omitempty"` + + FailureNodeContainerIndex int `json:",omitempty"` + + //ProgressReport is populated if the spec was interrupted or timed out + ProgressReport ProgressReport `json:",omitempty"` + + //AdditionalFailure is non-nil if a follow-on failure occurred within the same node after the primary failure. This only happens when a node has timed out or been interrupted. In such cases the AdditionalFailure can include information about where/why the spec was stuck. + AdditionalFailure *AdditionalFailure `json:",omitempty"` +} + +func (f Failure) IsZero() bool { + return f.Message == "" && (f.Location == CodeLocation{}) +} + +func (f Failure) GetTimelineLocation() TimelineLocation { + return f.TimelineLocation +} + +// FailureNodeContext captures the location context for the node containing the failing line of code +type FailureNodeContext uint + +const ( + FailureNodeContextInvalid FailureNodeContext = iota + + FailureNodeIsLeafNode + FailureNodeAtTopLevel + FailureNodeInContainer +) + +var fncEnumSupport = NewEnumSupport(map[uint]string{ + uint(FailureNodeContextInvalid): "INVALID FAILURE NODE CONTEXT", + uint(FailureNodeIsLeafNode): "leaf-node", + uint(FailureNodeAtTopLevel): "top-level", + uint(FailureNodeInContainer): "in-container", +}) + +func (fnc FailureNodeContext) String() string { + return fncEnumSupport.String(uint(fnc)) +} +func (fnc *FailureNodeContext) UnmarshalJSON(b []byte) error { + out, err := fncEnumSupport.UnmarshJSON(b) + *fnc = FailureNodeContext(out) + return err +} +func (fnc FailureNodeContext) MarshalJSON() ([]byte, error) { + return fncEnumSupport.MarshJSON(uint(fnc)) +} + +// AdditionalFailure capturs any additional failures that occur after the initial failure of a psec +// these typically occur in clean up nodes after the spec has failed. +// We can't simply use Failure as we want to track the SpecState to know what kind of failure this is +type AdditionalFailure struct { + State SpecState + Failure Failure +} + +func (f AdditionalFailure) GetTimelineLocation() TimelineLocation { + return f.Failure.TimelineLocation +} + +// SpecState captures the state of a spec +// To determine if a given `state` represents a failure state, use `state.Is(SpecStateFailureStates)` +type SpecState uint + +const ( + SpecStateInvalid SpecState = 0 + + SpecStatePending SpecState = 1 << iota + SpecStateSkipped + SpecStatePassed + SpecStateFailed + SpecStateAborted + SpecStatePanicked + SpecStateInterrupted + SpecStateTimedout +) + +var ssEnumSupport = NewEnumSupport(map[uint]string{ + uint(SpecStateInvalid): "INVALID SPEC STATE", + uint(SpecStatePending): "pending", + uint(SpecStateSkipped): "skipped", + uint(SpecStatePassed): "passed", + uint(SpecStateFailed): "failed", + uint(SpecStateAborted): "aborted", + uint(SpecStatePanicked): "panicked", + uint(SpecStateInterrupted): "interrupted", + uint(SpecStateTimedout): "timedout", +}) + +func (ss SpecState) String() string { + return ssEnumSupport.String(uint(ss)) +} +func (ss *SpecState) UnmarshalJSON(b []byte) error { + out, err := ssEnumSupport.UnmarshJSON(b) + *ss = SpecState(out) + return err +} +func (ss SpecState) MarshalJSON() ([]byte, error) { + return ssEnumSupport.MarshJSON(uint(ss)) +} + +var SpecStateFailureStates = SpecStateFailed | SpecStateTimedout | SpecStateAborted | SpecStatePanicked | SpecStateInterrupted + +func (ss SpecState) Is(states SpecState) bool { + return ss&states != 0 +} + +// ProgressReport captures the progress of the current spec. It is, effectively, a structured Ginkgo-aware stack trace +type ProgressReport struct { + Message string `json:",omitempty"` + ParallelProcess int `json:",omitempty"` + RunningInParallel bool `json:",omitempty"` + + ContainerHierarchyTexts []string `json:",omitempty"` + LeafNodeText string `json:",omitempty"` + LeafNodeLocation CodeLocation `json:",omitempty"` + SpecStartTime time.Time `json:",omitempty"` + + CurrentNodeType NodeType `json:",omitempty"` + CurrentNodeText string `json:",omitempty"` + CurrentNodeLocation CodeLocation `json:",omitempty"` + CurrentNodeStartTime time.Time `json:",omitempty"` + + CurrentStepText string `json:",omitempty"` + CurrentStepLocation CodeLocation `json:",omitempty"` + CurrentStepStartTime time.Time `json:",omitempty"` + + AdditionalReports []string `json:",omitempty"` + + CapturedGinkgoWriterOutput string `json:",omitempty"` + TimelineLocation TimelineLocation `json:",omitempty"` + + Goroutines []Goroutine `json:",omitempty"` +} + +func (pr ProgressReport) IsZero() bool { + return pr.CurrentNodeType == NodeTypeInvalid +} + +func (pr ProgressReport) Time() time.Time { + return pr.TimelineLocation.Time +} + +func (pr ProgressReport) SpecGoroutine() Goroutine { + for _, goroutine := range pr.Goroutines { + if goroutine.IsSpecGoroutine { + return goroutine + } + } + return Goroutine{} +} + +func (pr ProgressReport) HighlightedGoroutines() []Goroutine { + out := []Goroutine{} + for _, goroutine := range pr.Goroutines { + if goroutine.IsSpecGoroutine || !goroutine.HasHighlights() { + continue + } + out = append(out, goroutine) + } + return out +} + +func (pr ProgressReport) OtherGoroutines() []Goroutine { + out := []Goroutine{} + for _, goroutine := range pr.Goroutines { + if goroutine.IsSpecGoroutine || goroutine.HasHighlights() { + continue + } + out = append(out, goroutine) + } + return out +} + +func (pr ProgressReport) WithoutCapturedGinkgoWriterOutput() ProgressReport { + out := pr + out.CapturedGinkgoWriterOutput = "" + return out +} + +func (pr ProgressReport) WithoutOtherGoroutines() ProgressReport { + out := pr + filteredGoroutines := []Goroutine{} + for _, goroutine := range pr.Goroutines { + if goroutine.IsSpecGoroutine || goroutine.HasHighlights() { + filteredGoroutines = append(filteredGoroutines, goroutine) + } + } + out.Goroutines = filteredGoroutines + return out +} + +func (pr ProgressReport) GetTimelineLocation() TimelineLocation { + return pr.TimelineLocation +} + +type Goroutine struct { + ID uint64 + State string + Stack []FunctionCall + IsSpecGoroutine bool +} + +func (g Goroutine) IsZero() bool { + return g.ID == 0 +} + +func (g Goroutine) HasHighlights() bool { + for _, fc := range g.Stack { + if fc.Highlight { + return true + } + } + + return false +} + +type FunctionCall struct { + Function string + Filename string + Line int + Highlight bool `json:",omitempty"` + Source []string `json:",omitempty"` + SourceHighlight int `json:",omitempty"` +} + +// NodeType captures the type of a given Ginkgo Node +type NodeType uint + +const ( + NodeTypeInvalid NodeType = 0 + + NodeTypeContainer NodeType = 1 << iota + NodeTypeIt + + NodeTypeBeforeEach + NodeTypeJustBeforeEach + NodeTypeAfterEach + NodeTypeJustAfterEach + + NodeTypeBeforeAll + NodeTypeAfterAll + + NodeTypeBeforeSuite + NodeTypeSynchronizedBeforeSuite + NodeTypeAfterSuite + NodeTypeSynchronizedAfterSuite + + NodeTypeReportBeforeEach + NodeTypeReportAfterEach + NodeTypeReportAfterSuite + + NodeTypeCleanupInvalid + NodeTypeCleanupAfterEach + NodeTypeCleanupAfterAll + NodeTypeCleanupAfterSuite +) + +var NodeTypesForContainerAndIt = NodeTypeContainer | NodeTypeIt +var NodeTypesForSuiteLevelNodes = NodeTypeBeforeSuite | NodeTypeSynchronizedBeforeSuite | NodeTypeAfterSuite | NodeTypeSynchronizedAfterSuite | NodeTypeReportAfterSuite | NodeTypeCleanupAfterSuite +var NodeTypesAllowedDuringCleanupInterrupt = NodeTypeAfterEach | NodeTypeJustAfterEach | NodeTypeAfterAll | NodeTypeAfterSuite | NodeTypeSynchronizedAfterSuite | NodeTypeCleanupAfterEach | NodeTypeCleanupAfterAll | NodeTypeCleanupAfterSuite +var NodeTypesAllowedDuringReportInterrupt = NodeTypeReportBeforeEach | NodeTypeReportAfterEach | NodeTypeReportAfterSuite + +var ntEnumSupport = NewEnumSupport(map[uint]string{ + uint(NodeTypeInvalid): "INVALID NODE TYPE", + uint(NodeTypeContainer): "Container", + uint(NodeTypeIt): "It", + uint(NodeTypeBeforeEach): "BeforeEach", + uint(NodeTypeJustBeforeEach): "JustBeforeEach", + uint(NodeTypeAfterEach): "AfterEach", + uint(NodeTypeJustAfterEach): "JustAfterEach", + uint(NodeTypeBeforeAll): "BeforeAll", + uint(NodeTypeAfterAll): "AfterAll", + uint(NodeTypeBeforeSuite): "BeforeSuite", + uint(NodeTypeSynchronizedBeforeSuite): "SynchronizedBeforeSuite", + uint(NodeTypeAfterSuite): "AfterSuite", + uint(NodeTypeSynchronizedAfterSuite): "SynchronizedAfterSuite", + uint(NodeTypeReportBeforeEach): "ReportBeforeEach", + uint(NodeTypeReportAfterEach): "ReportAfterEach", + uint(NodeTypeReportAfterSuite): "ReportAfterSuite", + uint(NodeTypeCleanupInvalid): "DeferCleanup", + uint(NodeTypeCleanupAfterEach): "DeferCleanup (Each)", + uint(NodeTypeCleanupAfterAll): "DeferCleanup (All)", + uint(NodeTypeCleanupAfterSuite): "DeferCleanup (Suite)", +}) + +func (nt NodeType) String() string { + return ntEnumSupport.String(uint(nt)) +} +func (nt *NodeType) UnmarshalJSON(b []byte) error { + out, err := ntEnumSupport.UnmarshJSON(b) + *nt = NodeType(out) + return err +} +func (nt NodeType) MarshalJSON() ([]byte, error) { + return ntEnumSupport.MarshJSON(uint(nt)) +} + +func (nt NodeType) Is(nodeTypes NodeType) bool { + return nt&nodeTypes != 0 +} + +/* +SpecEvent captures a vareity of events that can occur when specs run. See SpecEventType for the list of available events. +*/ +type SpecEvent struct { + SpecEventType SpecEventType + + CodeLocation CodeLocation + TimelineLocation TimelineLocation + + Message string `json:",omitempty"` + Duration time.Duration `json:",omitempty"` + NodeType NodeType `json:",omitempty"` + Attempt int `json:",omitempty"` +} + +func (se SpecEvent) GetTimelineLocation() TimelineLocation { + return se.TimelineLocation +} + +func (se SpecEvent) IsOnlyVisibleAtVeryVerbose() bool { + return se.SpecEventType.Is(SpecEventByEnd | SpecEventNodeStart | SpecEventNodeEnd) +} + +func (se SpecEvent) GomegaString() string { + out := &strings.Builder{} + out.WriteString("[" + se.SpecEventType.String() + " SpecEvent] ") + if se.Message != "" { + out.WriteString("Message=") + out.WriteString(`"` + se.Message + `",`) + } + if se.Duration != 0 { + out.WriteString("Duration=" + se.Duration.String() + ",") + } + if se.NodeType != NodeTypeInvalid { + out.WriteString("NodeType=" + se.NodeType.String() + ",") + } + if se.Attempt != 0 { + out.WriteString(fmt.Sprintf("Attempt=%d", se.Attempt) + ",") + } + out.WriteString("CL=" + se.CodeLocation.String() + ",") + out.WriteString(fmt.Sprintf("TL.Offset=%d", se.TimelineLocation.Offset)) + + return out.String() +} + +type SpecEvents []SpecEvent + +func (se SpecEvents) WithType(seType SpecEventType) SpecEvents { + out := SpecEvents{} + for _, event := range se { + if event.SpecEventType.Is(seType) { + out = append(out, event) + } + } + return out +} + +type SpecEventType uint + +const ( + SpecEventInvalid SpecEventType = 0 + + SpecEventByStart SpecEventType = 1 << iota + SpecEventByEnd + SpecEventNodeStart + SpecEventNodeEnd + SpecEventSpecRepeat + SpecEventSpecRetry +) + +var seEnumSupport = NewEnumSupport(map[uint]string{ + uint(SpecEventInvalid): "INVALID SPEC EVENT", + uint(SpecEventByStart): "By", + uint(SpecEventByEnd): "By (End)", + uint(SpecEventNodeStart): "Node", + uint(SpecEventNodeEnd): "Node (End)", + uint(SpecEventSpecRepeat): "Repeat", + uint(SpecEventSpecRetry): "Retry", +}) + +func (se SpecEventType) String() string { + return seEnumSupport.String(uint(se)) +} +func (se *SpecEventType) UnmarshalJSON(b []byte) error { + out, err := seEnumSupport.UnmarshJSON(b) + *se = SpecEventType(out) + return err +} +func (se SpecEventType) MarshalJSON() ([]byte, error) { + return seEnumSupport.MarshJSON(uint(se)) +} + +func (se SpecEventType) Is(specEventTypes SpecEventType) bool { + return se&specEventTypes != 0 +} diff --git a/vendor/github.com/onsi/ginkgo/v2/types/version.go b/vendor/github.com/onsi/ginkgo/v2/types/version.go new file mode 100644 index 00000000000..52c9fc91c2b --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/v2/types/version.go @@ -0,0 +1,3 @@ +package types + +const VERSION = "2.5.0" diff --git a/vendor/github.com/open-policy-agent/cert-controller/LICENSE b/vendor/github.com/open-policy-agent/cert-controller/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/vendor/github.com/open-policy-agent/cert-controller/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/open-policy-agent/cert-controller/NOTICE b/vendor/github.com/open-policy-agent/cert-controller/NOTICE new file mode 100644 index 00000000000..61ca9071026 --- /dev/null +++ b/vendor/github.com/open-policy-agent/cert-controller/NOTICE @@ -0,0 +1,3 @@ +cert-controller +Copyright 2018-2020 The Gatekeeper Authors + diff --git a/vendor/github.com/open-policy-agent/cert-controller/pkg/rotator/rotator.go b/vendor/github.com/open-policy-agent/cert-controller/pkg/rotator/rotator.go new file mode 100644 index 00000000000..a8f9ee8afe6 --- /dev/null +++ b/vendor/github.com/open-policy-agent/cert-controller/pkg/rotator/rotator.go @@ -0,0 +1,779 @@ +package rotator + +import ( + "bytes" + "context" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/base64" + "encoding/pem" + "fmt" + "math/big" + "os" + "time" + + "github.com/pkg/errors" + "go.uber.org/atomic" + corev1 "k8s.io/api/core/v1" + k8sErrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/handler" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +const ( + certName = "tls.crt" + keyName = "tls.key" + caCertName = "ca.crt" + caKeyName = "ca.key" + rotationCheckFrequency = 12 * time.Hour + certValidityDuration = 10 * 365 * 24 * time.Hour + lookaheadInterval = 90 * 24 * time.Hour +) + +var crLog = logf.Log.WithName("cert-rotation") + +// WebhookType it the type of webhook, either validating/mutating webhook, a CRD conversion webhook, or an extension API server +type WebhookType int + +const ( + //ValidatingWebhook indicates the webhook is a ValidatingWebhook + Validating WebhookType = iota + //MutingWebhook indicates the webhook is a MutatingWebhook + Mutating + //CRDConversionWebhook indicates the webhook is a conversion webhook + CRDConversion + //APIServiceWebhook indicates the webhook is an extension API server + APIService +) + +var _ manager.Runnable = &CertRotator{} +var _ manager.LeaderElectionRunnable = &CertRotator{} +var _ manager.Runnable = controllerWrapper{} +var _ manager.LeaderElectionRunnable = controllerWrapper{} + +type controllerWrapper struct { + controller.Controller + needLeaderElection bool +} + +func (cw controllerWrapper) NeedLeaderElection() bool { + return cw.needLeaderElection +} + +// WebhookInfo is used by the rotator to receive info about resources to be updated with certificates +type WebhookInfo struct { + //Name is the name of the webhook for a validating or mutating webhook, or the CRD name in case of a CRD conversion webhook + Name string + Type WebhookType +} + +func (w WebhookInfo) gvk() schema.GroupVersionKind { + t2g := map[WebhookType]schema.GroupVersionKind{ + Validating: {Group: "admissionregistration.k8s.io", Version: "v1", Kind: "ValidatingWebhookConfiguration"}, + Mutating: {Group: "admissionregistration.k8s.io", Version: "v1", Kind: "MutatingWebhookConfiguration"}, + CRDConversion: {Group: "apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinition"}, + APIService: {Group: "apiregistration.k8s.io", Version: "v1", Kind: "APIService"}, + } + return t2g[w.Type] +} + +// AddRotator adds the CertRotator and ReconcileWH to the manager. +func AddRotator(mgr manager.Manager, cr *CertRotator) error { + if mgr == nil || cr == nil { + return fmt.Errorf("nil arguments") + } + ns := cr.SecretKey.Namespace + if ns == "" { + return fmt.Errorf("invalid namespace for secret") + } + cache, err := addNamespacedCache(mgr, ns) + if err != nil { + return fmt.Errorf("creating namespaced cache: %w", err) + } + + cr.reader = cache + cr.writer = mgr.GetClient() // TODO make overrideable + cr.certsMounted = make(chan struct{}) + cr.certsNotMounted = make(chan struct{}) + cr.wasCAInjected = atomic.NewBool(false) + cr.caNotInjected = make(chan struct{}) + if err := mgr.Add(cr); err != nil { + return err + } + + reconciler := &ReconcileWH{ + cache: cache, + writer: mgr.GetClient(), // TODO + scheme: mgr.GetScheme(), + ctx: context.Background(), + secretKey: cr.SecretKey, + wasCAInjected: cr.wasCAInjected, + webhooks: cr.Webhooks, + needLeaderElection: cr.RequireLeaderElection, + } + if err := addController(mgr, reconciler); err != nil { + return err + } + return nil +} + +// addNamespacedCache will add a new namespace-scoped cache.Cache to the provided manager. +// Informers in the new cache will be scoped to the provided namespace for namespaced resources, +// but will still have cluster-wide visibility into cluster-scoped resources. +// The cache will be started by the manager when it starts, and consumers should synchronize on +// it using WaitForCacheSync(). +func addNamespacedCache(mgr manager.Manager, namespace string) (cache.Cache, error) { + c, err := cache.New(mgr.GetConfig(), + cache.Options{ + Scheme: mgr.GetScheme(), + Mapper: mgr.GetRESTMapper(), + Namespace: namespace, + }) + if err != nil { + return nil, err + } + if err := mgr.Add(c); err != nil { + return nil, fmt.Errorf("registering namespaced cache: %w", err) + } + return c, nil +} + +// SyncingSource is a reader that needs syncing prior to being usable. +type SyncingReader interface { + client.Reader + WaitForCacheSync(ctx context.Context) bool +} + +// CertRotator contains cert artifacts and a channel to close when the certs are ready. +type CertRotator struct { + reader SyncingReader + writer client.Writer + + SecretKey types.NamespacedName + CertDir string + CAName string + CAOrganization string + DNSName string + IsReady chan struct{} + Webhooks []WebhookInfo + RestartOnSecretRefresh bool + ExtKeyUsages *[]x509.ExtKeyUsage + // RequireLeaderElection should be set to true if the CertRotator needs to + // be run in the leader election mode. + RequireLeaderElection bool + + certsMounted chan struct{} + certsNotMounted chan struct{} + wasCAInjected *atomic.Bool + caNotInjected chan struct{} +} + +func (cr *CertRotator) NeedLeaderElection() bool { + return cr.RequireLeaderElection +} + +// Start starts the CertRotator runnable to rotate certs and ensure the certs are ready. +func (cr *CertRotator) Start(ctx context.Context) error { + if cr.reader == nil { + return errors.New("nil reader") + } + if !cr.reader.WaitForCacheSync(ctx) { + return errors.New("failed waiting for reader to sync") + } + + if cr.ExtKeyUsages == nil { + cr.ExtKeyUsages = &[]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth} + } + + // explicitly rotate on the first round so that the certificate + // can be bootstrapped, otherwise manager exits before a cert can be written + crLog.Info("starting cert rotator controller") + defer crLog.Info("stopping cert rotator controller") + if err := cr.refreshCertIfNeeded(); err != nil { + crLog.Error(err, "could not refresh cert on startup") + return err + } + + // Once the certs are ready, close the channel. + go cr.ensureCertsMounted() + go cr.ensureReady() + + ticker := time.NewTicker(rotationCheckFrequency) + +tickerLoop: + for { + select { + case <-ticker.C: + if err := cr.refreshCertIfNeeded(); err != nil { + crLog.Error(err, "error rotating certs") + } + case <-ctx.Done(): + break tickerLoop + case <-cr.certsNotMounted: + return errors.New("could not mount certs") + case <-cr.caNotInjected: + return errors.New("could not inject certs to webhooks") + } + } + + ticker.Stop() + return nil +} + +// refreshCertIfNeeded returns whether there's any error when refreshing the certs if needed. +func (cr *CertRotator) refreshCertIfNeeded() error { + refreshFn := func() (bool, error) { + secret := &corev1.Secret{} + if err := cr.reader.Get(context.Background(), cr.SecretKey, secret); err != nil { + return false, errors.Wrap(err, "acquiring secret to update certificates") + } + if secret.Data == nil || !cr.validCACert(secret.Data[caCertName], secret.Data[caKeyName]) { + crLog.Info("refreshing CA and server certs") + if err := cr.refreshCerts(true, secret); err != nil { + crLog.Error(err, "could not refresh CA and server certs") + return false, nil + } + crLog.Info("server certs refreshed") + if cr.RestartOnSecretRefresh { + crLog.Info("Secrets have been updated; exiting so pod can be restarted (This behaviour can be changed with the option RestartOnSecretRefresh)") + os.Exit(0) + } + return true, nil + } + // make sure our reconciler is initialized on startup (either this or the above refreshCerts() will call this) + if !cr.validServerCert(secret.Data[caCertName], secret.Data[certName], secret.Data[keyName]) { + crLog.Info("refreshing server certs") + if err := cr.refreshCerts(false, secret); err != nil { + crLog.Error(err, "could not refresh server certs") + return false, nil + } + crLog.Info("server certs refreshed") + if cr.RestartOnSecretRefresh { + crLog.Info("Secrets have been updated; exiting so pod can be restarted (This behaviour can be changed with the option RestartOnSecretRefresh)") + os.Exit(0) + } + return true, nil + } + crLog.Info("no cert refresh needed") + return true, nil + } + if err := wait.ExponentialBackoff(wait.Backoff{ + Duration: 10 * time.Millisecond, + Factor: 2, + Jitter: 1, + Steps: 10, + }, refreshFn); err != nil { + return err + } + return nil +} + +func (cr *CertRotator) refreshCerts(refreshCA bool, secret *corev1.Secret) error { + var caArtifacts *KeyPairArtifacts + now := time.Now() + begin := now.Add(-1 * time.Hour) + end := now.Add(certValidityDuration) + if refreshCA { + var err error + caArtifacts, err = cr.CreateCACert(begin, end) + if err != nil { + return err + } + } else { + var err error + caArtifacts, err = buildArtifactsFromSecret(secret) + if err != nil { + return err + } + } + cert, key, err := cr.CreateCertPEM(caArtifacts, begin, end) + if err != nil { + return err + } + if err := cr.writeSecret(cert, key, caArtifacts, secret); err != nil { + return err + } + return nil +} + +func injectCert(updatedResource *unstructured.Unstructured, certPem []byte, webhookType WebhookType) error { + switch webhookType { + case Validating: + return injectCertToWebhook(updatedResource, certPem) + case Mutating: + return injectCertToWebhook(updatedResource, certPem) + case CRDConversion: + return injectCertToConversionWebhook(updatedResource, certPem) + case APIService: + return injectCertToApiService(updatedResource, certPem) + } + return fmt.Errorf("Incorrect webhook type") +} + +func injectCertToWebhook(wh *unstructured.Unstructured, certPem []byte) error { + webhooks, found, err := unstructured.NestedSlice(wh.Object, "webhooks") + if err != nil { + return err + } + if !found { + return nil + } + for i, h := range webhooks { + hook, ok := h.(map[string]interface{}) + if !ok { + return errors.Errorf("webhook %d is not well-formed", i) + } + if err := unstructured.SetNestedField(hook, base64.StdEncoding.EncodeToString(certPem), "clientConfig", "caBundle"); err != nil { + return err + } + webhooks[i] = hook + } + if err := unstructured.SetNestedSlice(wh.Object, webhooks, "webhooks"); err != nil { + return err + } + return nil +} + +func injectCertToConversionWebhook(crd *unstructured.Unstructured, certPem []byte) error { + _, found, err := unstructured.NestedMap(crd.Object, "spec", "conversion", "webhook", "clientConfig") + if err != nil { + return err + } + if !found { + return errors.New("`conversion.webhook.clientConfig` field not found in CustomResourceDefinition") + } + if err := unstructured.SetNestedField(crd.Object, base64.StdEncoding.EncodeToString(certPem), "spec", "conversion", "webhook", "clientConfig", "caBundle"); err != nil { + return err + } + + return nil +} + +func injectCertToApiService(apiService *unstructured.Unstructured, certPem []byte) error { + _, found, err := unstructured.NestedMap(apiService.Object, "spec") + if err != nil { + return err + } + if !found { + return errors.New("`spec` field not found in APIService") + } + if err := unstructured.SetNestedField(apiService.Object, base64.StdEncoding.EncodeToString(certPem), "spec", "caBundle"); err != nil { + return err + } + + return nil +} + +func (cr *CertRotator) writeSecret(cert, key []byte, caArtifacts *KeyPairArtifacts, secret *corev1.Secret) error { + populateSecret(cert, key, caArtifacts, secret) + return cr.writer.Update(context.Background(), secret) +} + +// KeyPairArtifacts stores cert artifacts. +type KeyPairArtifacts struct { + Cert *x509.Certificate + Key *rsa.PrivateKey + CertPEM []byte + KeyPEM []byte +} + +func populateSecret(cert, key []byte, caArtifacts *KeyPairArtifacts, secret *corev1.Secret) { + if secret.Data == nil { + secret.Data = make(map[string][]byte) + } + secret.Data[caCertName] = caArtifacts.CertPEM + secret.Data[caKeyName] = caArtifacts.KeyPEM + secret.Data[certName] = cert + secret.Data[keyName] = key +} + +func buildArtifactsFromSecret(secret *corev1.Secret) (*KeyPairArtifacts, error) { + caPem, ok := secret.Data[caCertName] + if !ok { + return nil, errors.New(fmt.Sprintf("Cert secret is not well-formed, missing %s", caCertName)) + } + keyPem, ok := secret.Data[caKeyName] + if !ok { + return nil, errors.New(fmt.Sprintf("Cert secret is not well-formed, missing %s", caKeyName)) + } + caDer, _ := pem.Decode(caPem) + if caDer == nil { + return nil, errors.New("bad CA cert") + } + caCert, err := x509.ParseCertificate(caDer.Bytes) + if err != nil { + return nil, errors.Wrap(err, "while parsing CA cert") + } + keyDer, _ := pem.Decode(keyPem) + if keyDer == nil { + return nil, errors.New("bad CA cert") + } + key, err := x509.ParsePKCS1PrivateKey(keyDer.Bytes) + if err != nil { + return nil, errors.Wrap(err, "while parsing CA key") + } + return &KeyPairArtifacts{ + Cert: caCert, + CertPEM: caPem, + KeyPEM: keyPem, + Key: key, + }, nil +} + +// CreateCACert creates the self-signed CA cert and private key that will +// be used to sign the server certificate +func (cr *CertRotator) CreateCACert(begin, end time.Time) (*KeyPairArtifacts, error) { + templ := &x509.Certificate{ + SerialNumber: big.NewInt(0), + Subject: pkix.Name{ + CommonName: cr.CAName, + Organization: []string{cr.CAOrganization}, + }, + DNSNames: []string{ + cr.CAName, + }, + NotBefore: begin, + NotAfter: end, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + IsCA: true, + } + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, errors.Wrap(err, "generating key") + } + der, err := x509.CreateCertificate(rand.Reader, templ, templ, key.Public(), key) + if err != nil { + return nil, errors.Wrap(err, "creating certificate") + } + certPEM, keyPEM, err := pemEncode(der, key) + if err != nil { + return nil, errors.Wrap(err, "encoding PEM") + } + cert, err := x509.ParseCertificate(der) + if err != nil { + return nil, errors.Wrap(err, "parsing certificate") + } + + return &KeyPairArtifacts{Cert: cert, Key: key, CertPEM: certPEM, KeyPEM: keyPEM}, nil +} + +// CreateCertPEM takes the results of CreateCACert and uses it to create the +// PEM-encoded public certificate and private key, respectively +func (cr *CertRotator) CreateCertPEM(ca *KeyPairArtifacts, begin, end time.Time) ([]byte, []byte, error) { + templ := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: cr.DNSName, + }, + DNSNames: []string{ + cr.DNSName, + }, + NotBefore: begin, + NotAfter: end, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + ExtKeyUsage: *cr.ExtKeyUsages, + BasicConstraintsValid: true, + } + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, nil, errors.Wrap(err, "generating key") + } + der, err := x509.CreateCertificate(rand.Reader, templ, ca.Cert, key.Public(), ca.Key) + if err != nil { + return nil, nil, errors.Wrap(err, "creating certificate") + } + certPEM, keyPEM, err := pemEncode(der, key) + if err != nil { + return nil, nil, errors.Wrap(err, "encoding PEM") + } + return certPEM, keyPEM, nil +} + +// pemEncode takes a certificate and encodes it as PEM +func pemEncode(certificateDER []byte, key *rsa.PrivateKey) ([]byte, []byte, error) { + certBuf := &bytes.Buffer{} + if err := pem.Encode(certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: certificateDER}); err != nil { + return nil, nil, errors.Wrap(err, "encoding cert") + } + keyBuf := &bytes.Buffer{} + if err := pem.Encode(keyBuf, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}); err != nil { + return nil, nil, errors.Wrap(err, "encoding key") + } + return certBuf.Bytes(), keyBuf.Bytes(), nil +} + +func lookaheadTime() time.Time { + return time.Now().Add(lookaheadInterval) +} + +func (cr *CertRotator) validServerCert(caCert, cert, key []byte) bool { + valid, err := ValidCert(caCert, cert, key, cr.DNSName, cr.ExtKeyUsages, lookaheadTime()) + if err != nil { + return false + } + return valid +} + +func (cr *CertRotator) validCACert(cert, key []byte) bool { + valid, err := ValidCert(cert, cert, key, cr.CAName, nil, lookaheadTime()) + if err != nil { + return false + } + return valid +} + +func ValidCert(caCert, cert, key []byte, dnsName string, keyUsages *[]x509.ExtKeyUsage, at time.Time) (bool, error) { + if len(caCert) == 0 || len(cert) == 0 || len(key) == 0 { + return false, errors.New("empty cert") + } + + pool := x509.NewCertPool() + caDer, _ := pem.Decode(caCert) + if caDer == nil { + return false, errors.New("bad CA cert") + } + cac, err := x509.ParseCertificate(caDer.Bytes) + if err != nil { + return false, errors.Wrap(err, "parsing CA cert") + } + pool.AddCert(cac) + + _, err = tls.X509KeyPair(cert, key) + if err != nil { + return false, errors.Wrap(err, "building key pair") + } + + b, _ := pem.Decode(cert) + if b == nil { + return false, errors.New("bad private key") + } + + crt, err := x509.ParseCertificate(b.Bytes) + if err != nil { + return false, errors.Wrap(err, "parsing cert") + } + + opt := x509.VerifyOptions{ + DNSName: dnsName, + Roots: pool, + CurrentTime: at, + } + if keyUsages != nil { + opt.KeyUsages = *keyUsages + } + + _, err = crt.Verify(opt) + if err != nil { + return false, errors.Wrap(err, "verifying cert") + } + return true, nil +} + +func reconcileSecretAndWebhookMapFunc(webhook WebhookInfo, r *ReconcileWH) func(object client.Object) []reconcile.Request { + return func(object client.Object) []reconcile.Request { + whKey := types.NamespacedName{Name: webhook.Name} + if object.GetNamespace() != whKey.Namespace { + return nil + } + if object.GetName() != whKey.Name { + return nil + } + return []reconcile.Request{{NamespacedName: r.secretKey}} + } +} + +// add adds a new Controller to mgr with r as the reconcile.Reconciler +func addController(mgr manager.Manager, r *ReconcileWH) error { + // Create a new controller + c, err := controller.NewUnmanaged("cert-rotator", mgr, controller.Options{Reconciler: r}) + if err != nil { + return err + } + if err := mgr.Add(controllerWrapper{c, r.needLeaderElection}); err != nil { + return err + } + + err = c.Watch( + source.NewKindWithCache(&corev1.Secret{}, r.cache), + &handler.EnqueueRequestForObject{}, + ) + if err != nil { + return fmt.Errorf("watching Secrets: %w", err) + } + + for _, webhook := range r.webhooks { + wh := &unstructured.Unstructured{} + wh.SetGroupVersionKind(webhook.gvk()) + err = c.Watch( + source.NewKindWithCache(wh, r.cache), + handler.EnqueueRequestsFromMapFunc(reconcileSecretAndWebhookMapFunc(webhook, r)), + ) + if err != nil { + return fmt.Errorf("watching webhook %s: %w", webhook.Name, err) + } + } + + return nil +} + +var _ reconcile.Reconciler = &ReconcileWH{} + +// ReconcileWH reconciles a validatingwebhookconfiguration, making sure it +// has the appropriate CA cert +type ReconcileWH struct { + writer client.Writer + cache cache.Cache + scheme *runtime.Scheme + ctx context.Context + secretKey types.NamespacedName + webhooks []WebhookInfo + wasCAInjected *atomic.Bool + needLeaderElection bool +} + +// Reconcile reads that state of the cluster for a validatingwebhookconfiguration +// object and makes sure the most recent CA cert is included +func (r *ReconcileWH) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { + if request.NamespacedName != r.secretKey { + return reconcile.Result{}, nil + } + + if !r.cache.WaitForCacheSync(ctx) { + return reconcile.Result{}, errors.New("cache not ready") + } + + secret := &corev1.Secret{} + if err := r.cache.Get(r.ctx, request.NamespacedName, secret); err != nil { + if k8sErrors.IsNotFound(err) { + // Object not found, return. Created objects are automatically garbage collected. + // For additional cleanup logic use finalizers. + return reconcile.Result{}, nil + } + // Error reading the object - requeue the request. + return reconcile.Result{Requeue: true}, err + } + + if secret.GetDeletionTimestamp().IsZero() { + artifacts, err := buildArtifactsFromSecret(secret) + if err != nil { + crLog.Error(err, "secret is not well-formed, cannot update webhook configurations") + return reconcile.Result{}, nil + } + + // Ensure certs on webhooks + if err := r.ensureCerts(artifacts.CertPEM); err != nil { + return reconcile.Result{}, err + } + + // Set CAInjected if the reconciler has not exited early. + r.wasCAInjected.Store(true) + } + + return reconcile.Result{}, nil +} + +// ensureCerts returns an arbitrary error if multiple errors are encountered, +// while all the errors are logged. +// This is important to allow the controller to reconcile the secret. If an error +// is returned, request will be requeued, and the controller will attempt to reconcile +// the secret again. +// When an error is encountered for when processing a webhook, the error is logged, but +// following webhooks are also attempted to be updated. If multiple errors occur for different +// webhooks, only the last one will be returned. This is ok, as the returned error is only meant +// to indicate that reconciliation failed. The information about all the errors is passed not +// by the returned error, but rather in the logged errors. +func (r *ReconcileWH) ensureCerts(certPem []byte) error { + var anyError error = nil + + for _, webhook := range r.webhooks { + gvk := webhook.gvk() + log := crLog.WithValues("name", webhook.Name, "gvk", gvk) + updatedResource := &unstructured.Unstructured{} + updatedResource.SetGroupVersionKind(gvk) + if err := r.cache.Get(r.ctx, types.NamespacedName{Name: webhook.Name}, updatedResource); err != nil { + if k8sErrors.IsNotFound(err) { + log.Error(err, "Webhook not found. Unable to update certificate.") + continue + } + anyError = err + log.Error(err, "Error getting webhook for certificate update.") + continue + } + if !updatedResource.GetDeletionTimestamp().IsZero() { + log.Info("Webhook is being deleted. Unable to update certificate") + continue + } + + log.Info("Ensuring CA cert", "name", webhook.Name, "gvk", gvk) + if err := injectCert(updatedResource, certPem, webhook.Type); err != nil { + log.Error(err, "Unable to inject cert to webhook.") + anyError = err + continue + } + if err := r.writer.Update(r.ctx, updatedResource); err != nil { + log.Error(err, "Error updating webhook with certificate") + anyError = err + continue + } + } + return anyError +} + +// ensureCertsMounted ensure the cert files exist. +func (cr *CertRotator) ensureCertsMounted() { + checkFn := func() (bool, error) { + certFile := cr.CertDir + "/" + certName + _, err := os.Stat(certFile) + if err == nil { + return true, nil + } + return false, nil + } + if err := wait.ExponentialBackoff(wait.Backoff{ + Duration: 1 * time.Second, + Factor: 2, + Jitter: 1, + Steps: 10, + }, checkFn); err != nil { + crLog.Error(err, "max retries for checking certs existence") + close(cr.certsNotMounted) + return + } + crLog.Info(fmt.Sprintf("certs are ready in %s", cr.CertDir)) + close(cr.certsMounted) +} + +// ensureReady ensure the cert files exist and the CAs are injected. +func (cr *CertRotator) ensureReady() { + <-cr.certsMounted + checkFn := func() (bool, error) { + return cr.wasCAInjected.Load(), nil + } + if err := wait.ExponentialBackoff(wait.Backoff{ + Duration: 1 * time.Second, + Factor: 2, + Jitter: 1, + Steps: 10, + }, checkFn); err != nil { + crLog.Error(err, "max retries for checking CA injection") + close(cr.caNotInjected) + return + } + crLog.Info("CA certs are injected to webhooks") + close(cr.IsReady) +} diff --git a/vendor/golang.org/x/mod/modfile/read.go b/vendor/golang.org/x/mod/modfile/read.go index 70947ee7794..a503bc2105d 100644 --- a/vendor/golang.org/x/mod/modfile/read.go +++ b/vendor/golang.org/x/mod/modfile/read.go @@ -494,7 +494,7 @@ func (in *input) endToken(kind tokenKind) { in.token.endPos = in.pos } -// peek returns the kind of the the next token returned by lex. +// peek returns the kind of the next token returned by lex. func (in *input) peek() tokenKind { return in.token.kind } diff --git a/vendor/golang.org/x/mod/modfile/rule.go b/vendor/golang.org/x/mod/modfile/rule.go index ed2f31aa70e..6bcde8fabe3 100644 --- a/vendor/golang.org/x/mod/modfile/rule.go +++ b/vendor/golang.org/x/mod/modfile/rule.go @@ -513,6 +513,9 @@ func parseReplace(filename string, line *Line, verb string, args []string, fix V nv := "" if len(args) == arrow+2 { if !IsDirectoryPath(ns) { + if strings.Contains(ns, "@") { + return nil, errorf("replacement module must match format 'path version', not 'path@version'") + } return nil, errorf("replacement module without version must be directory path (rooted or starting with ./ or ../)") } if filepath.Separator == '/' && strings.Contains(ns, `\`) { diff --git a/vendor/golang.org/x/mod/module/module.go b/vendor/golang.org/x/mod/module/module.go index c26d1d29ec3..e9dec6e6148 100644 --- a/vendor/golang.org/x/mod/module/module.go +++ b/vendor/golang.org/x/mod/module/module.go @@ -96,13 +96,13 @@ package module // Changes to the semantics in this file require approval from rsc. import ( + "errors" "fmt" "path" "sort" "strings" "unicode" "unicode/utf8" - "errors" "golang.org/x/mod/semver" ) @@ -258,7 +258,7 @@ func modPathOK(r rune) bool { return false } -// modPathOK reports whether r can appear in a package import path element. +// importPathOK reports whether r can appear in a package import path element. // // Import paths are intermediate between module paths and file paths: we allow // disallow characters that would be confusing or ambiguous as arguments to diff --git a/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go b/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go index 2ed25a75024..42adb8f697b 100644 --- a/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go +++ b/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go @@ -87,7 +87,11 @@ func NewReader(r io.Reader) (io.Reader, error) { // Read reads export data from in, decodes it, and returns type // information for the package. -// The package name is specified by path. +// +// The package path (effectively its linker symbol prefix) is +// specified by path, since unlike the package name, this information +// may not be recorded in the export data. +// // File position information is added to fset. // // Read may inspect and add to the imports map to ensure that references diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go b/vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go index 4caa0f55d9d..6e4c066b69b 100644 --- a/vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go +++ b/vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go @@ -51,6 +51,8 @@ const ( iexportVersionPosCol = 1 iexportVersionGo1_18 = 2 iexportVersionGenerics = 2 + + iexportVersionCurrent = 2 ) type ident struct { @@ -96,7 +98,7 @@ func IImportBundle(fset *token.FileSet, imports map[string]*types.Package, data } func iimportCommon(fset *token.FileSet, imports map[string]*types.Package, data []byte, bundle bool, path string) (pkgs []*types.Package, err error) { - const currentVersion = 1 + const currentVersion = iexportVersionCurrent version := int64(-1) if !debug { defer func() { diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/ureader_yes.go b/vendor/golang.org/x/tools/go/internal/gcimporter/ureader_yes.go index 3c1a4375435..2d421c9619d 100644 --- a/vendor/golang.org/x/tools/go/internal/gcimporter/ureader_yes.go +++ b/vendor/golang.org/x/tools/go/internal/gcimporter/ureader_yes.go @@ -36,6 +36,12 @@ type pkgReader struct { // laterFns holds functions that need to be invoked at the end of // import reading. laterFns []func() + // laterFors is used in case of 'type A B' to ensure that B is processed before A. + laterFors map[types.Type]int + + // ifaces holds a list of constructed Interfaces, which need to have + // Complete called after importing is done. + ifaces []*types.Interface } // later adds a function to be invoked at the end of import reading. @@ -63,6 +69,15 @@ func UImportData(fset *token.FileSet, imports map[string]*types.Package, data [] return } +// laterFor adds a function to be invoked at the end of import reading, and records the type that function is finishing. +func (pr *pkgReader) laterFor(t types.Type, fn func()) { + if pr.laterFors == nil { + pr.laterFors = make(map[types.Type]int) + } + pr.laterFors[t] = len(pr.laterFns) + pr.laterFns = append(pr.laterFns, fn) +} + // readUnifiedPackage reads a package description from the given // unified IR export data decoder. func readUnifiedPackage(fset *token.FileSet, ctxt *types.Context, imports map[string]*types.Package, input pkgbits.PkgDecoder) *types.Package { @@ -102,6 +117,10 @@ func readUnifiedPackage(fset *token.FileSet, ctxt *types.Context, imports map[st fn() } + for _, iface := range pr.ifaces { + iface.Complete() + } + pkg.MarkComplete() return pkg } @@ -231,11 +250,35 @@ func (r *reader) doPkg() *types.Package { for i := range imports { imports[i] = r.pkg() } - pkg.SetImports(imports) + pkg.SetImports(flattenImports(imports)) return pkg } +// flattenImports returns the transitive closure of all imported +// packages rooted from pkgs. +func flattenImports(pkgs []*types.Package) []*types.Package { + var res []*types.Package + + seen := make(map[*types.Package]bool) + var add func(pkg *types.Package) + add = func(pkg *types.Package) { + if seen[pkg] { + return + } + seen[pkg] = true + res = append(res, pkg) + for _, imp := range pkg.Imports() { + add(imp) + } + } + + for _, pkg := range pkgs { + add(pkg) + } + return res +} + // @@@ Types func (r *reader) typ() types.Type { @@ -372,6 +415,16 @@ func (r *reader) interfaceType() *types.Interface { if implicit { iface.MarkImplicit() } + + // We need to call iface.Complete(), but if there are any embedded + // defined types, then we may not have set their underlying + // interface type yet. So we need to defer calling Complete until + // after we've called SetUnderlying everywhere. + // + // TODO(mdempsky): After CL 424876 lands, it should be safe to call + // iface.Complete() immediately. + r.p.ifaces = append(r.p.ifaces, iface) + return iface } @@ -477,13 +530,41 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index) (*types.Package, string) { named.SetTypeParams(r.typeParamNames()) - // TODO(mdempsky): Rewrite receiver types to underlying is an - // Interface? The go/types importer does this (I think because - // unit tests expected that), but cmd/compile doesn't care - // about it, so maybe we can avoid worrying about that here. rhs := r.typ() - r.p.later(func() { + pk := r.p + pk.laterFor(named, func() { + // First be sure that the rhs is initialized, if it needs to be initialized. + delete(pk.laterFors, named) // prevent cycles + if i, ok := pk.laterFors[rhs]; ok { + f := pk.laterFns[i] + pk.laterFns[i] = func() {} // function is running now, so replace it with a no-op + f() // initialize RHS + } underlying := rhs.Underlying() + + // If the underlying type is an interface, we need to + // duplicate its methods so we can replace the receiver + // parameter's type (#49906). + if iface, ok := underlying.(*types.Interface); ok && iface.NumExplicitMethods() != 0 { + methods := make([]*types.Func, iface.NumExplicitMethods()) + for i := range methods { + fn := iface.ExplicitMethod(i) + sig := fn.Type().(*types.Signature) + + recv := types.NewVar(fn.Pos(), fn.Pkg(), "", named) + methods[i] = types.NewFunc(fn.Pos(), fn.Pkg(), fn.Name(), types.NewSignature(recv, sig.Params(), sig.Results(), sig.Variadic())) + } + + embeds := make([]types.Type, iface.NumEmbeddeds()) + for i := range embeds { + embeds[i] = iface.EmbeddedType(i) + } + + newIface := types.NewInterfaceType(methods, embeds) + r.p.ifaces = append(r.p.ifaces, newIface) + underlying = newIface + } + named.SetUnderlying(underlying) }) diff --git a/vendor/golang.org/x/tools/go/internal/pkgbits/decoder.go b/vendor/golang.org/x/tools/go/internal/pkgbits/decoder.go index 2bc793668ec..e08099c6635 100644 --- a/vendor/golang.org/x/tools/go/internal/pkgbits/decoder.go +++ b/vendor/golang.org/x/tools/go/internal/pkgbits/decoder.go @@ -9,6 +9,7 @@ import ( "fmt" "go/constant" "go/token" + "io" "math/big" "os" "runtime" @@ -94,7 +95,7 @@ func NewPkgDecoder(pkgPath, input string) PkgDecoder { pr.elemEnds = make([]uint32, pr.elemEndsEnds[len(pr.elemEndsEnds)-1]) assert(binary.Read(r, binary.LittleEndian, pr.elemEnds[:]) == nil) - pos, err := r.Seek(0, os.SEEK_CUR) + pos, err := r.Seek(0, io.SeekCurrent) assert(err == nil) pr.elemData = input[pos:] @@ -237,7 +238,7 @@ func (r *Decoder) Sync(mWant SyncMarker) { return } - pos, _ := r.Data.Seek(0, os.SEEK_CUR) // TODO(mdempsky): io.SeekCurrent after #44505 is resolved + pos, _ := r.Data.Seek(0, io.SeekCurrent) mHave := SyncMarker(r.rawUvarint()) writerPCs := make([]int, r.rawUvarint()) for i := range writerPCs { diff --git a/vendor/golang.org/x/tools/go/internal/pkgbits/encoder.go b/vendor/golang.org/x/tools/go/internal/pkgbits/encoder.go index c50c838caae..e98e41171e1 100644 --- a/vendor/golang.org/x/tools/go/internal/pkgbits/encoder.go +++ b/vendor/golang.org/x/tools/go/internal/pkgbits/encoder.go @@ -147,8 +147,9 @@ func (pw *PkgEncoder) NewEncoderRaw(k RelocKind) Encoder { type Encoder struct { p *PkgEncoder - Relocs []RelocEnt - Data bytes.Buffer // accumulated element bitstream data + Relocs []RelocEnt + RelocMap map[RelocEnt]uint32 + Data bytes.Buffer // accumulated element bitstream data encodingRelocHeader bool @@ -210,15 +211,18 @@ func (w *Encoder) rawVarint(x int64) { } func (w *Encoder) rawReloc(r RelocKind, idx Index) int { - // TODO(mdempsky): Use map for lookup; this takes quadratic time. - for i, rEnt := range w.Relocs { - if rEnt.Kind == r && rEnt.Idx == idx { - return i + e := RelocEnt{r, idx} + if w.RelocMap != nil { + if i, ok := w.RelocMap[e]; ok { + return int(i) } + } else { + w.RelocMap = make(map[RelocEnt]uint32) } i := len(w.Relocs) - w.Relocs = append(w.Relocs, RelocEnt{r, idx}) + w.RelocMap[e] = uint32(i) + w.Relocs = append(w.Relocs, e) return i } diff --git a/vendor/golang.org/x/tools/go/internal/pkgbits/reloc.go b/vendor/golang.org/x/tools/go/internal/pkgbits/reloc.go index 7a8f04ab3fc..fcdfb97ca99 100644 --- a/vendor/golang.org/x/tools/go/internal/pkgbits/reloc.go +++ b/vendor/golang.org/x/tools/go/internal/pkgbits/reloc.go @@ -5,11 +5,11 @@ package pkgbits // A RelocKind indicates a particular section within a unified IR export. -type RelocKind int +type RelocKind int32 // An Index represents a bitstream element index within a particular // section. -type Index int +type Index int32 // A relocEnt (relocation entry) is an entry in an element's local // reference table. diff --git a/vendor/golang.org/x/tools/go/packages/golist.go b/vendor/golang.org/x/tools/go/packages/golist.go index de881562de1..d9a7915bab0 100644 --- a/vendor/golang.org/x/tools/go/packages/golist.go +++ b/vendor/golang.org/x/tools/go/packages/golist.go @@ -60,6 +60,7 @@ func (r *responseDeduper) addAll(dr *driverResponse) { for _, root := range dr.Roots { r.addRoot(root) } + r.dr.GoVersion = dr.GoVersion } func (r *responseDeduper) addPackage(p *Package) { @@ -454,11 +455,14 @@ func (state *golistState) createDriverResponse(words ...string) (*driverResponse if err != nil { return nil, err } + seen := make(map[string]*jsonPackage) pkgs := make(map[string]*Package) additionalErrors := make(map[string][]Error) // Decode the JSON and convert it to Package form. - var response driverResponse + response := &driverResponse{ + GoVersion: goVersion, + } for dec := json.NewDecoder(buf); dec.More(); { p := new(jsonPackage) if err := dec.Decode(p); err != nil { @@ -730,7 +734,7 @@ func (state *golistState) createDriverResponse(words ...string) (*driverResponse } sort.Slice(response.Packages, func(i, j int) bool { return response.Packages[i].ID < response.Packages[j].ID }) - return &response, nil + return response, nil } func (state *golistState) shouldAddFilenameFromError(p *jsonPackage) bool { @@ -756,6 +760,7 @@ func (state *golistState) shouldAddFilenameFromError(p *jsonPackage) bool { return len(p.Error.ImportStack) == 0 || p.Error.ImportStack[len(p.Error.ImportStack)-1] == p.ImportPath } +// getGoVersion returns the effective minor version of the go command. func (state *golistState) getGoVersion() (int, error) { state.goVersionOnce.Do(func() { state.goVersion, state.goVersionError = gocommand.GoVersion(state.ctx, state.cfgInvocation(), state.cfg.gocmdRunner) diff --git a/vendor/golang.org/x/tools/go/packages/packages.go b/vendor/golang.org/x/tools/go/packages/packages.go index a93dc6add4d..54d880d206e 100644 --- a/vendor/golang.org/x/tools/go/packages/packages.go +++ b/vendor/golang.org/x/tools/go/packages/packages.go @@ -19,6 +19,7 @@ import ( "log" "os" "path/filepath" + "runtime" "strings" "sync" "time" @@ -233,6 +234,11 @@ type driverResponse struct { // Imports will be connected and then type and syntax information added in a // later pass (see refine). Packages []*Package + + // GoVersion is the minor version number used by the driver + // (e.g. the go command on the PATH) when selecting .go files. + // Zero means unknown. + GoVersion int } // Load loads and returns the Go packages named by the given patterns. @@ -256,7 +262,7 @@ func Load(cfg *Config, patterns ...string) ([]*Package, error) { return nil, err } l.sizes = response.Sizes - return l.refine(response.Roots, response.Packages...) + return l.refine(response) } // defaultDriver is a driver that implements go/packages' fallback behavior. @@ -532,6 +538,7 @@ type loaderPackage struct { needsrc bool // load from source (Mode >= LoadTypes) needtypes bool // type information is either requested or depended on initial bool // package was matched by a pattern + goVersion int // minor version number of go command on PATH } // loader holds the working state of a single call to load. @@ -618,7 +625,8 @@ func newLoader(cfg *Config) *loader { // refine connects the supplied packages into a graph and then adds type and // and syntax information as requested by the LoadMode. -func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) { +func (ld *loader) refine(response *driverResponse) ([]*Package, error) { + roots := response.Roots rootMap := make(map[string]int, len(roots)) for i, root := range roots { rootMap[root] = i @@ -626,7 +634,7 @@ func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) { ld.pkgs = make(map[string]*loaderPackage) // first pass, fixup and build the map and roots var initial = make([]*loaderPackage, len(roots)) - for _, pkg := range list { + for _, pkg := range response.Packages { rootIndex := -1 if i, found := rootMap[pkg.ID]; found { rootIndex = i @@ -648,6 +656,7 @@ func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) { Package: pkg, needtypes: needtypes, needsrc: needsrc, + goVersion: response.GoVersion, } ld.pkgs[lpkg.ID] = lpkg if rootIndex >= 0 { @@ -923,6 +932,33 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { lpkg.Errors = append(lpkg.Errors, errs...) } + // If the go command on the PATH is newer than the runtime, + // then the go/{scanner,ast,parser,types} packages from the + // standard library may be unable to process the files + // selected by go list. + // + // There is currently no way to downgrade the effective + // version of the go command (see issue 52078), so we proceed + // with the newer go command but, in case of parse or type + // errors, we emit an additional diagnostic. + // + // See: + // - golang.org/issue/52078 (flag to set release tags) + // - golang.org/issue/50825 (gopls legacy version support) + // - golang.org/issue/55883 (go/packages confusing error) + var runtimeVersion int + if _, err := fmt.Sscanf(runtime.Version(), "go1.%d", &runtimeVersion); err == nil && runtimeVersion < lpkg.goVersion { + defer func() { + if len(lpkg.Errors) > 0 { + appendError(Error{ + Pos: "-", + Msg: fmt.Sprintf("This application uses version go1.%d of the source-processing packages but runs version go1.%d of 'go list'. It may fail to process source files that rely on newer language features. If so, rebuild the application using a newer version of Go.", runtimeVersion, lpkg.goVersion), + Kind: UnknownError, + }) + } + }() + } + if ld.Config.Mode&NeedTypes != 0 && len(lpkg.CompiledGoFiles) == 0 && lpkg.ExportFile != "" { // The config requested loading sources and types, but sources are missing. // Add an error to the package and fall back to loading from export data. diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_darwin.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_darwin.go new file mode 100644 index 00000000000..0ca55e0d56f --- /dev/null +++ b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_darwin.go @@ -0,0 +1,119 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build darwin && cgo +// +build darwin,cgo + +package fastwalk + +/* +#include + +// fastwalk_readdir_r wraps readdir_r so that we don't have to pass a dirent** +// result pointer which triggers CGO's "Go pointer to Go pointer" check unless +// we allocat the result dirent* with malloc. +// +// fastwalk_readdir_r returns 0 on success, -1 upon reaching the end of the +// directory, or a positive error number to indicate failure. +static int fastwalk_readdir_r(DIR *fd, struct dirent *entry) { + struct dirent *result; + int ret = readdir_r(fd, entry, &result); + if (ret == 0 && result == NULL) { + ret = -1; // EOF + } + return ret; +} +*/ +import "C" + +import ( + "os" + "syscall" + "unsafe" +) + +func readDir(dirName string, fn func(dirName, entName string, typ os.FileMode) error) error { + fd, err := openDir(dirName) + if err != nil { + return &os.PathError{Op: "opendir", Path: dirName, Err: err} + } + defer C.closedir(fd) + + skipFiles := false + var dirent syscall.Dirent + for { + ret := int(C.fastwalk_readdir_r(fd, (*C.struct_dirent)(unsafe.Pointer(&dirent)))) + if ret != 0 { + if ret == -1 { + break // EOF + } + if ret == int(syscall.EINTR) { + continue + } + return &os.PathError{Op: "readdir", Path: dirName, Err: syscall.Errno(ret)} + } + if dirent.Ino == 0 { + continue + } + typ := dtToType(dirent.Type) + if skipFiles && typ.IsRegular() { + continue + } + name := (*[len(syscall.Dirent{}.Name)]byte)(unsafe.Pointer(&dirent.Name))[:] + name = name[:dirent.Namlen] + for i, c := range name { + if c == 0 { + name = name[:i] + break + } + } + // Check for useless names before allocating a string. + if string(name) == "." || string(name) == ".." { + continue + } + if err := fn(dirName, string(name), typ); err != nil { + if err != ErrSkipFiles { + return err + } + skipFiles = true + } + } + + return nil +} + +func dtToType(typ uint8) os.FileMode { + switch typ { + case syscall.DT_BLK: + return os.ModeDevice + case syscall.DT_CHR: + return os.ModeDevice | os.ModeCharDevice + case syscall.DT_DIR: + return os.ModeDir + case syscall.DT_FIFO: + return os.ModeNamedPipe + case syscall.DT_LNK: + return os.ModeSymlink + case syscall.DT_REG: + return 0 + case syscall.DT_SOCK: + return os.ModeSocket + } + return ^os.FileMode(0) +} + +// openDir wraps opendir(3) and handles any EINTR errors. The returned *DIR +// needs to be closed with closedir(3). +func openDir(path string) (*C.DIR, error) { + name, err := syscall.BytePtrFromString(path) + if err != nil { + return nil, err + } + for { + fd, err := C.opendir((*C.char)(unsafe.Pointer(name))) + if err != syscall.EINTR { + return fd, err + } + } +} diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_ino.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_ino.go index ea02b9ebfe8..d3922890b0b 100644 --- a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_ino.go +++ b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_ino.go @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build (linux || darwin) && !appengine -// +build linux darwin +//go:build (linux || (darwin && !cgo)) && !appengine +// +build linux darwin,!cgo // +build !appengine package fastwalk @@ -11,5 +11,5 @@ package fastwalk import "syscall" func direntInode(dirent *syscall.Dirent) uint64 { - return uint64(dirent.Ino) + return dirent.Ino } diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_bsd.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_bsd.go index d5c9c321ed2..38a4db6af3a 100644 --- a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_bsd.go +++ b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_bsd.go @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build darwin || freebsd || openbsd || netbsd -// +build darwin freebsd openbsd netbsd +//go:build (darwin && !cgo) || freebsd || openbsd || netbsd +// +build darwin,!cgo freebsd openbsd netbsd package fastwalk diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_unix.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_unix.go index 58bd87841e1..f12f1a734cc 100644 --- a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_unix.go +++ b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_unix.go @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build (linux || darwin || freebsd || openbsd || netbsd) && !appengine -// +build linux darwin freebsd openbsd netbsd +//go:build (linux || freebsd || openbsd || netbsd || (darwin && !cgo)) && !appengine +// +build linux freebsd openbsd netbsd darwin,!cgo // +build !appengine package fastwalk diff --git a/vendor/golang.org/x/tools/internal/gocommand/invoke.go b/vendor/golang.org/x/tools/internal/gocommand/invoke.go index 67256dc3974..d50551693f3 100644 --- a/vendor/golang.org/x/tools/internal/gocommand/invoke.go +++ b/vendor/golang.org/x/tools/internal/gocommand/invoke.go @@ -10,8 +10,10 @@ import ( "context" "fmt" "io" + "log" "os" "regexp" + "runtime" "strconv" "strings" "sync" @@ -232,6 +234,12 @@ func (i *Invocation) run(ctx context.Context, stdout, stderr io.Writer) error { return runCmdContext(ctx, cmd) } +// DebugHangingGoCommands may be set by tests to enable additional +// instrumentation (including panics) for debugging hanging Go commands. +// +// See golang/go#54461 for details. +var DebugHangingGoCommands = false + // runCmdContext is like exec.CommandContext except it sends os.Interrupt // before os.Kill. func runCmdContext(ctx context.Context, cmd *exec.Cmd) error { @@ -243,11 +251,24 @@ func runCmdContext(ctx context.Context, cmd *exec.Cmd) error { resChan <- cmd.Wait() }() - select { - case err := <-resChan: - return err - case <-ctx.Done(): + // If we're interested in debugging hanging Go commands, stop waiting after a + // minute and panic with interesting information. + if DebugHangingGoCommands { + select { + case err := <-resChan: + return err + case <-time.After(1 * time.Minute): + HandleHangingGoCommand(cmd.Process) + case <-ctx.Done(): + } + } else { + select { + case err := <-resChan: + return err + case <-ctx.Done(): + } } + // Cancelled. Interrupt and see if it ends voluntarily. cmd.Process.Signal(os.Interrupt) select { @@ -255,11 +276,63 @@ func runCmdContext(ctx context.Context, cmd *exec.Cmd) error { return err case <-time.After(time.Second): } + // Didn't shut down in response to interrupt. Kill it hard. - cmd.Process.Kill() + // TODO(rfindley): per advice from bcmills@, it may be better to send SIGQUIT + // on certain platforms, such as unix. + if err := cmd.Process.Kill(); err != nil && DebugHangingGoCommands { + // Don't panic here as this reliably fails on windows with EINVAL. + log.Printf("error killing the Go command: %v", err) + } + + // See above: don't wait indefinitely if we're debugging hanging Go commands. + if DebugHangingGoCommands { + select { + case err := <-resChan: + return err + case <-time.After(10 * time.Second): // a shorter wait as resChan should return quickly following Kill + HandleHangingGoCommand(cmd.Process) + } + } return <-resChan } +func HandleHangingGoCommand(proc *os.Process) { + switch runtime.GOOS { + case "linux", "darwin", "freebsd", "netbsd": + fmt.Fprintln(os.Stderr, `DETECTED A HANGING GO COMMAND + +The gopls test runner has detected a hanging go command. In order to debug +this, the output of ps and lsof/fstat is printed below. + +See golang/go#54461 for more details.`) + + fmt.Fprintln(os.Stderr, "\nps axo ppid,pid,command:") + fmt.Fprintln(os.Stderr, "-------------------------") + psCmd := exec.Command("ps", "axo", "ppid,pid,command") + psCmd.Stdout = os.Stderr + psCmd.Stderr = os.Stderr + if err := psCmd.Run(); err != nil { + panic(fmt.Sprintf("running ps: %v", err)) + } + + listFiles := "lsof" + if runtime.GOOS == "freebsd" || runtime.GOOS == "netbsd" { + listFiles = "fstat" + } + + fmt.Fprintln(os.Stderr, "\n"+listFiles+":") + fmt.Fprintln(os.Stderr, "-----") + listFilesCmd := exec.Command(listFiles) + listFilesCmd.Stdout = os.Stderr + listFilesCmd.Stderr = os.Stderr + if err := listFilesCmd.Run(); err != nil { + panic(fmt.Sprintf("running %s: %v", listFiles, err)) + } + } + panic(fmt.Sprintf("detected hanging go command (pid %d): see golang/go#54461 for more details", proc.Pid)) +} + func cmdDebugStr(cmd *exec.Cmd) string { env := make(map[string]string) for _, kv := range cmd.Env { diff --git a/vendor/golang.org/x/tools/internal/gocommand/version.go b/vendor/golang.org/x/tools/internal/gocommand/version.go index 71304368020..8db5ceb9d51 100644 --- a/vendor/golang.org/x/tools/internal/gocommand/version.go +++ b/vendor/golang.org/x/tools/internal/gocommand/version.go @@ -10,8 +10,15 @@ import ( "strings" ) -// GoVersion checks the go version by running "go list" with modules off. -// It returns the X in Go 1.X. +// GoVersion reports the minor version number of the highest release +// tag built into the go command on the PATH. +// +// Note that this may be higher than the version of the go tool used +// to build this application, and thus the versions of the standard +// go/{scanner,parser,ast,types} packages that are linked into it. +// In that case, callers should either downgrade to the version of +// go used to build the application, or report an error that the +// application is too old to use the go command on the PATH. func GoVersion(ctx context.Context, inv Invocation, r *Runner) (int, error) { inv.Verb = "list" inv.Args = []string{"-e", "-f", `{{context.ReleaseTags}}`, `--`, `unsafe`} @@ -38,7 +45,7 @@ func GoVersion(ctx context.Context, inv Invocation, r *Runner) (int, error) { if len(stdout) < 3 { return 0, fmt.Errorf("bad ReleaseTags output: %q", stdout) } - // Split up "[go1.1 go1.15]" + // Split up "[go1.1 go1.15]" and return highest go1.X value. tags := strings.Fields(stdout[1 : len(stdout)-2]) for i := len(tags) - 1; i >= 0; i-- { var version int diff --git a/vendor/golang.org/x/tools/internal/imports/fix.go b/vendor/golang.org/x/tools/internal/imports/fix.go index 9e373d64ebc..9b7b106fde1 100644 --- a/vendor/golang.org/x/tools/internal/imports/fix.go +++ b/vendor/golang.org/x/tools/internal/imports/fix.go @@ -807,6 +807,11 @@ type ProcessEnv struct { ModFlag string ModFile string + // SkipPathInScan returns true if the path should be skipped from scans of + // the RootCurrentModule root type. The function argument is a clean, + // absolute path. + SkipPathInScan func(string) bool + // Env overrides the OS environment, and can be used to specify // GOPROXY, GO111MODULE, etc. PATH cannot be set here, because // exec.Command will not honor it. @@ -1367,9 +1372,9 @@ func (r *gopathResolver) scan(ctx context.Context, callback *scanCallback) error return err } var roots []gopathwalk.Root - roots = append(roots, gopathwalk.Root{filepath.Join(goenv["GOROOT"], "src"), gopathwalk.RootGOROOT}) + roots = append(roots, gopathwalk.Root{Path: filepath.Join(goenv["GOROOT"], "src"), Type: gopathwalk.RootGOROOT}) for _, p := range filepath.SplitList(goenv["GOPATH"]) { - roots = append(roots, gopathwalk.Root{filepath.Join(p, "src"), gopathwalk.RootGOPATH}) + roots = append(roots, gopathwalk.Root{Path: filepath.Join(p, "src"), Type: gopathwalk.RootGOPATH}) } // The callback is not necessarily safe to use in the goroutine below. Process roots eagerly. roots = filterRoots(roots, callback.rootFound) diff --git a/vendor/golang.org/x/tools/internal/imports/mod.go b/vendor/golang.org/x/tools/internal/imports/mod.go index 46693f24339..7d99d04ca8a 100644 --- a/vendor/golang.org/x/tools/internal/imports/mod.go +++ b/vendor/golang.org/x/tools/internal/imports/mod.go @@ -129,22 +129,22 @@ func (r *ModuleResolver) init() error { }) r.roots = []gopathwalk.Root{ - {filepath.Join(goenv["GOROOT"], "/src"), gopathwalk.RootGOROOT}, + {Path: filepath.Join(goenv["GOROOT"], "/src"), Type: gopathwalk.RootGOROOT}, } r.mainByDir = make(map[string]*gocommand.ModuleJSON) for _, main := range r.mains { - r.roots = append(r.roots, gopathwalk.Root{main.Dir, gopathwalk.RootCurrentModule}) + r.roots = append(r.roots, gopathwalk.Root{Path: main.Dir, Type: gopathwalk.RootCurrentModule}) r.mainByDir[main.Dir] = main } if vendorEnabled { - r.roots = append(r.roots, gopathwalk.Root{r.dummyVendorMod.Dir, gopathwalk.RootOther}) + r.roots = append(r.roots, gopathwalk.Root{Path: r.dummyVendorMod.Dir, Type: gopathwalk.RootOther}) } else { addDep := func(mod *gocommand.ModuleJSON) { if mod.Replace == nil { // This is redundant with the cache, but we'll skip it cheaply enough. - r.roots = append(r.roots, gopathwalk.Root{mod.Dir, gopathwalk.RootModuleCache}) + r.roots = append(r.roots, gopathwalk.Root{Path: mod.Dir, Type: gopathwalk.RootModuleCache}) } else { - r.roots = append(r.roots, gopathwalk.Root{mod.Dir, gopathwalk.RootOther}) + r.roots = append(r.roots, gopathwalk.Root{Path: mod.Dir, Type: gopathwalk.RootOther}) } } // Walk dependent modules before scanning the full mod cache, direct deps first. @@ -158,7 +158,7 @@ func (r *ModuleResolver) init() error { addDep(mod) } } - r.roots = append(r.roots, gopathwalk.Root{r.moduleCacheDir, gopathwalk.RootModuleCache}) + r.roots = append(r.roots, gopathwalk.Root{Path: r.moduleCacheDir, Type: gopathwalk.RootModuleCache}) } r.scannedRoots = map[gopathwalk.Root]bool{} @@ -466,6 +466,16 @@ func (r *ModuleResolver) scan(ctx context.Context, callback *scanCallback) error // We assume cached directories are fully cached, including all their // children, and have not changed. We can skip them. skip := func(root gopathwalk.Root, dir string) bool { + if r.env.SkipPathInScan != nil && root.Type == gopathwalk.RootCurrentModule { + if root.Path == dir { + return false + } + + if r.env.SkipPathInScan(filepath.Clean(dir)) { + return true + } + } + info, ok := r.cacheLoad(dir) if !ok { return false diff --git a/vendor/golang.org/x/tools/internal/imports/zstdlib.go b/vendor/golang.org/x/tools/internal/imports/zstdlib.go index 437fbb78dbd..5db9b2d4c73 100644 --- a/vendor/golang.org/x/tools/internal/imports/zstdlib.go +++ b/vendor/golang.org/x/tools/internal/imports/zstdlib.go @@ -1,9 +1,13 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + // Code generated by mkstdlib.go. DO NOT EDIT. package imports var stdlib = map[string][]string{ - "archive/tar": []string{ + "archive/tar": { "ErrFieldTooLong", "ErrHeader", "ErrWriteAfterClose", @@ -34,7 +38,7 @@ var stdlib = map[string][]string{ "TypeXHeader", "Writer", }, - "archive/zip": []string{ + "archive/zip": { "Compressor", "Decompressor", "Deflate", @@ -54,7 +58,7 @@ var stdlib = map[string][]string{ "Store", "Writer", }, - "bufio": []string{ + "bufio": { "ErrAdvanceTooFar", "ErrBadReadCount", "ErrBufferFull", @@ -81,7 +85,7 @@ var stdlib = map[string][]string{ "SplitFunc", "Writer", }, - "bytes": []string{ + "bytes": { "Buffer", "Compare", "Contains", @@ -138,11 +142,11 @@ var stdlib = map[string][]string{ "TrimSpace", "TrimSuffix", }, - "compress/bzip2": []string{ + "compress/bzip2": { "NewReader", "StructuralError", }, - "compress/flate": []string{ + "compress/flate": { "BestCompression", "BestSpeed", "CorruptInputError", @@ -160,7 +164,7 @@ var stdlib = map[string][]string{ "WriteError", "Writer", }, - "compress/gzip": []string{ + "compress/gzip": { "BestCompression", "BestSpeed", "DefaultCompression", @@ -175,7 +179,7 @@ var stdlib = map[string][]string{ "Reader", "Writer", }, - "compress/lzw": []string{ + "compress/lzw": { "LSB", "MSB", "NewReader", @@ -184,7 +188,7 @@ var stdlib = map[string][]string{ "Reader", "Writer", }, - "compress/zlib": []string{ + "compress/zlib": { "BestCompression", "BestSpeed", "DefaultCompression", @@ -201,7 +205,7 @@ var stdlib = map[string][]string{ "Resetter", "Writer", }, - "container/heap": []string{ + "container/heap": { "Fix", "Init", "Interface", @@ -209,16 +213,16 @@ var stdlib = map[string][]string{ "Push", "Remove", }, - "container/list": []string{ + "container/list": { "Element", "List", "New", }, - "container/ring": []string{ + "container/ring": { "New", "Ring", }, - "context": []string{ + "context": { "Background", "CancelFunc", "Canceled", @@ -230,7 +234,7 @@ var stdlib = map[string][]string{ "WithTimeout", "WithValue", }, - "crypto": []string{ + "crypto": { "BLAKE2b_256", "BLAKE2b_384", "BLAKE2b_512", @@ -259,12 +263,12 @@ var stdlib = map[string][]string{ "Signer", "SignerOpts", }, - "crypto/aes": []string{ + "crypto/aes": { "BlockSize", "KeySizeError", "NewCipher", }, - "crypto/cipher": []string{ + "crypto/cipher": { "AEAD", "Block", "BlockMode", @@ -281,13 +285,13 @@ var stdlib = map[string][]string{ "StreamReader", "StreamWriter", }, - "crypto/des": []string{ + "crypto/des": { "BlockSize", "KeySizeError", "NewCipher", "NewTripleDESCipher", }, - "crypto/dsa": []string{ + "crypto/dsa": { "ErrInvalidPublicKey", "GenerateKey", "GenerateParameters", @@ -302,7 +306,7 @@ var stdlib = map[string][]string{ "Sign", "Verify", }, - "crypto/ecdsa": []string{ + "crypto/ecdsa": { "GenerateKey", "PrivateKey", "PublicKey", @@ -311,7 +315,7 @@ var stdlib = map[string][]string{ "Verify", "VerifyASN1", }, - "crypto/ed25519": []string{ + "crypto/ed25519": { "GenerateKey", "NewKeyFromSeed", "PrivateKey", @@ -323,7 +327,7 @@ var stdlib = map[string][]string{ "SignatureSize", "Verify", }, - "crypto/elliptic": []string{ + "crypto/elliptic": { "Curve", "CurveParams", "GenerateKey", @@ -336,28 +340,28 @@ var stdlib = map[string][]string{ "Unmarshal", "UnmarshalCompressed", }, - "crypto/hmac": []string{ + "crypto/hmac": { "Equal", "New", }, - "crypto/md5": []string{ + "crypto/md5": { "BlockSize", "New", "Size", "Sum", }, - "crypto/rand": []string{ + "crypto/rand": { "Int", "Prime", "Read", "Reader", }, - "crypto/rc4": []string{ + "crypto/rc4": { "Cipher", "KeySizeError", "NewCipher", }, - "crypto/rsa": []string{ + "crypto/rsa": { "CRTValue", "DecryptOAEP", "DecryptPKCS1v15", @@ -382,13 +386,13 @@ var stdlib = map[string][]string{ "VerifyPKCS1v15", "VerifyPSS", }, - "crypto/sha1": []string{ + "crypto/sha1": { "BlockSize", "New", "Size", "Sum", }, - "crypto/sha256": []string{ + "crypto/sha256": { "BlockSize", "New", "New224", @@ -397,7 +401,7 @@ var stdlib = map[string][]string{ "Sum224", "Sum256", }, - "crypto/sha512": []string{ + "crypto/sha512": { "BlockSize", "New", "New384", @@ -412,7 +416,7 @@ var stdlib = map[string][]string{ "Sum512_224", "Sum512_256", }, - "crypto/subtle": []string{ + "crypto/subtle": { "ConstantTimeByteEq", "ConstantTimeCompare", "ConstantTimeCopy", @@ -420,7 +424,7 @@ var stdlib = map[string][]string{ "ConstantTimeLessOrEq", "ConstantTimeSelect", }, - "crypto/tls": []string{ + "crypto/tls": { "Certificate", "CertificateRequestInfo", "CipherSuite", @@ -506,7 +510,7 @@ var stdlib = map[string][]string{ "X25519", "X509KeyPair", }, - "crypto/x509": []string{ + "crypto/x509": { "CANotAuthorizedForExtKeyUsage", "CANotAuthorizedForThisName", "CertPool", @@ -588,6 +592,7 @@ var stdlib = map[string][]string{ "ParsePKCS1PublicKey", "ParsePKCS8PrivateKey", "ParsePKIXPublicKey", + "ParseRevocationList", "PublicKeyAlgorithm", "PureEd25519", "RSA", @@ -611,7 +616,7 @@ var stdlib = map[string][]string{ "UnknownSignatureAlgorithm", "VerifyOptions", }, - "crypto/x509/pkix": []string{ + "crypto/x509/pkix": { "AlgorithmIdentifier", "AttributeTypeAndValue", "AttributeTypeAndValueSET", @@ -623,7 +628,7 @@ var stdlib = map[string][]string{ "RevokedCertificate", "TBSCertificateList", }, - "database/sql": []string{ + "database/sql": { "ColumnType", "Conn", "DB", @@ -664,7 +669,7 @@ var stdlib = map[string][]string{ "Tx", "TxOptions", }, - "database/sql/driver": []string{ + "database/sql/driver": { "Bool", "ColumnConverter", "Conn", @@ -712,12 +717,12 @@ var stdlib = map[string][]string{ "ValueConverter", "Valuer", }, - "debug/buildinfo": []string{ + "debug/buildinfo": { "BuildInfo", "Read", "ReadFile", }, - "debug/dwarf": []string{ + "debug/dwarf": { "AddrType", "ArrayType", "Attr", @@ -968,7 +973,7 @@ var stdlib = map[string][]string{ "UnsupportedType", "VoidType", }, - "debug/elf": []string{ + "debug/elf": { "ARM_MAGIC_TRAMP_NUMBER", "COMPRESS_HIOS", "COMPRESS_HIPROC", @@ -1238,6 +1243,7 @@ var stdlib = map[string][]string{ "EM_L10M", "EM_LANAI", "EM_LATTICEMICO32", + "EM_LOONGARCH", "EM_M16C", "EM_M32", "EM_M32C", @@ -1820,6 +1826,57 @@ var stdlib = map[string][]string{ "R_ARM_XPC25", "R_INFO", "R_INFO32", + "R_LARCH", + "R_LARCH_32", + "R_LARCH_64", + "R_LARCH_ADD16", + "R_LARCH_ADD24", + "R_LARCH_ADD32", + "R_LARCH_ADD64", + "R_LARCH_ADD8", + "R_LARCH_COPY", + "R_LARCH_IRELATIVE", + "R_LARCH_JUMP_SLOT", + "R_LARCH_MARK_LA", + "R_LARCH_MARK_PCREL", + "R_LARCH_NONE", + "R_LARCH_RELATIVE", + "R_LARCH_SOP_ADD", + "R_LARCH_SOP_AND", + "R_LARCH_SOP_ASSERT", + "R_LARCH_SOP_IF_ELSE", + "R_LARCH_SOP_NOT", + "R_LARCH_SOP_POP_32_S_0_10_10_16_S2", + "R_LARCH_SOP_POP_32_S_0_5_10_16_S2", + "R_LARCH_SOP_POP_32_S_10_12", + "R_LARCH_SOP_POP_32_S_10_16", + "R_LARCH_SOP_POP_32_S_10_16_S2", + "R_LARCH_SOP_POP_32_S_10_5", + "R_LARCH_SOP_POP_32_S_5_20", + "R_LARCH_SOP_POP_32_U", + "R_LARCH_SOP_POP_32_U_10_12", + "R_LARCH_SOP_PUSH_ABSOLUTE", + "R_LARCH_SOP_PUSH_DUP", + "R_LARCH_SOP_PUSH_GPREL", + "R_LARCH_SOP_PUSH_PCREL", + "R_LARCH_SOP_PUSH_PLT_PCREL", + "R_LARCH_SOP_PUSH_TLS_GD", + "R_LARCH_SOP_PUSH_TLS_GOT", + "R_LARCH_SOP_PUSH_TLS_TPREL", + "R_LARCH_SOP_SL", + "R_LARCH_SOP_SR", + "R_LARCH_SOP_SUB", + "R_LARCH_SUB16", + "R_LARCH_SUB24", + "R_LARCH_SUB32", + "R_LARCH_SUB64", + "R_LARCH_SUB8", + "R_LARCH_TLS_DTPMOD32", + "R_LARCH_TLS_DTPMOD64", + "R_LARCH_TLS_DTPREL32", + "R_LARCH_TLS_DTPREL64", + "R_LARCH_TLS_TPREL32", + "R_LARCH_TLS_TPREL64", "R_MIPS", "R_MIPS_16", "R_MIPS_26", @@ -2315,7 +2372,7 @@ var stdlib = map[string][]string{ "Type", "Version", }, - "debug/gosym": []string{ + "debug/gosym": { "DecodingError", "Func", "LineTable", @@ -2327,7 +2384,7 @@ var stdlib = map[string][]string{ "UnknownFileError", "UnknownLineError", }, - "debug/macho": []string{ + "debug/macho": { "ARM64_RELOC_ADDEND", "ARM64_RELOC_BRANCH26", "ARM64_RELOC_GOT_LOAD_PAGE21", @@ -2457,13 +2514,20 @@ var stdlib = map[string][]string{ "X86_64_RELOC_TLV", "X86_64_RELOC_UNSIGNED", }, - "debug/pe": []string{ + "debug/pe": { "COFFSymbol", + "COFFSymbolAuxFormat5", "COFFSymbolSize", "DataDirectory", "File", "FileHeader", "FormatError", + "IMAGE_COMDAT_SELECT_ANY", + "IMAGE_COMDAT_SELECT_ASSOCIATIVE", + "IMAGE_COMDAT_SELECT_EXACT_MATCH", + "IMAGE_COMDAT_SELECT_LARGEST", + "IMAGE_COMDAT_SELECT_NODUPLICATES", + "IMAGE_COMDAT_SELECT_SAME_SIZE", "IMAGE_DIRECTORY_ENTRY_ARCHITECTURE", "IMAGE_DIRECTORY_ENTRY_BASERELOC", "IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT", @@ -2508,6 +2572,8 @@ var stdlib = map[string][]string{ "IMAGE_FILE_MACHINE_EBC", "IMAGE_FILE_MACHINE_I386", "IMAGE_FILE_MACHINE_IA64", + "IMAGE_FILE_MACHINE_LOONGARCH32", + "IMAGE_FILE_MACHINE_LOONGARCH64", "IMAGE_FILE_MACHINE_M32R", "IMAGE_FILE_MACHINE_MIPS16", "IMAGE_FILE_MACHINE_MIPSFPU", @@ -2527,6 +2593,14 @@ var stdlib = map[string][]string{ "IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP", "IMAGE_FILE_SYSTEM", "IMAGE_FILE_UP_SYSTEM_ONLY", + "IMAGE_SCN_CNT_CODE", + "IMAGE_SCN_CNT_INITIALIZED_DATA", + "IMAGE_SCN_CNT_UNINITIALIZED_DATA", + "IMAGE_SCN_LNK_COMDAT", + "IMAGE_SCN_MEM_DISCARDABLE", + "IMAGE_SCN_MEM_EXECUTE", + "IMAGE_SCN_MEM_READ", + "IMAGE_SCN_MEM_WRITE", "IMAGE_SUBSYSTEM_EFI_APPLICATION", "IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER", "IMAGE_SUBSYSTEM_EFI_ROM", @@ -2553,7 +2627,7 @@ var stdlib = map[string][]string{ "StringTable", "Symbol", }, - "debug/plan9obj": []string{ + "debug/plan9obj": { "ErrNoSymbols", "File", "FileHeader", @@ -2567,16 +2641,16 @@ var stdlib = map[string][]string{ "SectionHeader", "Sym", }, - "embed": []string{ + "embed": { "FS", }, - "encoding": []string{ + "encoding": { "BinaryMarshaler", "BinaryUnmarshaler", "TextMarshaler", "TextUnmarshaler", }, - "encoding/ascii85": []string{ + "encoding/ascii85": { "CorruptInputError", "Decode", "Encode", @@ -2584,7 +2658,7 @@ var stdlib = map[string][]string{ "NewDecoder", "NewEncoder", }, - "encoding/asn1": []string{ + "encoding/asn1": { "BitString", "ClassApplication", "ClassContextSpecific", @@ -2622,7 +2696,7 @@ var stdlib = map[string][]string{ "Unmarshal", "UnmarshalWithParams", }, - "encoding/base32": []string{ + "encoding/base32": { "CorruptInputError", "Encoding", "HexEncoding", @@ -2633,7 +2707,7 @@ var stdlib = map[string][]string{ "StdEncoding", "StdPadding", }, - "encoding/base64": []string{ + "encoding/base64": { "CorruptInputError", "Encoding", "NewDecoder", @@ -2646,7 +2720,10 @@ var stdlib = map[string][]string{ "StdPadding", "URLEncoding", }, - "encoding/binary": []string{ + "encoding/binary": { + "AppendByteOrder", + "AppendUvarint", + "AppendVarint", "BigEndian", "ByteOrder", "LittleEndian", @@ -2663,7 +2740,7 @@ var stdlib = map[string][]string{ "Varint", "Write", }, - "encoding/csv": []string{ + "encoding/csv": { "ErrBareQuote", "ErrFieldCount", "ErrQuote", @@ -2674,7 +2751,7 @@ var stdlib = map[string][]string{ "Reader", "Writer", }, - "encoding/gob": []string{ + "encoding/gob": { "CommonType", "Decoder", "Encoder", @@ -2685,7 +2762,7 @@ var stdlib = map[string][]string{ "Register", "RegisterName", }, - "encoding/hex": []string{ + "encoding/hex": { "Decode", "DecodeString", "DecodedLen", @@ -2699,7 +2776,7 @@ var stdlib = map[string][]string{ "NewDecoder", "NewEncoder", }, - "encoding/json": []string{ + "encoding/json": { "Compact", "Decoder", "Delim", @@ -2726,13 +2803,13 @@ var stdlib = map[string][]string{ "UnsupportedValueError", "Valid", }, - "encoding/pem": []string{ + "encoding/pem": { "Block", "Decode", "Encode", "EncodeToMemory", }, - "encoding/xml": []string{ + "encoding/xml": { "Attr", "CharData", "Comment", @@ -2766,13 +2843,13 @@ var stdlib = map[string][]string{ "UnmarshalerAttr", "UnsupportedTypeError", }, - "errors": []string{ + "errors": { "As", "Is", "New", "Unwrap", }, - "expvar": []string{ + "expvar": { "Do", "Float", "Func", @@ -2789,7 +2866,7 @@ var stdlib = map[string][]string{ "String", "Var", }, - "flag": []string{ + "flag": { "Arg", "Args", "Bool", @@ -2822,6 +2899,7 @@ var stdlib = map[string][]string{ "Set", "String", "StringVar", + "TextVar", "Uint", "Uint64", "Uint64Var", @@ -2833,7 +2911,10 @@ var stdlib = map[string][]string{ "Visit", "VisitAll", }, - "fmt": []string{ + "fmt": { + "Append", + "Appendf", + "Appendln", "Errorf", "Formatter", "Fprint", @@ -2860,7 +2941,7 @@ var stdlib = map[string][]string{ "State", "Stringer", }, - "go/ast": []string{ + "go/ast": { "ArrayType", "AssignStmt", "Bad", @@ -2963,7 +3044,7 @@ var stdlib = map[string][]string{ "Visitor", "Walk", }, - "go/build": []string{ + "go/build": { "AllowBinary", "ArchChar", "Context", @@ -2980,7 +3061,7 @@ var stdlib = map[string][]string{ "Package", "ToolDir", }, - "go/build/constraint": []string{ + "go/build/constraint": { "AndExpr", "Expr", "IsGoBuild", @@ -2992,7 +3073,7 @@ var stdlib = map[string][]string{ "SyntaxError", "TagExpr", }, - "go/constant": []string{ + "go/constant": { "BinaryOp", "BitLen", "Bool", @@ -3033,7 +3114,7 @@ var stdlib = map[string][]string{ "Val", "Value", }, - "go/doc": []string{ + "go/doc": { "AllDecls", "AllMethods", "Example", @@ -3054,17 +3135,35 @@ var stdlib = map[string][]string{ "Type", "Value", }, - "go/format": []string{ + "go/doc/comment": { + "Block", + "Code", + "DefaultLookupPackage", + "Doc", + "DocLink", + "Heading", + "Italic", + "Link", + "LinkDef", + "List", + "ListItem", + "Paragraph", + "Parser", + "Plain", + "Printer", + "Text", + }, + "go/format": { "Node", "Source", }, - "go/importer": []string{ + "go/importer": { "Default", "For", "ForCompiler", "Lookup", }, - "go/parser": []string{ + "go/parser": { "AllErrors", "DeclarationErrors", "ImportsOnly", @@ -3079,7 +3178,7 @@ var stdlib = map[string][]string{ "SpuriousErrors", "Trace", }, - "go/printer": []string{ + "go/printer": { "CommentedNode", "Config", "Fprint", @@ -3089,7 +3188,7 @@ var stdlib = map[string][]string{ "TabIndent", "UseSpaces", }, - "go/scanner": []string{ + "go/scanner": { "Error", "ErrorHandler", "ErrorList", @@ -3098,7 +3197,7 @@ var stdlib = map[string][]string{ "ScanComments", "Scanner", }, - "go/token": []string{ + "go/token": { "ADD", "ADD_ASSIGN", "AND", @@ -3196,7 +3295,7 @@ var stdlib = map[string][]string{ "XOR", "XOR_ASSIGN", }, - "go/types": []string{ + "go/types": { "ArgumentError", "Array", "AssertableTo", @@ -3347,17 +3446,17 @@ var stdlib = map[string][]string{ "WriteSignature", "WriteType", }, - "hash": []string{ + "hash": { "Hash", "Hash32", "Hash64", }, - "hash/adler32": []string{ + "hash/adler32": { "Checksum", "New", "Size", }, - "hash/crc32": []string{ + "hash/crc32": { "Castagnoli", "Checksum", "ChecksumIEEE", @@ -3371,7 +3470,7 @@ var stdlib = map[string][]string{ "Table", "Update", }, - "hash/crc64": []string{ + "hash/crc64": { "Checksum", "ECMA", "ISO", @@ -3381,7 +3480,7 @@ var stdlib = map[string][]string{ "Table", "Update", }, - "hash/fnv": []string{ + "hash/fnv": { "New128", "New128a", "New32", @@ -3389,16 +3488,18 @@ var stdlib = map[string][]string{ "New64", "New64a", }, - "hash/maphash": []string{ + "hash/maphash": { + "Bytes", "Hash", "MakeSeed", "Seed", + "String", }, - "html": []string{ + "html": { "EscapeString", "UnescapeString", }, - "html/template": []string{ + "html/template": { "CSS", "ErrAmbigContext", "ErrBadHTML", @@ -3436,7 +3537,7 @@ var stdlib = map[string][]string{ "URL", "URLQueryEscaper", }, - "image": []string{ + "image": { "Alpha", "Alpha16", "Black", @@ -3489,7 +3590,7 @@ var stdlib = map[string][]string{ "ZP", "ZR", }, - "image/color": []string{ + "image/color": { "Alpha", "Alpha16", "Alpha16Model", @@ -3525,11 +3626,11 @@ var stdlib = map[string][]string{ "YCbCrModel", "YCbCrToRGB", }, - "image/color/palette": []string{ + "image/color/palette": { "Plan9", "WebSafe", }, - "image/draw": []string{ + "image/draw": { "Draw", "DrawMask", "Drawer", @@ -3541,7 +3642,7 @@ var stdlib = map[string][]string{ "RGBA64Image", "Src", }, - "image/gif": []string{ + "image/gif": { "Decode", "DecodeAll", "DecodeConfig", @@ -3553,7 +3654,7 @@ var stdlib = map[string][]string{ "GIF", "Options", }, - "image/jpeg": []string{ + "image/jpeg": { "Decode", "DecodeConfig", "DefaultQuality", @@ -3563,7 +3664,7 @@ var stdlib = map[string][]string{ "Reader", "UnsupportedError", }, - "image/png": []string{ + "image/png": { "BestCompression", "BestSpeed", "CompressionLevel", @@ -3578,11 +3679,11 @@ var stdlib = map[string][]string{ "NoCompression", "UnsupportedError", }, - "index/suffixarray": []string{ + "index/suffixarray": { "Index", "New", }, - "io": []string{ + "io": { "ByteReader", "ByteScanner", "ByteWriter", @@ -3634,7 +3735,7 @@ var stdlib = map[string][]string{ "WriterAt", "WriterTo", }, - "io/fs": []string{ + "io/fs": { "DirEntry", "ErrClosed", "ErrExist", @@ -3678,7 +3779,7 @@ var stdlib = map[string][]string{ "WalkDir", "WalkDirFunc", }, - "io/ioutil": []string{ + "io/ioutil": { "Discard", "NopCloser", "ReadAll", @@ -3688,7 +3789,7 @@ var stdlib = map[string][]string{ "TempFile", "WriteFile", }, - "log": []string{ + "log": { "Default", "Fatal", "Fatalf", @@ -3717,7 +3818,7 @@ var stdlib = map[string][]string{ "SetPrefix", "Writer", }, - "log/syslog": []string{ + "log/syslog": { "Dial", "LOG_ALERT", "LOG_AUTH", @@ -3752,7 +3853,7 @@ var stdlib = map[string][]string{ "Priority", "Writer", }, - "math": []string{ + "math": { "Abs", "Acos", "Acosh", @@ -3851,7 +3952,7 @@ var stdlib = map[string][]string{ "Y1", "Yn", }, - "math/big": []string{ + "math/big": { "Above", "Accuracy", "AwayFromZero", @@ -3878,7 +3979,7 @@ var stdlib = map[string][]string{ "ToZero", "Word", }, - "math/bits": []string{ + "math/bits": { "Add", "Add32", "Add64", @@ -3930,7 +4031,7 @@ var stdlib = map[string][]string{ "TrailingZeros8", "UintSize", }, - "math/cmplx": []string{ + "math/cmplx": { "Abs", "Acos", "Acosh", @@ -3959,7 +4060,7 @@ var stdlib = map[string][]string{ "Tan", "Tanh", }, - "math/rand": []string{ + "math/rand": { "ExpFloat64", "Float32", "Float64", @@ -3984,7 +4085,7 @@ var stdlib = map[string][]string{ "Uint64", "Zipf", }, - "mime": []string{ + "mime": { "AddExtensionType", "BEncoding", "ErrInvalidMediaParameter", @@ -3996,7 +4097,7 @@ var stdlib = map[string][]string{ "WordDecoder", "WordEncoder", }, - "mime/multipart": []string{ + "mime/multipart": { "ErrMessageTooLarge", "File", "FileHeader", @@ -4007,13 +4108,13 @@ var stdlib = map[string][]string{ "Reader", "Writer", }, - "mime/quotedprintable": []string{ + "mime/quotedprintable": { "NewReader", "NewWriter", "Reader", "Writer", }, - "net": []string{ + "net": { "Addr", "AddrError", "Buffers", @@ -4115,7 +4216,7 @@ var stdlib = map[string][]string{ "UnixListener", "UnknownNetworkError", }, - "net/http": []string{ + "net/http": { "AllowQuerySemicolons", "CanonicalHeaderKey", "Client", @@ -4168,6 +4269,7 @@ var stdlib = map[string][]string{ "ListenAndServe", "ListenAndServeTLS", "LocalAddrContextKey", + "MaxBytesError", "MaxBytesHandler", "MaxBytesReader", "MethodConnect", @@ -4290,25 +4392,25 @@ var stdlib = map[string][]string{ "TrailerPrefix", "Transport", }, - "net/http/cgi": []string{ + "net/http/cgi": { "Handler", "Request", "RequestFromMap", "Serve", }, - "net/http/cookiejar": []string{ + "net/http/cookiejar": { "Jar", "New", "Options", "PublicSuffixList", }, - "net/http/fcgi": []string{ + "net/http/fcgi": { "ErrConnClosed", "ErrRequestAborted", "ProcessEnv", "Serve", }, - "net/http/httptest": []string{ + "net/http/httptest": { "DefaultRemoteAddr", "NewRecorder", "NewRequest", @@ -4318,7 +4420,7 @@ var stdlib = map[string][]string{ "ResponseRecorder", "Server", }, - "net/http/httptrace": []string{ + "net/http/httptrace": { "ClientTrace", "ContextClientTrace", "DNSDoneInfo", @@ -4327,7 +4429,7 @@ var stdlib = map[string][]string{ "WithClientTrace", "WroteRequestInfo", }, - "net/http/httputil": []string{ + "net/http/httputil": { "BufferPool", "ClientConn", "DumpRequest", @@ -4346,7 +4448,7 @@ var stdlib = map[string][]string{ "ReverseProxy", "ServerConn", }, - "net/http/pprof": []string{ + "net/http/pprof": { "Cmdline", "Handler", "Index", @@ -4354,7 +4456,7 @@ var stdlib = map[string][]string{ "Symbol", "Trace", }, - "net/mail": []string{ + "net/mail": { "Address", "AddressParser", "ErrHeaderNotPresent", @@ -4365,7 +4467,7 @@ var stdlib = map[string][]string{ "ParseDate", "ReadMessage", }, - "net/netip": []string{ + "net/netip": { "Addr", "AddrFrom16", "AddrFrom4", @@ -4384,7 +4486,7 @@ var stdlib = map[string][]string{ "Prefix", "PrefixFrom", }, - "net/rpc": []string{ + "net/rpc": { "Accept", "Call", "Client", @@ -4411,14 +4513,14 @@ var stdlib = map[string][]string{ "ServerCodec", "ServerError", }, - "net/rpc/jsonrpc": []string{ + "net/rpc/jsonrpc": { "Dial", "NewClient", "NewClientCodec", "NewServerCodec", "ServeConn", }, - "net/smtp": []string{ + "net/smtp": { "Auth", "CRAMMD5Auth", "Client", @@ -4428,7 +4530,7 @@ var stdlib = map[string][]string{ "SendMail", "ServerInfo", }, - "net/textproto": []string{ + "net/textproto": { "CanonicalMIMEHeaderKey", "Conn", "Dial", @@ -4444,10 +4546,11 @@ var stdlib = map[string][]string{ "TrimString", "Writer", }, - "net/url": []string{ + "net/url": { "Error", "EscapeError", "InvalidHostError", + "JoinPath", "Parse", "ParseQuery", "ParseRequestURI", @@ -4461,7 +4564,7 @@ var stdlib = map[string][]string{ "Userinfo", "Values", }, - "os": []string{ + "os": { "Args", "Chdir", "Chmod", @@ -4577,16 +4680,17 @@ var stdlib = map[string][]string{ "UserHomeDir", "WriteFile", }, - "os/exec": []string{ + "os/exec": { "Cmd", "Command", "CommandContext", + "ErrDot", "ErrNotFound", "Error", "ExitError", "LookPath", }, - "os/signal": []string{ + "os/signal": { "Ignore", "Ignored", "Notify", @@ -4594,7 +4698,7 @@ var stdlib = map[string][]string{ "Reset", "Stop", }, - "os/user": []string{ + "os/user": { "Current", "Group", "Lookup", @@ -4607,7 +4711,7 @@ var stdlib = map[string][]string{ "UnknownUserIdError", "User", }, - "path": []string{ + "path": { "Base", "Clean", "Dir", @@ -4618,7 +4722,7 @@ var stdlib = map[string][]string{ "Match", "Split", }, - "path/filepath": []string{ + "path/filepath": { "Abs", "Base", "Clean", @@ -4644,12 +4748,12 @@ var stdlib = map[string][]string{ "WalkDir", "WalkFunc", }, - "plugin": []string{ + "plugin": { "Open", "Plugin", "Symbol", }, - "reflect": []string{ + "reflect": { "Append", "AppendSlice", "Array", @@ -4724,7 +4828,7 @@ var stdlib = map[string][]string{ "VisibleFields", "Zero", }, - "regexp": []string{ + "regexp": { "Compile", "CompilePOSIX", "Match", @@ -4735,7 +4839,7 @@ var stdlib = map[string][]string{ "QuoteMeta", "Regexp", }, - "regexp/syntax": []string{ + "regexp/syntax": { "ClassNL", "Compile", "DotNL", @@ -4759,6 +4863,7 @@ var stdlib = map[string][]string{ "ErrMissingBracket", "ErrMissingParen", "ErrMissingRepeatArgument", + "ErrNestingDepth", "ErrTrailingBackslash", "ErrUnexpectedParen", "Error", @@ -4813,7 +4918,7 @@ var stdlib = map[string][]string{ "UnicodeGroups", "WasDollar", }, - "runtime": []string{ + "runtime": { "BlockProfile", "BlockProfileRecord", "Breakpoint", @@ -4861,11 +4966,11 @@ var stdlib = map[string][]string{ "UnlockOSThread", "Version", }, - "runtime/cgo": []string{ + "runtime/cgo": { "Handle", "NewHandle", }, - "runtime/debug": []string{ + "runtime/debug": { "BuildInfo", "BuildSetting", "FreeOSMemory", @@ -4878,12 +4983,13 @@ var stdlib = map[string][]string{ "SetGCPercent", "SetMaxStack", "SetMaxThreads", + "SetMemoryLimit", "SetPanicOnFault", "SetTraceback", "Stack", "WriteHeapDump", }, - "runtime/metrics": []string{ + "runtime/metrics": { "All", "Description", "Float64Histogram", @@ -4896,7 +5002,7 @@ var stdlib = map[string][]string{ "Value", "ValueKind", }, - "runtime/pprof": []string{ + "runtime/pprof": { "Do", "ForLabels", "Label", @@ -4912,7 +5018,7 @@ var stdlib = map[string][]string{ "WithLabels", "WriteHeapProfile", }, - "runtime/trace": []string{ + "runtime/trace": { "IsEnabled", "Log", "Logf", @@ -4924,7 +5030,8 @@ var stdlib = map[string][]string{ "Task", "WithRegion", }, - "sort": []string{ + "sort": { + "Find", "Float64Slice", "Float64s", "Float64sAreSorted", @@ -4947,7 +5054,7 @@ var stdlib = map[string][]string{ "Strings", "StringsAreSorted", }, - "strconv": []string{ + "strconv": { "AppendBool", "AppendFloat", "AppendInt", @@ -4987,7 +5094,7 @@ var stdlib = map[string][]string{ "Unquote", "UnquoteChar", }, - "strings": []string{ + "strings": { "Builder", "Clone", "Compare", @@ -5041,7 +5148,7 @@ var stdlib = map[string][]string{ "TrimSpace", "TrimSuffix", }, - "sync": []string{ + "sync": { "Cond", "Locker", "Map", @@ -5052,24 +5159,28 @@ var stdlib = map[string][]string{ "RWMutex", "WaitGroup", }, - "sync/atomic": []string{ + "sync/atomic": { "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr", + "Bool", "CompareAndSwapInt32", "CompareAndSwapInt64", "CompareAndSwapPointer", "CompareAndSwapUint32", "CompareAndSwapUint64", "CompareAndSwapUintptr", + "Int32", + "Int64", "LoadInt32", "LoadInt64", "LoadPointer", "LoadUint32", "LoadUint64", "LoadUintptr", + "Pointer", "StoreInt32", "StoreInt64", "StorePointer", @@ -5082,9 +5193,12 @@ var stdlib = map[string][]string{ "SwapUint32", "SwapUint64", "SwapUintptr", + "Uint32", + "Uint64", + "Uintptr", "Value", }, - "syscall": []string{ + "syscall": { "AF_ALG", "AF_APPLETALK", "AF_ARP", @@ -10234,7 +10348,7 @@ var stdlib = map[string][]string{ "XP1_UNI_RECV", "XP1_UNI_SEND", }, - "syscall/js": []string{ + "syscall/js": { "CopyBytesToGo", "CopyBytesToJS", "Error", @@ -10256,7 +10370,7 @@ var stdlib = map[string][]string{ "ValueError", "ValueOf", }, - "testing": []string{ + "testing": { "AllocsPerRun", "B", "Benchmark", @@ -10284,12 +10398,12 @@ var stdlib = map[string][]string{ "TB", "Verbose", }, - "testing/fstest": []string{ + "testing/fstest": { "MapFS", "MapFile", "TestFS", }, - "testing/iotest": []string{ + "testing/iotest": { "DataErrReader", "ErrReader", "ErrTimeout", @@ -10301,7 +10415,7 @@ var stdlib = map[string][]string{ "TimeoutReader", "TruncateWriter", }, - "testing/quick": []string{ + "testing/quick": { "Check", "CheckEqual", "CheckEqualError", @@ -10311,7 +10425,7 @@ var stdlib = map[string][]string{ "SetupError", "Value", }, - "text/scanner": []string{ + "text/scanner": { "Char", "Comment", "EOF", @@ -10334,7 +10448,7 @@ var stdlib = map[string][]string{ "String", "TokenString", }, - "text/tabwriter": []string{ + "text/tabwriter": { "AlignRight", "Debug", "DiscardEmptyColumns", @@ -10345,7 +10459,7 @@ var stdlib = map[string][]string{ "TabIndent", "Writer", }, - "text/template": []string{ + "text/template": { "ExecError", "FuncMap", "HTMLEscape", @@ -10363,7 +10477,7 @@ var stdlib = map[string][]string{ "Template", "URLQueryEscaper", }, - "text/template/parse": []string{ + "text/template/parse": { "ActionNode", "BoolNode", "BranchNode", @@ -10419,7 +10533,7 @@ var stdlib = map[string][]string{ "VariableNode", "WithNode", }, - "time": []string{ + "time": { "ANSIC", "After", "AfterFunc", @@ -10491,7 +10605,7 @@ var stdlib = map[string][]string{ "Wednesday", "Weekday", }, - "unicode": []string{ + "unicode": { "ASCII_Hex_Digit", "Adlam", "Ahom", @@ -10777,14 +10891,14 @@ var stdlib = map[string][]string{ "Zp", "Zs", }, - "unicode/utf16": []string{ + "unicode/utf16": { "Decode", "DecodeRune", "Encode", "EncodeRune", "IsSurrogate", }, - "unicode/utf8": []string{ + "unicode/utf8": { "AppendRune", "DecodeLastRune", "DecodeLastRuneInString", @@ -10805,7 +10919,7 @@ var stdlib = map[string][]string{ "ValidRune", "ValidString", }, - "unsafe": []string{ + "unsafe": { "Alignof", "ArbitraryType", "Offsetof", diff --git a/vendor/modules.txt b/vendor/modules.txt index 44fd71d0078..eea426f63ea 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -396,6 +396,7 @@ github.com/go-kivik/kivik/v3/internal/registry # github.com/go-logr/logr v1.2.3 ## explicit; go 1.16 github.com/go-logr/logr +github.com/go-logr/logr/funcr # github.com/go-logr/zapr v1.2.3 ## explicit; go 1.16 github.com/go-logr/zapr @@ -905,6 +906,18 @@ github.com/onsi/ginkgo/reporters/stenographer github.com/onsi/ginkgo/reporters/stenographer/support/go-colorable github.com/onsi/ginkgo/reporters/stenographer/support/go-isatty github.com/onsi/ginkgo/types +# github.com/onsi/ginkgo/v2 v2.5.0 +## explicit; go 1.18 +github.com/onsi/ginkgo/v2 +github.com/onsi/ginkgo/v2/config +github.com/onsi/ginkgo/v2/formatter +github.com/onsi/ginkgo/v2/internal +github.com/onsi/ginkgo/v2/internal/global +github.com/onsi/ginkgo/v2/internal/interrupt_handler +github.com/onsi/ginkgo/v2/internal/parallel_support +github.com/onsi/ginkgo/v2/internal/testingtproxy +github.com/onsi/ginkgo/v2/reporters +github.com/onsi/ginkgo/v2/types # github.com/onsi/gomega v1.24.1 ## explicit; go 1.18 github.com/onsi/gomega @@ -917,6 +930,9 @@ github.com/onsi/gomega/matchers/support/goraph/edge github.com/onsi/gomega/matchers/support/goraph/node github.com/onsi/gomega/matchers/support/goraph/util github.com/onsi/gomega/types +# github.com/open-policy-agent/cert-controller v0.5.0 +## explicit; go 1.17 +github.com/open-policy-agent/cert-controller/pkg/rotator # github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 ## explicit github.com/phayes/freeport @@ -1225,7 +1241,7 @@ go.uber.org/zap/internal/color go.uber.org/zap/internal/exit go.uber.org/zap/zapcore go.uber.org/zap/zapgrpc -# golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 => golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 +# golang.org/x/crypto v0.1.0 => golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 ## explicit; go 1.17 golang.org/x/crypto/blake2b golang.org/x/crypto/cryptobyte @@ -1242,7 +1258,7 @@ golang.org/x/crypto/pkcs12/internal/rc2 golang.org/x/crypto/salsa20/salsa golang.org/x/crypto/scrypt golang.org/x/crypto/sha3 -# golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 +# golang.org/x/mod v0.6.0 ## explicit; go 1.17 golang.org/x/mod/internal/lazyregexp golang.org/x/mod/modfile @@ -1319,7 +1335,7 @@ golang.org/x/text/unicode/norm # golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 ## explicit golang.org/x/time/rate -# golang.org/x/tools v0.1.12 +# golang.org/x/tools v0.2.0 ## explicit; go 1.18 golang.org/x/tools/go/ast/astutil golang.org/x/tools/go/gcexportdata diff --git a/version/version.go b/version/version.go index 628b5ed4277..baf3677838d 100644 --- a/version/version.go +++ b/version/version.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 659925c480db0ead3ccaaebec2e2fe30c1cfd1df Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Fri, 30 Dec 2022 19:30:10 +0100 Subject: [PATCH 02/41] update missing changes Signed-off-by: Jorge Turrado --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index f7d67e69cfe..3c6de54adf3 100644 --- a/go.mod +++ b/go.mod @@ -80,6 +80,7 @@ require ( k8s.io/klog/v2 v2.80.2-0.20221028030830-9ae4992afb54 k8s.io/kube-openapi v0.0.0-20221123214604-86e75ddd809a k8s.io/metrics v0.25.4 + k8s.io/utils v0.0.0-20221108210102-8e77b1f39fe2 knative.dev/pkg v0.0.0-20221123154742-05b694ec4d3a sigs.k8s.io/controller-runtime v0.13.1 sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20221201045826-d9912251cd81 @@ -299,7 +300,6 @@ require ( k8s.io/apiserver v0.25.4 // indirect k8s.io/component-base v0.25.4 // indirect k8s.io/gengo v0.0.0-20221011193443-fad74ee6edd9 // indirect - k8s.io/utils v0.0.0-20221108210102-8e77b1f39fe2 // indirect nhooyr.io/websocket v1.8.7 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.33 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect From b3f83126d43d2bcd58d12cadb49d66e18629d395 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Sat, 31 Dec 2022 18:33:49 +0100 Subject: [PATCH 03/41] add prometheus metrics to webhooks Signed-off-by: Jorge Turrado --- BUILD.md | 2 +- apis/keda/v1alpha1/scaledobject_webhook.go | 19 ++++-- cmd/webhooks/main.go | 4 +- config/webhooks/secret.yaml | 2 +- config/webhooks/service.yaml | 9 ++- config/webhooks/validation_webhooks.yaml | 2 +- config/webhooks/webhooks.yaml | 5 +- .../webhook/webhook_prommetrics.go | 64 +++++++++++++++++++ tests/helper/helper.go | 20 ++++++ .../prometheus_metrics_test.go | 59 +++++++++++++++++ .../scaled_object_validation_test.go | 32 ++-------- 11 files changed, 176 insertions(+), 42 deletions(-) create mode 100644 pkg/prommetrics/webhook/webhook_prommetrics.go diff --git a/BUILD.md b/BUILD.md index 8abbe18ae41..c733972f612 100644 --- a/BUILD.md +++ b/BUILD.md @@ -260,7 +260,7 @@ Follow these instructions if you want to debug the KEDA webhook using VS Code. - v1 clientConfig: service: - name: keda-webhook-service + name: keda-webhooks namespace: keda path: /validate-keda-sh-v1alpha1-scaledobject ``` diff --git a/apis/keda/v1alpha1/scaledobject_webhook.go b/apis/keda/v1alpha1/scaledobject_webhook.go index eb8469024a8..4db0495a16c 100644 --- a/apis/keda/v1alpha1/scaledobject_webhook.go +++ b/apis/keda/v1alpha1/scaledobject_webhook.go @@ -27,6 +27,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" + + prommetrics "github.com/kedacore/keda/v2/pkg/prommetrics/webhook" ) var scaledobjectlog = logf.Log.WithName("scaledobject-validation-webhook") @@ -55,24 +57,25 @@ var _ webhook.Validator = &ScaledObject{} func (so *ScaledObject) ValidateCreate() error { val, _ := json.MarshalIndent(so, "", " ") scaledobjectlog.V(1).Info(fmt.Sprintf("validating scaledobject creation for %s", string(val))) - return validateWorkload(so) + return validateWorkload(so, "create") } func (so *ScaledObject) ValidateUpdate(old runtime.Object) error { val, _ := json.MarshalIndent(so, "", " ") scaledobjectlog.V(1).Info(fmt.Sprintf("validating scaledobject update for %s", string(val))) - return validateWorkload(so) + return validateWorkload(so, "update") } -func validateWorkload(so *ScaledObject) error { - err := verifyScaledObjects(so) +func validateWorkload(so *ScaledObject, action string) error { + prommetrics.RecordScaledObjectValidatingTotal(so.Namespace, action) + err := verifyScaledObjects(so, action) if err != nil { return err } - return verifyHpas(so) + return verifyHpas(so, action) } -func verifyHpas(incomingSo *ScaledObject) error { +func verifyHpas(incomingSo *ScaledObject, action string) error { hpaList := &autoscalingv2.HorizontalPodAutoscalerList{} opt := &client.ListOptions{ Namespace: incomingSo.Namespace, @@ -129,6 +132,7 @@ func verifyHpas(incomingSo *ScaledObject) error { } scaledobjectlog.Error(err, "validation error") + prommetrics.RecordScaledObjectValidatingErrors(incomingSo.Namespace, action, "hpa") return err } } @@ -137,7 +141,7 @@ func verifyHpas(incomingSo *ScaledObject) error { return nil } -func verifyScaledObjects(incomingSo *ScaledObject) error { +func verifyScaledObjects(incomingSo *ScaledObject, action string) error { soList := &ScaledObjectList{} opt := &client.ListOptions{ Namespace: incomingSo.Namespace, @@ -179,6 +183,7 @@ func verifyScaledObjects(incomingSo *ScaledObject) error { soTarget.Name == incomingTarget.Name { err = fmt.Errorf("the workload '%s' of type '%s/%s' is already managed by the ScaledObject '%s'", soTarget.Name, sotargetAPI, soTargetKind, so.Name) scaledobjectlog.Error(err, "validation error") + prommetrics.RecordScaledObjectValidatingErrors(incomingSo.Namespace, action, "scaled_object") return err } } diff --git a/cmd/webhooks/main.go b/cmd/webhooks/main.go index 8b6039c7b7d..af3bb0f46da 100644 --- a/cmd/webhooks/main.go +++ b/cmd/webhooks/main.go @@ -51,8 +51,8 @@ var webhooks = []rotator.WebhookInfo{ var ( scheme = apimachineryruntime.NewScheme() setupLog = ctrl.Log.WithName("setup") - secretName = "kedaorg-webhook-secret" // #nosec - serviceName = "keda-webhook-service" + secretName = "kedaorg-webhooks-secret" // #nosec + serviceName = "keda-webhooks" caName = "kedaorg-ca" caOrganization = "kedaorg" // DNSName is ..svc diff --git a/config/webhooks/secret.yaml b/config/webhooks/secret.yaml index 619ba6f6d99..f22beac6991 100644 --- a/config/webhooks/secret.yaml +++ b/config/webhooks/secret.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: Secret metadata: - name: kedaorg-webhook-secret + name: kedaorg-webhooks-secret namespace: keda labels: app: keda-webhooks diff --git a/config/webhooks/service.yaml b/config/webhooks/service.yaml index c7e4baf4661..04b401a3d16 100644 --- a/config/webhooks/service.yaml +++ b/config/webhooks/service.yaml @@ -9,12 +9,17 @@ metadata: app.kubernetes.io/created-by: keda app.kubernetes.io/part-of: keda app.kubernetes.io/managed-by: kustomize - name: keda-webhook-service + name: keda-webhooks namespace: keda spec: ports: - - port: 443 + - name: http + port: 443 protocol: TCP targetPort: 9443 + - name: metrics + port: 8080 + protocol: TCP + targetPort: 8080 selector: app: keda-webhooks diff --git a/config/webhooks/validation_webhooks.yaml b/config/webhooks/validation_webhooks.yaml index 31300bddc5f..b73ee382561 100644 --- a/config/webhooks/validation_webhooks.yaml +++ b/config/webhooks/validation_webhooks.yaml @@ -14,7 +14,7 @@ webhooks: - v1 clientConfig: service: - name: keda-webhook-service + name: keda-webhooks namespace: keda path: /validate-keda-sh-v1alpha1-scaledobject failurePolicy: Ignore diff --git a/config/webhooks/webhooks.yaml b/config/webhooks/webhooks.yaml index 0c28c51cc14..ee802a21911 100644 --- a/config/webhooks/webhooks.yaml +++ b/config/webhooks/webhooks.yaml @@ -56,6 +56,9 @@ spec: - containerPort: 9443 name: http protocol: TCP + - containerPort: 8080 + name: metrics + protocol: TCP env: - name: POD_NAMESPACE valueFrom: @@ -85,4 +88,4 @@ spec: - name: cert secret: defaultMode: 420 - secretName: kedaorg-webhook-secret + secretName: kedaorg-webhooks-secret diff --git a/pkg/prommetrics/webhook/webhook_prommetrics.go b/pkg/prommetrics/webhook/webhook_prommetrics.go new file mode 100644 index 00000000000..8f7a10012a9 --- /dev/null +++ b/pkg/prommetrics/webhook/webhook_prommetrics.go @@ -0,0 +1,64 @@ +/* +Copyright 2023 The KEDA Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import ( + "github.com/prometheus/client_golang/prometheus" + "sigs.k8s.io/controller-runtime/pkg/metrics" +) + +const ( + DefaultPromMetricsNamespace = "keda" +) + +var ( + scaledObjectValidatingTotal = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: DefaultPromMetricsNamespace, + Subsystem: "webhook", + Name: "scaled_object_validating_total", + Help: "Total number of scaled object validations", + }, + []string{"namespace", "action"}, + ) + scaledObjectValidatingErrors = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: DefaultPromMetricsNamespace, + Subsystem: "webhook", + Name: "scaled_object_validating_errors", + Help: "Total number of scaled object validating errors", + }, + []string{"namespace", "action", "reason"}, + ) +) + +func init() { + metrics.Registry.MustRegister(scaledObjectValidatingTotal) + metrics.Registry.MustRegister(scaledObjectValidatingErrors) +} + +// RecordScaledObjectValidatingTotal counts the number of ScaledObject validations +func RecordScaledObjectValidatingTotal(namespace, action string) { + labels := prometheus.Labels{"namespace": namespace, "action": action} + scaledObjectValidatingTotal.With(labels).Inc() +} + +// RecordScaledObjectValidatingErrors counts the number of ScaledObject validating errors +func RecordScaledObjectValidatingErrors(namespace, action, reason string) { + labels := prometheus.Labels{"namespace": namespace, "action": action, "reason": reason} + scaledObjectValidatingErrors.With(labels).Inc() +} diff --git a/tests/helper/helper.go b/tests/helper/helper.go index 630a4e570e3..cf0bf8ca7fb 100644 --- a/tests/helper/helper.go +++ b/tests/helper/helper.go @@ -477,6 +477,26 @@ func KubectlApplyWithTemplate(t *testing.T, data interface{}, templateName strin assert.NoErrorf(t, err, "cannot close temp file - %s", err) } +func KubectlApplyWithErrors(t *testing.T, data interface{}, templateName string, config string) error { + t.Logf("Applying template: %s", templateName) + + tmpl, err := template.New("kubernetes resource template").Parse(config) + assert.NoErrorf(t, err, "cannot parse template - %s", err) + + tempFile, err := os.CreateTemp("", templateName) + assert.NoErrorf(t, err, "cannot create temp file - %s", err) + if err != nil { + defer tempFile.Close() + defer os.Remove(tempFile.Name()) + } + + err = tmpl.Execute(tempFile, data) + assert.NoErrorf(t, err, "cannot insert data into template - %s", err) + + _, err = ExecuteCommand(fmt.Sprintf("kubectl apply -f %s", tempFile.Name())) + return err +} + // Apply templates in order of slice func KubectlApplyMultipleWithTemplate(t *testing.T, data interface{}, templates []Template) { for _, tmpl := range templates { diff --git a/tests/internals/prometheus_metrics/prometheus_metrics_test.go b/tests/internals/prometheus_metrics/prometheus_metrics_test.go index 24e142e7658..1be309535cc 100644 --- a/tests/internals/prometheus_metrics/prometheus_metrics_test.go +++ b/tests/internals/prometheus_metrics/prometheus_metrics_test.go @@ -31,6 +31,7 @@ var ( cronScaledJobName = fmt.Sprintf("%s-cron-sj", testName) clientName = fmt.Sprintf("%s-client", testName) kedaOperatorPrometheusURL = "http://keda-operator.keda.svc.cluster.local:8080/metrics" + kedaWebhookPrometheusURL = "http://keda-webhooks.keda.svc.cluster.local:8080/metrics" ) type templateData struct { @@ -227,6 +228,7 @@ func TestScaler(t *testing.T) { testScalerMetricValue(t) testMetricsServerScalerMetricValue(t) testOperatorMetrics(t, kc, data) + testWebhookMetrics(t, kc, data) // cleanup DeleteKubernetesResources(t, kc, testNamespace, data, templates) @@ -321,6 +323,16 @@ func testOperatorMetrics(t *testing.T, kc *kubernetes.Clientset, data templateDa testOperatorMetricValues(t, kc) } +func testWebhookMetrics(t *testing.T, kc *kubernetes.Clientset, data templateData) { + t.Log("--- testing webhook metrics ---") + + data.ScaledObjectName = "other-so" + err := KubectlApplyWithErrors(t, data, "scaledObjectTemplate", scaledObjectTemplate) + assert.Errorf(t, err, "can deploy the scaledObject - %s", err) + testWebhookMetricValues(t, kc) + data.ScaledObjectName = scaledObjectName +} + func getOperatorMetricsManually(t *testing.T, kc *kubernetes.Clientset) (map[string]int, map[string]map[string]int) { kedaKc := GetKedaKubernetesClient(t) @@ -381,6 +393,11 @@ func getOperatorMetricsManually(t *testing.T, kc *kubernetes.Clientset) (map[str return triggerTotals, crTotals } +func testWebhookMetricValues(t *testing.T, kc *kubernetes.Clientset) { + families := fetchAndParsePrometheusMetrics(t, fmt.Sprintf("curl --insecure %s", kedaWebhookPrometheusURL)) + checkWebhookValues(t, families) +} + func testOperatorMetricValues(t *testing.T, kc *kubernetes.Clientset) { families := fetchAndParsePrometheusMetrics(t, fmt.Sprintf("curl --insecure %s", kedaOperatorPrometheusURL)) expectedTriggerTotals, expectedCrTotals := getOperatorMetricsManually(t, kc) @@ -446,3 +463,45 @@ func checkCRTotalValues(t *testing.T, families map[string]*promModel.MetricFamil expectedMetricValue, metricValue, crType, namespace) } } + +func checkWebhookValues(t *testing.T, families map[string]*promModel.MetricFamily) { + t.Log("--- testing webhook metrics ---") + + family, ok := families["keda_webhook_scaled_object_validating_errors"] + if !ok { + t.Errorf("metric keda_webhook_scaled_object_validating_errors not available") + return + } + + metricValue := 0.0 + metrics := family.GetMetric() + for _, metric := range metrics { + labels := metric.GetLabel() + for _, label := range labels { + if *label.Name == "namespace" && *label.Value != testNamespace { + continue + } + } + metricValue = *metric.Counter.Value + } + assert.GreaterOrEqual(t, 1.0, metricValue, "keda_webhook_scaled_object_validating_errors has to be greater than 0") + + family, ok = families["keda_webhook_scaled_object_validating_total"] + if !ok { + t.Errorf("metric keda_webhook_scaled_object_validating_total not available") + return + } + + metricValue = 0.0 + metrics = family.GetMetric() + for _, metric := range metrics { + labels := metric.GetLabel() + for _, label := range labels { + if *label.Name == "namespace" && *label.Value != testNamespace { + continue + } + } + metricValue = *metric.Counter.Value + } + assert.GreaterOrEqual(t, 1.0, metricValue, "keda_webhook_scaled_object_validating_total has to be greater than 0") +} diff --git a/tests/internals/scaled_object_validation/scaled_object_validation_test.go b/tests/internals/scaled_object_validation/scaled_object_validation_test.go index 9fb88d14ba4..963c2d23682 100644 --- a/tests/internals/scaled_object_validation/scaled_object_validation_test.go +++ b/tests/internals/scaled_object_validation/scaled_object_validation_test.go @@ -5,9 +5,7 @@ package cache_metrics_test import ( "fmt" - "os" "testing" - "text/template" "github.com/stretchr/testify/assert" @@ -120,7 +118,7 @@ func testWithNotScaledWorkload(t *testing.T, data templateData) { t.Log("--- unscaled workload ---") data.ScaledObjectName = scaledObject1Name - err := kubectlApplyWithErrors(t, data, "scaledObjectTemplate", scaledObjectTemplate) + err := KubectlApplyWithErrors(t, data, "scaledObjectTemplate", scaledObjectTemplate) assert.NoErrorf(t, err, "cannot deploy the scaledObject - %s", err) KubectlDeleteWithTemplate(t, data, "scaledObjectTemplate", scaledObjectTemplate) @@ -130,11 +128,11 @@ func testScaledWorkloadByOtherScaledObject(t *testing.T, data templateData) { t.Log("--- already scaled workload by other scaledobject---") data.ScaledObjectName = scaledObject1Name - err := kubectlApplyWithErrors(t, data, "scaledObjectTemplate", scaledObjectTemplate) + err := KubectlApplyWithErrors(t, data, "scaledObjectTemplate", scaledObjectTemplate) assert.NoErrorf(t, err, "cannot deploy the scaledObject - %s", err) data.ScaledObjectName = scaledObject2Name - err = kubectlApplyWithErrors(t, data, "scaledObjectTemplate", scaledObjectTemplate) + err = KubectlApplyWithErrors(t, data, "scaledObjectTemplate", scaledObjectTemplate) assert.Errorf(t, err, "can deploy the scaledObject - %s", err) assert.Contains(t, err.Error(), fmt.Sprintf("the workload '%s' of type 'apps/v1/Deployment' is already managed by the ScaledObject '%s", deploymentName, scaledObject1Name)) @@ -146,37 +144,17 @@ func testScaledWorkloadByOtherHpa(t *testing.T, data templateData) { t.Log("--- already scaled workload by other hpa---") data.HpaName = hpaName - err := kubectlApplyWithErrors(t, data, "hpaTemplate", hpaTemplate) + err := KubectlApplyWithErrors(t, data, "hpaTemplate", hpaTemplate) assert.NoErrorf(t, err, "cannot deploy the hpa - %s", err) data.ScaledObjectName = scaledObject1Name - err = kubectlApplyWithErrors(t, data, "scaledObjectTemplate", scaledObjectTemplate) + err = KubectlApplyWithErrors(t, data, "scaledObjectTemplate", scaledObjectTemplate) assert.Errorf(t, err, "can deploy the scaledObject - %s", err) assert.Contains(t, err.Error(), fmt.Sprintf("the workload '%s' of type 'apps/v1/Deployment' is already managed by the hpa '%s", deploymentName, hpaName)) KubectlDeleteWithTemplate(t, data, "hpaTemplate", hpaTemplate) } -func kubectlApplyWithErrors(t *testing.T, data interface{}, templateName string, config string) error { - t.Logf("Applying template: %s", templateName) - - tmpl, err := template.New("kubernetes resource template").Parse(config) - assert.NoErrorf(t, err, "cannot parse template - %s", err) - - tempFile, err := os.CreateTemp("", templateName) - assert.NoErrorf(t, err, "cannot create temp file - %s", err) - if err != nil { - defer tempFile.Close() - defer os.Remove(tempFile.Name()) - } - - err = tmpl.Execute(tempFile, data) - assert.NoErrorf(t, err, "cannot insert data into template - %s", err) - - _, err = ExecuteCommand(fmt.Sprintf("kubectl apply -f %s", tempFile.Name())) - return err -} - func getTemplateData() (templateData, []Template) { return templateData{ TestNamespace: testNamespace, From d6e5cf749ea8079e26f97c0f8c5ea20e01fd08ec Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Sat, 31 Dec 2022 18:39:56 +0100 Subject: [PATCH 04/41] fix test Signed-off-by: Jorge Turrado --- tests/internals/prometheus_metrics/prometheus_metrics_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/internals/prometheus_metrics/prometheus_metrics_test.go b/tests/internals/prometheus_metrics/prometheus_metrics_test.go index 1be309535cc..4beab9a353f 100644 --- a/tests/internals/prometheus_metrics/prometheus_metrics_test.go +++ b/tests/internals/prometheus_metrics/prometheus_metrics_test.go @@ -484,7 +484,7 @@ func checkWebhookValues(t *testing.T, families map[string]*promModel.MetricFamil } metricValue = *metric.Counter.Value } - assert.GreaterOrEqual(t, 1.0, metricValue, "keda_webhook_scaled_object_validating_errors has to be greater than 0") + assert.GreaterOrEqual(t, metricValue, 1.0, "keda_webhook_scaled_object_validating_errors has to be greater than 0") family, ok = families["keda_webhook_scaled_object_validating_total"] if !ok { @@ -503,5 +503,5 @@ func checkWebhookValues(t *testing.T, families map[string]*promModel.MetricFamil } metricValue = *metric.Counter.Value } - assert.GreaterOrEqual(t, 1.0, metricValue, "keda_webhook_scaled_object_validating_total has to be greater than 0") + assert.GreaterOrEqual(t, metricValue, 1.0, "keda_webhook_scaled_object_validating_total has to be greater than 0") } From 061ddf2eb4bd66bc47138f260ad22e7305eb60c3 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Sun, 1 Jan 2023 22:27:41 +0100 Subject: [PATCH 05/41] fix styles Signed-off-by: Jorge Turrado --- .../prometheus_metrics/prometheus_metrics_test.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/internals/prometheus_metrics/prometheus_metrics_test.go b/tests/internals/prometheus_metrics/prometheus_metrics_test.go index 4beab9a353f..740ca6d3494 100644 --- a/tests/internals/prometheus_metrics/prometheus_metrics_test.go +++ b/tests/internals/prometheus_metrics/prometheus_metrics_test.go @@ -32,6 +32,7 @@ var ( clientName = fmt.Sprintf("%s-client", testName) kedaOperatorPrometheusURL = "http://keda-operator.keda.svc.cluster.local:8080/metrics" kedaWebhookPrometheusURL = "http://keda-webhooks.keda.svc.cluster.local:8080/metrics" + namespaceString = "namespace" ) type templateData struct { @@ -211,7 +212,7 @@ spec: ` ) -func TestScaler(t *testing.T) { +func TestPrometheusMetrics(t *testing.T) { // setup t.Log("--- setting up ---") @@ -329,7 +330,7 @@ func testWebhookMetrics(t *testing.T, kc *kubernetes.Clientset, data templateDat data.ScaledObjectName = "other-so" err := KubectlApplyWithErrors(t, data, "scaledObjectTemplate", scaledObjectTemplate) assert.Errorf(t, err, "can deploy the scaledObject - %s", err) - testWebhookMetricValues(t, kc) + testWebhookMetricValues(t) data.ScaledObjectName = scaledObjectName } @@ -393,7 +394,7 @@ func getOperatorMetricsManually(t *testing.T, kc *kubernetes.Clientset) (map[str return triggerTotals, crTotals } -func testWebhookMetricValues(t *testing.T, kc *kubernetes.Clientset) { +func testWebhookMetricValues(t *testing.T) { families := fetchAndParsePrometheusMetrics(t, fmt.Sprintf("curl --insecure %s", kedaWebhookPrometheusURL)) checkWebhookValues(t, families) } @@ -451,7 +452,7 @@ func checkCRTotalValues(t *testing.T, families map[string]*promModel.MetricFamil for _, label := range labels { if *label.Name == "type" { crType = *label.Value - } else if *label.Name == "namespace" { + } else if *label.Name == namespaceString { namespace = *label.Value } } @@ -478,7 +479,7 @@ func checkWebhookValues(t *testing.T, families map[string]*promModel.MetricFamil for _, metric := range metrics { labels := metric.GetLabel() for _, label := range labels { - if *label.Name == "namespace" && *label.Value != testNamespace { + if *label.Name == namespaceString && *label.Value != testNamespace { continue } } @@ -497,7 +498,7 @@ func checkWebhookValues(t *testing.T, families map[string]*promModel.MetricFamil for _, metric := range metrics { labels := metric.GetLabel() for _, label := range labels { - if *label.Name == "namespace" && *label.Value != testNamespace { + if *label.Name == namespaceString && *label.Value != testNamespace { continue } } From 39897674811117993577c1c7e2460d04567080cb Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Mon, 2 Jan 2023 00:49:02 +0100 Subject: [PATCH 06/41] remove unused parameter Signed-off-by: Jorge Turrado --- tests/internals/prometheus_metrics/prometheus_metrics_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/internals/prometheus_metrics/prometheus_metrics_test.go b/tests/internals/prometheus_metrics/prometheus_metrics_test.go index 740ca6d3494..af6105086d1 100644 --- a/tests/internals/prometheus_metrics/prometheus_metrics_test.go +++ b/tests/internals/prometheus_metrics/prometheus_metrics_test.go @@ -229,7 +229,7 @@ func TestPrometheusMetrics(t *testing.T) { testScalerMetricValue(t) testMetricsServerScalerMetricValue(t) testOperatorMetrics(t, kc, data) - testWebhookMetrics(t, kc, data) + testWebhookMetrics(t, data) // cleanup DeleteKubernetesResources(t, kc, testNamespace, data, templates) @@ -324,7 +324,7 @@ func testOperatorMetrics(t *testing.T, kc *kubernetes.Clientset, data templateDa testOperatorMetricValues(t, kc) } -func testWebhookMetrics(t *testing.T, kc *kubernetes.Clientset, data templateData) { +func testWebhookMetrics(t *testing.T, data templateData) { t.Log("--- testing webhook metrics ---") data.ScaledObjectName = "other-so" From c4a8359709be7e7ecfcb725202dbf71a93cb7a0d Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Mon, 2 Jan 2023 01:41:54 +0100 Subject: [PATCH 07/41] use k8s 1.26 for smoke test Signed-off-by: Jorge Turrado --- .github/workflows/template-arm64-smoke-tests.yml | 4 ++-- .github/workflows/template-versions-smoke-tests.yml | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/template-arm64-smoke-tests.yml b/.github/workflows/template-arm64-smoke-tests.yml index 18e2a383a1b..bd2076bb3dc 100644 --- a/.github/workflows/template-arm64-smoke-tests.yml +++ b/.github/workflows/template-arm64-smoke-tests.yml @@ -10,5 +10,5 @@ jobs: uses: kedacore/keda/.github/workflows/template-smoke-tests.yml@main with: runs-on: ARM64 - kubernetesVersion: v1.25 - kindImage: kindest/node:v1.25.0@sha256:428aaa17ec82ccde0131cb2d1ca6547d13cf5fdabcc0bbecf749baa935387cbf + kubernetesVersion: v1.26 + kindImage: kindest/node:v1.26.0@sha256:691e24bd2417609db7e589e1a479b902d2e209892a10ce375fab60a8407c7352 diff --git a/.github/workflows/template-versions-smoke-tests.yml b/.github/workflows/template-versions-smoke-tests.yml index f5f526b8f15..d6521df72fa 100644 --- a/.github/workflows/template-versions-smoke-tests.yml +++ b/.github/workflows/template-versions-smoke-tests.yml @@ -9,8 +9,10 @@ jobs: strategy: fail-fast: false matrix: - kubernetesVersion: [v1.25, v1.24, v1.23] - include: + kubernetesVersion: [v1.26, v1.25, v1.24, v1.23] + include: + - kubernetesVersion: v1.26 + kindImage: kindest/node:v1.26.0@sha256:691e24bd2417609db7e589e1a479b902d2e209892a10ce375fab60a8407c7352 - kubernetesVersion: v1.25 kindImage: kindest/node:v1.25.0@sha256:428aaa17ec82ccde0131cb2d1ca6547d13cf5fdabcc0bbecf749baa935387cbf - kubernetesVersion: v1.24 From bfcd999359ce543f9d0f94df706c2babfbc7f421 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Mon, 2 Jan 2023 02:13:19 +0100 Subject: [PATCH 08/41] fix style Signed-off-by: Jorge Turrado --- .github/workflows/template-versions-smoke-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/template-versions-smoke-tests.yml b/.github/workflows/template-versions-smoke-tests.yml index d6521df72fa..8eab93aba4d 100644 --- a/.github/workflows/template-versions-smoke-tests.yml +++ b/.github/workflows/template-versions-smoke-tests.yml @@ -10,7 +10,7 @@ jobs: fail-fast: false matrix: kubernetesVersion: [v1.26, v1.25, v1.24, v1.23] - include: + include: - kubernetesVersion: v1.26 kindImage: kindest/node:v1.26.0@sha256:691e24bd2417609db7e589e1a479b902d2e209892a10ce375fab60a8407c7352 - kubernetesVersion: v1.25 From 15f221777e04500418007105730bdc7f2ac6d39a Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Mon, 2 Jan 2023 02:17:36 +0100 Subject: [PATCH 09/41] add webhook logs to e2e output Signed-off-by: Jorge Turrado --- tests/run-all.sh | 5 +++++ tests/run-smoke-tests.sh | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/tests/run-all.sh b/tests/run-all.sh index 132f93414c2..7f84e502d0b 100755 --- a/tests/run-all.sh +++ b/tests/run-all.sh @@ -150,6 +150,11 @@ function print_logs { kubectl get pods --no-headers -n keda | awk '{print $1}' | grep keda-metrics-apiserver | xargs kubectl -n keda logs printf "##############################################\n" printf "##############################################\n" + + echo ">>> KEDA Webhooks log <<<" + kubectl get pods --no-headers -n keda | awk '{print $1}' | grep keda-webhooks| xargs kubectl -n keda logs + printf "##############################################\n" + printf "##############################################\n" } function print_chaos_logs { diff --git a/tests/run-smoke-tests.sh b/tests/run-smoke-tests.sh index e9e8bea3498..118f2f54f9c 100755 --- a/tests/run-smoke-tests.sh +++ b/tests/run-smoke-tests.sh @@ -131,6 +131,11 @@ function print_logs { kubectl get pods --no-headers -n keda | awk '{print $1}' | grep keda-metrics-apiserver | xargs kubectl -n keda logs printf "\n\n##############################################\n" printf "##############################################\n\n" + + echo ">>> KEDA Webhooks log <<<" + kubectl get pods --no-headers -n keda | awk '{print $1}' | grep keda-webhooks| xargs kubectl -n keda logs + printf "##############################################\n" + printf "##############################################\n" } function run_cleanup { From caabea111e858ea317c905a8ad8738755664d7a7 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Mon, 2 Jan 2023 13:45:44 +0100 Subject: [PATCH 10/41] apply feedback Signed-off-by: Jorge Turrado --- apis/keda/v1alpha1/scaledobject_webhook.go | 11 ++---- apis/keda/v1alpha1/withtriggers_types.go | 2 +- cmd/adapter/main.go | 2 +- cmd/operator/main.go | 2 +- controllers/keda/hpa.go | 26 +++++++------- controllers/keda/scaledobject_controller.go | 40 ++++++++++----------- pkg/provider/provider.go | 8 ++--- pkg/scaling/resolver/scale_resolvers.go | 2 +- 8 files changed, 43 insertions(+), 50 deletions(-) diff --git a/apis/keda/v1alpha1/scaledobject_webhook.go b/apis/keda/v1alpha1/scaledobject_webhook.go index 4db0495a16c..f93731bb31e 100644 --- a/apis/keda/v1alpha1/scaledobject_webhook.go +++ b/apis/keda/v1alpha1/scaledobject_webhook.go @@ -113,24 +113,17 @@ func verifyHpas(incomingSo *ScaledObject, action string) error { hpaTargetKind == incomingSoTargetKind && hpaTarget.Name == incomingSoTarget.Name { owned := false - ownerName := "" for _, owner := range hpa.OwnerReferences { if owner.Kind == incomingSo.Kind { - ownerName = owner.Name if owner.Name == incomingSo.Name { owned = true + break } } } if !owned { - var err error - if len(hpa.OwnerReferences) == 0 { - err = fmt.Errorf("the workload '%s' of type '%s/%s' is already managed by the hpa '%s'", incomingSoTarget.Name, incomingSotargetAPI, incomingSoTargetKind, hpa.Name) - } else { - err = fmt.Errorf("the workload '%s' of type '%s/%s' is already managed by the ScaledObject '%s'", incomingSoTarget.Name, incomingSotargetAPI, incomingSoTargetKind, ownerName) - } - + err = fmt.Errorf("the workload '%s' of type '%s/%s' is already managed by the hpa '%s'", incomingSoTarget.Name, incomingSotargetAPI, incomingSoTargetKind, hpa.Name) scaledobjectlog.Error(err, "validation error") prommetrics.RecordScaledObjectValidatingErrors(incomingSo.Namespace, action, "hpa") return err diff --git a/apis/keda/v1alpha1/withtriggers_types.go b/apis/keda/v1alpha1/withtriggers_types.go index 32df0e91c23..5d280499498 100644 --- a/apis/keda/v1alpha1/withtriggers_types.go +++ b/apis/keda/v1alpha1/withtriggers_types.go @@ -1,5 +1,5 @@ /* -Copyright 2023 The KEDA Authors +Copyright 2021 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/cmd/adapter/main.go b/cmd/adapter/main.go index 360b970084a..cd7e428a769 100644 --- a/cmd/adapter/main.go +++ b/cmd/adapter/main.go @@ -1,5 +1,5 @@ /* -Copyright 2023 The KEDA Authors +Copyright 2021 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/cmd/operator/main.go b/cmd/operator/main.go index b78200adce5..d4e31ee389f 100644 --- a/cmd/operator/main.go +++ b/cmd/operator/main.go @@ -1,5 +1,5 @@ /* -Copyright 2023 The KEDA Authors +Copyright 2021 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/controllers/keda/hpa.go b/controllers/keda/hpa.go index 46a254ccd81..f76d5a79e8c 100644 --- a/controllers/keda/hpa.go +++ b/controllers/keda/hpa.go @@ -29,7 +29,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "github.com/kedacore/keda/v2/apis/keda/v1alpha1" + kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1" kedacontrollerutil "github.com/kedacore/keda/v2/controllers/keda/util" "github.com/kedacore/keda/v2/pkg/scaling/executor" version "github.com/kedacore/keda/v2/version" @@ -41,7 +41,7 @@ const ( ) // createAndDeployNewHPA creates and deploy HPA in the cluster for specified ScaledObject -func (r *ScaledObjectReconciler) createAndDeployNewHPA(ctx context.Context, logger logr.Logger, scaledObject *v1alpha1.ScaledObject, gvkr *v1alpha1.GroupVersionKindResource) error { +func (r *ScaledObjectReconciler) createAndDeployNewHPA(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject, gvkr *kedav1alpha1.GroupVersionKindResource) error { hpaName := getHPAName(scaledObject) logger.Info("Creating a new HPA", "HPA.Namespace", scaledObject.Namespace, "HPA.Name", hpaName) hpa, err := r.newHPAForScaledObject(ctx, logger, scaledObject, gvkr) @@ -70,7 +70,7 @@ func (r *ScaledObjectReconciler) createAndDeployNewHPA(ctx context.Context, logg } // newHPAForScaledObject returns HPA as it is specified in ScaledObject -func (r *ScaledObjectReconciler) newHPAForScaledObject(ctx context.Context, logger logr.Logger, scaledObject *v1alpha1.ScaledObject, gvkr *v1alpha1.GroupVersionKindResource) (*autoscalingv2.HorizontalPodAutoscaler, error) { +func (r *ScaledObjectReconciler) newHPAForScaledObject(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject, gvkr *kedav1alpha1.GroupVersionKindResource) (*autoscalingv2.HorizontalPodAutoscaler, error) { scaledObjectMetricSpecs, err := r.getScaledObjectMetricSpecs(ctx, logger, scaledObject) if err != nil { return nil, err @@ -148,7 +148,7 @@ func (r *ScaledObjectReconciler) newHPAForScaledObject(ctx context.Context, logg } // updateHPAIfNeeded checks whether update of HPA is needed -func (r *ScaledObjectReconciler) updateHPAIfNeeded(ctx context.Context, logger logr.Logger, scaledObject *v1alpha1.ScaledObject, foundHpa *autoscalingv2.HorizontalPodAutoscaler, gvkr *v1alpha1.GroupVersionKindResource) error { +func (r *ScaledObjectReconciler) updateHPAIfNeeded(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject, foundHpa *autoscalingv2.HorizontalPodAutoscaler, gvkr *kedav1alpha1.GroupVersionKindResource) error { hpa, err := r.newHPAForScaledObject(ctx, logger, scaledObject, gvkr) if err != nil { logger.Error(err, "Failed to create new HPA resource", "HPA.Namespace", scaledObject.Namespace, "HPA.Name", getHPAName(scaledObject)) @@ -181,7 +181,7 @@ func (r *ScaledObjectReconciler) updateHPAIfNeeded(ctx context.Context, logger l } // deleteAndCreateHpa delete old HPA and create new one -func (r *ScaledObjectReconciler) renameHPA(ctx context.Context, logger logr.Logger, scaledObject *v1alpha1.ScaledObject, foundHpa *autoscalingv2.HorizontalPodAutoscaler, gvkr *v1alpha1.GroupVersionKindResource) error { +func (r *ScaledObjectReconciler) renameHPA(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject, foundHpa *autoscalingv2.HorizontalPodAutoscaler, gvkr *kedav1alpha1.GroupVersionKindResource) error { logger.Info("Deleting old HPA", "HPA.Namespace", scaledObject.Namespace, "HPA.Name", foundHpa.Name) if err := r.Client.Delete(ctx, foundHpa); err != nil { logger.Error(err, "Failed to delete old HPA", "HPA.Namespace", foundHpa.Namespace, "HPA.Name", foundHpa.Name) @@ -192,7 +192,7 @@ func (r *ScaledObjectReconciler) renameHPA(ctx context.Context, logger logr.Logg } // getScaledObjectMetricSpecs returns MetricSpec for HPA, generater from Triggers defitinion in ScaledObject -func (r *ScaledObjectReconciler) getScaledObjectMetricSpecs(ctx context.Context, logger logr.Logger, scaledObject *v1alpha1.ScaledObject) ([]autoscalingv2.MetricSpec, error) { +func (r *ScaledObjectReconciler) getScaledObjectMetricSpecs(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject) ([]autoscalingv2.MetricSpec, error) { var scaledObjectMetricSpecs []autoscalingv2.MetricSpec var externalMetricNames []string var resourceMetricNames []string @@ -218,7 +218,7 @@ func (r *ScaledObjectReconciler) getScaledObjectMetricSpecs(ctx context.Context, // add the scaledobject.keda.sh/name label. This is how the MetricsAdapter will know which scaledobject a metric is for when the HPA queries it. metricSpec.External.Metric.Selector = &metav1.LabelSelector{MatchLabels: make(map[string]string)} - metricSpec.External.Metric.Selector.MatchLabels[v1alpha1.ScaledObjectOwnerAnnotation] = scaledObject.Name + metricSpec.External.Metric.Selector.MatchLabels[kedav1alpha1.ScaledObjectOwnerAnnotation] = scaledObject.Name externalMetricNames = append(externalMetricNames, externalMetricName) } } @@ -246,9 +246,9 @@ func (r *ScaledObjectReconciler) getScaledObjectMetricSpecs(ctx context.Context, return scaledObjectMetricSpecs, nil } -func updateHealthStatus(scaledObject *v1alpha1.ScaledObject, externalMetricNames []string, status *v1alpha1.ScaledObjectStatus) { +func updateHealthStatus(scaledObject *kedav1alpha1.ScaledObject, externalMetricNames []string, status *kedav1alpha1.ScaledObjectStatus) { health := scaledObject.Status.Health - newHealth := make(map[string]v1alpha1.HealthStatus) + newHealth := make(map[string]kedav1alpha1.HealthStatus) for _, metricName := range externalMetricNames { entry, exists := health[metricName] if exists { @@ -259,19 +259,19 @@ func updateHealthStatus(scaledObject *v1alpha1.ScaledObject, externalMetricNames } // getHPAName returns generated HPA name for ScaledObject specified in the parameter -func getHPAName(scaledObject *v1alpha1.ScaledObject) string { +func getHPAName(scaledObject *kedav1alpha1.ScaledObject) string { if scaledObject.Spec.Advanced != nil && scaledObject.Spec.Advanced.HorizontalPodAutoscalerConfig != nil && scaledObject.Spec.Advanced.HorizontalPodAutoscalerConfig.Name != "" { return scaledObject.Spec.Advanced.HorizontalPodAutoscalerConfig.Name } return getDefaultHpaName(scaledObject) } -func getDefaultHpaName(scaledObject *v1alpha1.ScaledObject) string { +func getDefaultHpaName(scaledObject *kedav1alpha1.ScaledObject) string { return fmt.Sprintf("keda-hpa-%s", scaledObject.Name) } // getHPAMinReplicas returns MinReplicas based on definition in ScaledObject or default value if not defined -func getHPAMinReplicas(scaledObject *v1alpha1.ScaledObject) *int32 { +func getHPAMinReplicas(scaledObject *kedav1alpha1.ScaledObject) *int32 { if scaledObject.Spec.MinReplicaCount != nil && *scaledObject.Spec.MinReplicaCount > 0 { return scaledObject.Spec.MinReplicaCount } @@ -280,7 +280,7 @@ func getHPAMinReplicas(scaledObject *v1alpha1.ScaledObject) *int32 { } // getHPAMaxReplicas returns MaxReplicas based on definition in ScaledObject or default value if not defined -func getHPAMaxReplicas(scaledObject *v1alpha1.ScaledObject) int32 { +func getHPAMaxReplicas(scaledObject *kedav1alpha1.ScaledObject) int32 { if scaledObject.Spec.MaxReplicaCount != nil { return *scaledObject.Spec.MaxReplicaCount } diff --git a/controllers/keda/scaledobject_controller.go b/controllers/keda/scaledobject_controller.go index 48033eac5d0..ddca8599c06 100644 --- a/controllers/keda/scaledobject_controller.go +++ b/controllers/keda/scaledobject_controller.go @@ -41,7 +41,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/predicate" - "github.com/kedacore/keda/v2/apis/keda/v1alpha1" + kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1" kedacontrollerutil "github.com/kedacore/keda/v2/controllers/keda/util" "github.com/kedacore/keda/v2/pkg/eventreason" "github.com/kedacore/keda/v2/pkg/prommetrics" @@ -120,7 +120,7 @@ func (r *ScaledObjectReconciler) SetupWithManager(mgr ctrl.Manager, options cont // predicate.GenerationChangedPredicate{} ignore updates to ScaledObject Status // (in this case metadata.Generation does not change) // so reconcile loop is not started on Status updates - For(&v1alpha1.ScaledObject{}, builder.WithPredicates( + For(&kedav1alpha1.ScaledObject{}, builder.WithPredicates( predicate.Or( kedacontrollerutil.PausedReplicasPredicate{}, kedacontrollerutil.ScaleObjectReadyConditionPredicate{}, @@ -136,7 +136,7 @@ func (r *ScaledObjectReconciler) Reconcile(ctx context.Context, req ctrl.Request reqLogger := log.FromContext(ctx) // Fetch the ScaledObject instance - scaledObject := &v1alpha1.ScaledObject{} + scaledObject := &kedav1alpha1.ScaledObject{} err := r.Client.Get(ctx, req.NamespacedName, scaledObject) if err != nil { if errors.IsNotFound(err) { @@ -166,7 +166,7 @@ func (r *ScaledObjectReconciler) Reconcile(ctx context.Context, req ctrl.Request // ensure Status Conditions are initialized if !scaledObject.Status.Conditions.AreInitialized() { - conditions := v1alpha1.GetInitializedConditions() + conditions := kedav1alpha1.GetInitializedConditions() if err := kedacontrollerutil.SetStatusConditions(ctx, r.Client, reqLogger, scaledObject, conditions); err != nil { return ctrl.Result{}, err } @@ -186,7 +186,7 @@ func (r *ScaledObjectReconciler) Reconcile(ctx context.Context, req ctrl.Request r.Recorder.Event(scaledObject, corev1.EventTypeNormal, eventreason.ScaledObjectReady, "ScaledObject is ready for scaling") } reqLogger.V(1).Info(msg) - conditions.SetReadyCondition(metav1.ConditionTrue, v1alpha1.ScaledObjectConditionReadySucccesReason, msg) + conditions.SetReadyCondition(metav1.ConditionTrue, kedav1alpha1.ScaledObjectConditionReadySucccesReason, msg) } if err := kedacontrollerutil.SetStatusConditions(ctx, r.Client, reqLogger, scaledObject, &conditions); err != nil { @@ -197,7 +197,7 @@ func (r *ScaledObjectReconciler) Reconcile(ctx context.Context, req ctrl.Request } // reconcileScaledObject implements reconciler logic for ScaledObject -func (r *ScaledObjectReconciler) reconcileScaledObject(ctx context.Context, logger logr.Logger, scaledObject *v1alpha1.ScaledObject) (string, error) { +func (r *ScaledObjectReconciler) reconcileScaledObject(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject) (string, error) { // Check scale target Name is specified if scaledObject.Spec.ScaleTargetRef.Name == "" { err := fmt.Errorf("ScaledObject.spec.scaleTargetRef.name is missing") @@ -249,20 +249,20 @@ func (r *ScaledObjectReconciler) reconcileScaledObject(ctx context.Context, logg } logger.Info("Initializing Scaling logic according to ScaledObject Specification") } - return v1alpha1.ScaledObjectConditionReadySuccessMessage, nil + return kedav1alpha1.ScaledObjectConditionReadySuccessMessage, nil } // ensureScaledObjectLabel ensures that scaledobject.keda.sh/name= label exist in the ScaledObject // This is how the MetricsAdapter will know which ScaledObject a metric is for when the HPA queries it. -func (r *ScaledObjectReconciler) ensureScaledObjectLabel(ctx context.Context, logger logr.Logger, scaledObject *v1alpha1.ScaledObject) error { +func (r *ScaledObjectReconciler) ensureScaledObjectLabel(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject) error { if scaledObject.Labels == nil { - scaledObject.Labels = map[string]string{v1alpha1.ScaledObjectOwnerAnnotation: scaledObject.Name} + scaledObject.Labels = map[string]string{kedav1alpha1.ScaledObjectOwnerAnnotation: scaledObject.Name} } else { - value, found := scaledObject.Labels[v1alpha1.ScaledObjectOwnerAnnotation] + value, found := scaledObject.Labels[kedav1alpha1.ScaledObjectOwnerAnnotation] if found && value == scaledObject.Name { return nil } - scaledObject.Labels[v1alpha1.ScaledObjectOwnerAnnotation] = scaledObject.Name + scaledObject.Labels[kedav1alpha1.ScaledObjectOwnerAnnotation] = scaledObject.Name } logger.V(1).Info("Adding \"scaledobject.keda.sh/name\" label on ScaledObject", "value", scaledObject.Name) @@ -270,7 +270,7 @@ func (r *ScaledObjectReconciler) ensureScaledObjectLabel(ctx context.Context, lo } // checkTargetResourceIsScalable checks if resource targeted for scaling exists and exposes /scale subresource -func (r *ScaledObjectReconciler) checkTargetResourceIsScalable(ctx context.Context, logger logr.Logger, scaledObject *v1alpha1.ScaledObject) (v1alpha1.GroupVersionKindResource, error) { +func (r *ScaledObjectReconciler) checkTargetResourceIsScalable(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject) (kedav1alpha1.GroupVersionKindResource, error) { gvkr, err := kedautil.ParseGVKR(r.restMapper, scaledObject.Spec.ScaleTargetRef.APIVersion, scaledObject.Spec.ScaleTargetRef.Kind) if err != nil { logger.Error(err, "Failed to parse Group, Version, Kind, Resource", "apiVersion", scaledObject.Spec.ScaleTargetRef.APIVersion, "kind", scaledObject.Spec.ScaleTargetRef.Kind) @@ -338,7 +338,7 @@ func (r *ScaledObjectReconciler) checkTargetResourceIsScalable(ctx context.Conte // checkTriggers checks that general trigger metadata are valid, it checks: // - triggerNames in ScaledObject are unique // - useCachedMetrics is defined only for a supported triggers -func (r *ScaledObjectReconciler) checkTriggers(scaledObject *v1alpha1.ScaledObject) error { +func (r *ScaledObjectReconciler) checkTriggers(scaledObject *kedav1alpha1.ScaledObject) error { triggersCount := len(scaledObject.Spec.Triggers) if triggersCount > 1 { @@ -368,7 +368,7 @@ func (r *ScaledObjectReconciler) checkTriggers(scaledObject *v1alpha1.ScaledObje // checkReplicaCountBoundsAreValid checks that Idle/Min/Max ReplicaCount defined in ScaledObject are correctly specified // ie. that Min is not greater then Max or Idle greater or equal to Min -func (r *ScaledObjectReconciler) checkReplicaCountBoundsAreValid(scaledObject *v1alpha1.ScaledObject) error { +func (r *ScaledObjectReconciler) checkReplicaCountBoundsAreValid(scaledObject *kedav1alpha1.ScaledObject) error { min := int32(0) if scaledObject.Spec.MinReplicaCount != nil { min = *getHPAMinReplicas(scaledObject) @@ -387,7 +387,7 @@ func (r *ScaledObjectReconciler) checkReplicaCountBoundsAreValid(scaledObject *v } // ensureHPAForScaledObjectExists ensures that in cluster exist up-to-date HPA for specified ScaledObject, returns true if a new HPA was created -func (r *ScaledObjectReconciler) ensureHPAForScaledObjectExists(ctx context.Context, logger logr.Logger, scaledObject *v1alpha1.ScaledObject, gvkr *v1alpha1.GroupVersionKindResource) (bool, error) { +func (r *ScaledObjectReconciler) ensureHPAForScaledObjectExists(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject, gvkr *kedav1alpha1.GroupVersionKindResource) (bool, error) { var hpaName string if scaledObject.Status.HpaName != "" { hpaName = scaledObject.Status.HpaName @@ -431,7 +431,7 @@ func (r *ScaledObjectReconciler) ensureHPAForScaledObjectExists(ctx context.Cont return false, nil } -func isHpaRenamed(scaledObject *v1alpha1.ScaledObject, foundHpa *autoscalingv2.HorizontalPodAutoscaler) bool { +func isHpaRenamed(scaledObject *kedav1alpha1.ScaledObject, foundHpa *autoscalingv2.HorizontalPodAutoscaler) bool { // if HPA name defined in SO -> check if equals to the found HPA if scaledObject.Spec.Advanced != nil && scaledObject.Spec.Advanced.HorizontalPodAutoscalerConfig != nil && scaledObject.Spec.Advanced.HorizontalPodAutoscalerConfig.Name != "" { return scaledObject.Spec.Advanced.HorizontalPodAutoscalerConfig.Name != foundHpa.Name @@ -441,7 +441,7 @@ func isHpaRenamed(scaledObject *v1alpha1.ScaledObject, foundHpa *autoscalingv2.H } // requestScaleLoop tries to start ScaleLoop handler for the respective ScaledObject -func (r *ScaledObjectReconciler) requestScaleLoop(ctx context.Context, logger logr.Logger, scaledObject *v1alpha1.ScaledObject) error { +func (r *ScaledObjectReconciler) requestScaleLoop(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject) error { logger.V(1).Info("Notify scaleHandler of an update in scaledObject") key, err := cache.MetaNamespaceKeyFunc(scaledObject) @@ -461,7 +461,7 @@ func (r *ScaledObjectReconciler) requestScaleLoop(ctx context.Context, logger lo } // stopScaleLoop stops ScaleLoop handler for the respective ScaledObject -func (r *ScaledObjectReconciler) stopScaleLoop(ctx context.Context, logger logr.Logger, scaledObject *v1alpha1.ScaledObject) error { +func (r *ScaledObjectReconciler) stopScaleLoop(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject) error { key, err := cache.MetaNamespaceKeyFunc(scaledObject) if err != nil { logger.Error(err, "Error getting key for scaledObject") @@ -477,7 +477,7 @@ func (r *ScaledObjectReconciler) stopScaleLoop(ctx context.Context, logger logr. } // scaledObjectGenerationChanged returns true if ScaledObject's Generation was changed, ie. ScaledObject.Spec was changed -func (r *ScaledObjectReconciler) scaledObjectGenerationChanged(logger logr.Logger, scaledObject *v1alpha1.ScaledObject) (bool, error) { +func (r *ScaledObjectReconciler) scaledObjectGenerationChanged(logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject) (bool, error) { key, err := cache.MetaNamespaceKeyFunc(scaledObject) if err != nil { logger.Error(err, "Error getting key for scaledObject") @@ -494,7 +494,7 @@ func (r *ScaledObjectReconciler) scaledObjectGenerationChanged(logger logr.Logge return true, nil } -func (r *ScaledObjectReconciler) updatePromMetrics(scaledObject *v1alpha1.ScaledObject, namespacedName string) { +func (r *ScaledObjectReconciler) updatePromMetrics(scaledObject *kedav1alpha1.ScaledObject, namespacedName string) { scaledObjectPromMetricsLock.Lock() defer scaledObjectPromMetricsLock.Unlock() diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 9c95debb744..18441dea953 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -1,5 +1,5 @@ /* -Copyright 2023 The KEDA Authors +Copyright 2021 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -31,7 +31,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/custom-metrics-apiserver/pkg/provider" - "github.com/kedacore/keda/v2/apis/keda/v1alpha1" + kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1" "github.com/kedacore/keda/v2/pkg/fallback" "github.com/kedacore/keda/v2/pkg/metricsservice" prommetrics "github.com/kedacore/keda/v2/pkg/prommetrics/adapter" @@ -98,7 +98,7 @@ func (p *KedaProvider) GetExternalMetric(ctx context.Context, namespace string, } // selector is in form: `scaledobject.keda.sh/name: scaledobject-name` - scaledObjectName := selector.Get(v1alpha1.ScaledObjectOwnerAnnotation) + scaledObjectName := selector.Get(kedav1alpha1.ScaledObjectOwnerAnnotation) metrics, promMetrics, err := p.grpcClient.GetMetrics(ctx, scaledObjectName, namespace, info.Metric) logger.V(1).WithValues("scaledObjectName", scaledObjectName, "scaledObjectNamespace", namespace, "metrics", metrics).Info("Receiving metrics") @@ -128,7 +128,7 @@ func (p *KedaProvider) GetExternalMetric(ctx context.Context, namespace string, // ------ Deprecated way of getting metric directly from MS ------ // // --------------------------------------------------------------- // // Get Metrics by querying directly the external service - scaledObjects := &v1alpha1.ScaledObjectList{} + scaledObjects := &kedav1alpha1.ScaledObjectList{} opts := []client.ListOption{ client.InNamespace(namespace), client.MatchingLabels(selector), diff --git a/pkg/scaling/resolver/scale_resolvers.go b/pkg/scaling/resolver/scale_resolvers.go index 9d71e619bee..afd58ea2fcf 100644 --- a/pkg/scaling/resolver/scale_resolvers.go +++ b/pkg/scaling/resolver/scale_resolvers.go @@ -1,5 +1,5 @@ /* -Copyright 2023 The KEDA Authors +Copyright 2021 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 462dd9068af538063603f07d2755b25d67c71350 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Mon, 2 Jan 2023 13:56:20 +0100 Subject: [PATCH 11/41] use kedautil.GetPodNamespace() in adapter Signed-off-by: Jorge Turrado --- cmd/adapter/main.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/cmd/adapter/main.go b/cmd/adapter/main.go index cd7e428a769..ec5440b7f28 100644 --- a/cmd/adapter/main.go +++ b/cmd/adapter/main.go @@ -200,14 +200,7 @@ func runScaledObjectController(ctx context.Context, mgr manager.Manager, scaleHa // generateDefaultMetricsServiceAddr generates default Metrics Service gRPC Server address based on the current Namespace. // By default the Metrics Service gRPC Server runs in the same namespace on the keda-operator pod. func generateDefaultMetricsServiceAddr() string { - const defaultNamespace = "keda" - podNamespace := os.Getenv("POD_NAMESPACE") - - if podNamespace == "" { - podNamespace = defaultNamespace - } - - return fmt.Sprintf("keda-operator.%s.svc.cluster.local:9666", podNamespace) + return fmt.Sprintf("keda-operator.%s.svc.cluster.local:9666", kedautil.GetPodNamespace()) } func printVersion() { From 0b9298784b2e57fa3f1c2d5e4f06a932fb80f86e Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Mon, 2 Jan 2023 14:45:42 +0100 Subject: [PATCH 12/41] use gvkr parser for webhooks Signed-off-by: Jorge Turrado --- {pkg/util => apis/keda/v1alpha1}/gvkr.go | 12 ++-- apis/keda/v1alpha1/scaledobject_webhook.go | 77 ++++++++------------- apis/keda/v1alpha1/zz_generated.deepcopy.go | 2 +- controllers/keda/scaledobject_controller.go | 3 +- 4 files changed, 35 insertions(+), 59 deletions(-) rename {pkg/util => apis/keda/v1alpha1}/gvkr.go (87%) diff --git a/pkg/util/gvkr.go b/apis/keda/v1alpha1/gvkr.go similarity index 87% rename from pkg/util/gvkr.go rename to apis/keda/v1alpha1/gvkr.go index 63b1d924823..8bd455f7961 100644 --- a/pkg/util/gvkr.go +++ b/apis/keda/v1alpha1/gvkr.go @@ -14,13 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util +package v1alpha1 import ( "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime/schema" - - kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1" ) const ( @@ -31,7 +29,7 @@ const ( ) // ParseGVKR returns GroupVersionKindResource for specified apiVersion (groupVersion) and Kind -func ParseGVKR(restMapper meta.RESTMapper, apiVersion string, kind string) (kedav1alpha1.GroupVersionKindResource, error) { +func ParseGVKR(restMapper meta.RESTMapper, apiVersion string, kind string) (GroupVersionKindResource, error) { var group, version, resource string // if apiVersion is not specified, we suppose the default one should be used @@ -41,7 +39,7 @@ func ParseGVKR(restMapper meta.RESTMapper, apiVersion string, kind string) (keda } else { groupVersion, err := schema.ParseGroupVersion(apiVersion) if err != nil { - return kedav1alpha1.GroupVersionKindResource{}, err + return GroupVersionKindResource{}, err } group = groupVersion.Group @@ -56,10 +54,10 @@ func ParseGVKR(restMapper meta.RESTMapper, apiVersion string, kind string) (keda // get resource resource, err := getResource(restMapper, group, version, kind) if err != nil { - return kedav1alpha1.GroupVersionKindResource{}, err + return GroupVersionKindResource{}, err } - return kedav1alpha1.GroupVersionKindResource{ + return GroupVersionKindResource{ Group: group, Version: version, Kind: kind, diff --git a/apis/keda/v1alpha1/scaledobject_webhook.go b/apis/keda/v1alpha1/scaledobject_webhook.go index f93731bb31e..ec3713fb558 100644 --- a/apis/keda/v1alpha1/scaledobject_webhook.go +++ b/apis/keda/v1alpha1/scaledobject_webhook.go @@ -22,6 +22,7 @@ import ( "fmt" autoscalingv2 "k8s.io/api/autoscaling/v2" + "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -34,14 +35,11 @@ import ( var scaledobjectlog = logf.Log.WithName("scaledobject-validation-webhook") var kc client.Client - -const ( - defaultAPI = "apps/v1" - defaultKind = "Deployment" -) +var restMapper meta.RESTMapper func (so *ScaledObject) SetupWebhookWithManager(mgr ctrl.Manager) error { kc = mgr.GetClient() + restMapper = mgr.GetRESTMapper() return ctrl.NewWebhookManagedBy(mgr). For(so). Complete() @@ -85,33 +83,24 @@ func verifyHpas(incomingSo *ScaledObject, action string) error { return err } + var incomingSoGckr GroupVersionKindResource + incomingSoGckr, err = ParseGVKR(restMapper, incomingSo.Spec.ScaleTargetRef.APIVersion, incomingSo.Spec.ScaleTargetRef.Kind) + if err != nil { + scaledobjectlog.Error(err, "Failed to parse Group, Version, Kind, Resource from incoming ScaledObject", "apiVersion", incomingSo.Spec.ScaleTargetRef.APIVersion, "kind", incomingSo.Spec.ScaleTargetRef.Kind) + return err + } + for _, hpa := range hpaList.Items { val, _ := json.MarshalIndent(hpa, "", " ") scaledobjectlog.V(1).Info(fmt.Sprintf("checking hpa %s: %v", hpa.Name, string(val))) - hpaTarget := hpa.Spec.ScaleTargetRef - incomingSoTarget := incomingSo.Spec.ScaleTargetRef - // prepare default values - hpatargetAPI := defaultAPI - if hpaTarget.APIVersion != "" { - hpatargetAPI = hpaTarget.APIVersion - } - hpaTargetKind := defaultKind - if hpaTarget.Kind != "" { - hpaTargetKind = hpaTarget.Kind - } - incomingSotargetAPI := defaultAPI - if incomingSoTarget.APIVersion != "" { - incomingSotargetAPI = incomingSoTarget.APIVersion - } - incomingSoTargetKind := defaultKind - if incomingSoTarget.Kind != "" { - incomingSoTargetKind = incomingSoTarget.Kind + hpaGckr, err := ParseGVKR(restMapper, hpa.Spec.ScaleTargetRef.APIVersion, hpa.Spec.ScaleTargetRef.Kind) + if err != nil { + scaledobjectlog.Error(err, "Failed to parse Group, Version, Kind, Resource from HPA", "hpaName", hpa.Name, "apiVersion", hpa.Spec.ScaleTargetRef.APIVersion, "kind", hpa.Spec.ScaleTargetRef.Kind) + return err } - if hpatargetAPI == incomingSotargetAPI && - hpaTargetKind == incomingSoTargetKind && - hpaTarget.Name == incomingSoTarget.Name { + if hpaGckr.GVKString() == incomingSoGckr.GVKString() { owned := false for _, owner := range hpa.OwnerReferences { if owner.Kind == incomingSo.Kind { @@ -123,7 +112,7 @@ func verifyHpas(incomingSo *ScaledObject, action string) error { } if !owned { - err = fmt.Errorf("the workload '%s' of type '%s/%s' is already managed by the hpa '%s'", incomingSoTarget.Name, incomingSotargetAPI, incomingSoTargetKind, hpa.Name) + err = fmt.Errorf("the workload '%s' of type '%s/%s' is already managed by the hpa '%s'", incomingSo.Spec.ScaleTargetRef.Name, incomingSo.Spec.ScaleTargetRef.APIVersion, incomingSo.Spec.ScaleTargetRef.Kind, hpa.Name) scaledobjectlog.Error(err, "validation error") prommetrics.RecordScaledObjectValidatingErrors(incomingSo.Namespace, action, "hpa") return err @@ -144,37 +133,27 @@ func verifyScaledObjects(incomingSo *ScaledObject, action string) error { return err } + incomingSoGckr, err := ParseGVKR(restMapper, incomingSo.Spec.ScaleTargetRef.APIVersion, incomingSo.Spec.ScaleTargetRef.Kind) + if err != nil { + scaledobjectlog.Error(err, "Failed to parse Group, Version, Kind, Resource from incoming ScaledObject", "apiVersion", incomingSo.Spec.ScaleTargetRef.APIVersion, "kind", incomingSo.Spec.ScaleTargetRef.Kind) + return err + } + for _, so := range soList.Items { if so.Name == incomingSo.Name { continue } val, _ := json.MarshalIndent(so, "", " ") scaledobjectlog.V(1).Info(fmt.Sprintf("checking scaledobject %s: %v", so.Name, string(val))) - soTarget := so.Spec.ScaleTargetRef - incomingTarget := incomingSo.Spec.ScaleTargetRef - // prepare default values - sotargetAPI := defaultAPI - if soTarget.APIVersion != "" { - sotargetAPI = soTarget.APIVersion - } - soTargetKind := defaultKind - if soTarget.Kind != "" { - soTargetKind = soTarget.Kind - } - incomingSotargetAPI := defaultAPI - if incomingTarget.APIVersion != "" { - incomingSotargetAPI = incomingTarget.APIVersion - } - incomingSoTargetKind := defaultKind - if incomingTarget.Kind != "" { - incomingSoTargetKind = incomingTarget.Kind + soGckr, err := ParseGVKR(restMapper, so.Spec.ScaleTargetRef.APIVersion, so.Spec.ScaleTargetRef.Kind) + if err != nil { + scaledobjectlog.Error(err, "Failed to parse Group, Version, Kind, Resource from ScaledObject", "soName", so.Name, "apiVersion", so.Spec.ScaleTargetRef.APIVersion, "kind", so.Spec.ScaleTargetRef.Kind) + return err } - if sotargetAPI == incomingSotargetAPI && - soTargetKind == incomingSoTargetKind && - soTarget.Name == incomingTarget.Name { - err = fmt.Errorf("the workload '%s' of type '%s/%s' is already managed by the ScaledObject '%s'", soTarget.Name, sotargetAPI, soTargetKind, so.Name) + if soGckr.GVKString() == incomingSoGckr.GVKString() { + err = fmt.Errorf("the workload '%s' of type '%s/%s' is already managed by the ScaledObject '%s'", so.Spec.ScaleTargetRef.Name, so.Spec.ScaleTargetRef.APIVersion, so.Spec.ScaleTargetRef.Kind, so.Name) scaledobjectlog.Error(err, "validation error") prommetrics.RecordScaledObjectValidatingErrors(incomingSo.Namespace, action, "scaled_object") return err diff --git a/apis/keda/v1alpha1/zz_generated.deepcopy.go b/apis/keda/v1alpha1/zz_generated.deepcopy.go index 0d5a81c8a00..07f8596249d 100644 --- a/apis/keda/v1alpha1/zz_generated.deepcopy.go +++ b/apis/keda/v1alpha1/zz_generated.deepcopy.go @@ -2,7 +2,7 @@ // +build !ignore_autogenerated /* -Copyright 2022 The KEDA Authors +Copyright 2023 The KEDA Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/controllers/keda/scaledobject_controller.go b/controllers/keda/scaledobject_controller.go index ddca8599c06..aa4d32530b1 100644 --- a/controllers/keda/scaledobject_controller.go +++ b/controllers/keda/scaledobject_controller.go @@ -46,7 +46,6 @@ import ( "github.com/kedacore/keda/v2/pkg/eventreason" "github.com/kedacore/keda/v2/pkg/prommetrics" "github.com/kedacore/keda/v2/pkg/scaling" - kedautil "github.com/kedacore/keda/v2/pkg/util" ) // +kubebuilder:rbac:groups=keda.sh,resources=scaledobjects;scaledobjects/finalizers;scaledobjects/status,verbs="*" @@ -271,7 +270,7 @@ func (r *ScaledObjectReconciler) ensureScaledObjectLabel(ctx context.Context, lo // checkTargetResourceIsScalable checks if resource targeted for scaling exists and exposes /scale subresource func (r *ScaledObjectReconciler) checkTargetResourceIsScalable(ctx context.Context, logger logr.Logger, scaledObject *kedav1alpha1.ScaledObject) (kedav1alpha1.GroupVersionKindResource, error) { - gvkr, err := kedautil.ParseGVKR(r.restMapper, scaledObject.Spec.ScaleTargetRef.APIVersion, scaledObject.Spec.ScaleTargetRef.Kind) + gvkr, err := kedav1alpha1.ParseGVKR(r.restMapper, scaledObject.Spec.ScaleTargetRef.APIVersion, scaledObject.Spec.ScaleTargetRef.Kind) if err != nil { logger.Error(err, "Failed to parse Group, Version, Kind, Resource", "apiVersion", scaledObject.Spec.ScaleTargetRef.APIVersion, "kind", scaledObject.Spec.ScaleTargetRef.Kind) return gvkr, err From 7a56a8d6997d43bece02139f6e47fa58f119dc0c Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Tue, 3 Jan 2023 01:57:19 +0100 Subject: [PATCH 13/41] remove the empty secret as requirement Signed-off-by: Jorge Turrado --- cmd/webhooks/main.go | 61 +++++++++++++++++++++++++++++------ config/webhooks/secret.yaml | 11 ------- config/webhooks/webhooks.yaml | 7 ++-- 3 files changed, 56 insertions(+), 23 deletions(-) delete mode 100644 config/webhooks/secret.yaml diff --git a/cmd/webhooks/main.go b/cmd/webhooks/main.go index af3bb0f46da..4165f941adc 100644 --- a/cmd/webhooks/main.go +++ b/cmd/webhooks/main.go @@ -17,6 +17,7 @@ limitations under the License. package main import ( + "context" "flag" "fmt" "os" @@ -24,12 +25,15 @@ import ( "github.com/open-policy-agent/cert-controller/pkg/rotator" "github.com/spf13/pflag" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" apimachineryruntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" _ "k8s.io/client-go/plugin/pkg/client/auth" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/manager" @@ -51,7 +55,7 @@ var webhooks = []rotator.WebhookInfo{ var ( scheme = apimachineryruntime.NewScheme() setupLog = ctrl.Log.WithName("setup") - secretName = "kedaorg-webhooks-secret" // #nosec + secretName = "kedaorg-webhooks-certificates" // This should be the same for the secret volume serviceName = "keda-webhooks" caName = "kedaorg-ca" caOrganization = "kedaorg" @@ -69,15 +73,15 @@ func init() { func main() { var metricsAddr string var probeAddr string - var adapterClientRequestQPS float32 - var adapterClientRequestBurst int + var webhooksClientRequestQPS float32 + var webhooksClientRequestBurst int var webhookCertDir string var disableCertRotation bool var tlsMinVersion string pflag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") pflag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") - pflag.Float32Var(&adapterClientRequestQPS, "kube-api-qps", 20.0, "Set the QPS rate for throttling requests sent to the apiserver") - pflag.IntVar(&adapterClientRequestBurst, "kube-api-burst", 30, "Set the burst for throttling requests sent to the apiserver") + pflag.Float32Var(&webhooksClientRequestQPS, "kube-api-qps", 20.0, "Set the QPS rate for throttling requests sent to the apiserver") + pflag.IntVar(&webhooksClientRequestBurst, "kube-api-burst", 30, "Set the burst for throttling requests sent to the apiserver") pflag.StringVar(&webhookCertDir, "webhook-cert-dir", "/certs", "Webhook certificates dir to use. Defaults to /certs") pflag.BoolVar(&disableCertRotation, "disable-cert-rotation", false, "disable automatic generation and rotation of webhook TLS certificates/keys") pflag.StringVar(&tlsMinVersion, "tls-min-version", "1.3", "Minimum TLS version") @@ -89,9 +93,11 @@ func main() { ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + ctx := ctrl.SetupSignalHandler() + cfg := ctrl.GetConfigOrDie() - cfg.QPS = adapterClientRequestQPS - cfg.Burst = adapterClientRequestBurst + cfg.QPS = webhooksClientRequestQPS + cfg.Burst = webhooksClientRequestBurst mgr, err := ctrl.NewManager(cfg, ctrl.Options{ Scheme: scheme, @@ -109,6 +115,7 @@ func main() { // Make sure certs are generated and valid if cert rotation is enabled. setupFinished := make(chan struct{}) if !disableCertRotation { + ensureSecret(ctx, mgr) setupLog.V(1).Info("setting up cert rotation") if err := rotator.AddRotator(mgr, &rotator.CertRotator{ SecretKey: types.NamespacedName{ @@ -156,14 +163,50 @@ func main() { os.Exit(1) } - ctx := ctrl.SetupSignalHandler() - if err := mgr.Start(ctx); err != nil { setupLog.Error(err, "problem running webhooks") os.Exit(1) } } +func ensureSecret(ctx context.Context, mgr manager.Manager) { + secrets := &corev1.SecretList{} + kedaNamespace := kedautil.GetPodNamespace() + opt := &client.ListOptions{ + Namespace: kedaNamespace, + } + + err := mgr.GetAPIReader().List(ctx, secrets, opt) + if err != nil { + setupLog.Error(err, "unable to check secret") + os.Exit(1) + } + + exists := false + for _, secret := range secrets.Items { + if secret.Name == secretName { + exists = true + break + } + } + if !exists { + secret := &corev1.Secret{ + ObjectMeta: v1.ObjectMeta{ + Name: secretName, + Namespace: kedaNamespace, + Labels: map[string]string{ + "app": "keda-webhooks", + "app.kubernetes.io/name": "keda-webhooks", + "app.kubernetes.io/component": "webhooks", + "app.kubernetes.io/part-of": "keda-operator", + }, + }, + } + mgr.GetClient().Create(ctx, secret) + setupLog.V(1).Info(fmt.Sprintf("created the secret %s to store cert-controller certificates", secretName)) + } +} + func setupWebhook(mgr manager.Manager, tlsMinVersion string, setupFinished chan struct{}) { // Block until the setup (certificate generation) finishes. <-setupFinished diff --git a/config/webhooks/secret.yaml b/config/webhooks/secret.yaml deleted file mode 100644 index f22beac6991..00000000000 --- a/config/webhooks/secret.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: kedaorg-webhooks-secret - namespace: keda - labels: - app: keda-webhooks - app.kubernetes.io/name: keda-webhooks - app.kubernetes.io/version: latest - app.kubernetes.io/component: webhooks - app.kubernetes.io/part-of: keda-operator diff --git a/config/webhooks/webhooks.yaml b/config/webhooks/webhooks.yaml index ee802a21911..324fefcb072 100644 --- a/config/webhooks/webhooks.yaml +++ b/config/webhooks/webhooks.yaml @@ -79,13 +79,14 @@ spec: type: RuntimeDefault volumeMounts: - mountPath: /certs - name: cert + name: cert-controller readOnly: true terminationGracePeriodSeconds: 10 nodeSelector: kubernetes.io/os: linux volumes: - - name: cert + - name: cert-controller secret: defaultMode: 420 - secretName: kedaorg-webhooks-secret + secretName: kedaorg-webhooks-certificates + optional: true From 8f62b2983edacbe0707223249cab209558d2d440 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Tue, 3 Jan 2023 02:05:10 +0100 Subject: [PATCH 14/41] update go.sum Signed-off-by: Jorge Turrado --- go.sum | 55 ++----------------------------------------------------- 1 file changed, 2 insertions(+), 53 deletions(-) diff --git a/go.sum b/go.sum index 8e08fc7e76d..feed6eca86c 100644 --- a/go.sum +++ b/go.sum @@ -1011,61 +1011,10 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= -golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220513224357-95641704303c/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220725212005-46097bf591d3/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= From 01cbb328498f53aae6ba15ffa278c599430618f2 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Tue, 3 Jan 2023 02:20:43 +0100 Subject: [PATCH 15/41] update errors Signed-off-by: Jorge Turrado --- cmd/webhooks/main.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/cmd/webhooks/main.go b/cmd/webhooks/main.go index 4165f941adc..4af32840228 100644 --- a/cmd/webhooks/main.go +++ b/cmd/webhooks/main.go @@ -82,7 +82,7 @@ func main() { pflag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") pflag.Float32Var(&webhooksClientRequestQPS, "kube-api-qps", 20.0, "Set the QPS rate for throttling requests sent to the apiserver") pflag.IntVar(&webhooksClientRequestBurst, "kube-api-burst", 30, "Set the burst for throttling requests sent to the apiserver") - pflag.StringVar(&webhookCertDir, "webhook-cert-dir", "/certs", "Webhook certificates dir to use. Defaults to /certs") + pflag.StringVar(&webhookCertDir, "webhooks-cert-dir", "/certs", "Webhook certificates dir to use. Defaults to /certs") pflag.BoolVar(&disableCertRotation, "disable-cert-rotation", false, "disable automatic generation and rotation of webhook TLS certificates/keys") pflag.StringVar(&tlsMinVersion, "tls-min-version", "1.3", "Minimum TLS version") @@ -178,7 +178,7 @@ func ensureSecret(ctx context.Context, mgr manager.Manager) { err := mgr.GetAPIReader().List(ctx, secrets, opt) if err != nil { - setupLog.Error(err, "unable to check secret") + setupLog.Error(err, "unable to check secrets") os.Exit(1) } @@ -202,7 +202,11 @@ func ensureSecret(ctx context.Context, mgr manager.Manager) { }, }, } - mgr.GetClient().Create(ctx, secret) + err = mgr.GetClient().Create(ctx, secret) + if err != nil { + setupLog.Error(err, "unable to create certificates secret") + os.Exit(1) + } setupLog.V(1).Info(fmt.Sprintf("created the secret %s to store cert-controller certificates", secretName)) } } From ae0957214018080524e83aee0c15bd580d8fcb69 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Tue, 3 Jan 2023 03:21:30 +0100 Subject: [PATCH 16/41] update pictures Signed-off-by: Jorge Turrado --- images/keda-arch.png | Bin 86412 -> 82658 bytes images/keda-architecture.pptx | Bin 48445 -> 49226 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/images/keda-arch.png b/images/keda-arch.png index b5751183644ddf856168de456f35f81f7518ef0b..87e49841834297a20e16fbc3920bcfc9b0a27be6 100644 GIT binary patch literal 82658 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Ⓢ8cR1WGoSg)vYr67$GLmX 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

5|#w6X+P2A@5~P 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

^Ji@^3*P~hdjx|sTc95X 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 diff --git a/images/keda-architecture.pptx b/images/keda-architecture.pptx index 34b56f6c5610ce87ddefcaf553c07d12ac024307..8b9d1122b3e0411e026fc4ae4c231f926f1fc823 100644 GIT binary patch delta 31087 zcmYhgV{l+y)HIq&G85ajZQGgHwrwXTw(ZQswrx8T+qN~i^Ss~v-h2P7+N*k<-n-7O z)790d>kV|=AFMVT3SI@>j53M<1O$oY8weUmQeGVfaKWeRywB-$T{aFVyb>X_?S#Jk zAkXO7JKj`#vTJcWa*CtUOtifmPmE18H)l`z!e<3hBBhIrzr0;R*;dY z#Q`tCG4K|5xxQ(|&m>>tZeMauZ};?mxz1tl){r%H>GJ$O0=R z_@Ey2==K;Yn&9Egqg&1Jac??$pH)$DXp>-T=H3$ZxJ5*TQE|HU;N#w~aYb#jj%c&) zRL5d*u~0?-*{8tn-IF=6z4;{4rqR3jwx0)#U%p?)6?J_s#|fk+9d6v58`bA-VK?h^ z-g5A!f42R#VAyiO@2RFQu@}%yX}MXRWq*CR>Sz37`FQ`ahMxD7lwqBXr)AC|fE$pr zc_8s?TZO@6!GlWSklQ^ES2f{&VC2+_n8Ammd;78ieluml9pg(Gm2}s$PrLlRXMXL_(x`3a5Nw&yY+JRhe(K4ywfmG*2Q?o4!NNd4 zJN6~@y@VJ87Y%Aku{=YN8V%YJn`sCoq=ENMU%uzm`Egn4b~x$$l%HD#f*-3{=kHtE z)P{qJSIf4BpG#=85;5OS*=aO@Z!TRs8Ylf&z{S4MvhLLKwP57T6AZ{*fiySSb?ia@Ya{E za~j~Hv!#Ag_`nWL8F9}n-;e#hUve<9z3BVC)2TbjKwdGlrmK1Oyl_Yjeg_9UOw^yB zSuq6>IR$}Kwusbzb!xS4eKoc+4}3wnTuH{2G-S+~Debe)g1DM`=?O@N5j@<4;V&X2 z?S`FGTx4;a4-B+SJQnkY8Z^~lUhd*O#-3fSf0mfAe8JA>X7-32-E z=UqJs$W4^lva0!f1-%|yH8BC5TQ3z+V^hT+ml{01II^tnQ#;AZ#_aB2ty!sNrE1$% z?xAx^C_@b7-p^5r;?V(=+Ryab&r9wtr)H*8`KQ0TSU;(sE6(o9H#YwIDo-1GG`#ZZ z&P={<>rOtIK9X#v!Mi9=qdQ*Et{u7YYyv%gJvplT2xm9D3)>}!OBKM>mc@oX$F+U& zdO`WV!S1jZ3XX2K)>~F=gW9m$`*W`+O&>ZcZ0}{`$Cg{a&W8zu8UY^D#N$SEcY(2M z*OSU5hB`rU+BM+q{+nq4sqR7h*U-betI}ViRj>iP^)UJpo}JdZ1Z!a3e@kY0dHCVe zqb2u}CQ@CnJLUJ~#R#yv1r;Bgzn07T4VfZDb{4wQm7}}9xC>%>yQ`B0^nEw!aN=bkF-1% zLY%}hF2M_*m(=Qco=w-imSqrO5mu4?VX#;M!|}cG(g!eIX5dTe-9!IGU)BS33=L{n ziYiixibU)2Mc!0l%?DPvVBEL89M2!P&RxejiFm|V;pY1^3pAYG@4MmJ=}Vey+qOJo zI<{n+aahnr9iDY+wB5U``;}okF@1Xf-qtYZ4@|WyYQ`r$w!sZ-)&p;#Q3!JjMDOKtJm%`%&Q$hXvte>y-!h@ zpWyb}u@4U=8&00pbY7?a20a4@O@rQ=+;SG)L!PJ<=~U0;SDvVg!nf(g(h{$09G(j` z!M`_o?rCPD=z%-WlmPf^Hr9T883=Z4R;a=>MGZ@d=5f&Af-9nT!YA26c2NS8$F z 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 SLc5iNv0Lci8>;>~_k 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 F=4=yH-5*&c>MwUT~BAXJsn2d7I>fx9VB&?k` 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 Date: Tue, 3 Jan 2023 13:56:06 +0100 Subject: [PATCH 17/41] update external name to use admission-webhooks Signed-off-by: Jorge Turrado --- BUILD.md | 8 ++--- Dockerfile.webhooks | 4 +-- Makefile | 6 ++-- cmd/webhooks/main.go | 19 ++++++----- config/webhooks/kustomization.yaml | 4 +-- config/webhooks/service.yaml | 9 +++-- config/webhooks/validation_webhooks.yaml | 9 +++-- config/webhooks/webhooks.yaml | 31 +++++++++--------- images/keda-arch.png | Bin 82658 -> 99764 bytes images/keda-architecture.pptx | Bin 49226 -> 49225 bytes .../prometheus_metrics_test.go | 2 +- tests/run-all.sh | 4 +-- tests/run-smoke-tests.sh | 4 +-- 13 files changed, 50 insertions(+), 50 deletions(-) diff --git a/BUILD.md b/BUILD.md index c733972f612..3a9f0cffe9a 100644 --- a/BUILD.md +++ b/BUILD.md @@ -223,7 +223,7 @@ You can query list metrics executing `curl --insecure https://localhost:6443/api If you prefer to use an authenticated user, you can use a user or service account with access over external metrics API adding their token as authorization header in `curl`, ie: `curl -H "Authorization:Bearer TOKEN" --insecure https://localhost:6443/apis/external.metrics.k8s.io/v1beta1/` -### Webhooks +### Admission Webhooks Follow these instructions if you want to debug the KEDA webhook using VS Code. @@ -253,14 +253,14 @@ Follow these instructions if you want to debug the KEDA webhook using VS Code. Refer to [this](https://code.visualstudio.com/docs/editor/debugging) for more information about debugging with VS Code. 2. Expose your local instance to internet. If you can't expose it directly, you can use something like [localtunnel](https://theboroer.github.io/localtunnel-www/) using the command `lt --port 9443 --local-https --allow-invalid-cert` after installing the tool. -3. Update the `validation_webhooks.yaml` in `config/webhooks`, replacing the section (but not commiting this change) +3. Update the `admissing_webhooks.yaml` in `config/webhooks`, replacing the section (but not commiting this change) ```yaml webhooks: - admissionReviewVersions: - v1 clientConfig: service: - name: keda-webhooks + name: keda-admission-webhooks namespace: keda path: /validate-keda-sh-v1alpha1-scaledobject ``` @@ -294,7 +294,7 @@ To solve this and be able to work with devcontainers and a local cluster, you sh You can change default log levels for both KEDA Operator and Metrics Server. KEDA Operator uses [Operator SDK logging](https://sdk.operatorframework.io/docs/building-operators/golang/references/logging/) mechanism. -### KEDA Operator and webhooks logging +### KEDA Operator and Admission webhooks logging To change the logging level, find `--zap-log-level=` argument in Operator Deployment section in `config/manager/manager.yaml` file or in Webhooks Deployment section in `config/webhooks/webhooks.yaml` file, modify its value and redeploy. diff --git a/Dockerfile.webhooks b/Dockerfile.webhooks index 2739a8da48a..b480ea66e66 100644 --- a/Dockerfile.webhooks +++ b/Dockerfile.webhooks @@ -30,8 +30,8 @@ RUN VERSION=${BUILD_VERSION} GIT_COMMIT=${GIT_COMMIT} GIT_VERSION=${GIT_VERSION} # Refer to https://github.com/GoogleContainerTools/distroless for more details FROM gcr.io/distroless/static:nonroot WORKDIR / -COPY --from=builder /workspace/bin/keda-webhooks . +COPY --from=builder /workspace/bin/keda-admission-webhooks . # 65532 is numeric for nonroot USER 65532:65532 -ENTRYPOINT ["/keda-webhooks", "--zap-log-level=info", "--zap-encoder=console"] +ENTRYPOINT ["/keda-admission-webhooks", "--zap-log-level=info", "--zap-encoder=console"] diff --git a/Makefile b/Makefile index cfea052d013..ed5dfcf1799 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ IMAGE_REPO ?= kedacore IMAGE_CONTROLLER = $(IMAGE_REGISTRY)/$(IMAGE_REPO)/keda$(SUFFIX):$(VERSION) IMAGE_ADAPTER = $(IMAGE_REGISTRY)/$(IMAGE_REPO)/keda-metrics-apiserver$(SUFFIX):$(VERSION) -IMAGE_WEBHOOKS = $(IMAGE_REGISTRY)/$(IMAGE_REPO)/keda-webhooks$(SUFFIX):$(VERSION) +IMAGE_WEBHOOKS = $(IMAGE_REGISTRY)/$(IMAGE_REPO)/keda-admission-webhooks$(SUFFIX):$(VERSION) BUILD_TOOLS_GO_VERSION = 1.18.8 IMAGE_BUILD_TOOLS = $(IMAGE_REGISTRY)/$(IMAGE_REPO)/build-tools:$(BUILD_TOOLS_GO_VERSION) @@ -212,7 +212,7 @@ release: manifests kustomize set-version ## Produce new KEDA release in keda-$(V cd config/metrics-server && \ $(KUSTOMIZE) edit set image ghcr.io/kedacore/keda-metrics-apiserver=${IMAGE_ADAPTER} cd config/webhooks && \ - $(KUSTOMIZE) edit set image ghcr.io/kedacore/keda-webhooks=${IMAGE_WEBHOOKS} + $(KUSTOMIZE) edit set image ghcr.io/kedacore/keda-admission-webhooks=${IMAGE_WEBHOOKS} # Need this workaround to mitigate a problem with inserting labels into selectors, # until this issue is solved: https://github.com/kubernetes-sigs/kustomize/issues/1009 @sed -i".out" -e 's@version:[ ].*@version: $(VERSION)@g' config/default/kustomize-config/metadataLabelTransformer.yaml @@ -261,7 +261,7 @@ deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in fi cd config/webhooks && \ - $(KUSTOMIZE) edit set image ghcr.io/kedacore/keda-webhooks=${IMAGE_WEBHOOKS} + $(KUSTOMIZE) edit set image ghcr.io/kedacore/keda-admission-webhooks=${IMAGE_WEBHOOKS} # Need this workaround to mitigate a problem with inserting labels into selectors, # until this issue is solved: https://github.com/kubernetes-sigs/kustomize/issues/1009 diff --git a/cmd/webhooks/main.go b/cmd/webhooks/main.go index 4af32840228..7ae19c840b5 100644 --- a/cmd/webhooks/main.go +++ b/cmd/webhooks/main.go @@ -55,8 +55,8 @@ var webhooks = []rotator.WebhookInfo{ var ( scheme = apimachineryruntime.NewScheme() setupLog = ctrl.Log.WithName("setup") - secretName = "kedaorg-webhooks-certificates" // This should be the same for the secret volume - serviceName = "keda-webhooks" + secretName = "kedaorg-admission-webhooks-certs" // This should be the same for the secret volume + serviceName = "keda-admission-webhooks" caName = "kedaorg-ca" caOrganization = "kedaorg" // DNSName is ..svc @@ -108,7 +108,7 @@ func main() { CertDir: webhookCertDir, }) if err != nil { - setupLog.Error(err, "unable to start webhooks") + setupLog.Error(err, "unable to start admission webhooks") os.Exit(1) } @@ -145,7 +145,7 @@ func main() { os.Exit(1) } - setupLog.Info("Starting webhooks") + setupLog.Info("Starting admission webhooks") setupLog.Info(fmt.Sprintf("KEDA Version: %s", version.Version)) setupLog.Info(fmt.Sprintf("Git Commit: %s", version.GitCommit)) setupLog.Info(fmt.Sprintf("Go Version: %s", runtime.Version())) @@ -164,7 +164,7 @@ func main() { } if err := mgr.Start(ctx); err != nil { - setupLog.Error(err, "problem running webhooks") + setupLog.Error(err, "problem running admission webhooks") os.Exit(1) } } @@ -195,10 +195,11 @@ func ensureSecret(ctx context.Context, mgr manager.Manager) { Name: secretName, Namespace: kedaNamespace, Labels: map[string]string{ - "app": "keda-webhooks", - "app.kubernetes.io/name": "keda-webhooks", - "app.kubernetes.io/component": "webhooks", - "app.kubernetes.io/part-of": "keda-operator", + "app": "keda-admission-webhooks", + "app.kubernetes.io/name": "keda-admission-webhooks", + "app.kubernetes.io/component": "admission-webhooks", + "app.kubernetes.io/part-of": "keda", + "TODO": "keda", }, }, } diff --git a/config/webhooks/kustomization.yaml b/config/webhooks/kustomization.yaml index 03c532e1d3a..6b9ad7e88c1 100644 --- a/config/webhooks/kustomization.yaml +++ b/config/webhooks/kustomization.yaml @@ -7,6 +7,6 @@ resources: apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization images: -- name: ghcr.io/kedacore/keda-webhooks - newName: ghcr.io/kedacore/keda-webhooks +- name: ghcr.io/kedacore/keda-admission-webhooks + newName: ghcr.io/kedacore/keda-admission-webhooks newTag: main diff --git a/config/webhooks/service.yaml b/config/webhooks/service.yaml index 04b401a3d16..24755b3981e 100644 --- a/config/webhooks/service.yaml +++ b/config/webhooks/service.yaml @@ -3,13 +3,12 @@ apiVersion: v1 kind: Service metadata: labels: - app.kubernetes.io/name: service - app.kubernetes.io/instance: webhook-service - app.kubernetes.io/component: webhook + app.kubernetes.io/instance: admission-webhooks + app.kubernetes.io/component: admission-webhooks app.kubernetes.io/created-by: keda app.kubernetes.io/part-of: keda app.kubernetes.io/managed-by: kustomize - name: keda-webhooks + name: keda-admission-webhooks namespace: keda spec: ports: @@ -22,4 +21,4 @@ spec: protocol: TCP targetPort: 8080 selector: - app: keda-webhooks + app: keda-admission-webhooks diff --git a/config/webhooks/validation_webhooks.yaml b/config/webhooks/validation_webhooks.yaml index b73ee382561..4eb57d09b7c 100644 --- a/config/webhooks/validation_webhooks.yaml +++ b/config/webhooks/validation_webhooks.yaml @@ -2,19 +2,18 @@ apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: labels: - app.kubernetes.io/name: service - app.kubernetes.io/instance: webhook-service - app.kubernetes.io/component: webhook + app.kubernetes.io/instance: admission-webhooks + app.kubernetes.io/component: admission-webhooks app.kubernetes.io/created-by: keda app.kubernetes.io/part-of: keda app.kubernetes.io/managed-by: kustomize - name: keda-validating-webhooks + name: keda-admission webhooks: - admissionReviewVersions: - v1 clientConfig: service: - name: keda-webhooks + name: keda-admission-webhooks namespace: keda path: /validate-keda-sh-v1alpha1-scaledobject failurePolicy: Ignore diff --git a/config/webhooks/webhooks.yaml b/config/webhooks/webhooks.yaml index 324fefcb072..f59af6298f1 100644 --- a/config/webhooks/webhooks.yaml +++ b/config/webhooks/webhooks.yaml @@ -2,34 +2,34 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: keda-webhooks + name: keda-admission namespace: keda labels: - app: keda-webhooks - app.kubernetes.io/name: keda-webhooks + app: keda-admission-webhooks + app.kubernetes.io/name: admission-webhooks app.kubernetes.io/version: latest - app.kubernetes.io/component: webhooks - app.kubernetes.io/part-of: keda-operator + app.kubernetes.io/component: admission-webhooks + app.kubernetes.io/part-of: keda spec: replicas: 1 selector: matchLabels: - app: keda-webhooks + app: keda-admission-webhooks template: metadata: labels: - app: keda-webhooks - name: keda-webhooks - name: keda-webhooks + app: keda-admission-webhooks + name: keda-admission-webhooks + name: keda-admission-webhooks spec: securityContext: runAsNonRoot: true serviceAccountName: keda-operator containers: - - name: keda-webhooks - image: ghcr.io/kedacore/keda-webhooks:latest + - name: keda-admission-webhooks + image: ghcr.io/kedacore/keda-admission-webhooks:latest command: - - /keda-webhooks + - /keda-admission-webhooks args: - --zap-log-level=info - --zap-encoder=console @@ -79,14 +79,15 @@ spec: type: RuntimeDefault volumeMounts: - mountPath: /certs - name: cert-controller + name: certificates readOnly: true terminationGracePeriodSeconds: 10 nodeSelector: kubernetes.io/os: linux volumes: - - name: cert-controller + - name: certificates secret: defaultMode: 420 - secretName: kedaorg-webhooks-certificates + secretName: kedaorg-admission-webhooks-certs optional: true + readOnly: true diff --git a/images/keda-arch.png b/images/keda-arch.png index 87e49841834297a20e16fbc3920bcfc9b0a27be6..ab574853c51e4f73e7510a6bca35f208722bb7fd 100644 GIT binary patch literal 99764 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

y>b`}jtl#wg(azyv7?>^3 z>DFsV+nRs<`Ze?TpAZWOOCLUzf{XWAJUZR&q}3$^Pa7Qdxk$4flN$h6pqI#ZPtWVK zAJUIrVNXv_!)B%p^oSaTKiBaJrBz9R&F(oPeX>IR`RiAewk>KY zTcMTjk6!QX^-8Qlv^-fibIk}>;lVMvS43D$t%-P+6h~b0a zJ|bS1%Dm03={lR4KR4?C{87YFIFm!$C=sn+zCN9fk}-dj-suhv<0P)4TY-L){MQS?}N@?`Tv97(^>fd zA0MB`V1eFe#irwidH#R_>JNHK+}zwA19q4N3ID9m193O7^77Us9PchFoT&2f@Ce+y zcMpF-wZ_FcxiO?#Ku}QSfnB08TZ>XuXF)<|ega@r*Uz0AoJPx|;81;>6A}}>!F6Xi z`TqR*Q`kzuIts#9a-5hBjH;{?+7o9l7iVE@ZJpdXJ2fSVM?fIIyr?m(r(n6ewMB6_ zP7#%!KZYdimo8nBbNK=>c0RTyfAR_7Vj@Mv!K?^0A5RHu+_PHgOD7P)WCQFDIK+EC z4d$v0v-H(Ftpulmye(VA#KFngdSLx5MKmJ8!{Z+SQNb##%ZrPtusmvr&3#xx>>M1F z&!6Xyg|H{jB*%g?Mea;4D4TG-aBh#|ot!yCLb|HxdjRfU-rm%QXo_LBhY$I{HTE)f zzR>dF;^Lxy_;BhAx(0@2ajBVY@XciQ2Inx4t6|0%l}`nsSJK+L@bv7=cQeu5-5p{m z9zMQJ9hQnHY`u$%3&Ero1_s9Q@i8S0jVJ;LYBh9TH0GkFIt+KejY_cMP;^z(XRqrq&h4}$ctRF0xQZi z87;;?uk0?)ILIq02@47e;V2lJHu%j4*Nh3DzxU}9lW)AX8J^D;5@ zt_{gA&dq6$2@99CY=oT}k4L(a12NT(f$%qcEMT_FVAJ!adL7Y>UWL)o4+^wc?}ITt zZ{fIm_ikB5MOX!3+z4V^2;RznrS?><+DR!@B5nX87!}P3ooea3qCs10YZ|&JPP@<` zt!)|x*f1@7y{M>BSu>@jc3%IYvR^&jEurb^l-#z-4z0?r<)^zJSdP6;5uCDfuj+w3(=MC-c?b)#tfH-P95ux%0A?XgLcku{~V_{xi`p%3KFI+5ea&pQmw%IIo{leUYsTbn?jp0H^ z2cu@K_$J%i+bj+^$PSiOWR^H?KmQXL2t$4lN`QPgq$lwQKD@##lUvGqQzcgWmSu#? zUABE9xAyjE6xzleRUW1*uP4>i#WI2a)4{2^mX0WTZ2E}*D*UCneT6mk9vK(gyNIoLsW-I z79f{YF)^q0+veuxe$E_)T=j;mVwZ_CA0-pj<~swn`zvx<#be)wa2t%v3iLYVS3f8y zC?tM|@D}dm?3^7<2myl?5pb=crIi(}ZVB=%@JVtccB%gU*Ki0GRSWkw>CHCJR{$+v zd3r5_o|{`OFRxxCN#akeq_t>awU1ZEPAT{(6+FwjFW5ma`d z+qJa!bzh8@e*PG2^_-5*#1@lxajC{3rlO0wMar4=`YX=LS5lX>ozj=?*Oc%qA-^?n zD+Igz@i#9}=5jeLwcVUB!87ew5+A9s+l<31`B7)x82=LcJ03%4UT$^Ik2>DFmZ{<~ z89U)Ut@4{yhwA&AlQowD0fiX-ok)LZTwE?d1p$73{YF1x1Ssr;b2&RPkA+XOMq@pV zuNM&)ANcVa!{(^#J@E_cpUSQB+%H}*JM8KE-eY>B)!_3!lmdkkyNS4$(jQAp`ibU< zy<#7(!;XK!Ax?Q;_cbpXFD(VezeW5a33g4c%y|DJ21!>qqZaZU><8>WKf;XMM*g~H z0uXqSRwIs9ii)(fbTZm@rB5hP(7#(PaYK`XQqo^IG<##TLW0YdpMsn`wZ|C%i0`lk zT(R&8+&S|rWY_pKT#l0&TTK8|u#Fvxlal)0Q#-wTCS^A{&UfQusL5t5cYXblpC?sV z7VrsGZ|>Ov#AB6wg+iG>LGCZ)Q&LiTGhGbRh~cnG>2Z#Uj)tic`jRjy-+IBI%<=)^>=D`bFCpCRV1|ET(S;l9a8q0sHy zw~?K~J|uo_o%V*8K^Z1K;2H4bKE)%q5%!<1t`-s1R$u)d@Ho{u3;Gfz10kS4^XJ>j zXo=aCXaAs3$n2Q4EG#TW3Jro-%_g#t6s26F#B_YG_DZ6!lIeJbomp)#YNZu|I<0PF za&od<`6gs|EEY5TV$tkU>gwv);ddYiA(@YxE*aQi6r0&VidYob;VRr?XS#Ho}_JUZ(Ry*=yoq1!+F*<7>QquA2(Yp(eau?XXmiFW<@ zhC9EqGEz%<38f$VS7-^a|AhvLe#E{HZ{!w!_3s8u#5CgwiFBk=85h+>MpRaTCaDo0eZ=d8ikQ4*I-yvyST zZa+BeYogcV)4@%2FSJJ$+V|>0>pJ8G7>1_1AsSWrc2sd zPK}cjipJ_^|Ba9K&ZmS+xw0&zaNpyfRPJW-uovWaDsE<3bt0 zvj>@2WS`qIsL|hmtj{kV`y#!_Xwa}dl0{fZDBV1+VzauDV%%YWrN8#Zr9yrPbSy2U z+>X_0=A?RhdfeN8a3fhwB26pSi@#uBNAW!o1TV?=bzHCUx2eVsWHuis41?*iyt5`R&!2R139u1QOmU zb%>FXtm`+pd2BLXaC3{jy^dldASKN`9L(+=DKdf(xokNKcI2aEVK-(WScd^(4Twj? zdP%L!QUDPLTvh~g$2^Q+c%P8M%_uh8$lF)DjEXby9GrLQ#PE&Fgs$$wb3t9I_A^F_QjOMFXs5DZDtH>~W z=jAph@R$~p&8{ErQh%`TJ^$n30jBi=Syt9ldiqX9O$VUpU@p(+8O35)<+K{lGe9zK z`_2c$smdj`F|5Xl8q)_5z=%X-*VopF*olF={A*PX#de$OTE(Lhyl1ke6?PZri1bs$ z9ABwb*s?r&gksJrEM(4BQ#qZiADV4!HK{pXi1JmkNPOe_9KJ;j2q4?TZ4a7^M?{nX zq7NM%$dIEJi#qT~5s%6MkJag64P^e>#97%uHf^3j^gA zf`kc43=oE_zJA)P*RQE7FHgnr@cf;Pcub0T3@3{dA4)&T($caJ@{w${3VDq;0wJTY zlR0i~C`b^}AO?{=g`GP8`z$O-$KPxp%=+^x8yoee_@+p>O82IS^={pMA}V|xCD!=s z3PvG-t*XO`8teHMd~cJQqk4Lu#~pQbbx9!JK5WDqS1R2E!v3)2~n1ueIq{?WYTTxntJqF^eqO8wzvCeL>7J6=z>O>%RhN(|)5ZnF|RaRW<6 zQ85j8Ihx`k)A13CnUjNcBWZ9ZEr?Gl{NQ-7CIu@14x!ZqAtf!|e+JYM^&JN#8E|^) zH2Vn~As~x^&Va;>&u-GW-dOSWENPFA+dU$t zhE5QWTe+v_P|nXT2IrmTP|Hy=iP)^#T0W>FMSj1D>gqwvRoMsd!eFJTlDPtn* zF9-?_PJ;DAYLYQl{FTH(5vbyBbMXdj6m17$3&z9GO@^hEbR5_)vL>&ya7V}*9)*Wpkfv>dXTB%D>FrU#pZDQklBd=#g~|`*LfGyo{2xs zr96AA_^$egmmfcVw6wKl1x1Z7Lx2;rJ#Z74H*QG5YI4-84D;4MV!SaDpL*a+!rhQ$ zjdE)+Z1lTMi=~tT(Mr*0(z;g+#3>7tLQ+diN?!SfF`hP4 zz*%S1d*3x0Wo^aPo#c7v22@gFBWawW^mAC^{yBOT5A>1b_9<@SFS7vTyLRJ=y_qJY~ zv%EI}t#lZj+TSHmAxrQ@mRR^*<$Qq++8|ZfZ>0yF#vbFw?dt}l>dVqhe#o}mFD;!03TY_QpqcurFBVQSfO+$LyT2BEz}6& zA((|qTm=EGa?&?k@<>ER;fQGw`2zAVfE;rqzkY|F<;<)`4eKslpT~{UP`VV+@MJY z$2&R>`ymP~F?7?P@2Ynt2zYConVBIH)UTeGk8Qu|j*3r4+*$Zc&Ba+RvT&d%y|*>a45JC?R4FEZxr>2@ z@U8R1<|?S$3xFB4YmPNG3WD%^K#oSp%-h}|3Lr@?9^Exl@4XL1s6ft7yO5<*fy(NLYpxc%f!II?Zu5!I1rF`IaC6$3-X2b+}f zmB@r*!t^N^Ue~J!FU7paueV#t6gymkMTS0+ABQWtRiM(17v$hjs)ppuV7>Hs_P-(blV{3j_l%WU z$w^AS^MSP(!1&4?^v4s%a_8!!p|FwO-zccm!$zRx5C`qPP$swkQN3? zPEKVY9V59Niv^Ak)(Q&n9A}=+17FO-V9=voAIqMu7k5@>3}vS-C{WA;SsETrh}#R% z$qN}@E4TV2W21By2l8h^tt;1X`t+doluIq&G%zsm2cQbGSaEUj$*LFri80z1n~=SW z(BIRu3u4&*N@i|kI6UGmQp%7Wy{qB$1M+67=g)gIPbFMW3e?prhmvIB2@>@5%1Je+ z_4(Gj`}_Uimu)(^55sXWQIgc4b8d#my>aJBgX)g$nrnb)ku7p^auyT5isIWPz`vIs zAOCQDc=vPt(Wk3edtvaLXi1bC5n^m(lE%hay6ut4kj@fnWvi47+y})6duCC-l< zn9&Z3>@KAL{MkiV0Y&a2D1QQ<6%~FHB}t47Eez}cVAP{WJ9~To z-7n$fRC!KFn0A4#k?_fjGA$QEHp}N8lc?x5R2n9@x!!qvyp>l2Ix(HqNmxo@HjmKx zjG=bvT|+I*57+1b(bW4~Q$Lgb#^bv?*2O`Bod?)EBVx^<#Tv*cpsNi-#O~J&=Dk9Q zIE}**HRk(P_Z7$BfK+!CVCFq7Q&Up}vACSAYWx)wGMIdjf@GS9)v=dQQ~%%%UW!N0 zCc~Qt!hN=ErAZex^xeKXd&g0-{U5t;k3GYv+ON4Vl75V^5%e!ED&=!j-!l^6;U$4z z3J<=Hl0=@mPn8=l$#|oTlQ1gTJx|Fu-#oMBx*7TSMz@rfQ$6aKQzG zilF}k#s-Ijwe%2b1;gJSH^|7YxP^LO#^Eda^Jme}=70Jwmg?_ktY@wmJGD2)DO)ypDX8CCjSL>RNw`_ z7zs!>ZCE?USPC&xUKMWEVL?US(nAjr4fV=ji8k49M3<+9_zr(_TL6#hcGauoWV*Yc z>T<*307}BKAW~mPt_t7#m!Anf=BokY-t2rJjMuBr3gr2EevNCIc>Mv#;A!Dv~5rKlM87k^7~ z^O|E7_^g%m)L3a?cNh-<-!E@wDzGU6%Nm()B+%1@f1}OmwYRm%U@_-jC3&mVM+eAy z3g6MNnJAKQkFNTktM4ogPh{jm51I7Ut5;#e?%hcMY~7uaiv?IQe{a|N>ZWiBAy#>= z_bG-=VASjT5LEZ+DbZfPYg(kX7DFcw!$_7g;?WhLi~26gQN$>|I<0{yjyoOmMh!dq zneAVwX=wU3T@JZ`XlWo^9WF>cuXZSE@Y&8h9J~Uk8O=E#SU($&F4w2i`6u!b+gH{G zy9(J5jHh=tIMyoiVVy!=c9yJ*TuSh=DYVfcNw6`4-)huibMe)AH8w?3;%BQs|w#6(L5<5t&Ia$qB@u@-|)~u$dx+rMe zhT;cU@}HaR@e$M!1_H+eAD%_G4i*kpXLYyz0W^(dm<01G(DhK{dU2_R4Y^!j8+Kkd zOS^gbb5^Ua;7j}+OiUIn`$CXGy5%I)9}J*KK9aBF&3MrC#DAss*K5QvvuJ_OfbI+6 zB=u7Uj3wlkLSGE0Iq&JeV;7@&@%RIq(u@X`8xUv zj=nAogkL@%3_hjJ?~}4JEKq~|H2!s641kWw^l7xUwN*azcU|fhBxkYaM{efB zW`LXD2-6kw9S`i)KbEsgQ~f7*;h5qL@BLeD_trIwO~Rn-N#VF!#f8x=009*B7Vlaprn zB@AW@`>UTZZ~DqXR4YiV73Q?w?)h$T(G&E)6T14 zy&KK@Y8z*gDTsxsqu1(M{;^>V7_yeu)?P@XLc+pQE0JGusg;#40RLph$Mb=$l!M7^ zcYYEf@E0-VJ=+V)Q7w~pqOR23F8!%pIcM?yM~+%WN5#nL!($WtH}sq$w2Z(*qEuQmDP4gvfK?rUpneHr-P;Dq>ge^1W1k!5HP8;HKj8J6neqtjL!7_$>!T3^HnC%&qG z(9_!+5)klXEsI8HQJb2ad~s35)~|kcE!ucqtx_M)^!V8Kf>Z!zN+yIrm}Vg=-C!$X z&ZZnFcjRwAg{r0v6d#~C8t6>!=nb`i2Xj+PX~2VL0ua@01aVRT->3hHeNm+~g8+Y@y_<>pBAss)bl+^x0u5d;Tl@hkN-dq8 zgP$S4v~hwQvaifa&t{`XK#kLm(W{J3ts+g(pJb)G>TCJo(hmr)fGUe)dqH=Cfx%^d z`9noqs=DZ%2Kj>Y?@P&Zi;L8hltOwPPw!rc@+3m{FU7vYur160iW%6UjZiz*(BH`W{N*xBAk1G+!6kI-9XZMf6v75Wi66UyJT3LE9C76I_HEL+`=YX}_r z9ajQn;IiNIg<k21>`8|?1REA?4bS8XaQ zEp4jr7XVOXX<>l?_YvsoOML-Kh^SzJftlA|^STt|921^t$HS4W*H94!8}~27Y8fY| z=$V-rJkqB@^u5zV6Ox+NCaN5L{i{GHpFe*d91xr1 z(U!N?n=E*IKq9P(P5dM&IBSU6tSXkwYD(ltkl8iVzUVBI2 z-b*HwXCc#xKZXbp=?QKIbJTKVjZj}Z^ecH&!Pl6RJ#Blu$j~^K6QKY>f2!wH(&U;F zOMgqbyg8ue{YPb{)yKMJ<>f8?{n~qz=pf|8Y;6DLd`qSK^(}n>j#VpEs(FW(O@Xbfv^@Wxf8{~895F)T^3s-p zc{j_oZJ}b(-qFnYq3K`j17e!VU)4mYBnKVULGCuDFp>MpfSP zBdyQqp^=f5SjUa3>NQhOCufXSF#eB0mls-ETBNn-q=-2z`2z%>6geI)hQuCr4GhSi zpUdp^$w(L+dEUm3#=8USgPPcX>q9ac`ioT+2%C|_SH<44H5Q*7ir7UGU!nb@py86v zHt8fo>Qb8bd)^|9ob3fq-T`kE~BxaOcUplj1! z+U-mpe&N6LH5B>|ev)9a#|J1Ea4Q)-=?EvjBG%i-rwp?5OSZC z!8{&Fgd})Gf2ss2(7j>`nwklSn2~aP_|lT)sD^r_qU%X-oY>oe=Tfylo`H2`rG^Y& zLIsC`2^Bjhpq3`r#sZ`QHOWt?@MAqv8KDy zo_-ndSI;l^uY16O&=%KJ>IWbRl+ZGSIW;J~_(}qVi9W=($^Xz^qASS9ROE7o;ecMR zx#zE@s%m|-b^FY*oe0pypFi4syU#(bk-v#~ocQVJ=o*ej%`U`N2VLL&P5bU6`wPw& zbJ6*Aj9OE9Ry$g{&F`Zwh12U&iwlIUOz#zfz`(+IyQ}i}=wzjD`6-f#H4N39AJgOF zLfSL}WG_)b<)>Gmjt;x$eg&Lya@+m;ZXXMO&m{`A$UMwRQ!1%N)$cC}U z*?hyIkDplg6rTNb^CxgM)m>ZfUjrQ>4579iDUGo>4M`#_$FLL#j2ICZ|4?Xen~S@g zhZCWh?LW*~Na1g{lRH(#>^K>cwv+nY`Kh}J` zfpFdg{?vz$pV+Y}w4cqA;*(zN^2P_B?;>dlqJX!jeq;S))&2F0sM_CqFn_*v9(oiR zt4&7bxjCH6%k5S(pxJV1UckH1Y*Rf*`m*m}b(Le$(R`TApxTrEY@@r#WpFG9x)!0N z6iTa+vpIXT3DqpI2r(%sses`}ZlN?}JPKgGx{X~O^G{lRxwmG{sr;!cqf-kQqih)0 z9o0nrd*>@}+APf^Z#q?!PoD6FbOzwn71WguVu9A{BP?g= z$xTYoL)(opspUNA;~t#)^_p3`>EC0VgMN71mne8EQ|}s)JH$U?ZU@A-*vvKEM&2GD zhjfYqM)Dy;OAK6`c3Kx1%AGFeB=EZaBuTwWPD`R!Z5t&dm@3aoJUE9s`F+%7B z?Q*+xWqy281&)0w)VNHX%+tFLcbQ#EQjavA)xY-^a9#sDK87-5L zwrj+JL9@xVx0puC7jk|RJl~Up7U?61uhI^gjE@>DTo>eKO$!3ZLg0THhe7Ih*(sUh zMoB4mX7Jwp7plXGt#LY?rw_hwYHc>valQQQi?G~XrLey7ychuM zKG3tJ^77>_9v-s zBI>aK$#w#>vrS%C6UQUT`~QOK{~#kSxx^B`@fx;XamWC;@Nw6RE9Dm-(KZk-nt+6E zN$-{%a1BXRx)1_C6AwbUHz^>@UaN{{?)L3*LQk8YB(=e|W2RhnNU8D%p-Go&Ri1O# z%&@_W4eyKWd?)Jl(ax~eiLx5d3+2T@ze!Sgonv`r4mNt}$$bbeG4FqTG z*_5JCt43X!wP{P6xoyF*sEJp@D;oPmuWxxj=<%e&LV6tw=YsIdoNBaF*|isiN#eON z_9vd=q-qMYvv+WELaVG?!3QX%4(8tCO6b`;T2gQOLAc4CJ)A1EeL zOg%NwlPyp2dp~fM=quMXY$^rOq~v5uKx}{mxgdF#0I3=K@fg49&lAm2?HuLP(`0Lhr8*yH=W(ZC;(ti`txB8PoH?$vq>*hrZl{ zTEtuKw>%r~OF1aWbz#2{E6W>gXbW=opP^>9soB2-KQ6>d=p?$7#xSm8na)vgrDjzZ z0{d&c6CcL(hGY!K2gD~`^L}gAL|bVLp4hC8Hlk%Hr@GQ?)<@(H$L(r1Dmr(@e4$ys zVW$R38OiZGzlaEn_e9KT$MCvVD|g1hc{m8QF)u6uP=( zBfP=I-c%h_#c8_+C%?F$*Y>6D`ntBB9(e^0v6rhlX$iFIZtHcAK`-qxlo-JiQCEkI zb#^i}xE@Vo%rDGQaRJKb;Am-Xma-M!J{yaE0Ou@Lp#)JVTn(qCTEUJ@a}f0o$&AW` zjwme}_XRtk%rk&$X884SNHl%t@#+8R1$Y*nIdLQMe$?3RJQh3o;iLJ65w5bK)p zi@VBIEyt~uc9$cZ5~1~_?lL!|y5x#tDMmqyu*-3)hP*>aAJp=igOZZ69JqX6^+Q-sADjnGvs&!vr>uciJ8;nSDk>_&BO@6m6}I(sgU$&6zDCRfq%7 ztsPl^mhTTVoy~kp$VKNwOrwC(x-04;Ep086iH7c>CsqPsCZ%aOR#I_JtlIg0NJvOpR+f`Bx_IrJ__Nmh{5%CUbtd|o z(PaQ7VYFhl76G&pwzgk_3UI;6b%D)UJBFLjTIxK0{Me92wD$J$uT4R(J>pkYatfJr z7lwL0QYMqdWEri+czS=9Uld0s>qUkJ7n`K&8nfiBvR1DIPTQ`_O-2{3zRT%3n5s54 zJBk{aZi^DFmCDr8)%~JgjO38XagIf%) zZDf<4z0|DHWHmRGX}g7Qm1+xO?(;J^SuF>A4K>nB=T+Wj*IPk0&D z+o|)n(>y8p^U#}Ka&=cP)T3a^v+&_$V>XZs*yZ9%O83B=NNGER>ANe{w8|Fha)A(; z0wN@>Jk|cj64VMTHYd2pYFsrk=aY8OMYJ;6_B8+(^kK1_)=TmBH;f%|3(Y1;pqCsk zw3~`HlO;QEZJ_NY0Re$%H1ypDbicdomlknvXGg|Rt@LI{ntd~_J)Do^*LE6N;X*yu z`aPf87uXc`;|^SW`?gfCCR~2c;@E(?ul6_2$$?%d|Ky{8|9)5G06WZ*h+9?yJM6AB zRFP2_LNK4Ut|A|t*`vGMRW+d%HEJVAJo_eg1T#^GO}4REzji3HgT8Gdwa1plC{s8p zBu&u0!IX;JN@Sn-@_z$nHvaQKiitDW;Hbbii`X%g` zkB02YaOs{=*O1tJZkDF=S3YFb{Cz8V*RM!1HCIXdt=8M^5x;SVyQK^&TuUyyUYxR) z^RV7dk=Imx5$Vwqc)8yyMiy6*(|@z?e)8wV;1Rk9Vy~noUJlvV--Pzux_R7;8w^bM z^{g3kU3VmS4nl&WLPoskQ=ZtI$$C2ap)xWp=I3 zJIXkJef8gj3>iTuMU_RiuUb{mneThoeohn;UcZ&GG4(rO(};UetIG(RWYT$xy3|(p z?#5)cq+#xN^*5jQ@*8#?ZhkY^8j3-S@%>7VDYbw0I=!lpLU8uED$3j(+oNDG{F?uy z?0E}S1Ji5OJ>qNs6L|fzH2(G^mbDAAMGhK*(3mkB&eX-9L)SKcT|*Jm3M_G0r-(0> ze(-nK>b#9ja{oIwj#?oTqd@L=SJsvxvCrt*zt6eNLQ6K~87b5O|M}o+S6bzp&dz zC-B!75}zyb8;^o+cA==Q|$TA4!YKJlv-y4Qnq#ce@^VQ|+{Yp53$(kUZiUa`k%iXZ)|xu_R~y1!Wc(_;ILAQnbUZo0(S|K^dljQFLS zH{Tzqyw)`=Vk`ROZuIbh>Xj&GW7N+(haMrNdQ}}on+t>Sd1BcQ-W$$SWPUQNZK2*= z<@)Dw;vE7aFZjO%H&AKi7kr)F|K47vW1JRw`Yd@W(VzEE*N;ert9^}Lo#(#oQ6+?9 zMc+IP>ktiyXTg6LKR~%@C@>!5shoFoG|yl#NINfvu8Kp1n0A7lm6d1E2~_(X6UTw`7zBlx3OVH-+>eLkcx_EdfHb( zu_65J(>s84-&<-wotX_=<$CZqvFOm>*?e{5%kST>9&p);PEMzVv!H3$#!D)MbkkVo z|IQ9^2n-(2(zM6GQ%lM#C2ul#DknkH$sJ0{W|UiZV&l91m3{}mCi55$XCivhSeh4c z{C%hJFABa#Mt-rF{r!-@dwVr>{RHQmXA=c7%|D82fj0#n;nwq0iEC>=B;zDPO-)aK z+@Ew#T?92C(j6@({`r%k5&k&@qxrjkrDblday@{`aS+-YMeyfkaPc{j08H5?v2?>oNW0i{|bE?NF%&^xHd$n_NV% z8N*3dOn7KwW#whfJi=U2M^UXRNm6`a=K02h?k%fdffp! zTGi*`Ix$e5^_m_YHw9()ZYH~+4@ql39g{wOta+#M#Nqw=h|O<~M~@zP`bFSCU_2Ih z_&D*;J#&7swa7jOy;`|(%DVl6itknrFS(jUey(waeiQd^r7pH?8wv)8;eIAgxaq4J zQYTc^A7nGFyy-~Qg+ub3;?FL)L2&DGTBHT@b+KQ z8rAf-U($lEkO{tdOiwSd8zipn0j-)}PIr&5Uk{f1c(}j+S&^n8pwQa)Y|_b!0CrPr zt|`FNGu-3j75ASj-u>*+ktr6@z=J(vTE%PdC;fT@x8cdIHrYbW#U*I&@X!&N?XJ<_ z<>wdu^p>V!p9Uj!}}MQxp!v_f3d`zq{#r%Nte;6 zXEPf7)zbqJI_Tfqj5jm|8Q}&7o0Uw%&ek^0&pIMuSpJO_act1E;0{%_{^JjxO-JU} zRI!+B*A&;r%5YJVYHB83P)hH;hGKx8@U@}5*Q>jbOnv|U9p&ok3V+v4>D>=jm>9IX z&PmN)h0B2+oDwhNPge_!!$dI@-Kn$EzxlwCTWYaL*gM~b2rX{#sAB_t`Saqrq+ysk z4-QJdH-#3$(U(luak&aZzx6&n*_g$-vmyd}*K9dM>PzNlV*9}s$ge|$*JrD8yK50#~c zrbSK73SdpIu2L7?B4xxYIKP)_@jygGBnzN?shO$im!u@TtP@S_341ni!i%zRoti7H zz0jSXOI6T@H?OVtgoOngm&`{Yl2$=hHc5t5jMe1y1{n2iw1ZEJDBrP9*&h#jIn5Vw zVW~S~e;TU4wFO16+pdL6bKSnJSIb>!GRg`8F&=86AY0!p=)!k{E68$pyQD>=TO<_$k?xcfBn2tyPU-G`>v(_v zH{Ltrj;qKyXYc*Rin->RUw?*HJ*q^j5p=zQ0nQrW^LS@zvRa_~%X=Ig3{C>m18z_t ziIs>hEaU{*IZycBLQJa65t<*cC9EeWKSX=eM`1w8ikY+U!X{EXBLm@6a(}TQ_bGi; z(ZGu&(L~G9UDk=}nrz+o6eJ>OuwKkf&#zy{RdKU^j~^$J!lYm_D9A{7izZ#+$FsXc zp;SVoprdj(IwnR{^zm~A1u`P>R>phxFh76(92gu-WCB@o3K>2=el1eTNN^KKPKb7s zZeYLiua;ZfiIR@|@ZlY_CbH=lxMhbG;FziPwWN1|`HdU8z?R==&X^bD{WUU(pJTfj z5nJU_qmp~s-`Qs8+!HK$ivrQTsp+dd++#jidD`fieChd+0Dix!|*{`zsH)TsDPg6ZG7qfKjsF{apaXi@cF{Iu1T5OQ&bKad+C2#fNGo7;7C z^y}v9f1sUuWd3v1C^;k^f^*Cs%?#(yFD(8+bx%+*@^#z$YXJQZ8Wone&S|``a1o%w@+`fZDEWaYUQTYV zlUBtTIp{TnwYN({Xv4o&#om%{k!fo^O(vM);@pGvfpn3_^}N(OPr_~I`ls&ij^|7| zLg}7OMcjoioHB7Ty=`<$Sqmn4x-PJ?XAenU#~t&LUTHVz#ru*HQ{_8P7q{|Q99qU! zY;R-Gy-$~V)%lo#D5!{yPgq#|RS0^E@gGjHt2F!Ppg$5}tJP)Jga*LwQEW{c(wf$03$YIIx_C8H1gRc1Ce6&WpF7u4*( zUFC_Q$1|RzUiJwI8eCUTDsmjm8L2+jtl@Xis~Kb^{N?1`jlq zB0qd}D}8Nb!u*sx*d9OIYHMx}2VJ<16792Ic=tt$^7xYE=TiqKyMOuOD<&7$08fnR zAl6y#W!~~7622y7MW@osu`*uYtTuw<=UV>tMoT|s2A*U@?C;w7O{_hS5m#I1)k-lX zZQNRVaC|s{>lx5buc56uAE$q*P;~|J9=t0imx+hW*cg$&TO)hA#nOw|pBNgxRkVGy zlgqceHrP7Ws+Aj0aa-0%cQ4=j@`Xm?>9~#1znJ0mJBc(m*6a7J+Zaf=PEN^9Xod=k zURWrCpB@8dLcY7iTcq@0xKfiO(}I zaK9ys*f#hq1Qzh^bCavER~^v|O&ib~Wbjb>Il%SD5TCp|1Cp;-Oq)p{M8f-2Z`G`I zUrgHc7;TI7x-VEdLKY&w>DRjyBv6H~lK)pzxwlA!0}_{z8>%j`V}kMHjV^Nyvjb{Fzv7sD*%?%9xoVv?vaj1<9{|5$siua+a^Q$9%xbezz zq6}zVFTWQgL0@~mdoxexp7zIjr?ta32T>G)ju<{;G(9J^!c|T#x!%1J5?s62bU^v! z-8ir=1ih=-rvYD>(#sZTjMEooNzd14{$kQ_+C|<8v7ww?Xn#3iQ?`mfqp^b|-h%&3 zt$J5iCjy}lPkm6N^xY;RIlw$U&h0jm)&uv8xHOnd~JFPU(z#n8LM4>1uJSQD{d9bV#%kT`r1bi$V?F9734g4DtP3rl@W$1 zU~Uy3=zf>O8V{I_Xphul_x-M8n{O0uy5tH6ecN**3Q`PG5q0=I+VWID6zpzlisb(q}Q-3X2vVeB2p_KFU@|1J03IiM{R${hSj1XZ{2-CeBp zai*l}OIuqvu(2J!-p$NXA)2hRiy0H?OO%m!u8_gutEfOAupJ!o&tAixeG5@ADke+cZ6`pZhglg8s3D#JeK>c#jmGM{3D4 zODW!@n+@Ad*y4nKi5K*D?l`f$M7lW?G;~@zy2mS8(1=Wa-#Aca9gGNwGf0Swzb*23 z$`VUYM~4C&Mb)z#4Rd)v`qHH=cv-hkmQu*9ui*dYXGk9!7#b>Z`jelyyhWEnHw8^s zkJU)sDN`;Z-!CCwM%&$CFoArvd$8rACdWOrj9;|@C#NnucJ<_lWlgLi(P1zQ1wmzT z_ZrHVRqM4;{$@oJ3+26+^ztSbF4Ol^?}kOFUn)x4;g=-(<3r+z@vLmja;!`#!)jyf z3ac{3OY-A61e0Wk8PDb9de?|LzXfJ+7p!b%qs*4OUbCiPh$Sm*1CjtaPq*8mT2*kg^Y=rBsfuS4(CK7FO0opG7$QabnX!>S%M3^TrbYx;cH=H(IY@jq5<0;;w z>H4cncVtik;URupyu=)I9kxN|5&>bVSNa^Z%VU=|3kwSuK}!;7N?u1q#>V=G-tMGo z4ZY$1DozL`0}S7z_gk=VrkH{EUg238IG0t*xjC4*mL2*oPU$ACe7G^G%*vJ+G~+ zvk>|G@#A}_J5}%xMePzj#prK;_A>}>nElF1{x+^iUq{+W6ssEc~ON$j2>iKTx zmBC0+D#8cy1Z-&DdiVaePo&XEW5RCEzh0vgYAZd6p`h9g)bGy4gDG>Q$cD<>J4 zX?PkIm%Q0WX&b&eiP+|E2Kh}c&r_UFg}2~h?{TQ;_oJlm$^sfq%PtfvZ&X(eBe-Hi2#6bm%)!7xy%1!Hb;e;SS752CHo124r8k?F> zJ37RWpaa4Oh^ij7uj%@gBY2#aQOQi1T;1L8@E&j>gDnA|;jysM4StJYgqU|{N4D5( zUIB?GM9Z6?A|S@4J3Js~oDR%VBKJh?hHwtFI(`I`ux>-O9udi|8ObyU{MBn9AeHJZ zDfvE90Ccn<2ow51B-P0KYm!sDLT<&wEv2eTfNsQmGKt18 zn?PfWHdbZ_92>}J+(HUCa*eYI3+ucx(EzK?_3{T||HF+IjM%`r|hypGE)*lB@0@GW`o1HM@{38{C89cL4PWg z7ZWouC>$M83%LqNhWK5Iielj6`XMtD(R11@{{WgP1i{C`U##xt_raXf&8q#m8Y)yj zo#cf{N^TeMZI&2acW&89)b=!7*_ua#a1k~DWRqm`ZDD8#g9Z`Z!i2cE0LXdD+mMJ* z!TY7y_V}*E3sRHcUb4CMYeN_68SFfcd1D#5AO{l|taf>(Tc4RM=zg@JHeRrqoM17A$8nh;EF!bQNWyp`^bKZCvY#IsvKd zCkAIE^euf4$|5P+=*3IHUAJ`X;Qzkz#}vao8j`qx$ZA3rFv^$i7xtfsluNEu`M&JugCY3sA{%J@Lxi@&hq6W|l&) zdv%}U`6Z4zG%U?tab!)ic2+zQ>N;|2>J$-bME=z}qocB_>LDb`FDz2P6DW@9%OSTD z@|kl6b5+C5SRN^s>L%@w_y?(onHs=p9b$JE4UxC|F-(u9vGO#JC7s43nH{p;90k~ zrBWQPF3+}{k~3I<9xYuDDtAWEFWLV4_fsb)-phkRZ(j4RFb1w|rNy?dU+?DgJ-Xa! zqwya{yD?P%J(s@4qVkuB7Ig{NJX4l2xqx=e%?{JrMG4pW=QdK^=yYjnzu`gIT)S?q z^z65yikVhvIt{o;LwV&F7m7M|3;nfSP4i&)^1*UT`^jpjrVY3RWDS^0!{zHLQHF#* zkY%M-S8L4=gZV%(GnMiD=IDg-v6+L5i+T|Lqa{qBB z5|6ANhcBHLPkj=~SMxHlRv7Zfe#$Z?EyOC z1IbcuS;Dw;_R)w0J0Y=+gt1(_>mB;EzlH;tRp7wpfP^^UdOlJtHJGs=%L@8TEM;|j z-9iV{+Vu(vd@!Q^o(2#El_kdV88&0g9)cD^j{P~P%e4Pi70bT_he~GM03W!Wk`B4^ zEa<7;-1NRYSu#J%$jr=r?sM<3&_b;GRQN{NffnzF)Gn41`_=q@XCyE>W|@4(V`*8u zI^sWiX1jKJE2HB4&h)(3pj8GR_6`0r3mdhq;@=z9X>|#^{VTt;uUFaIhbU{0h6(FP zznJXl7|J+g`SJc;DQ_6X|L4I>$P;`SL0``Q_}KLw%sWWj6jfA6s;QBBU7na4{P6}i z1_Cr#K6!{A(98WEc=7u^|%EC^ZS|7XUpDYG{}AkB%nt05yQ7 z@^Xg4M3z_?!kahWJq;nnJnMgjbb2d)jzN9Wb|Em2@)#WUOHws z=fqI(BncnB31%5O*)gB7o;ZMHhhu6Fl+>BKJkX3w`_0Eqt(pUw(!Nvw9~XcSZUML2 zG3qxdvKece651#wb3IobJ&|T9FE8(1aXkn)8a!D>Qnocy%lSTjd%rW?HCD`z)pL=lA@3=PHaN&EP>wd-v!V1*T^IE^*hn{S`?G$=Q4A+QN(GonI*k33*xT$KU3SWmk)1`mmi;nEbw-0hLMb z3tptM>DSE6I}j)#Wq11@?vCo%)b6~GG=~0mg?HTGP6()}Ic~c3f|q;{85QLV;)O`> z144yEm%DF?cD;D9$t0wt?f_bCL~jeJ_VQ3Z1td1$U86u=&yEyAsZK7_@V{Z{ZK1c& zf+W)TOiuQ4aUn>4bT*6?N#YOkw2!Q>)m0sN~r?*`?`*S)*LA6rZAcr%c*(%f)B)?x=N<6SL8e+GoTa}l#aLaMo! z#l<%CfR`U`j!!m6nVDW)q9O4Jz{|x|952KondnhNJuuBdj2Xn*eIQRh25}E6iU%e* zNMr}ru!ID&`ta_~P9xZqBG917*jotVJt69b!j9!+wQ9D4tE&KXTSo{U3BNFf=1f3= z{1hq0yQimV`uh3=pov`>{NpvGI>^8@{3Gb1^|Gvx5EA;rmv<%cE4&KIYG@EcUJVp2 zv|?=RAwGKK=*RscTu_hm3Nz=K zlyQUia|d1vogrZ}d%%B@CBK3vG7HjvdY z^GB4I=p_}L;-g{JnK;$^xKED0#ND>C>@g_Ls&&HkA9Y`YqVN1$C}MxA7>rUgx0y@KJ4>~vE>?q;cmcJ(I50jJ4hV>T!T zHGS39jxV#&UZUuK0?Cf2h1bFAO3+Bc13OUjh~0y-CiVrgws~_pC={o;J2?mf@Yo&r zpm|=XyhQ#B8qQI@@0kjpl-$p4&2O`@bs^Jd05G#C#Xj4iB4!4Mb64KprN2*i^l;G8;<|JJmHNhQ}DU@cP^7%Wi9zow_cImD%yBAT5n&;i%-L5!;LtbetLsu zmX9hjbuu^oZ1>C=t#yU`qb)KGOg64jklBm~xc_X`tPy*(_MoEP4xLhI1FC7t%G4Xr z+;~K&nC{>2)ejLD=j}M8c>c)H5EafQ2#6qKH@EfJoA#!W{g^fVN3FhL1yt~z#!h_$ zakO7AYp`kGj`$g%dQ{EM!Xkco;&JWzb$Td{AgiCtii@$hQvJqHhtctGkFf0!5tf^H zR6czZ_u<1`6~>DhKm2#jZi8x1mgWu8w+L1<@}J!Z6n_6`LFtO&+a{WR_R)8~Zcl^x z39?DYiKKk)R=&~=99RSZXT5M)VkFnspZJ7pQNgI>FkWVsE;EkgZ>geku&`(`Q#!j^WjE(wZo~|u(3DshnfQi$agl@|q{Y=Byk7%$}c=uul$RedDR zkT%qoTS%t-;*YKH{5xg(4V9r=8XECNpm-&IKR+uA1JX@|EvTk_h*PI!mUcTFZw*U= znkn!0<`S~%iSX&ld!cdAa(jZgIyJhPg_ZS*&O}X7Sy?l1W)TP|4 z4sx3nAMF_gIqKB3G$NpSx8`sT{QyG2y=-T=2GkSLkk`3;_nM&{)9fkW-t+s}4`hZp zVcg+s+r?4}iM*ASvG6j#8BA7R4V`*;LFv4_qcVZp=1XlY4u}iB`~LkF7Pbv7?}2Up zFg^WzQx{@I7vu*23u~|-C`Ib_=Dx*z-`rP!r+{p_&xKs#_QNrkcQJL@^*?FNi;+mc z>uSq;Ljc4@%4A5($st`Wa46IY7e=9O^6pyl`Pe~*tJm)Z3c_2Jjeh8JN#$#{ciDB# z&^66h4IphtL!hEwL?tGEQ^}iM=*SCbJAdZ@9Ea`SV@@(1Oy5UF_=}<1=xRMzDo%ut z*zH-Gk2QM{Yf>Y^zoQ92Q3#YXUpBpCQZ*UTj89_yO}U4^{S$ZqhENUoKxAt%?1hcc z*eP^9J{28H;b(b+4j@u9l%;2AW|-LWC(Cx2fJq>=zQ)Z`xR`u;rc1}_+! zAAl+#5N6p@2w|DpgYEry@a!x@kznzFz9LgPy727#5|iZ1e^n;fc4$6D_({nlN_l*Y zZia;=Y2u6V)y=UZN^EzleWV`jpQD|toT3s$+q*6Gpt*i*#sSpNojxo5nh@t;PmEEj2`5qWucChuVmSzz>Jla-Wt&tGw|m z3qCbL4Npny1FmcoQ=UZ0&Ulj6u$$LTOFDDAvS)hMh7o66vXq$tpA2>n3L=lv{pVOQ zS=UCjxs#V{U(Zh&n5lA*zO*4GdcvvTsrfpE*Md~fRfFsGaKSVob%Aytc}sIY7y{g2 zZUFZ=7W4_Cl^mu$$vWfMev}QQ-mY4qSjWET`*nIiDw> zW~&5y%UuN&byTOp%paee8U1{JH#KJ!LXK2|<{|yc9JWr?i6;tP>qx?j!!jT$R~`p6 z`$s#C@D?OMV++2!_<<=vLW&u0)?g*PLlT%pwgROSynSduvgB>qz@6=bgO=F=p4d4o zv>WIK04KJ0bhi$^wSbxTanWB~-CD$td9_>jXMiwdkZHm^C!x7Nwz^r@3fdmmHoy9w zFU@=<{5jal4)sd=MXd8BF^giiJyKBJx(-|=ZmY3QbJHclD7$|QQ}QOFfwzBzRL53j z#3u`@!`AC^ZBLpx%_CgOh|-seGz1QD`rJI!uc~ur^n~_=x;IbwHzzriUVA!z`ux+d zJSR=d!Sc*{RqUCnirR+|hqL-eJ5LYJ_<}?NemacE#cK);yXAlMAyEwCiQ(ifFD?B^ ztPn~xMq=Y#v#Fg=Yzq_$2+o_`_x@s9LC?6QZ8Cr}lHMtot{_PxouLf>(GgafADCwx zL-_NNQycveU#<->lA4O4_TA;SMAhYaq)Yi+p$!JOJXVh+#s<+l-F5@ckU&B#_V&^J zynDO59}atCw4p|d7hym`k@Z!DU6$^mPhrpKDY)nleVB3 zrXBDM1R+n&b8`;?87^6rjf5x#yZ{mzj)xvwjqmo?*Rqv$l4lPF%+>g5{yB?zp11XXdl@n zrip2QytA&q_{&U6$0(>3uUg4Sm)aLC{U@{M2LOCU#w*GrqoV-qV|oJhr|yt`irAL~o_jj4ByS_Syw0o)fswHO#rn(xN&K!P6&bFPtLQkgN|6*+XV~$JY#ZV z0tcXU8jc6Lx)!@d0hvsI{bCmvwm~R?TI0OY_ji^citrx^o4kolzzWH)TbWErEBX2; z_foyIg8rzdkizQxyll&MvH8kuu=2QxFnPSDq^}joI!_K!HV!GO^_?h8oxR^jzs-}R zXt=lUVk2_J7j!$xaE_Uq`>}LdN5jn{niuQ2xMJSkmL~`5 z$1hE_1qG^fU~}AM>J0wHOsbAB?qsezbAyAih+!xRZ}KQE-c}GMq}NFYY5x@sd1pQ~ zk3*YS2M9%sAV!d}g0>XtXYxo7DU>8fvD<_L!J1kUL5#ZeC$8CNsqA;}N>~Z|pZ5qoezYg7Ej}N9F(!rn4*oU(=)1nM(fECtr;TOcIjVkveyN zsM&0H*ErnXkDvNP5pc5Q?W1`W`b1h9qj^pq;RE_*X+=c@u+o^>uR_BU!fW^3kTLZ7 zWQ~4&UtEF{7IKn6^|3-cCmG03Q*JJJq+M48Fti97PAv>L+wE%xbz?un4!Hmq#^1jt z2xAg;cD61;QniL_cL>Wxk!=5%#wonxn-5R%(i?wSF95ewE%q?{BYhPl_fnnmRr^Pb zAIO$+_d68M@X$V+X`pU4_2HV2vp=OSvfCA}Is8`Tx-Jzv4;(-%Thz_|=_Eyt4t2B2 zwfhLXNtzqLkKb6|V!6%$Zguuo&~Hdy#r;}sST00XIn*8-hnjC{e=|#|09l?p3=FrR z)^qzv7V?t8alKcbb*EUBHdD2j2td7(dMg%(^(lDo9S!_ZxFr43oQ{m2p2w#u9G z6S60;`ADA&Wp`|cYu9m`7dm2Y>sY=G)WS#d?1MaLs{S=B$oRGAda~RvwR`+Xf?iN# z1PxJrEbJ%S=G`>*k>1`phxwaNkcji3o_9=)s zABk(04icsvDlaNFQ zG9kgu=DI1Ms<&?=F_{OeTLxh%hx&sq8qHPu79l#h^b!C|GwaX?8s5L=GgCoA;I3C z>USe5iBlVDR_9 zk|8ii^(39U(3L{UApH9jtuQ-F$l{)i z3slL$iyMLwRAl@&?Ch#l(V@buQ%H{w!A-oV=lj;xeBQR+#hql4%xcq2#@(f@0NhpV z4d$F42N}53SDR#H=b46Gv{tYewc073FFZTU=D+TQF0ChY=;l<^TNe5*OaO<%%bW|j z=@UF|u|$d5o!tAcb7#*0HC!5B@{Gb7wZJdIoGwU+goszfS7@;%XDMlX(0%>zTTUl< zSn0vTx?IL@??Z7B8-vN-nd|QpE9eJ{*|ml&AE=2j{C5GQd0+8Wz^4!xa~W_UKQLt2 z%;;x)=&g~F|CJVdF?vtN%4)nk;1$ym^j-Cp5DRJre;C_TlcxI~`E9`Y%KG}(xbWO$ z8-`8ItzwiPTE9;K-&GweNS;XRH<_QPgk~p3*$2e?E~ZcQ{#`KbZ__o(sL$l9dbX?4 z8pAyozJF@wlsG!120z_j$jp}-3U4v*R((FTB3hp*Gd?=WYP<8AXj6SsvT@~8`-m_Z zkFg4fbm5zGKk?w%;1p zlKzcy*7t2H@~F>pw8Khne-4s(FL`+!ZqP8hle=R2YdK!Ffm- zPT}UwQm#5t8|rYTHak(Tdb`Kr@b>sjF_5~HlXN=a;-hEk!*2@eEMEGx*|7O(c>Fh? zS(>mr;UVq0-+%r&)q9dGKS?9SKm81sdOX91oYFnKcZKqW1=BudfqNG(G@eOD%-j-8 z8MbKFE^@X!UTNMRzh$p{T)%DshPFHv)70VCL z4VlYB}AnJw?w$hRSRa)DP=$!{WZ%2RaJ z^{>@ckF!>umFuG*d!m)3frLgn7*(1`L~++GI8Yhi}rJwOZSl z<8Bq<&&==EY`bLG+3&GdQk_5%*;pQscm@u9nZu|4(;n{!pAx$%;0w>+931QG1k(JL0*cX0S6T&fXIjtFg>E@{K z8SpsY=UI(Z%u?>ASg+AtiPnIttHijU?wKDpWl1gc`#jpo+ytC9_~>a$8*_r#*U%o` z=P0HqeED;lVcY(Gi|_E{+PePU^u=$6^RuX|Vp}lK*aT#oW<{kl?Oe42)gjQ=LQjAH zHn-mfVa*B)-?{EvT<^GLXoqUXseq0P3CRfeAB5!yC}wbtj6a?>P-JbXn}z{QRR3DY zt|qdC%E^T67nJPo?6{fx+2Qfm!u4UVo->IPOA(SQI74;d}clcI9Y*2TaPLb6RoIaV9-T(cv z{@*V(5)-&ErNaKMyfm$Hspxef9|)fO?jQwl$1U7T?>q2JmS9E7xq9%Nd~d`s4m{fY z#J;^xV2Zq#-YUC!|M9)Q-Gj&xeY$g%Gfmu$nV)oZ2Cv}SL7aRQfTCM_+R+~H?yu2@1>78{& zGPo4_Y62YxTkWIgjQ8#D8ycEm9dYVbBfE08cV#*0*m+eWq6o`5%(AmxPcq^_aFYO3 zRPUCxHFrrEFBpqRO2T&@G(yiAHpzvG3&&H2Pk`e#VM+&7e7g=h5b0aQ^Pur)p6i>>pel$3WsvpPOK;dO9q{XJXvLgt7eq;CHvrqW*a z$=2oH==wLo@3IcSVPyHAnO&*gJ)d?%4%JMHlJ3sjasd!nLVHsJ@Sq#4Tv}dU{MDr>?vke4>C{K+gAEQHzIKHR(k)WH3NP3#`( z&(L4j(M(yc!EZhB2r}Ffhmxq+1plrsIb)O0%^-MNF4zFa0g<2BW;;u_OKWJ@UJZYI ziz#ql5}7_x!F}LJY{uG4vRab@`z!?fqw;^VBYec4hBG>-+MJW!zpqMfgqN%cDx z#IMfG_&{9=pV(exW@QC)*t%eOES~~oZ6_p@h&yzU01k&f*ud(2FTpPme?(VjA zwg(Y_2hE0d#KKtSxX_Pim;VKEeHcqoQPBs=OmlPoAthU6oHL*oQRRMA&^^q{%j@sb z;I+IoiSJo=M*l+$f#U0{qh|gHHnUMP?7lyft@W^ zAJw?pn~H`G$L&GgO8E@*P9hWBh%^&41swi{a&>}af!uug=2Tq>2xBiSEWBR-0}7g& z;@vIRyXE0lo!^frfds%iIQgZcUdT~AGvH_(DOVK{n>Y;OLU8Ij?gTSS6K)4C6xC;2 zaYv7u*8G_xnZ^tBo<4qz0tyzxUv}8o+5JZo;KV*j{jGXfApy{%&)*LwkUnk52mC1- zG1+X;?yoa}5830vid;uhXOv?3S3Rra($RXI4`Pcw*jO@b*fxZ zU_vDf3?^x0CSVoLhJFjtYB8{}!H<%@=6>VG$k<2|2n#@KRZ)4lKL`!jjw64pa4sq< zc^ex`0=I?KfGBK}kN6N9o0^x017}|`v$Ff|f4d3l+Rbou5M8W{l6|Q}mFM2y=M!w- zgA9YN0WJfT8S8!5W<&03=QVU#X0||M{`mN4dA5TAIQ9}>NsueIH=uA|av*rVy-vAc zOHmphB9oV`*Z4_wY18{bw9|2O;uf6prv6Y(PcIw}T9^TSO6081f?Ru(94yG|f=sXA zsFAf#?PhQUwzhS0dS!48rr4aT-u=#f#S}^R`HgpSjI*Jk?xxsmDSdGr z{PfpBX<>VJ_ibF<--`sOTq-FlekmJPS2E~JUku_8HkKu&;ZU^Sy|cR z;nY)LeiH;q*e(xXrpaS7mMv1N0%MzsGRn7v=YauHcRRS> zA8+H{aGyx3;KN&BwgMoLR=ZdXdT3@>+`1UiMAROg#0+X2@QlzEg?*VId|&x+vI}dZ z6ea6*!ZUGf>=3C^jQqTprf07^3iI>XEjKJ!lj2)+w_Uk%Skoje;24kbOLUxUr^FU=DhMwG*nd&cLyIw^+P;w)*i; zVq!uLTU{*EC>uL9`=40OahPE%qxxi6gA#8*8Y0Tqh^XKorpLo*H>{ltnj%Tzi~(S? z4Y@G02wrfa@o!DtK|uE4STy#(q>B|98nEuqvex}C+&I|p zA|PXn|7D(uyhotqTn9gsud@GQk`UqtaI%6(-yVu-9naku1{XIcs-OZnpenaanTRN) zlEy7E*jrk~rUBKoc0&k82OOV_1WXfucQAIz-+@C3uYoaS*ZMCv$BG}PN9P*Jsn^#a zvR1V@X!}w>QG?8zA+q3wLP|SEd&mPwY*eRH6>)bjrZiT{W~1#x3`#czYP< zpu+p_NqyG(14G+ojj9cbD$(@x?J$mh+_F2pt8Z`4Tsrk_v(9JB{;qjM&~Lfk=_yKL z9V>!)2J^q|)T4IUHy2g$jVn3hUOuCZ+xw>zqST2DEhYaxZc(+==SBw@dqwDGIxi$I zL%$Dr;}^7bu~>79)_$?q?kq9Xbql*^XNokNFcxu{g)B{4t$BV@2q$~Fen@%E=HYl? z-+%YV4L|_>ojo{a4izE4aej?sgR{|YhN4Q(L#jvV)(xq%a2U|vGsHGZo)X(7G3*I- zZk1T$9FtA6Yl{eN(S}ZFKbqhKaspmZlYeop4oMDdxO3Z&_Vde)O z<&NF?v7HATi`#TjIX|T)xH&b7TKd!d<%b50l#B&iO>?yGU=%{qtI5ypUs%OX7BMv2ry~{mtml3O~%L z<2@q8Oba!h2LA*JqUf(m)R;3uzf9?kDCPe--E*i^J>@P{FxS&`|E8K6=FanV>rCbI+2Ry=?1OZ&uBq+^l2Oa7oQ~>|8+OCEF0b<=xOFksOY6Hy=_k z($8D+4ZmksIO}i_THgr%9UK0s&W&Q;`7c%w=l7;xEFN)p8dKL^9Xo{vC=*%3s#>qM zm(9L9=@-(7)bn8y)Ly$eKAKd?ZMgQ3PKi++ZY7{FZocdHxFCDN^Iz<(p=f0O!MVXZ zEh$y+8yHAN2*;jmd?)tq`N>IUy?PV6oZxj!bl%NH+PE3n$_OoadR1aTN)hb2wXqf0 zcNuvnGQZW3Lap*a#ib3Y`f_G@Pq3o)P;YUe`pfM%!SKnX)afg&-;`5I%(i zuigZGZ>{y3ON{hLBAH`XnaCb^HR4__Be3}=3I#!{(s#YO@+Zwt4-~{NXDh_(7`N($ zq^6czAvcZjp!y9_Cr^O}8|Wby>usbp_B_JzD^Tf}NU*l8S7LNTdB|7VX& zY{Erk?$RUrS1X}G4CRhg*!`1<6{B-*&rq@Ol;M|cQ4|7kE~Ps$T2V}I1r3`b<;h2T z#%GCb1lKq}tXjRO5-@V+w5e-W_K?r!y0BLZbi9IZKTfT>t@)~agi|KR)2>*~wbnq+ zrTH4G{}T4;sq}5p__V6S&)XWple~-?*)CRJL{XN8hBP@7s&*}l6;CScU#lBLPAhXd|-h-emFRE{{?4FmY zySRv@id0n#NvysR*mJMt#pR}WxlVk)YBcR1H*O6xHB^p~x=)eULZAI^+wvalm~$HT zVlF`cqT8_9XR&{189|G`Q5V_*Y!Z>za_h65Bhk6sRa<>dYt*{A|7qM?wi)A}i^5RdKGm5=y4QYM=0ySbaRxp}08R1LvLJvuf; zDDSwd7hXQI`lPqpdGkF{@V^beaICe35uHVH3_S@zBEg&eo-t_Cy8JD3in%6?c&U@} z{w=ZrRo^JjSn^SY4)aPWiQ^ih;f0r}aCVXdfA4fn-EW43H|`Z}8OvT8(Q0?|)H5v* z#f969qPM4h|1~6)C7*uM-$#;F=#iTwBpn_Z7^rpqDx8D4T66lxlKqLu{^u1xd|Ozf`O;L zOm9Wv9UnZl={eLG>AA7>RsRMe?-$$0yQSwk559gHyW!{*dyj~|4f3QYziQiARN(5siYDW*j5-UB zu|wFN*P^@mSK7+!n#Eu~)NNv~%0<0%>PFYD-v-@J=<>!Wb#Vl1@k)PB(}y04@*}QtDJigCejy~4 zc12lO)4KIR1QE{S{nm!+`0G`97uf50PflFFt214zDu3ECDd-WS?n`th)~;1zv!mtF z7GC_wZz{se%_(0CVA)CkCxvNG2FNB+1;04k{q>K(ET?eGQT#{s?PDu z&9~C4EAU!KDp4M}UNYP5cBzlzA$mtkMQ#>Y%`6S#VGZ*w(+m(&K{5OHpnpe&3DN9U zxS5D>@x1+Y^Hu7Z1LEPLqZIrqCt(jVGUK#Kv-$!W}Z5YACW9y^wB$6UkomAj)_;#5$y<)2-;%MpEzdw0hiU+@^c zjooOU}(5YST&i`FN~2A6fst?VbW82@>yz9&&$rK$URB4uc-X$aCN$e6Evfrr8n z<7Zpl3P|yK_A8O4L|Y2+NKG@ph`qOt>XcQt*dwrEs@gFnbou?Tg3iyNIrD)55uc_O z&HgB-MMZB|EiEo_Hdt+lv4h#S!ea)ZDek4}4aFy@gi4)(%Zu-A3~S~$77U*;P)4Qi zIu|sVat6$0w@ohK`M1vshk2?x)9UkaZu#oe14XGEX`3s~YE%P7YT>2m+4=*z6F4ucd4b|#@||IecWqBR!A#$U z9LGsO2qmP7n)G2&oBLBbfGnT(hBN7T9;kkQLT@k79Y!NAaVSxHI9@9-u6Y*Lep_Yr zzJVv7f5-z0DrA(b>FYW{ORYc90dWIwb0|n=^!ETHw1i_`s)4A z9S12NZEXX0*d89*iX6fzQ-5T`w*%J`O>0i|FIYBhv8bYW%VuQ`j7U-@&@ZnAf=C1KBzn;`7hb! zdEKEXyPJ=}LGhrMdzKA`_xn$?Saj< z)zbb>p@nqo3x2`2ZpC}lC#yi)G(GvN4>>JSyuxSeDmaupK>c1DVpnS<)Wn3Tah6xU zXe~!pHz0)#YM#e}6f6&!?+AU2jD%w(Q4vVIar+mm!y@j#HwCUwu@-hcg?~gKK=N8z zS~`-Smj1>Jy$eFZ_~DK|JmP6sVn;gFgVJB-{#FJu>d6UxH+CLoxNaP)B7Wo%*npun zQZ?Z&fnOUL;_Cyk3Kua0!K_B_u$2Aeak8>U2FH_+-$;3#@MmCTp6LqF8+hQ?odso( zsv0<%aCgT}ia)cWy2;T^KD+c@LDKPwoVh4`8ypMeaAT#e0=Dy=6LSW6(qV*;?Pl7g zSF~Blx7zymI(b!L-pQOT_$`C&xwU?Yf*U+jkB*!JvG$vOrCvtQ->`0=4RUW8T|1sL zyaizt0`dOrUQtmYzTw-0;r<%%NQEg?CfgTKJfgopSX(7JcGTiG-!ho$V-=6?xGCEoT=aJWx1$2W4KUd=k{crFOAyk( zIm*d)_d)ZIJ$4GNZgiJ~B?19^2jAp(rr_LSh?@N{Ib`)x1PGZw>O0*4oBtytsWOU( zyM5;S`>CXu7T)&$*M&^s#rL*uASYV`Hel`9aKjwnqSCT}CEtmbK{SfU_>j_^@LAzhxr^Tye`j$j@y?-RO&5CnB{+~8;YCL^yl zBopEu@V8E3jYJq2NL2-7SCP4DlELo|mBxX(-r(!W;V32~^? zmj6FQeRV)oZS(db0t(V4tw>1{LT95wV{OUvpeq#5;lJ7SOCUhExdXuD=tg=ubjJ>DDfHa^ZC>jqPZ-V% z+~LsQzkg$fF(n59Xl3>ic>|E_3n)w|JzdC91#6~ZUzXI6y`Yiy#A*>@G|yvPTXg@> zzN2lU+ix$0f>)ui{I%3a|8Du)&DX0#s`jMmc@s_UkMC&5;m{Fk0RZ7W1Y+ zFEzfC*R-wU_gO=bDSg7>f?YdGqJ4WRaV>C|G4}~^EgjDBZ)T1+)pST@i{))fg^$96 zGd0lX;sz5_THuWW15Zz3Xd(aYQ8RW^bF)dEKN|GQsE10)x6#qV?E5Dt+dXgAkD-(` z?q{OYRUs=9cnWkBBp29jUbsfT{r3r*`jvH0rAwHas;hNdSH6~XDdJ`N3E}%2Nxlp^ zDV@vRbpKvXh3NIe^mr@l(DFQdRPx@EiP|4VaIMhwo%p~DzwbLp1KvMzSZ0ky8KtZ? z_wd!NW0Pzi=qUJL3C8f=^YAF2+C!RVw6=DFACWm|m93M10Sd%*zCC<4NbCsn&2gtH z8NY7oy>hR*PWzG|!UItUywKLW0mg%)p&(5Y!}l5#t9}XC9uRQU53QbRsws@)w4HmC z6L%Z*Ji+R4SVE(Q2hc@+eEsLoK*&;b5U6hJfyRf?>bOc-#Xv&h8TcI(Ph~sut)6wi z00U-v2Nf3bKXv%CAe$ikRBkaic$GN*7QrZmiei>>57kO zWXsCv7^uJ}9BihzSFRWq%D*gWn0ITedhd~AzEIh} z5}A+?3O%H#CfuNk2&!S?eZIqL6n0aIx_=wpj}i4K==BMT3<+oD;%bFzGqg|b{nl=4 zV}rlAS))3xvtCB4>+dIqp>_3XikDGE^AW(fZl@xNpg};>FeF=0u-=e8T8Nlc;f5+h zNAN2ci=RSl8317$V{N6-_vJTj+5j2N?I=NIezvN(I5`{=?Kf2$&Yc^a1#Y?{7?^G2Hi`)zkt2eOj{GG zY4JMn^brI`SJ%@Cj5_Pq$iIOjVQ}&VCv*LzO{`qe*EamsSkg-fyCUFt!aHq5e4n0t zZJ7#Zv<$?Y#s?%`EIB0wH}|*%rsh>DlYs5M&cuYt8ij3DObG?I(%~P4oYR`Ei`J%V zjW@&+Zd($8#z}Tt1%dQx++48KRUX0F*I9&AqWk(AevTmzrXtd@7HrUgTJava7`=p z2ULr%gN=}qS{MPYVLm9w>NTVV8B_*xNJ?>-PE~p6Km~ri546KadQ|ETcXoF2mQ787 zht2IcC-q{9fsrxxDZV}vt)KV#={}N}4eccurOO42z})2b_*F>kWn*Jk<sZ_4EaFiMFSiXcB_hq$}l7z zdb4S~2q}aH!X4SgD_0o%K`sOLZ^003`_u9_R05CS@yw-d)75kX8!K!YwiBR-oQrziKLhgD70G43GMszDCzCu^}7^hfePT z^f!_|2+=2haLk|M=Qljkb*H76nX2nm+c?o-5T#|*s|7$Wqxe?Idi;&shPPTF#}8d@ zvP-M0swNgJGoV;nC2P=gyjflOjiMwp-N*`w7^`V(OYg08vqz1EeS-@{0AzCv`K$;U zg7c@#j5l)TLsoSWb9&II`Pte6lmaM>+4jlv`{3@PY)SPMzNtAGEhHL9t@XT1C_Gd@RM^A$#v692CuyljPL%MQ zO5GN)8F{!FH+QoN={hoC1LocWEf$-D=T6F^?d zNjh*2zXso_UTZGicleY|2FLNLsPgPwC863G5!6>g z$Zs~^^mwvxsp|tbQSzdxl|Y{i#R@0D(gS)vJH0ZP!#?8$kL9y0D<0ZHmx3STd%t$n zX(n$E$YjlEu#!%BZHT>G78dd9wt=?MuBfrCj*cj{$ADe^eefV3NIS8x_Hi;y7g{JQ zd^}3%LLxfC2oBz&b$uXVG%EPnSi-|#4m+I;A}AUZyH{#)BI zU8&_ka7HtA*9zHAl&RXs=P#!V zx1@beOHI879s=}M(+XF|NX*92x!l51+}#e^QtK_H=s}k z&4}Y4*kt8-kx8;`*LPN zMz!n~&xk|sz))JTv9H5LB^`2&eyOj6gKv2%K|D~uyF(f0%PQXTV_*=NG>DmalW30* zZFX;2?L0YgSiOo`JNOAkq`FI$aFkQ&E@Oe}+{FoK76V{I+ulcr(;1?#WHhq6B-#Ad5-t@_v>q_2LUVSg}N*>(tV@Ust7Yg z^<{eWpyA06_TebWa9Xijb`$QiZ=WQYOW`mzA#%W#y0~{?U+)wDVkL-1tTw%_gm2!OgBA>D%(LMgNpi8UR(YD7xiUj)P zitoKqG{^(&`r3uJB!l2vJIf(1yuYhh)%2bglJR&`GYz`;)92lpxWbz+at@)F#1%Wg z-9ha)iTw$NVw$X^bMy1@YeyWmN1@A*mudu~9|NRH4|Mth)oc(6Zoq9LU6=aPVB`K! zRd3S|B~*Tno@j^QO)tcyVjd6%Zl5Gdut^Vw&JZvKK#_yYIe!16fF6KSs^>weJUSBR zgOp149wlN~KklqVdVOTit*=Va{-hQy8>X4rc?*65oYcUkaL!!g^bA#>jc51wlb&Xv zz|5+sr;F7BIsqYL@WeaoohtCQd)c#hqIcW5!`>7@g#O_EaMkx=KJ|RDL`WzU7c}$&PI{W8 z;>+6%yuAItH2N|!SYC|tQNF3i#2!)uUKbGR%qxB6iNm$`Hm1Xq>3w^MC>pRq@^0k% zdi~D4jMQI&t7l1ky@H{|K_XD4{sY7Vr^8=Wo^I4QWXl-_A)!R-cR*!g#!_yeKF!(D zgc;;T80do;Ulyw-pqL4ZdC17AHPKSge}BZW`~tvm--HWCSl?VgT@z2gSa`3Bd!Q5@ zy0K>yEoe{PMRKy>g{dj-F72!|SVMeL_qukqQqKo}ONg;STpctW#5}%=B#zXUjHNqY zo0}e~Z3i~#gBN`AIjQ8)8r_n7VCn>1>_ObsfacRv71yPj%`umyon0$?ttyCzW>?0@ zHiF!qEtzt*-K>4HOD>0of-QnLXK<$HF=TD8?5_E5a84r`?rOnAs|5?nB=uJijG!{k zLxZRD>~T7f;1|i002uy)Mnk~3?^@Tm=D}!)>8Q)HMK*Z4-THI4MCq(=h~}1g)jqos z(iXvPTtf{CpH~of81+=@X#XG<>b#~mbvxsS78FmX_NVw^uTH$(jhvmoO%U~$%vDnV%fRE^8&X4XwK zeCZe6FNH*3@^h)U5el#+nZBlLE((H3 zikc<28p}EsFnu1MKR5l{cH}$IPzfx#WF>imUWPpPcSj1V8HblQCTT(LVxDdsZ0~N! z!@7ePe{EI&2f2gw3Ohp?!_v?9*qSe4Um2HjXh{9(US)ySdVN~U@WDm4>HK)vVz#Wt zXbqPsgR;=t>Dz*Z^Zbz5YMB8VY9!GZ#u+K?j}mCh#1Xf)>h898fF~#)nSyh4Aw0?F z%HP~9pt-#6lh?vsBaK+P40 zy4iOmW=F!(VPCKLHv*xFNLrtAq6T826j>2d@EPhaLMbgh;$I3Y%sGS7Kp4Ky)m1`l z+M?LB9RQ}76-rNvo*q%$NN%YV7N`zwCit2D!9~$m3^7lz$3C|PPE;x5Wfe); z06%(mL zVD>YT#~A8EGraXV3k$V&q>r44)__9LWo)`BYoC7Q3-+3X${Avw`z19(TJjO36OnVK zsfBZ2--5-U$XIz%Y1gJL#ftHmAp5u;u!ut66X^$dm|${uTA55EyC*8s08ea4&qAEpaEr!sF0fb8H_mx zge$^!&OTj=U+UyOpY73YiW}D>$Btbt0i?1ORiW^N-T_oE0;7M*%e%i~S?%~I?uVfZ zuvOYg?n_RJ0=qL~8M&jyu2`6@tNh|JtK`{1t3;ew6c0cDP&Qt6L_~!2K8&Kf?O78Y zO%8U7syaP?5r>#r$!3SPMFG4W#AT4JhsImdsYDs z`zRTp3N}(kMzp|Mq8EkkszIQrm-T-PV*wdv=25e4wy1t6M|qran)ru@kn=o|(=hp< zKj@&{4O05K@aImVk*E5t9HHF-gI6Wc8loi*H>Q&RV?%R!J>OdBQ?FfT+t=(*h$Ddh z5NFVA(W7umy*wpvSPVJG*zOTe2mi;46rqZ{Ho$2N&~1&6G~;Cw6{Ut1wYi-ilaO$w zSTCnkOjx+0-MYBBsp+Z#&iZzbMC+fQo$)TV`rOciwJZEJI*MAzsp)g%Ye+^->xI|^ zlpWc2ytnv~sm@H1a-Am^Fan0qjnAdNdI7bS-#CZc=O|wfGrT6}b9&q8Z#<-l?mrmY z?0KvWFgpXjBI#m|Pab54ujGD4{K+ec0zWFnKSWvVnmS5_`-W*)o$gDY@Ln}(aIyH> z%9$Z`@pct$Lhj8MH-O>W@KrO*;b_P5-Pa@6wXw$z$3>QkNwI&Rr|sB`t0}45UWR(Q zQVh>e3eWxrA2iy*}=^>bJQ85FK}V)I>um z#?O2A<1^DhljE|2=sBDYiOu>PuK!V#CD+OyX^k2ITl0Qk$OMiRk`S^A54*=WSD(!4 z?RLm?56~~$g=@?Ls(hbwd99TW*Q7;~UrESphZ)u9Q}O*;`H~abe|iPAW?`@DCy@e8 zedy<1R_b$f^6%Lv<=&L^_O7}T!Dk2oHyEea;558r8wMTO%eNWFbE;v{@&4u|JiMW- zZ|Yft02p0`hUSwCUL((!dDCtPc?dvQu=rhI`aR$g;|LysTjsJ%6h@IKVTd)oNG zJhQ1G9|#70i;&t7tBgc@bT^zfA^akl z#>&5$vwNF&OulRyExWh^coVb8bK?i`3fB|1>qw5VOoC?Ap}+xtC8Oe)08wh-lgv^+ z0#bs_)&9vD5);VRDN4d>0mT7G?R?fB`GpWz7g|z*O_tWtxf+B=W7S*K3PBT9LZtPZ z<@7mg3ORRQ@J_?t`NxZ+*CvUO1Fh%4(V)WRa<%7fq!26pIFgOUB>?UE@rW9xlRgd& zdSI1dkF}sRDg7Z$E92-7WVMMq@Pd5|ZfZ0y!?AewxrSyF6q1wDCohrEKhBNnwG0Y? z2)6w5toi=GpDqOu9Aqqd3%LFeLK^BuT~5vqmkU2y=tzGB0v^uoaEwuyR}Nef2s7@f z`Ii46eG2d+7bXI(hQkh`fMbWaOR09+;Q$=$buisj!rkGz|4#`wcYIK`E#qxJeD1`A8u_^QgxJvl( z$LDuLGE_m&OCssfnb!dqyAo~7G=&I9g8*>|3{Zn42#<`US%t!Pjw@$-%&0ql8+Zqf zM|oy|gPavRx<PggInie5m~a$_-{e~4D9l?5Scrf3 zjyO$m{>WacguOUJ?m^8mr7FUlh!_IQX?7u1Q6WkADK)pGd!8W5J5<_m>G|hMkW){M)Dn3nBSsMu$L(*5htg=#msE91wEm)5ipj3uJpX0sD z;pvL@p0Azhtt!BaaBy~%mKr_^aQ{bbx_;YOrA%;qbPr|vIW?*9g@wg3X^hAbF7`U778dd~C;yo7N6PuB|gFf`o=BHgD_Oztp-?OdLqariQA9t ziObKBRI;4w#({> zYRiAWL7*%_!|yHl&K__@dU5J0P4|iZ(|qZ8UXX6S`!IKz+vdPwRw$lZ#kvK#CVc-R ziyry2;I^Tk*6VZQ*>YxNQ!V8yrH)@tawo1w#EyAX&VdhZ0!&NL9DI z)=jDCy`iB9vCJ6l!JAUt9E5HKqj^RaJ-5nAV5)=yN z8eEcoL(5?gb3K_J2pWbxuC$-o6Fd9;u!mL{eb8{DF6SFf0&N=&E_gz(_G~|XJBjTM zrg=13eAiFV^$MV{7^oeXv3(Vbu$Uh_`QmSY1wWdpdxk?q=_1oxO$@cVwEIPv!R)cXX5vgl5zhmrN!o<8(mb<Y5tc+s(Y%L?4`aNcWX#mnY*MOW*(u9NU38t0t5tF!&H-02nT+YI zA@-Kz^H8V~;o~%RChv5eervPsV*7$Z+5MVbDpCE%v33#)*hRjmqa_g?zvL_9ylkqf zUdAIeHv71tIBGh!ie(}1nC^W!qVzAhX40E{U#pxmLTr{qBbiJ?T^$|fs?Q$&WYg{~ z&U)He84{emoYE*^dU!@OTb^!BWfQ&@b~b77IP?|pZnAG~>v5Pf7(_75G9Asm*&15@ zV5p-LOgo7lurxS0sKP>`Rn8w#J#&JdOvaGwOGQoF4J9?BRb36^kxRTsUbhaRu6E(! zk(+aKD6=TTIPEFTr<*Y3_*MPvY=g6smfs1zk*I7^Og+MNz=!%L{-JpCzR7oe8* z0AU6RHNSc9+bVG+az|ZpPS|0f<8MK7D(+RLj=$@f`+hn~bSVx5Qz~q^cjG|D59=5dUtLM1`~kUR>gse;YNrOQ7GsT20W^_{{VG3klD{(VAeM_ zqN5ysc94QI-%brbjnwU81b1e=EJSnyy!38vxd->FWb*y{q*g~d)a=4Pg0~(sd<3&& z6p^P!i@Z2-3EY0Dv)>hfrz_m>t@R9J2aiW<0gTV@RCA*D%OO~x!YfCK@{4;4@e z?gkL~6_X*?;g!W2AZ+|gCWF6y;5+9?IQ%SS2ufm;&6&>b&4Q6cUISxqAhA|_5BZZ6 zh=Eel(lAj3K&L|nhQrB2UJ+FlZX026;8AxJ6>*U;9rW#YXl0sg&TwXpm-UZC7F?u* zA(MlP+8VP|94$mgy{xTQBWudZ0{NwCBkm;alx|2U9S8m9fTCEPiHdz?nbP`>HjLs=9}gj zm}c5~5m8l0C3P@3|F?M$6hxIC-N1BmK`x3EB|=wbY4>hWn15etbB=wG`c=`@y$Q6# z??Bl|Hb(-D*{zvP2Nn|S|2C^Y3_I|`i^j&lr8!5Eh!jK;D9j!|z8sFVLwD`}9#CG? zh$uWInELO6ToVz|cc0)YN{aN1f6vyI1N8~0fnb=CwBp~__`>}B3y8DP zP*&E~TTs%D2=g|=lj_j!CtU%w*k zlxz?fdbY^h4=>?JLeD>d;{X0u+a8R=lvb;%V-<9gg2m7!{NKSRj1BKsX-78E?0z41 znF``xAg=$pguu`F#H(gndw&$CX5^$4?bt~ATh6f z6ly5f@O8AYZ@KQqeTHcG4+`tyTnv@-*uOn#T1h*kuPoFdyF>&>+}!9~v2>B@o&CZu zmn6qrXQXa%;h<@r1vvl+ONw#TA1RUn(Gfx(A4Do5>Vfbl{%sPCzspH4bAwhUpO{sM z5)rAHv(w{njXGHLe}5D9LniIh6pTFwt(1+BnAia3B%sB*c=66>mA1NBIg9_e0OVKx zomm<+7LrSsE)gWYZq=3A3q$^dz>j<9*Qk1lAhHHPQ;KyL1w}7Jgk12NgoLp>^$4#D zr11W}R-@fT8EGvDM@5j2@0>)(Cc;tpxAPB_wdx{XI7Ps`beU@(pN~n_+yScWkBBPD zleLF8d2p;9kEbNd8?TG}sL3Hda(mjGl=!E9(x@Cs875js&DP`<}~*3>o!dE#O4r%r^AlQ0HB}DRN=gY_jj@|B(so5 zCu(QU#z5g+YiFlj8-KW+jY91vU6(vKr1vuUZK3xv$+jS%Jc(C-5K_c~LPBULqzk}O zHx0sY$AdMDdoU3zbMpAEloUalk|RoXvCqdGvW2ZZi?u;g)ls;2?JkhFBEy3h=1H$R{M}5{gH}yMkwIjtN(>4_ilb0p#PAWc!!4+v2YwL~btNzu$wD|7 z88IMr4eb1Y0J3uCzmgOyEt(5QgBszHD>9tjdx(yCOaQ|)W@a7&b#=CQ*yx=I^*i`0 zV3gs)*s@D(L&!DTOducUB*p23b;oRj;j&;Zng#ju=g;59$FsRzHJ6u@Yk*z|Kk*=2 z36@zmq*YU)E+Pd?AlzU2r@i+aNR$IFnIA7m9Txl%TmBqs#{b3*VGX@OHfxor|ZP?G60ti+BAKoj(G<1H6OT~Xx)W|zjI;H+O22Oyj z|5gir_*CPFgmnwnlgfyofPlL8_RDf#h;Yc*{iRa~!-k)-k%G_9YaUvUmj=HCL%bSx z6A}^#UU0Hr)>RFr3bWera(lDfvhu9+q8m) zu;*dHsMa3H|Ggrcrvr_k(=EhmJv(>}E!b3Q?%3!+YW2mS84Qx*_FR|esty1=6jcX} z^YXp+?Wn~(Nq~pi0{@GK499g}{t_KJrJtiU56l_&gU#!-;@%?_nr8!@NcathRh9Sw)vcYkTm)F}6B5`pqVCJc ztT2Y9Da1a%E*W$UCV>FE3L2b9!G(5niRtLV;lAQslqHw&DR?fTW`LgwWTl4j@o}rI zsOV_qF(MC2Y(}43BzjOEIiewY!bqN;j5mNq^j!eodK&~OMh>-hfJje(2Wc}AqP+S>WN;V`89 zx~Hb@BbYJ98{>bB%qMCMV(tIyb~Ma`Kl^))PS_^Frbo~R;HyKtsI*@Q{C zaou{rb0K5bK_sjlA<|xtcw(paw{-pi-rQD{`^0@SZyJgvydT>+^U&)zF~OYp zYiIP|T=~?k$w^2UM7z7XZo79un~{K48WGo`vFc_`GbEdGP#DE4E(uep0)P*C!VU9U z06xk;K-K_?FAm%s5LrR&CyYlDHW5xLEaZJJ_oa(a=mV5ZVuyo2YPlH$jrdjEMS*vh zHQ@%QrXkL9WjIejxLPCkfc?4=`ro*qjg}=!PEf>Jos5)}G$SkP9mF|l5DC)bRjr6IQ<92V`5=Ma?hddO*)oaztrG%18eqikI*chY@B|3ts>D+nVk$ zJ2{NowB@Sk-fZJb4qCcvImz-c`HXcuj8^OwV9zIO_I}XxLI{Z61k}{*A@wRLILSj+ zCuJ~1|VoytApcRgPFd7aQr9~&ija$0X5@?9f8Kkrjf>c!XRMVCxlDbLPX;by&< zmFiA`0}jgBa2{DKFdo8B0UK)l2?N@J9{uR_X!jC6K6#0khsSkcVM@?2(jfMl>$@>3&iOFd*4#glu zV)tzD=#XhNFicB#Z7OKJ98NjAUQyeE*KG@L+_fa0$Jn4;9yu6;Liwzg33)w*inK3z zv2BD0M@Px2sTW`3({N`#x6$u|!QJG-=a+>;_*IsJwR8x`tGOC(&uP-K4wXgv)@V?VyBRBhfPp%Q`nQX~j3 zgrH=V2HY@qaMOWSxd@>TcmyeFi)4|JB^08Iu(L2g65OAQa6>9;YL7O{Qd4c+=fmx1 z2QqJ8#KbW9Jzz1r-AOV$679#V;Jmibe;I@ony_PhGJMT>o1Fyy@7F@gxnY52zj=m+ zzM_#^Zcz~li0#a4KgMekT=?(>P#gaDW?`PLhC$gTG3p`onnf;gosUm5SO9(lo~unL z$+WK;z8FWvHq#b?6!HDrz_aRuIUJ;^*H9CK)csRw`}3h3*#^fJGc&V?YZ7yxRb1-4 zHqJ?3VB!+(K^BC<_=FA3*r%9ujEw0^Q4WNJ^HiBN|<4U)s92gTur;4Qv1TS}k(w-IusJ8QKL5*()-?U>stgV4g3ko%?#Vr{mF0 z2!yY=-er_Qx+@~*>aT2;8o6J`Mt$?rE-*XRHnto#_gaiirxHDf-2&+fWy|YVduyVSJk}5QDip|_3aSpvTAU{nhaFH0>M96FsV@OUts4yNfMDvzk$lq` zXm6{Y-uD)s=$_~9Jxj}*Fwkxm-b_!j6TPej(m3BL_IR+2gk8+Kf>XujeZCF|)gt}9Q5w@oqh#@snS4W08AMkG}yoJDDEZL_r zqBX~MFu`@(?;D3@<+n)^?E6?CbbRii@$n7WF_e{w+-VGJAewLAZVJJw&2G-#xJS>) zy+xI+yDd;iqS5Di7xrKPO%z#2(3+H#sF15E65=AM1 zWfvtxNq@l~Bm$Am85oWiBeui{Q{`~rvY}K0L#f-O*aF#x$FjUKmT@p)C9WzgKKG{7 z`WN|wV`l4!k5O;%hr9z|#yr3C`vE-;g9BmMQ^$kj7m;hQFoAIR;tw~1SEKWLu8uNW zSXh8@gN9dVKf)S?9S`XW)R74OPM`m?L4cR=W=AdjaHFi)#C5g_t*q>>Gc97gW3BhZ z+>^TN`%E_^Z;@auFVFRd$FJZ~$AF#Rfwd<;ZAIxwU%&>%-E0`Tx!=d=y+3J}(Ahy0 zXN(%_`A%$N5+l4Diy7}tFp6WEvf*!MdZNPuGS=4J z(Xu}2wSX6~dX0nW=U&+6vP-r?d=iQg8%xWO8d1C`|KjuDR`PSXx7obWAER(l2a_9*IKrO&UQVk&JUW^KAx3mkK`p4f z@yL?KVo5CBxk`swX4n~}c9^qD1UaUI?Qy@mp#4ww-10roC0rNUR2Ou%!f^+Vf?M_H z9Ir-HBj)FVP2C-{c*xSUdQk}cnEZ~B_wuNn25x6FP$ zT%D?xd0>V%mZ=>S$p!ktWR~ts!h0)E5mp&7lk=_dZpxCFxRe1fx$hF95JyKx@3F&f z%$3%@CQVCI`gp|N4Zz&74-^!@G9s6OuMaE;>Qy2e1Crg#`z^gUjSObC2FFX}{d{qw zpkQI4IwP|@5R#dE2QmTR?X?MuC9N7WBhUi^p%xX7-4iRtIyL16rX`?) z+KUQtw=aXES^xf~`jsgGUS480?c639pvABcIbYT7>JhD9y-iev5fiR!1G!Ik@438h zZZ8F!Dd>F=oEql@bDOW8F9^Pn)px||g||nF*>rU-`oY`5VID>qHlj`oEK`Tu=h#c; z7LO5YkGj(rs4*ey1UB(t&Nx0`d0?)i8g$R2{aiCM3yYLA>Gqk zn7qw6k}i7*n+&mi>j_5Q@9Ne$@cAlmN6MYaL6yu-?w>_#>U>YVX{~1tW^%Y&3~@lf zV4>EQn1zo#+AStW{D|jzN^Z)@%ja{R@wT6a8nzCcV`dzR?Twf=m~IJ%woM@p{xk3q zvBR5EaY)!C)qhD8TY@fq4%k6X$2xRASO3xS+_)!n<$+5S@t$ z62vUSU_b}DNj?9&&z^Y>ceSTpJ zt8vV)Nq*gtlMM&FM8T)OQ#UJU0e3~*8IBsRSWed80q1ca9WPU1U!kv`(sSy|24;O6O;qdD&g++wG_qA1H&=XnBgv z#NzilbDH=&bNBDQU0MPo*@2tG9&_rF>aW25f zd+R=pkdqWd`EkH4>;3#8K7NIyrn#l%_ve?ikqqhcu#=g9Cd`+GhF$?aI>m4Tzik`< zRgAJ@P|wr*25bL4pZoF22>#LR+?Pk;T>E@}XJ1ZQ%baNhUNq5hmZ?tnJgyxYE!p#- zqN&cE7)G;7SzWb(!n<-(;p*K*k&ccoH5Zp6dAH~rqA-z6qjbHr{31py1x1H=)Eber zV1+yI|3BA?9y8yUmS*MQ>H8Ica~YT8=>z9`C+W>YynbgF7@3$@VHLQP5*J()o!^$t z%%3}ZwK-c^eVy{N4}yfs?$}D6@KcuWNws(AzJE;jJ6(DA2)$jINc^VjU6jTfa%Sbe z6?=s!j>$CU#H;Br##3zP#|uVGQ@~;P&uo~L;${Xux*zuryEm-CMG74sc_OL?~aLVRG9|YLp5t3cxQ%Tm8W~-QAtS~E)$TP zkrRaxfpP%03_%m)uZy^zywgFRuH)@IZdG4?lCg4>extNy?%{ zGemDH7I59#T8vlcTkLHuvKe)H^n2e4$_1sSj-R>ctV9ySeK39m6Q&N6~3Vyi) ze2(vJZEqQJ3@f!FeQHYC8UGU&a}T@sB%$9z*h`!j>)CQ%lE?m;>XdL$%d zL94Wg!#_H@yR{r1IK(dfgfY^+z|`ZjUbIn6*fge;I15*DrZD>6{c+F3yK?`R*6PaL zyL%q1VdgYXJ#xxw(KC#?(6H?9Vo}dv3eSl4zY&hVplISwlCU7fLI}ODcFJHz*jzendcct0;?n@4( zdAf0AUtG2WjdOd!Yq@(3?gC7yQ}Yew^_s5CCyLPS}O}WwPqREKXpwC7;wFPCdG%U9!SaNp0MYZNPLxf z`0!Jj;(0tb{%n*VI3H97yVM{Iiq6eKT#;|z7nM(-O$v_G*vyOt`qxB?WjkMV3!8_S_EDAH-l#>A=BD9{ zoZl7u&y6=4cU^n5uVShy5Z(IUf3?b`YxP`fUcO$Rb~#N3r2|zrzWr!a##lKxVS3 zaQX7(S0N#)PEG|-4@^o&mj}QbRbDuq#MFrIdil~ok)^t9A1KG$9)HZz=P|EbeVFZf zv|hPE*(&y@?M90$SidyoBs*$ya`JT0w_3a7*~MLV4XFGmt7a$nHY7g55O6jT=5gdH zA;GVBl(z($BCDxM>J8nwO#0ogtgKIc4~Wxi-li7zu2m{=H!n&$k6X@BQeI&^RSp&3GK5{ zT5*>L8&#(gk(^i-Y&FnfRavdioWuC+eylaMxOWEFykhS|a+pwF+_?g>{Kh(Y@iU@Q zMM&y!UFtm2$c^0Zs;4P%lNpr#DF(E1-tozk3@GXP1gHw$Ya;-+zzX=`9X&n6Ks_2UDysO43keN8)CQylHHJ%V%W z7T3vstWi=>xHfC4(DkClF|X=Ra-yGaB;dQnK=4a1@1n*L3H1~1UJK(XbX^;#+8z`m z-46wYaFgZM1Fl-0a)s9L*ECtHtNn5c3Ii~{$2m@)jrWEmEZ(cIu%R-YeeHAT0sPi= zedG&l)alQj9udRFEs8h^37vUwlh1SW61_6cOF}+9G}GwqXVrZ8r?>t5a!(42zl?{r zg5NXvGEjOi{0q0VY zlY0eAmjV;h5q~Svr)?RCS+}9|-t<@ID2!7dNLQw-27|BEsNWh+mj`=tK@gRJ0rZ&N zaEcA9{xk;LiI~)`&?`5jaIg#VW=3+3*DB7>rYT!5Vq*UOE|6~Zvtw}Vn@LLl8Qgq@ z#mJ1@$K$nFi$p}Y9?O^B1lk2WFWc}IEUWHEyX8+GMbtvuH&0KzNoi?wE03o%Z#uhH zpLCc#UGa_A%GUW=RAB>}Urg|iZ+Z{@&@UHxW=IaP-Qbl3t=f{L?(pE7Z-04V5I&vW zYL#`^Mo-Hd*%oeJ-#x_x2`+np3`Yt1ze_#dIM2B!Dfyn@ zJky3hzTk{k#sq%q(IeUc`whC;Jq!P@_uq+jj3^+KaI z{LQU?$qfzLN1cU!PGjeVJ!d)l)A1(M#FRwIpstUpW7RLqV(+9v&dEAsoL|YDR9vk$B#!M15=*cG^Sw;+d|Gx zPCFVCywLi>BfqhM-)@A#Si;!nU^e^F%TP3w%47_3Ry9@71Y*uxjiXU`DA9(ZWk2W3 zQQ)<_!7G3P=vRB7F9fUX(W$Aa!Qup5XW$?`iFJ?C=V)iBY$RU^Tv!j;7L^}!Bm#%0 zop}mc;Jg&@6uvUVC=djP4{>)IL@F^o;VC^G7EU8mA9re%QOC!|ei4{XkUPLcWpIN> zbJ1bb%mpHWhnHAU?rDH$eM#TYTwgYLY-TQ;BW-D3ILdsH%kuet{R=D#Nx9eW-WTbD zv!(93XZd)^43uBWKt*!O2Ip?^!GZGo#F|nC;?0eb8uiG5obk2h8d}Ko$#%4Lxp?GQ z^w4t7UWZOGJk$2v7fBOC5k)1bf>u`u1(d%aq3Wi4pPPX}C(__v8TfI5Zg^b#PXT-4 z+qJi+TypN}9ZHeuQPW;oap@dnE*Ce}(aF^lj}uV_e_lJ+^gZ_g@>^>v1 z```@;0i*fTWnv~BLrm0bp@vcjrqVUL~- z4HD&LN58=RBdKeMN}{t``YzmQeU)YVV*JBO!u|wrYSZ;16v`c&%*4O+At7cR?&5S6 zK^@wi?~6JH*E8UMi2<5c+WcS9q#Wl=mT z0VdL8Z;)t+tC(afKv~O)y?k*agKaqhexB&RGZ5pq4gMG^V^r96^%~T;jVEXkeTN93 zi{f14?z5E%E4U@8NH~fv(StJYBKg_49aB2F!)B0wtG}F>m3(=7pVc`ZY-pT>-?M&a zWX;YFW#QHaHfI|Xf(lEjW<6#1IIGzzw=nT(Q^B`FzRTxa(pijnC?UjWSp%LZRL0F; z{x3>F%4UKfUZ*(Uu7xhGh7K8Ofbd18LMss~$kUQ(qq`@9w}VS3uF&~`V_2T6Ci(3T z71_N_)>|wOU_~H4cM@JaJNC=+@xq7Mo~nL_+uJSAuR#2&u4VqAMS>qpb|a<)8^J;Zv#SuUD;9o6CpaXqrcpzx(7{)nWXju+e853H4Gsm%Pm zUeY7F{T_4yDWbWWn%*c)`Yi^sz;i5L^||;j82ZqFiR$?iZE>C&6N3fL85j7P@RNu- zp7M6K3c(?*0s{u|SO)M4U{PT42k2QI`1!2W$|vZrft6&14l5WKOJ-mY!7~A?Q}uho z8N|(VX~UkCfnlUf>!He9#28h1I44w0Qzv>X-h_hzlNd^Dmia+3Qse;+VKsG94*tgn zhWUii^a4^EnjCzI1kDfOxF}R7_tB-P=Xekvum>6z0_D#NYW(ynTr$Cu>+X`=$!}>6 z8w6#-*;9LdcK+e`s_a`FQ)X0qo_AUy=M@8eXNp|Hf#>5@azwpu!+dd_O_slLj_*4DzM~Aok zzgpBxLs{LZ zMz#$PJ;CdSyvzXb{7jR8Ku&qAm?S>AeDDtx$~3HWZ4n1FRvmoozP`R!)#mVh;1L-7 zP7?ES&Drq5Zy(i=BSTLDK)~lI2i#?A+Yk`-q6yKSo_i zih6o+s*ph^ke&6`v50XkqWke{WL`2!UQjv_Zw-T~t zZ%RgHw(J!jd+!;c?Cd>~^|ANnx!(Hz{?GG2=lPsd$9a$YeUIzD)_q;CYvW`W`aHPe zyu+WRT+leZ7ZP&SkAm|#xN_|wJvRMY81z~@mZt!*!a&)Ige-E%+5uW2pDF^>1H+Nh z_pD723&eQihKP2PMr^JC3yY+*z}hD`q*vS4EYS4>6dnjoU=WA6G3Me%P}Uge>EA&V z2gUujVqeD;VR+yVo8MFe9RP8R!S?`z>>+ps!LZWNNi!X<2Icr(w-vGo%(>@qDsib` z1zNK@TCo(%ku3*p@j2}mdV6~pg3?0g8VR5;65$GVNQwhQkMw0K7x>P;(bg`4yT)Uc z$~)sOHS`X%;dG9MLxpKj*hqKrf$f-95!`5fpksdm0nglZ zC6H!{=%>((O5PjyNG{`auq6c7eq3pw=7&Hk@LXMM%G9VTf6;$NrVJtFMoG9{e) z@oOko6|X1c6D&Y)BNK(^>Yrej2EB;lYWsG;1fDC1tEeP4`OkM@wQvaWVow#nJ_g{H z77Lf-jX1w0X8t-TbbA$)QHV@<9yI21;so88A2zMw+`h)9<@&xIPD=@{-T#sHc&VU6 z=)q}c+alQOrv;;y1`C>|EkH^54H@a7U%tcx38-yV&BFG>^rQtXeu&$P1Yz?#Sv;5n zVpJd$3}QdB23SjBiA#i`pc5*d@6ZwSEL%LpvcFp=!v%}l{PSlgMCQTTup>T3Zti~l zh#By?sAH!$zpWno8A#_tkO%-_Z!FzTy-`&a?XQ9EkV&R?Ied?M=1l4J6C`DOIHziR zNZt^V7-WF90R}1sf>!jQvI}?ya%EQ)UcH-vgePn5-MI*MZ8eRR-i+|PD&01Ua0|cd zyg{qLR#2SO$R0=bb2Q%s`A3yR0Ms*tlt$K$ap!}jr;OVWIwPk~0NG63#dLIZ5+gaV zDf!I*dJ!8yKi)y!1qG);2>59@Vf>QOJTGB}dUn^QVjex-=h)P{TYbR3;X!M z_{p)2PX==oY&wLH02Z`S;60e`n9+(7Wb^m&YT5(LlcU3=I~lSCpa`c5p!NW=+HK}6 zfYU_PQi95B`Vbt~9|%h`U{A#6QGu}!;b`QKsR24g6_W>LcXjNu?}DbMZ`46 zt^kH#=Z3C>VZo{0co9y1^kKw}3t-2-gc(n6$%tGY$eT*v;vtnJ=P`>Pvz;OU;<~18 z@vYb98w9tI4>K!dvXNRaF4ute60%nbJrR+7^#5{QCf#2_3=Kjq;Cz$FCz`J`x9aH- zI%v%@bocO}z*2tn=p%9D*DSTNF7o|rSFXtZDFgP$q2EP6m8Tss2c{mqI4Hc4V6LcI z&CJYDY;=S^L2zJ}Y{s4XvkbA36{h;%3eUMR$HDS4b8{8JUqP(NH;?V)z8`j}c}HpMM1zLt%ff%WN$TE5e@&@aX$xPZ|KIuR2&N zn~e>o2mb>zE2|WEtsC>eV+wY)W!bVthoGKiBSIF4Cv`74IG7W>C`qKAoQ8eiE*j%m ziGS$kyEofR%fTTBl*7@^`L6Q@GTgbVmX#%?rKR`duD}@3`iRbP12}-zF^w>lSp&o? z==m<)kxfBEL=bRQSqMxbzOFtGfpgp+Gl789upUr1N_hcPQ|2{-P)shW#Mudh%ZD=eUl)g z6Y%x(<5VCeARy?ibH`Vo`D}5>4e@_TVc`7u&U1o(ndH0iJe(iZf}uIt^?VOOc7`05 zb26N!V``uwH9Az}2Va6AtACU;Ly$^h**ZxC`%#U-J4nA@tuzc&X%dm4Xh zaMs@k`uS1t*VxTjC7CykzSme+xEUB4rXzB5*^q-6aOZ{7SFJTt0ymD#lkQHDB%!3t zfXG)(E8JdYWOIWa2b~X-u;n(J5UBz-&uWDbRP3jl=vC5w6*v<33?vl=Tj<@-FE?2` zAfP>j91YPf$kLL>>MMXO_*3+pzx^gKYOuR?E_}Q0b1&qXa==%%*N^g6;IzNDVOewN zfbiV8PB@z2v|b#@(BNibO7F42LMWHuvCQNt-`6^BYiYnIW63-Nmr<4Bd(J+>Wcl(f-JXFHCeLH z#5S(Qym$2UuHV6rO};y>k5@fxrf65K7fXBZUdI5}xF=Su2iP}mc16t~$yAljqk4>p zvna%Zp57B^_oHuKT$HXc*sPlhVs-h~NMh!{MxNQ#KqJ80|7!%X5IIc8Mh4p3T3B?X zQRvZIN4I2l+IZ9dwmx#NwC=>1Pkew&xjp^6&fmL3wqv+f>TM`VUWToFO=TN5{d#)t zcB-$KYM9Tv;<#SkI=|H&)HIy_EH_7y!1G~md3JVy{h0%UUsJ}tw|jDa96>{}AzS=Q zwKkN?j4L~>vJopKu#s+qd&$gA7Eg_p$_ynFDU{%>VFfCK`wCel$vA|JIo5!yMBv(~ zicw?t3kpC2Ld)IEv&fuQcEI1!q_5-ur}Dwh*kF?@pFnQLzfvzlxdjkC&Yvtp3^zEK zf~z9EuW{vUuK=A(-u;0*hLZQhC7+N)p5SHq@#Wcn%H^#MCsKU2+HT}HE8qMahGlXx zC>g@26&I{NU}kpx`KveJU9=$i#|x&9fRFz=#F?<FnFEIFAK<2{$Z^qS)I|0_wPFy_XerUil zw=E?$Pds+3@HsByk$37~;G<9zPg-{r!Le`_d9=`EbZB7PbPm}>$UQhlQJgtot(AoZ zBY|L4BO^T>9c6{n_H3_g9EI*x$R)ZxrE}D~b?}g7XNt0w z#K7896} z-lJz+A8-qx5Mv4*zrVGRA)lcTq~@Z8Jb-F$AL;2#muK$H(H#Hd?b-~wj2w2VJ2|<> z1?0BL>)kt*&>NZa+1LMFVHDmtx(eMpk6Z-x?uCTz>2x-?I|?J6gH0rd%{PI(9RCLq zu*iTv*y+T!X1>5@KAoNpo$9zZ|8{TuWgeo3ZTXp}6u0{J*}SV;_~{FKc=c;jO>oGm z_1{Q7T)1$-5|Tux*#GzO?6MgOtL2eFTO$c+I8jEvxGFFMg_vXb7a|R_^KvQT&4G$S z=>-Hv2Bb><;~RDnD@j0OuG}E_nLbLO8Kb8q7xD^reGiJNnF<3*cii1~+U#PFrF~1hrEvzbc9*F0; z;<0bd`Z!9y&GF>N1@!7uATEYC405QknLf&F3wr@W!`LUGi0RC+z&snbrg%&lpPq-! zR7nNv^f_ALk0WG+q$`nTIv?QUx2?1-w!FL&SoESi^xmpVV$+* zjRWI`Hp2&1*#N3F`l%m*4AcY=fc)v*NX3Kd-RFCMF}L%7dzrJ!#Cxp{Sx{WRd;WR7jIgEQQDtP zHAz`{`LWE0=Z$PIbGk1|QubY9C{@A#heDq{$+6J(o9`=B$@#S^+jADYc%gUUTwNmU zZXo?q(Na+<1qyxL;*Cn0O+?PEoZT^f9wyX^rDo@jLH|TFq=jy8@i~r-*Lg!Fy46GR zBbC(Z3k%#OXD=gV^QwVQw*j2Lar=` zlq;Z(D5MQ?hOp%pMFsr z`W)kI5s#mZA-Q_>9o$(PaHPRKl}s1FFChlTCnq%^8!F&uARFH7Ti3r%EETZ5cR*sHD56YNP95@Bew0e&guMgdfrc^Cl`A^5Nm?A>G%fQmwVQ z>vR~s?Bln;uEe2CEwg%YQDe=AIH2{y7e(6Gk3xm{1gSa9E4!z9&#x%z@fL;c34I3j zlb;V7ybSRqVEBmzwZ{r(jHadiCyhAkmwV)51RWHDdQv1Q-9LR&QX`frLVEDG2e-uU zpOg_XuNF=`ASglvpnI#ZSOnfFvt2}{n*h1?-vza=D*@*wWGF%6%cCJN8O-lsdL3O| z3@Fk6eXkF^#1R0_$sq3R?2G~6!Uhs7`2Y|rZ!EtfGgqsV>Fhn^g?f24 zbjZYgb%{6ncB)y3u#|-EjmzKzLzZtn#L@ZxE)^# zMZtT{(77T+bP==)D-|2lor--K{;w;ZFmy!O>Ae9)BiMF<^vJwc$jY*0pQ;h*{r}fg zdw8h8R68Lb_`^YTT^*rhl)<$Pr!RZQ=gZa+n;@`_Z=m zg@&`ZKTFl{Uw>teT6Z4K9MHvcflNvl2X8eADd{b7J^%p^;xf)Jr9~P?Q;SD&%>&$8 zP%_wBh}?tCh5-pAmjt=Ll*3SmtswXE>61b;2Q(W_#4?DL()$idG&F9jcqAa(7tPSR zBRq3J8()lkDR2+3Ej#3DjDTHNodNP@JO=)LRq^VTZ_^nwG(yS-e`jY?osagf1@!0W zkt2H&wW6!xn4nqX4D?ZuOHoS!yOPr)63>JM>(rgQQtUixuN}3_-GeYBPGCsNO{W_j zRT;fk4LZ7vs=A>SmWVOv|71nR!wP^;BoB_iV*1+)?P$h~7U-udUgYMG!;m&VqqxCn zy1S6=ECaBgXk&3H1$dJF4iAr#dbwp@DQztUaA{Jr<&WB@eQREklgcKp7VlaMPA$|f zX{+-_fig)|J}wgl#71g%VM56Zp;Hs3BIk@2i>R6*?S~8^zgl zE-~%~SElJ}PIz=xRMmQ@zIdiB@@L6+tDxl3dA!WK=grmuXWsCj*tk)f59kJI9I*!X zx0ehBG^ELpiD-V^WD?~cC%_<%87gxt|1^GAoG-@aX-oetWn~I2wtLl`C8nC3CL;qd z^@e%paI^wp84W@31tnV<_RmjxK2udAhIRzp?a=d`&NdjMhaZZUUj)-H9q-3`5X?=P zxB8aF84N~B1|^@D7G`p(IjeHma5nYHbq}KQ79K8kyVR5#Zq1+`hC^LRr+NKwi{%01 z?iN`+@gNVPN{ROYf-;|1F2|Z2m2Wk2)l0t|{*8sF^y3i63(jv*sF0>Gs%_NrjP-Yn znXFg24T0i<0t>yks!htgoRmpg6Ak}yRm$;-K=_mE$LzCL7#vMjRJ{E$DKJlBLE8g_ zqz+ORm&mEC92&;1R{EFW_)n&ym%ujj8L=KVhEs+j`gy2QKeXMe_>Z+n=QzmbRu=ZQA;eAjy ztt!J_IXCu>Wz^Iau|OZq(1=0;;XN?GftT}z1{=0+^})0kox8}UVMt7_WtmSU5d95j z{B4+Rth0@kja{~(EuQv)+IPEf3k}|jz1Pq9gZrUsqSeu*{?HWV!-fjGe{y4}T*n7u zb*_?=zkB@zT1NkBtrsa-e$gEtI3+P+Gt8>`x&G0SBW1{50#0D*75&uXnk$YEcXfgSX4o%ipF!fPu~F zO1ehQ)pCF3b6yXjKUnR?a&jQ87#|`SE^M;yjA+Sn=wH2{S)mz6?X6kfNbZ3-_eZ6~ z;QJqZvpmaRue75kQ(Zw_)ygBLdpg%ZrWr!dqMWZJe7u`P!DaOBy*x4p`U;sf0?8}w z)Won%3X9IT?z4WA2^lqq>94R$zaQD(ah|6D*CX10BT7*Nmwq0yM0l&3!YBD2Vrs4= zuV3%oo=jEDuDc-C1!gcfp@3A!swzMtzI*>fq)h~c*Mr&_wTa8CdJ7f`bTyjc)=JNO z<%1wKMPZh9$=AjV;_kC9UF${Sp|2jxNguqypH9Om3fOb@SmvGpuT%V4wi7Lt;zbK6 z>(O}1k7c^)O^3COY#=-_W7%{}uVuUNdD{0%ayNQ{{Whvla}C>o2YT)A2J<`ofM3_@ zKpbWdYH7FxGJvZJ0kT_syKKVwECVam+ccW=pl{>8tw?d#s?oqn{xsRf^48?OPl^Jn0&1Qo1D z02$&9SW1~!K~ACR-z35?5WCTJYHrCy9nF6$iJ{)XkiML;`y*3vS3SRHAmxC-3>;4~ zZV@gra1f_5?s8J)-V2{YS+_-T{$BlH6Y9Wzu7?o04hLGs0=mcCwIoz#A_QHaQX!sc zO0I#~ex-8D&y9XQMWu)c>`};EdoWz2nQ=71l>@XU~6T3+}6ls7V)1|Hu#TnRS9Y* zQ2RUOJ(`U;9&|NhZd77job zZlXm9INtm?*U&gbra4fkp9JZ|>0k=0sF6UgWHt_u6DFk|OC}|ir+v5DDl%%jCH|^; z`3N({Ig-9!IRptX2!zbkn$ymxo;te-0r>1>a3I6J&$=Y9I6Lv49AemTLjt?Y9IPKIO{Bc*~WdVQY~l>Dc?2 z(mR=1jlPvlY5vSv77m!ziuVg|!cvULo(u1!WI z`NKTya~)WIZaM~%@^v-+>x9!o%NDQJ|8x+7Ih9m{f)QRMg7`NL&~OGi3a2&uP&t`i z{G`MzL1S;_q_~(77FB5mR5e6{Kt5PaklnpPLy`R&XCILs`)>#Bfq+vGdIPlpl$|90 z3agigzc_=E{Zwsu33>%U#-1^P=_S9}i)ca6@hQ7!O~b%k_vbWQLdW1VLT>jtn;oLAvX2`og|lQ^i=ZVB-MEOWvv23E#bj>>}U&oM=kI(1jCHR*vG8vRP`}GnXu}zk%Zw{hiwG zFR`}6_m2VpjuaFQcGg9c3XBe~&~bt^5r5?w^@wBpH!;E$>GfNz9Z^y7@`_is2*f#QK40JI6~w5I=24o=F1u(LHfaprRSKB4o5YAIG{1u5#13l z>!TIuM~hpWU%bw|JAnlM!@FD559h_e_U4raTUVs z<0U9YYA}J^1XpWu3{Hmw=ztl`sML#%wb{$k78sdPO9#Dl(6U0{JY{ zjRZkQ@>!+R>wj;;=WyJu`llr_^LQA>w)0OFzI&)&K4xD)T+Qo|qQQIE!*lYv#UjgT zGe@Tpn?R>`qI`dPsD^_`m`w?fP!F^FOP0@;N1}VYrEBEmKn=)-$La^~qF}^pqo~Ki za(=ip_t=W(opG8G<7^V3c8$-Nf!C)G9FV=`R)#(qk7AOWGFh=uYSDsc6)W`Z-zuDt+_zbvz$TAs()8qFIYoQKJ$*K6^4f|46u@|*F+1_|aXG!9 zZdo1vy`%lm6KH$0HJDv$qjS{U0-aX{`?P`Z<&$XycqHtuvXKC_pnV|-s#rIqPkfO1 zg_s8ro)ffa!2;0{Z+$X8c5rW;_KG8HbL%tT^UGIW+{_f1 z9+~hp-)!!t)oxfoN{dSIS0Wtk4atCA@*42}t~#gP_3f=^tn z1s%2%^J#}Avv8XY6ax?*R+ye&Amlr~UpI5bV=J+WQltcvO=Ri4tdgqub^=;4>Pp+) zbUiZhZQg;)LYjtW-YXLJ`pylE4QSNpKYP-WB?rp-Z{W9F)Nv>CdiWV>qpdr1h(Lkk zhgB3{bL8e_A_0X(_FGK<6f-Nz%_x4~r5)iHov_;}G6JbVRRBv$zsqo??32asm!mzE z%5XfLg^8O4Bp4&m^#VaS!oi`8yS8; z$^<<8{W8KFp|csFqd(8Tm;YsTZ|F^c)$Y{a5{`F&cb{hvWh>-pd~?HF!ZHyNaiw_gDKb89H)!3zinxktypr7MYTX2k4R5E zKSxO=S~$Q#?B21lN8axD@Ab<2O(X{+Bdnz#SAMitiu?evU1-wHHB~fnNb_9nOdVLN z6z`=qQ)e#++fb|+19ya>zojl^$~D6Ro23E_F(P&VA)#~i3D8WoIX<=lsjI9ezx2|w zn><4T<(jc?KXo4w6vQ~CduJ-!KowzRi%$zGS^7;q{P0vpI60S6sus)itD3TM*?Rkz zkKV~#aC)|erRGzMsyfzuuL34^@#2VfW+l_0TM1WIc0XcE3_1L2J9>zik(OiEfUB(9 zTh+)1`N~KJfgzAUxb$dq6~|3o;h`VcU>%BsIE}264K8QV5s-%0&~)!Ub-^ySjF1pS zLp)I&ClHY|1C86T`__SQG{CoKL#?oOL>1)x)#Jiv%==I~oQ>6m6YE8*S-y9apDsC{4dvl`(%LH610%EX3W1NTI#{PF${Bf^Z z8&FcyRm0ttUDv@774Y3Fk;Er;%mmF{=~TTznOXyaB#Y8&-de;mcda0V&3MlLPV%(h zF1*L$GT+6(qV`km)W4M82(bZ{a7Fx8N339P(la822(LiS$%p8Q30PG4$q{TEorN_% z$Sx{Jp{5b2nWK4WmoC6rE@WnSqFQ3~GKYVqu>5Y%BOzY?>4GKcb>6K|XQw|wz0>jO zG5jff6&isZ347(1J55XmJGs3i-ycyHuXJ;N9lvv8^npH2pH8^Q==Y2Iee0sq`*%(} zv5iNnrSS@?Wl#9|7m8IMWwNHd=rcN2aUhD5Ihdfldvx7qsja7)?zYwXfq-~Wjj0nZ zmtiKBjkQ8|qjOjf=jvG7I3$(tj@@(&^CCP zrbpe_v9~|_-2~%wWIC`itfzPPfu>l=es4JxANK!J23#WuY?9HiPJ+%1Bg>3Rs1wmJ9|LC_m-vD)AZPQ z@#K5P-eR0S(Kv?l<;w!IjFyK4W{eB`U@(79@_aTA%V#;va?{?-Ou9G2A*)-c_t)<& zg`DLggQ1@fJjAJgmI{30W{l5k=oLHgkLD=G8z)K7P&e%|F8TEC@0Zx8PJ?B_?`rgg zQDaag>g&H_Qs@t+J2e0{O{^dAcP%8yYI{++0Q~1g;?M{6@*~b8ir{awH3}p`aUGNH z=>^Bmdw&-*KVf8|FE}+G8_V^uE7bT8&=t+8?=^f+G8#SdV^uqR64X<@L?D&JX}p;F zMd3qGGBQ8eRbQutPT>FNN@bEw)%@zq+DA?c3t`(?TL;88X*xc-(?VkxtQItqzEHYt zcIIk$&&lxCbc(^#K;+Cj@ArhSl3P?4l~;dlVe2ML2tTPLqOqWMzE}$`;(M(-RHzZi zsH=7JyY}EIltWC<&(F66Yp(6#NPe+1oF@@q=~VRq4IIgNw69y_!!Z zJJ9H@PV*X~siV@pYbO-J)7zYrPUanI)J%gZI<74Xk}s}o#}HGdKCN(b_*Mu0Qlb`$^`KzGu-+=X>PQ{p~rq zl&SkKEcS6ZjsLFi`}J!Qa&{alJ@9D7MuV%{5e?Nj#DYG$xpC^Yk{jYK(hA-!NaZM}ncTOt9ISv9+UNm(4YgV2Fqdi(xCi*qcp z8P~GXgV%#(;q*@^6|&y@yXE21w?4g|$+K~Srs(!QJ>ONyAV;Ze*%*SPC5ybWgf1cN z9(!IpJ-;RY*V$CLeutFg!(`5TMI*CBRqSFnxzCE{!<{$~i7IIR-Bz*Ie~;6Itwuaz;?sUG*0UkLL*1cd~LU+_!I-EZ1;{ zrctg9FScyhbD8#aPFq=AvR`|%cVdKP8>S3mClpbH%#b4GTU(*@OHz`(P}AdX_BqDe zCtT${D-YDmZ7Kxm%gvs`fH9E`>dhpV_pTqQ1zD!`SDf>xh zQE5HT_&1G}A^r(^pcOg2di0+Ko~=;F_L$CZ6~cWv(rV=fJ5eoLHO|9hZ8kei+dGpH zHcNHa_O2hIH{B#&iN~?a^hR=RcmKR82;h__tmeTsM@_BqCV&CN51_@;2`y!asIB z@hCX;W{=~oRJ&TP|G=t+@fPxP$_M>jSdf4dj>{hL*wopOO6%?C*9$SWEaH$s5C-9d zChz4%_DFE8Wnb2G{0%OU71HlsOc*@=T0XOTK^?P-M`HEWvDZS4V%0s5O*Al>Z&v!( zY^S4mkn~JV4&P!I?4GCnsxLE+%QCP6z81;2Ae5Ugf{L*_G8%E+|*R zXcW@@WiWB!U@mf3%fV^Z96^P?JLsGJ-c1qJHM67^>E*4ix5bq#f9`MqjPBgcbg|6b^}K8nBk;g z*dxgV!#n!Wz|%`MyK`yg1WULw2d$CwBS(F?35JSyUDx5rs?wF$Rkc93$l7Q;dl^z$ z)kqBZo1)(-GGWM7G0{d1?xI$&G=c~n<0WeL3V&S6LN8UZX5^C=?fVhe&j0D1Rb2ly zXtg)6s^?uJH7AzzxP7*wEgMU%>K4g`eAXu~ES>9W{Jd30UK5?Y7yaHotz1~UR$cZ) z+0ka3Wl7agY5b4d-L%~s?d&y!tK%aIX*XYOyLj~XfxwX1ok4$-_tE{IxH_i0t?n^N zT4MW9N2&UC$u>2g#-d?x?A@bq6UDRd{du>uw+C_1F~~SL4HYN@zy@O?0-gHaB?wCd z=}#H(6`yb{o-sGdTV)J9{xfCTT|{^iLK7+~GNzT_5p|jqjO~UBQrWE-B?v z95U-M{K#kA(YvUWj4aK*hb{=O>Yw#xb#ctbzp?R}qD>_!_CiYoOx^YyttX|))HEQ# z;IDQTY2Pg|V^!R10c{(5Sgvk+LT^CuLlh`-$W;LmShk-b-kb%>qy}xSHEnElwcKN- zr~mOMT_!5jX8FN`Q=amH$hYhlOkF~vf1Z3kcE;_g2T!dw9^lG=EL!SX+S+jUX)M-m z=a|Q+t9*NJX%fSHs2p_lYNrx0)zDnAszFCb>1(Ho^3g2aW$KVcD(59PQyIDB4TJ0% z;hcim_KN5xfs@!h2UM*C1UO-)0%rfs85yX649lfcgaqVlA`s>fO5YaCDS-r?l`_R& zZ>N%tDb(K%70SBcPBEfo6vj~#)lOAasoq~dBbs(Tj$J@SMud*;C$IV4VFp+JPz$@Q z4xm$W#bYAe=Tb*cpGoT~s;lr zGq!xiZ}U}KwwgJ%KXy}7e<+xU)ROAb6aD|I@etoI~$h{LhSB0 zDP2k^=NrS0;_NGXbZ}^5$mOzhpJ*g+MQY*i9fyeN71eQ_UV?W{2uK zGjL4_5l!&Zm)p|Qh_0J@MD*5CFFFTGurtJ*b@hb&pw3L?2YyZO-@ga{wC!y;4e}Xr zSgFEvee;17_9RHrMG{8rw+Cl4S@9V|J9fkyADvh5eDp;9Rt6*8_v+^PUsmY_2JzlQ zguxijK-TEMj=(34*cYEH(QHLDTrlX;yV%hO{H2_C_bgRwKAr%67(+(4%SUv`=0!RV zpSg)KHIHkz^R3ugs5j+yWV%6=cmI;LD>qvsTN;b|26knJB(sB;?e>V>lAz^?txF)M z?QRC0BeUVEE~%Y_SCBPzr@K$hT(JCb&)!U5x@Jx`A&sEU8G9f-#*ICctv|#yVMfX< zf|6O)%jW>wwFr(~58z~3;}#c}0;C3nzBQ%04e_67@r2HASeoeeAqU?}?X!RR!kxT7 zKAY=Or%{%PIXE4NwM~Fjb31tI)QF43c8x_9KuXmXm+( z@kMOK5TA|Q@_LsF5`t;wA}7>j?w&Odxgqk z-P56VZXPie=>>DFvNc#*C=CJwkfhM0X_%+@0cu2j2F4!P1?9ZM40p9!Ht$yf%?{?vh1>KyEwa9C)2s>R5cq;v3h__B~o*I z1Q?dLcp$GtzfNy_Ify^IWjRSybpJ>Fv*VVcPjj3fgWkm!Y=~Uvx4(+YH5$C{xI5|j z@e1*L%GpGmOMw`O^sfDh&iuZ$o2M=I{Q1>rNGpe5q2#MqiE@q)?%zj%syW|a`HSHx zD=Gavn!ZL&s?jirAnJ-H2QVG;-@I`HMM_GF-F<1Ei*&-vQmz4eBwL;T{miER2mRBw z#G;~kohT}-mqPppkDexL+^r>^M+12N_ok+2d>RE$UHbBFAn>V)>GFp=9|8i1AXV%Z z8$sZ*Hhk4n{*ZLxV1=7@s`c9c+M|3M&Fi*OGtNj=aNs9)q~uE1^%}}ed4z|DTUJ8G z!vA?r?$G7~l$C>g#=Fp-vlg`1)b{nkGy7_t7Tx4HfjewaGX;L1L!8&?!N+-*O7U<9 zn>dXK-%OOx1WKd;XlABmB~(WIUz5q+qo%Nx8frh_;HKQ{qCv*;!F${Qz)J}9)6!rT zkoE%4!QTVL#u=8a%iOeWui?8yJbZk0uIzXj)Qr!QE4M?OeA??VzN ztct&1wKL`x6BAR*A-eC0%R?*pYE3nJcb8{zIGp~!vFg`VJBfP*1O%Thh8;41Kqy6_ z4RsH@*em`O)UecS=r{_#E&UXdNswVmBTs$x9=Bc^uOR|EkvQUCX@Q`p){3B0&v&OvG>s6S)-n*Y$x>P~S}K8SQ{TchE=PEXx#XdqC3`YCFIfFuef^`c z@!X5Yr&EHtL`msJ=Sd`i4E6G9|Do)IJCV??2%j$5Y0Zkj<+NSi6E`9u*U->VWR!l$ zC^_}ouai?yV59s~zP(=Pd-wemwb)BczSzXC@pmBy$~7V3X2rPI8HA#wl~q)lj~H56 z0n8MBqc7JsjsOZt3y>;dijb}~LAn)=wzf8^Hf__=XzVa);6XnHoo6T1DL9BSn@rQ01t!YMoZC zp}#R;tw;2FsB+c5;bG+0^C%>5yf9$X8?_Jz6=r+wEzgcYc2nq#2P>q zgYWSxuX1GEb2^D_mpx`EGmVUBD}nsVFE-E&^6eEXHh>p zI>HYmqL6JY)*r#enWmr@)o-QUyy$Tp^*A1=8E89leC3jS+XSx4P$3%o@`h5B>DB{|T!95I2EB z)j-wE-td`A}q76X?&o6(Uw~?K7c>=+G~yVhRN`z5M)eWr}kbmIWQoq@MQBzB=*G<*Ov& zH%cbXFJBdCtpAhxlVul&cTc)m!{OCO&jtpRt7n5W7SuN*MY#@WO2?;tzKMeWh&A5n zAJ)uzyff@0H*Tez#C@P$$ZqIM*6N3QXsN?wQn@-4W%>s4i0kq%{oc415*8NbP54lB z2nmRrgKRy^N{0KFwVU?OArvXUw>YeB-n4PM<|ZT5j+#L_^k6~Ad_C?4^8xYO5*pea z7k4hncZOr+TuU!r8xNkTGK#Dp%D?kMvS)Z9-^!4qac@t&DOEbo1DKx5inyp%J@$2C zdC=sP0eku$3I!3P4TBc5FoQsvc>b~VM|ID-)}}K`rZrs3Wclaqd~_vOU2yE*-32(t z^0%Y*Ud$yvs~MS>@`s56trimI#4Zd)azDMdK$=@@^bt$2RetPFf59^wwMtuJ)arLU z8Z9`+W~ve*PD998?i*xE);Bhuh1^4wtF+M3RY2Ui6b%`gs2dyWRzHp$Q&eX8cI

eG|u@P&`EDNhP+*_!rf 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 literal 82658 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Ⓢ8cR1WGoSg)vYr67$GLmX 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 KEDA Webhooks log <<<" - kubectl get pods --no-headers -n keda | awk '{print $1}' | grep keda-webhooks| xargs kubectl -n keda logs + echo ">>> KEDA Admission Webhooks log <<<" + kubectl get pods --no-headers -n keda | awk '{print $1}' | grep keda-admission| xargs kubectl -n keda logs printf "##############################################\n" printf "##############################################\n" } diff --git a/tests/run-smoke-tests.sh b/tests/run-smoke-tests.sh index 118f2f54f9c..bbf9bc39cf0 100755 --- a/tests/run-smoke-tests.sh +++ b/tests/run-smoke-tests.sh @@ -132,8 +132,8 @@ function print_logs { printf "\n\n##############################################\n" printf "##############################################\n\n" - echo ">>> KEDA Webhooks log <<<" - kubectl get pods --no-headers -n keda | awk '{print $1}' | grep keda-webhooks| xargs kubectl -n keda logs + echo ">>> KEDA Admission Webhooks log <<<" + kubectl get pods --no-headers -n keda | awk '{print $1}' | grep keda-admission | xargs kubectl -n keda logs printf "##############################################\n" printf "##############################################\n" } From 7fac0f39a670ee8612df9af48510a38f154aae9e Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Tue, 3 Jan 2023 14:16:22 +0100 Subject: [PATCH 18/41] update bin output Signed-off-by: Jorge Turrado --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ed5dfcf1799..8d6f5f85897 100644 --- a/Makefile +++ b/Makefile @@ -180,7 +180,7 @@ adapter: generate ${GO_BUILD_VARS} go build -ldflags $(GO_LDFLAGS) -mod=vendor -o bin/keda-adapter cmd/adapter/main.go webhooks: generate - ${GO_BUILD_VARS} go build -ldflags $(GO_LDFLAGS) -mod=vendor -o bin/keda-webhooks cmd/webhooks/main.go + ${GO_BUILD_VARS} go build -ldflags $(GO_LDFLAGS) -mod=vendor -o bin/keda-admission-webhooks cmd/webhooks/main.go run: manifests generate ## Run a controller from your host. WATCH_NAMESPACE="" go run -ldflags $(GO_LDFLAGS) ./main.go $(ARGS) From a7d506b05a78be727ceead5651b288cbf3034cbb Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Tue, 3 Jan 2023 16:50:59 +0100 Subject: [PATCH 19/41] add a core release Signed-off-by: Jorge Turrado --- .github/workflows/release-build.yml | 12 +++++++ Makefile | 1 + config/manager/manager.yaml | 1 - config/metrics-server/deployment.yaml | 1 - config/minimal/kustomization.yaml | 31 +++++++++++++++++++ .../metadataLabelTransformer.yaml | 10 ++++++ config/webhooks/kustomization.yaml | 1 - .../webhook/webhook_prommetrics.go | 4 +-- .../prometheus_metrics_test.go | 12 +++---- 9 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 config/minimal/kustomization.yaml create mode 100644 config/minimal/kustomize-config/metadataLabelTransformer.yaml diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index ec3c41551d7..f01ab2a60f7 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -102,3 +102,15 @@ jobs: asset_path: keda-${{ steps.get_version.outputs.VERSION }}.yaml asset_name: keda-${{ steps.get_version.outputs.VERSION }}.yaml asset_content_type: application/x-yaml + + # Upload core deployment YAML file to GitHub release + - name: Upload Deployment YAML file + id: upload-deployment-yaml + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: https://uploads.github.com/repos/kedacore/keda/releases/${{ steps.get-release-info.outputs.id }}/assets?name=keda-${{ steps.get_version.outputs.VERSION }}-core.yaml + asset_path: keda-${{ steps.get_version.outputs.VERSION }}-core.yaml + asset_name: keda-${{ steps.get_version.outputs.VERSION }}-core.yaml + asset_content_type: application/x-yaml diff --git a/Makefile b/Makefile index 8d6f5f85897..3afda6377ae 100644 --- a/Makefile +++ b/Makefile @@ -218,6 +218,7 @@ release: manifests kustomize set-version ## Produce new KEDA release in keda-$(V @sed -i".out" -e 's@version:[ ].*@version: $(VERSION)@g' config/default/kustomize-config/metadataLabelTransformer.yaml rm -rf config/default/kustomize-config/metadataLabelTransformer.yaml.out $(KUSTOMIZE) build config/default > keda-$(VERSION).yaml + $(KUSTOMIZE) build config/minimal > keda-$(VERSION)-core.yaml sign-images: ## Sign KEDA images published on GitHub Container Registry COSIGN_EXPERIMENTAL=1 cosign sign ${COSIGN_FLAGS} $(IMAGE_CONTROLLER) diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 5f623752c08..dd2da83dd25 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -69,7 +69,6 @@ spec: - ALL allowPrivilegeEscalation: false readOnlyRootFilesystem: true - runAsNonRoot: true seccompProfile: type: RuntimeDefault terminationGracePeriodSeconds: 10 diff --git a/config/metrics-server/deployment.yaml b/config/metrics-server/deployment.yaml index 9a38fe709dc..945c9ff0cb5 100644 --- a/config/metrics-server/deployment.yaml +++ b/config/metrics-server/deployment.yaml @@ -77,7 +77,6 @@ spec: drop: - ALL allowPrivilegeEscalation: false - runAsNonRoot: true readOnlyRootFilesystem: true seccompProfile: type: RuntimeDefault diff --git a/config/minimal/kustomization.yaml b/config/minimal/kustomization.yaml new file mode 100644 index 00000000000..743dc07b4b1 --- /dev/null +++ b/config/minimal/kustomization.yaml @@ -0,0 +1,31 @@ +# Adds namespace to all resources. +#namespace: keda + +# Value of this field is prepended to the +# names of all resources, e.g. a deployment named +# "wordpress" becomes "alices-wordpress". +# Note that it should also match with the prefix (text before '-') of the namespace +# field above. +#namePrefix: keda- + +# Labels to add to all resources and selectors. +#commonLabels: +# someName: someValue + +# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. +#- ../prometheus + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +# Need this transformer to mitigate a problem with inserting labels into selectors, +# until this issue is solved: https://github.com/kubernetes-sigs/kustomize/issues/1009 +transformers: +- kustomize-config/metadataLabelTransformer.yaml + +resources: +- ../crd +- ../general +- ../rbac +- ../manager +- ../metrics-server +- ../service_account \ No newline at end of file diff --git a/config/minimal/kustomize-config/metadataLabelTransformer.yaml b/config/minimal/kustomize-config/metadataLabelTransformer.yaml new file mode 100644 index 00000000000..a9deb62005b --- /dev/null +++ b/config/minimal/kustomize-config/metadataLabelTransformer.yaml @@ -0,0 +1,10 @@ +apiVersion: builtin +kind: LabelTransformer +metadata: + name: metadataLabelTransformer +labels: + app.kubernetes.io/version: main + app.kubernetes.io/part-of: keda-operator +fieldSpecs: +- path: metadata/labels + create: true diff --git a/config/webhooks/kustomization.yaml b/config/webhooks/kustomization.yaml index 6b9ad7e88c1..bdd46dc282e 100644 --- a/config/webhooks/kustomization.yaml +++ b/config/webhooks/kustomization.yaml @@ -1,6 +1,5 @@ resources: - webhooks.yaml -- secret.yaml - service.yaml - validation_webhooks.yaml diff --git a/pkg/prommetrics/webhook/webhook_prommetrics.go b/pkg/prommetrics/webhook/webhook_prommetrics.go index 8f7a10012a9..c7ba4df49c0 100644 --- a/pkg/prommetrics/webhook/webhook_prommetrics.go +++ b/pkg/prommetrics/webhook/webhook_prommetrics.go @@ -30,7 +30,7 @@ var ( prometheus.CounterOpts{ Namespace: DefaultPromMetricsNamespace, Subsystem: "webhook", - Name: "scaled_object_validating_total", + Name: "scaled_object_validation_total", Help: "Total number of scaled object validations", }, []string{"namespace", "action"}, @@ -39,7 +39,7 @@ var ( prometheus.CounterOpts{ Namespace: DefaultPromMetricsNamespace, Subsystem: "webhook", - Name: "scaled_object_validating_errors", + Name: "scaled_object_validation_errors", Help: "Total number of scaled object validating errors", }, []string{"namespace", "action", "reason"}, diff --git a/tests/internals/prometheus_metrics/prometheus_metrics_test.go b/tests/internals/prometheus_metrics/prometheus_metrics_test.go index baad445da75..a4694251555 100644 --- a/tests/internals/prometheus_metrics/prometheus_metrics_test.go +++ b/tests/internals/prometheus_metrics/prometheus_metrics_test.go @@ -468,9 +468,9 @@ func checkCRTotalValues(t *testing.T, families map[string]*promModel.MetricFamil func checkWebhookValues(t *testing.T, families map[string]*promModel.MetricFamily) { t.Log("--- testing webhook metrics ---") - family, ok := families["keda_webhook_scaled_object_validating_errors"] + family, ok := families["keda_webhook_scaled_object_validation_errors"] if !ok { - t.Errorf("metric keda_webhook_scaled_object_validating_errors not available") + t.Errorf("metric keda_webhook_scaled_object_validation_errors not available") return } @@ -485,11 +485,11 @@ func checkWebhookValues(t *testing.T, families map[string]*promModel.MetricFamil } metricValue = *metric.Counter.Value } - assert.GreaterOrEqual(t, metricValue, 1.0, "keda_webhook_scaled_object_validating_errors has to be greater than 0") + assert.GreaterOrEqual(t, metricValue, 1.0, "keda_webhook_scaled_object_validation_errors has to be greater than 0") - family, ok = families["keda_webhook_scaled_object_validating_total"] + family, ok = families["keda_webhook_scaled_object_validation_total"] if !ok { - t.Errorf("metric keda_webhook_scaled_object_validating_total not available") + t.Errorf("metric keda_webhook_scaled_object_validation_total not available") return } @@ -504,5 +504,5 @@ func checkWebhookValues(t *testing.T, families map[string]*promModel.MetricFamil } metricValue = *metric.Counter.Value } - assert.GreaterOrEqual(t, metricValue, 1.0, "keda_webhook_scaled_object_validating_total has to be greater than 0") + assert.GreaterOrEqual(t, metricValue, 1.0, "keda_webhook_scaled_object_validation_total has to be greater than 0") } From fc4ac7d8b877268a9a9da7f01c0a5d7aec9706db Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Tue, 3 Jan 2023 18:14:31 +0100 Subject: [PATCH 20/41] split rbac Signed-off-by: Jorge Turrado --- Makefile | 12 +++++++-- apis/keda/v1alpha1/scaledobject_webhook.go | 2 +- config/rbac/role.yaml | 30 ---------------------- config/rbac/role_binding.yaml | 16 +----------- config/webhooks/role.yaml | 30 ++++++++++++++++++++++ config/webhooks/role_binding.yaml | 13 ++++++++++ 6 files changed, 55 insertions(+), 48 deletions(-) create mode 100644 config/webhooks/role.yaml create mode 100644 config/webhooks/role_binding.yaml diff --git a/Makefile b/Makefile index 3afda6377ae..c954f3ddbf7 100644 --- a/Makefile +++ b/Makefile @@ -117,13 +117,21 @@ smoke-test: ## Run e2e tests against Kubernetes cluster configured in ~/.kube/co ##@ Development -manifests: controller-gen ## Generate ClusterRole and CustomResourceDefinition objects. - $(CONTROLLER_GEN) crd:crdVersions=v1 rbac:roleName=keda-operator paths="./..." output:crd:artifacts:config=config/crd/bases +manifests: controller-gen core-crd-manifests core-rbac-manifests webhook-rbac-manifests + +core-crd-manifests: ## Generate CustomResourceDefinition objects for core componenets. + $(CONTROLLER_GEN) crd:crdVersions=v1 paths="./..." output:crd:artifacts:config=config/crd/bases # withTriggers is only used for duck typing so we only need the deepcopy methods # However operator-sdk generate doesn't appear to have an option for that # until this issue is fixed: https://github.com/kubernetes-sigs/controller-tools/issues/398 rm config/crd/bases/keda.sh_withtriggers.yaml +core-rbac-manifests: ## Generate ClusterRole objects for core componenets. + $(CONTROLLER_GEN) rbac:roleName=keda-operator paths="./controllers/..." + +webhook-rbac-manifests: ## Generate Role for webhooks. + $(CONTROLLER_GEN) rbac:roleName=keda-admission-webhooks paths="./apis/keda/v1alpha1/..." output:dir=config/webhooks + generate: controller-gen mockgen-gen proto-gen ## Generate code containing DeepCopy, DeepCopyInto, DeepCopyObject method implementations (API), mocks and proto. $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." diff --git a/apis/keda/v1alpha1/scaledobject_webhook.go b/apis/keda/v1alpha1/scaledobject_webhook.go index ec3713fb558..1a6bd94cd5e 100644 --- a/apis/keda/v1alpha1/scaledobject_webhook.go +++ b/apis/keda/v1alpha1/scaledobject_webhook.go @@ -46,7 +46,7 @@ func (so *ScaledObject) SetupWebhookWithManager(mgr ctrl.Manager) error { } // +kubebuilder:webhook:path=/validate-keda-sh-v1alpha1-scaledobject,mutating=false,failurePolicy=ignore,sideEffects=None,groups=keda.sh,resources=scaledobjects,verbs=create;update,versions=v1alpha1,name=vscaledobject.kb.io,admissionReviewVersions=v1 -// +kubebuilder:rbac:groups=admissionregistration.k8s.io,resources=validatingwebhookconfigurations,verbs=get;list;watch;update;patch +// +kubebuilder:rbac:groups=admissionregistration.k8s.io,namespace=keda,resources=validatingwebhookconfigurations,verbs=get;list;watch;update;patch // +kubebuilder:rbac:groups="",namespace=keda,resources=secrets,verbs=get;list;watch;create;update;patch;delete var _ webhook.Validator = &ScaledObject{} diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 50fdd338b85..1846281cdbf 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -43,16 +43,6 @@ rules: - '*/scale' verbs: - '*' -- apiGroups: - - admissionregistration.k8s.io - resources: - - validatingwebhookconfigurations - verbs: - - get - - list - - patch - - update - - watch - apiGroups: - apps resources: @@ -109,23 +99,3 @@ rules: - triggerauthentications/status verbs: - '*' ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - creationTimestamp: null - name: keda-operator - namespace: keda -rules: -- apiGroups: - - "" - resources: - - secrets - verbs: - - create - - delete - - get - - list - - patch - - update - - watch diff --git a/config/rbac/role_binding.yaml b/config/rbac/role_binding.yaml index c43c8f78abf..9906633cf80 100644 --- a/config/rbac/role_binding.yaml +++ b/config/rbac/role_binding.yaml @@ -9,18 +9,4 @@ roleRef: subjects: - kind: ServiceAccount name: keda-operator - namespace: keda ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: keda-operator - namespace: keda -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: keda-operator -subjects: -- kind: ServiceAccount - name: keda-operator - namespace: keda + namespace: keda \ No newline at end of file diff --git a/config/webhooks/role.yaml b/config/webhooks/role.yaml new file mode 100644 index 00000000000..7a952585579 --- /dev/null +++ b/config/webhooks/role.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + creationTimestamp: null + name: keda-admission-webhooks + namespace: keda +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - admissionregistration.k8s.io + resources: + - validatingwebhookconfigurations + verbs: + - get + - list + - patch + - update + - watch diff --git a/config/webhooks/role_binding.yaml b/config/webhooks/role_binding.yaml new file mode 100644 index 00000000000..983e9123f67 --- /dev/null +++ b/config/webhooks/role_binding.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: keda-admission-webhooks + namespace: keda +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: keda-admission-webhooks +subjects: +- kind: ServiceAccount + name: keda-operator + namespace: keda From 6becd32ebb3370b1fcb768001a5e9d1ec8fea272 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Tue, 3 Jan 2023 18:18:05 +0100 Subject: [PATCH 21/41] update changelog Signed-off-by: Jorge Turrado --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af2a2293016..b5841a6f9cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,7 +49,7 @@ To learn more about active deprecations, we recommend checking [GitHub Discussio Here is an overview of all **stable** additions: - **General**: Introduce new ArangoDB Scaler ([#4000](https://github.com/kedacore/keda/issues/4000)) -- **General**: Validate incoming ScaledObjects to ensure the workload isn't already autoscaled ([#3755](https://github.com/kedacore/keda/issues/3755)) +- **General**: Introduce admission webhooks to automatically validate resource changes to prevent misconfiguration and enforce best practices. ([#3755](https://github.com/kedacore/keda/issues/3755)) Here is an overview of all new **experimental** features: From 001fdc54c0a219f5220a765410b51019a6d22f11 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Tue, 3 Jan 2023 19:06:42 +0100 Subject: [PATCH 22/41] fix styles Signed-off-by: Jorge Turrado --- .github/workflows/release-build.yml | 2 +- config/minimal/kustomization.yaml | 2 +- config/rbac/role_binding.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index f01ab2a60f7..9dd78816e15 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -102,7 +102,7 @@ jobs: asset_path: keda-${{ steps.get_version.outputs.VERSION }}.yaml asset_name: keda-${{ steps.get_version.outputs.VERSION }}.yaml asset_content_type: application/x-yaml - + # Upload core deployment YAML file to GitHub release - name: Upload Deployment YAML file id: upload-deployment-yaml diff --git a/config/minimal/kustomization.yaml b/config/minimal/kustomization.yaml index 743dc07b4b1..a9a8390e9ea 100644 --- a/config/minimal/kustomization.yaml +++ b/config/minimal/kustomization.yaml @@ -28,4 +28,4 @@ resources: - ../rbac - ../manager - ../metrics-server -- ../service_account \ No newline at end of file +- ../service_account diff --git a/config/rbac/role_binding.yaml b/config/rbac/role_binding.yaml index 9906633cf80..5df22274191 100644 --- a/config/rbac/role_binding.yaml +++ b/config/rbac/role_binding.yaml @@ -9,4 +9,4 @@ roleRef: subjects: - kind: ServiceAccount name: keda-operator - namespace: keda \ No newline at end of file + namespace: keda From acddb50674e512501a76dbd2f10346db56313032 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Tue, 3 Jan 2023 19:07:38 +0100 Subject: [PATCH 23/41] fix typo Signed-off-by: Jorge Turrado --- config/webhooks/webhooks.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/config/webhooks/webhooks.yaml b/config/webhooks/webhooks.yaml index f59af6298f1..ebd7356584e 100644 --- a/config/webhooks/webhooks.yaml +++ b/config/webhooks/webhooks.yaml @@ -90,4 +90,3 @@ spec: defaultMode: 420 secretName: kedaorg-admission-webhooks-certs optional: true - readOnly: true From 69bae43f4fcb768b120a82e176f4997ea64254a0 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Wed, 4 Jan 2023 20:28:31 +0100 Subject: [PATCH 24/41] update parameter name Signed-off-by: Jorge Turrado --- cmd/webhooks/main.go | 16 ++++++++-------- config/webhooks/webhooks.yaml | 1 + tests/helper/helper.go | 1 + tests/utils/setup_test.go | 11 +++++++---- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/cmd/webhooks/main.go b/cmd/webhooks/main.go index 7ae19c840b5..f8187c74e4f 100644 --- a/cmd/webhooks/main.go +++ b/cmd/webhooks/main.go @@ -55,7 +55,6 @@ var webhooks = []rotator.WebhookInfo{ var ( scheme = apimachineryruntime.NewScheme() setupLog = ctrl.Log.WithName("setup") - secretName = "kedaorg-admission-webhooks-certs" // This should be the same for the secret volume serviceName = "keda-admission-webhooks" caName = "kedaorg-ca" caOrganization = "kedaorg" @@ -76,14 +75,16 @@ func main() { var webhooksClientRequestQPS float32 var webhooksClientRequestBurst int var webhookCertDir string - var disableCertRotation bool + var webhookSecretName string + var enableCertRotation bool var tlsMinVersion string pflag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") pflag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") pflag.Float32Var(&webhooksClientRequestQPS, "kube-api-qps", 20.0, "Set the QPS rate for throttling requests sent to the apiserver") pflag.IntVar(&webhooksClientRequestBurst, "kube-api-burst", 30, "Set the burst for throttling requests sent to the apiserver") pflag.StringVar(&webhookCertDir, "webhooks-cert-dir", "/certs", "Webhook certificates dir to use. Defaults to /certs") - pflag.BoolVar(&disableCertRotation, "disable-cert-rotation", false, "disable automatic generation and rotation of webhook TLS certificates/keys") + pflag.StringVar(&webhookSecretName, "webhooks-cert-secret-name", "kedaorg-admission-webhooks-certs", "Webhook certificates secret name. Defaults to kedaorg-admission-webhooks-certs") + pflag.BoolVar(&enableCertRotation, "enable-cert-rotation", false, "enable automatic generation and rotation of webhook TLS certificates/keys") pflag.StringVar(&tlsMinVersion, "tls-min-version", "1.3", "Minimum TLS version") opts := zap.Options{} @@ -114,13 +115,13 @@ func main() { // Make sure certs are generated and valid if cert rotation is enabled. setupFinished := make(chan struct{}) - if !disableCertRotation { - ensureSecret(ctx, mgr) + if enableCertRotation { + ensureSecret(ctx, mgr, webhookSecretName) setupLog.V(1).Info("setting up cert rotation") if err := rotator.AddRotator(mgr, &rotator.CertRotator{ SecretKey: types.NamespacedName{ Namespace: kedautil.GetPodNamespace(), - Name: secretName, + Name: webhookSecretName, }, CertDir: webhookCertDir, CAName: caName, @@ -169,7 +170,7 @@ func main() { } } -func ensureSecret(ctx context.Context, mgr manager.Manager) { +func ensureSecret(ctx context.Context, mgr manager.Manager, secretName string) { secrets := &corev1.SecretList{} kedaNamespace := kedautil.GetPodNamespace() opt := &client.ListOptions{ @@ -199,7 +200,6 @@ func ensureSecret(ctx context.Context, mgr manager.Manager) { "app.kubernetes.io/name": "keda-admission-webhooks", "app.kubernetes.io/component": "admission-webhooks", "app.kubernetes.io/part-of": "keda", - "TODO": "keda", }, }, } diff --git a/config/webhooks/webhooks.yaml b/config/webhooks/webhooks.yaml index ebd7356584e..89a620e25df 100644 --- a/config/webhooks/webhooks.yaml +++ b/config/webhooks/webhooks.yaml @@ -34,6 +34,7 @@ spec: - --zap-log-level=info - --zap-encoder=console - --zap-time-encoding=rfc3339 + - --enable-cert-rotation=true imagePullPolicy: Always resources: requests: diff --git a/tests/helper/helper.go b/tests/helper/helper.go index cf0bf8ca7fb..ad73a30878a 100644 --- a/tests/helper/helper.go +++ b/tests/helper/helper.go @@ -40,6 +40,7 @@ const ( KEDANamespace = "keda" KEDAOperator = "keda-operator" KEDAMetricsAPIServer = "keda-metrics-apiserver" + KEDAAdmissionWebhooks = "keda-admission-webhooks" DefaultHTTPTimeOut = 3000 diff --git a/tests/utils/setup_test.go b/tests/utils/setup_test.go index ef6e680f3dd..6bcc90f7561 100644 --- a/tests/utils/setup_test.go +++ b/tests/utils/setup_test.go @@ -182,19 +182,22 @@ func TestVerifyKEDA(t *testing.T) { metricsServerDeployment, err := KubeClient.AppsV1().Deployments(KEDANamespace).Get(context.Background(), KEDAMetricsAPIServer, v1.GetOptions{}) require.NoErrorf(t, err, "unable to get %s deployment - %s", KEDAMetricsAPIServer, err) + webhooksDeployment, err := KubeClient.AppsV1().Deployments(KEDANamespace).Get(context.Background(), KEDAAdmissionWebhooks, v1.GetOptions{}) + require.NoErrorf(t, err, "unable to get %s deployment - %s", KEDAAdmissionWebhooks, err) + operatorReadyReplicas := operatorDeployment.Status.ReadyReplicas metricsServerReadyReplicas := metricsServerDeployment.Status.ReadyReplicas + webhooksReadyReplicas := webhooksDeployment.Status.ReadyReplicas - if operatorReadyReplicas != 1 || metricsServerReadyReplicas != 1 { + if operatorReadyReplicas != 1 || metricsServerReadyReplicas != 1 || webhooksReadyReplicas != 1 { t.Log("KEDA is not ready. sleeping") time.Sleep(10 * time.Second) } else { - t.Logf("KEDA is running 1 pod for %s and 1 pod for %s", KEDAOperator, KEDAMetricsAPIServer) + t.Logf("KEDA is running 1 pod for %s, 1 pod for %s and 1 pod for %s", KEDAOperator, KEDAMetricsAPIServer, KEDAAdmissionWebhooks) success = true - break } } - require.True(t, success, "expected KEDA deployments to start 2 pods successfully") + require.True(t, success, "expected KEDA deployments to start 3 pods successfully") } From 954550fc9e2cfbc177c89aa88d5b05941ff98d2b Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Wed, 4 Jan 2023 21:23:36 +0100 Subject: [PATCH 25/41] update docs Signed-off-by: Jorge Turrado --- BUILD.md | 27 +++++++++++++--------- CONTRIBUTING.md | 4 ++++ apis/keda/v1alpha1/scaledobject_webhook.go | 4 ++-- config/webhooks/kustomization.yaml | 2 ++ tests/helper/helper.go | 2 +- 5 files changed, 25 insertions(+), 14 deletions(-) diff --git a/BUILD.md b/BUILD.md index 3a9f0cffe9a..7aeafe4fe04 100644 --- a/BUILD.md +++ b/BUILD.md @@ -4,17 +4,22 @@ **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* -- [Build & Deploy KEDA](#build--deploy-keda) - - [Building](#building) - - [Quick start with Visual Studio Code Remote - Containers](#quick-start-with-visual-studio-code-remote---containers) - - [Locally directly](#locally-directly) - - [Deploying](#deploying) - - [Custom KEDA locally outside cluster](#custom-keda-locally-outside-cluster) - - [Custom KEDA as an image](#custom-keda-as-an-image) - - [Miscellaneous](#miscellaneous) - - [Setting log levels](#setting-log-levels) - - [KEDA Operator logging](#keda-operator-logging) - - [Metrics Server logging](#metrics-server-logging) +- [Building](#building) + - [Quick start with Visual Studio Code Remote - Containers](#quick-start-with-visual-studio-code-remote---containers) + - [Locally directly](#locally-directly) +- [Deploying](#deploying) + - [Custom KEDA locally outside cluster](#custom-keda-locally-outside-cluster) + - [Custom KEDA as an image](#custom-keda-as-an-image) +- [Debugging with VS Code](#debugging-with-vs-code) + - [Operator](#operator) + - [Metrics server](#metrics-server) + - [Admission Webhooks](#admission-webhooks) +- [Miscellaneous](#miscellaneous) + - [How to use devcontainers and a local Kubernetes cluster](#how-to-use-devcontainers-and-a-local-kubernetes-cluster) + - [Setting log levels](#setting-log-levels) + - [KEDA Operator and Admission webhooks logging](#keda-operator-and-admission-webhooks-logging) + - [Metrics Server logging](#metrics-server-logging) + - [CPU/Memory Profiling](#cpumemory-profiling) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a29c04e1832..384457212ed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,6 +51,10 @@ It is mandatory to provide end-to-end (e2e) tests for new scaler. For more infor check the [test documentation](./tests/README.md). Those tests are run nightly on our [CI system](https://github.com/kedacore/keda/actions?query=workflow%3A%22nightly+e2e+test%22). +## Contributing webhooks + +Another easy way to contribute is improving the validations to avoid misconfigurations. New rules can be added in the proper type's webhooks file (`apis/keda/v1alpha1/*_webhook.go`). + ## Changelog Every change should be added to our changelog under `Unreleased` which is located in `CHANGELOG.md`. This helps us keep track of all changes in a given release. diff --git a/apis/keda/v1alpha1/scaledobject_webhook.go b/apis/keda/v1alpha1/scaledobject_webhook.go index 1a6bd94cd5e..d57fbad2640 100644 --- a/apis/keda/v1alpha1/scaledobject_webhook.go +++ b/apis/keda/v1alpha1/scaledobject_webhook.go @@ -114,7 +114,7 @@ func verifyHpas(incomingSo *ScaledObject, action string) error { if !owned { err = fmt.Errorf("the workload '%s' of type '%s/%s' is already managed by the hpa '%s'", incomingSo.Spec.ScaleTargetRef.Name, incomingSo.Spec.ScaleTargetRef.APIVersion, incomingSo.Spec.ScaleTargetRef.Kind, hpa.Name) scaledobjectlog.Error(err, "validation error") - prommetrics.RecordScaledObjectValidatingErrors(incomingSo.Namespace, action, "hpa") + prommetrics.RecordScaledObjectValidatingErrors(incomingSo.Namespace, action, "other-hpa") return err } } @@ -155,7 +155,7 @@ func verifyScaledObjects(incomingSo *ScaledObject, action string) error { if soGckr.GVKString() == incomingSoGckr.GVKString() { err = fmt.Errorf("the workload '%s' of type '%s/%s' is already managed by the ScaledObject '%s'", so.Spec.ScaleTargetRef.Name, so.Spec.ScaleTargetRef.APIVersion, so.Spec.ScaleTargetRef.Kind, so.Name) scaledobjectlog.Error(err, "validation error") - prommetrics.RecordScaledObjectValidatingErrors(incomingSo.Namespace, action, "scaled_object") + prommetrics.RecordScaledObjectValidatingErrors(incomingSo.Namespace, action, "other-scaled-object") return err } } diff --git a/config/webhooks/kustomization.yaml b/config/webhooks/kustomization.yaml index bdd46dc282e..1af6db63d41 100644 --- a/config/webhooks/kustomization.yaml +++ b/config/webhooks/kustomization.yaml @@ -1,4 +1,6 @@ resources: +- role.yaml +- role_binding.yaml - webhooks.yaml - service.yaml - validation_webhooks.yaml diff --git a/tests/helper/helper.go b/tests/helper/helper.go index ad73a30878a..9163db77a94 100644 --- a/tests/helper/helper.go +++ b/tests/helper/helper.go @@ -40,7 +40,7 @@ const ( KEDANamespace = "keda" KEDAOperator = "keda-operator" KEDAMetricsAPIServer = "keda-metrics-apiserver" - KEDAAdmissionWebhooks = "keda-admission-webhooks" + KEDAAdmissionWebhooks = "keda-admission" DefaultHTTPTimeOut = 3000 From b2482d71f5263ea693bdf1bc8fd7274e74ddd82e Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Wed, 4 Jan 2023 21:39:27 +0100 Subject: [PATCH 26/41] update rbac Signed-off-by: Jorge Turrado --- apis/keda/v1alpha1/scaledobject_webhook.go | 2 +- config/webhooks/role.yaml | 23 ++++++++++++++-------- config/webhooks/role_binding.yaml | 13 ++++++++++++ 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/apis/keda/v1alpha1/scaledobject_webhook.go b/apis/keda/v1alpha1/scaledobject_webhook.go index d57fbad2640..6c6f34b09fe 100644 --- a/apis/keda/v1alpha1/scaledobject_webhook.go +++ b/apis/keda/v1alpha1/scaledobject_webhook.go @@ -46,7 +46,7 @@ func (so *ScaledObject) SetupWebhookWithManager(mgr ctrl.Manager) error { } // +kubebuilder:webhook:path=/validate-keda-sh-v1alpha1-scaledobject,mutating=false,failurePolicy=ignore,sideEffects=None,groups=keda.sh,resources=scaledobjects,verbs=create;update,versions=v1alpha1,name=vscaledobject.kb.io,admissionReviewVersions=v1 -// +kubebuilder:rbac:groups=admissionregistration.k8s.io,namespace=keda,resources=validatingwebhookconfigurations,verbs=get;list;watch;update;patch +// +kubebuilder:rbac:groups=admissionregistration.k8s.io,resources=validatingwebhookconfigurations,verbs=get;list;watch;update;patch // +kubebuilder:rbac:groups="",namespace=keda,resources=secrets,verbs=get;list;watch;create;update;patch;delete var _ webhook.Validator = &ScaledObject{} diff --git a/config/webhooks/role.yaml b/config/webhooks/role.yaml index 7a952585579..fe2e134dea3 100644 --- a/config/webhooks/role.yaml +++ b/config/webhooks/role.yaml @@ -1,28 +1,35 @@ --- apiVersion: rbac.authorization.k8s.io/v1 -kind: Role +kind: ClusterRole metadata: creationTimestamp: null name: keda-admission-webhooks - namespace: keda rules: - apiGroups: - - "" + - admissionregistration.k8s.io resources: - - secrets + - validatingwebhookconfigurations verbs: - - create - - delete - get - list - patch - update - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + creationTimestamp: null + name: keda-admission-webhooks + namespace: keda +rules: - apiGroups: - - admissionregistration.k8s.io + - "" resources: - - validatingwebhookconfigurations + - secrets verbs: + - create + - delete - get - list - patch diff --git a/config/webhooks/role_binding.yaml b/config/webhooks/role_binding.yaml index 983e9123f67..657dff91c1c 100644 --- a/config/webhooks/role_binding.yaml +++ b/config/webhooks/role_binding.yaml @@ -1,4 +1,17 @@ apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: keda-admission-webhooks +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: keda-admission-webhooks +subjects: +- kind: ServiceAccount + name: keda-operator + namespace: keda +--- +apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: keda-admission-webhooks From 360110971e7dc9cd6060b25106f18032662704cd Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Wed, 4 Jan 2023 22:25:41 +0100 Subject: [PATCH 27/41] update contributing Signed-off-by: Jorge Turrado --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 384457212ed..1c317835989 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,6 +13,7 @@ There are many areas we can use contributions - ranging from code, documentation - [Making Breaking Changes](#making-breaking-changes) - [Contributing Scalers](#contributing-scalers) - [Testing](#testing) +- [Contributing webhooks](#contributing-webhooks) - [Changelog](#changelog) - [Including Documentation Changes](#including-documentation-changes) - [Creating and building a local environment](#creating-and-building-a-local-environment) From de9b9fb32af955e6048cfd9213adfa486da2070e Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Thu, 5 Jan 2023 12:34:27 +0100 Subject: [PATCH 28/41] add cpu/memory validation Signed-off-by: Jorge Turrado --- apis/keda/v1alpha1/scaledobject_webhook.go | 84 +++++- .../v1alpha1/scaledobject_webhook_test.go | 273 ++++++++++++++++-- cmd/webhooks/main.go | 2 +- .../prometheus_metrics_test.go | 4 +- 4 files changed, 330 insertions(+), 33 deletions(-) diff --git a/apis/keda/v1alpha1/scaledobject_webhook.go b/apis/keda/v1alpha1/scaledobject_webhook.go index 6c6f34b09fe..2ff5ca9a0d8 100644 --- a/apis/keda/v1alpha1/scaledobject_webhook.go +++ b/apis/keda/v1alpha1/scaledobject_webhook.go @@ -21,9 +21,12 @@ import ( "encoding/json" "fmt" + appsv1 "k8s.io/api/apps/v1" autoscalingv2 "k8s.io/api/autoscaling/v2" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -64,13 +67,27 @@ func (so *ScaledObject) ValidateUpdate(old runtime.Object) error { return validateWorkload(so, "update") } +func (so *ScaledObject) ValidateDelete() error { + return nil +} + func validateWorkload(so *ScaledObject, action string) error { prommetrics.RecordScaledObjectValidatingTotal(so.Namespace, action) - err := verifyScaledObjects(so, action) + err := verifyCpuMemoryScalers(so, action) + if err != nil { + return err + } + err = verifyScaledObjects(so, action) if err != nil { return err } - return verifyHpas(so, action) + err = verifyHpas(so, action) + if err != nil { + return err + } + + scaledobjectlog.V(1).Info(fmt.Sprintf("scaledobject %s is valid", so.Name)) + return nil } func verifyHpas(incomingSo *ScaledObject, action string) error { @@ -112,14 +129,13 @@ func verifyHpas(incomingSo *ScaledObject, action string) error { } if !owned { - err = fmt.Errorf("the workload '%s' of type '%s/%s' is already managed by the hpa '%s'", incomingSo.Spec.ScaleTargetRef.Name, incomingSo.Spec.ScaleTargetRef.APIVersion, incomingSo.Spec.ScaleTargetRef.Kind, hpa.Name) + err = fmt.Errorf("the workload '%s' of type '%s' is already managed by the hpa '%s'", incomingSo.Spec.ScaleTargetRef.Name, incomingSoGckr.GVKString(), hpa.Name) scaledobjectlog.Error(err, "validation error") prommetrics.RecordScaledObjectValidatingErrors(incomingSo.Namespace, action, "other-hpa") return err } } } - scaledobjectlog.V(1).Info(fmt.Sprintf("scaledobject %s is valid", incomingSo.Name)) return nil } @@ -153,17 +169,71 @@ func verifyScaledObjects(incomingSo *ScaledObject, action string) error { } if soGckr.GVKString() == incomingSoGckr.GVKString() { - err = fmt.Errorf("the workload '%s' of type '%s/%s' is already managed by the ScaledObject '%s'", so.Spec.ScaleTargetRef.Name, so.Spec.ScaleTargetRef.APIVersion, so.Spec.ScaleTargetRef.Kind, so.Name) + err = fmt.Errorf("the workload '%s' of type '%s' is already managed by the ScaledObject '%s'", so.Spec.ScaleTargetRef.Name, incomingSoGckr.GVKString(), so.Name) scaledobjectlog.Error(err, "validation error") prommetrics.RecordScaledObjectValidatingErrors(incomingSo.Namespace, action, "other-scaled-object") return err } } - scaledobjectlog.V(1).Info(fmt.Sprintf("scaledobject %s is valid", incomingSo.Name)) return nil } -func (so *ScaledObject) ValidateDelete() error { +func verifyCpuMemoryScalers(incomingSo *ScaledObject, action string) error { + var podSpec *corev1.PodSpec + for _, trigger := range incomingSo.Spec.Triggers { + if trigger.Type == "cpu" || trigger.Type == "memory" { + if podSpec == nil { + key := types.NamespacedName{ + Namespace: incomingSo.Namespace, + Name: incomingSo.Spec.ScaleTargetRef.Name, + } + if incomingSo.Spec.ScaleTargetRef.APIVersion == "apps/v1" && + incomingSo.Spec.ScaleTargetRef.Kind == "Deployment" { + deployment := &appsv1.Deployment{} + err := kc.Get(context.Background(), key, deployment, &client.GetOptions{}) + if err != nil { + return err + } + podSpec = &deployment.Spec.Template.Spec + } else if incomingSo.Spec.ScaleTargetRef.APIVersion == "apps/v1" && + incomingSo.Spec.ScaleTargetRef.Kind == "StatefulSet" { + statefulset := &appsv1.StatefulSet{} + err := kc.Get(context.Background(), key, statefulset, &client.GetOptions{}) + if err != nil { + return err + } + podSpec = &statefulset.Spec.Template.Spec + } else { + return nil + } + } + conainerName := trigger.Metadata["containerName"] + for _, container := range podSpec.Containers { + if conainerName != "" && container.Name != conainerName { + continue + } + if trigger.Type == "cpu" { + if container.Resources.Requests == nil || + container.Resources.Requests.Cpu() == nil || + container.Resources.Requests.Cpu().AsApproximateFloat64() == 0 { + err := fmt.Errorf("the scaledobject has a cpu trigger but the container %s doesn't have the cpu request defined", container.Name) + scaledobjectlog.Error(err, "validation error") + prommetrics.RecordScaledObjectValidatingErrors(incomingSo.Namespace, action, "missing-requests") + return err + } + } else if trigger.Type == "memory" { + if container.Resources.Requests == nil || + container.Resources.Requests.Memory() == nil || + container.Resources.Requests.Memory().AsApproximateFloat64() == 0 { + err := fmt.Errorf("the scaledobject has a memory trigger but the container %s doesn't have the memory request defined", container.Name) + scaledobjectlog.Error(err, "validation error") + prommetrics.RecordScaledObjectValidatingErrors(incomingSo.Namespace, action, "missing-requests") + return err + } + } + } + } + } return nil } diff --git a/apis/keda/v1alpha1/scaledobject_webhook_test.go b/apis/keda/v1alpha1/scaledobject_webhook_test.go index 14cabb3b00b..8303f3b08bf 100644 --- a/apis/keda/v1alpha1/scaledobject_webhook_test.go +++ b/apis/keda/v1alpha1/scaledobject_webhook_test.go @@ -28,8 +28,10 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" admissionv1beta1 "k8s.io/api/admission/v1beta1" + appsv1 "k8s.io/api/apps/v1" v2 "k8s.io/api/autoscaling/v2" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -54,8 +56,8 @@ var ctx context.Context var cancel context.CancelFunc const ( - deploymentName = "deploymentName" - soName = "test-so" + workloadName = "deployment-name" + soName = "test-so" ) func TestAPIs(t *testing.T) { @@ -141,7 +143,7 @@ var _ = It("should validate the so creation when there isn't any hpa", func() { namespaceName := "valid" namespace := createNamespace(namespaceName) - so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment") + so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment", false) err := k8sClient.Create(context.Background(), namespace) Expect(err).ToNot(HaveOccurred()) @@ -155,7 +157,7 @@ var _ = It("should validate the so creation when it's own hpa is already generat hpaName := "test-so-hpa" namespaceName := "own-hpa" namespace := createNamespace(namespaceName) - so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment") + so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment", false) hpa := createHpa(hpaName, namespaceName, "apps/v1", "Deployment", so) err := k8sClient.Create(context.Background(), namespace) @@ -173,7 +175,7 @@ var _ = It("should validate the so update when it's own hpa is already generated hpaName := "test-so-hpa" namespaceName := "update-so" namespace := createNamespace(namespaceName) - so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment") + so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment", false) hpa := createHpa(hpaName, namespaceName, "apps/v1", "Deployment", so) err := k8sClient.Create(context.Background(), namespace) @@ -196,7 +198,7 @@ var _ = It("shouldn't validate the so creation when there is another unmanaged h namespaceName := "unmanaged-hpa" namespace := createNamespace(namespaceName) hpa := createHpa(hpaName, namespaceName, "apps/v1", "Deployment", nil) - so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment") + so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment", false) err := k8sClient.Create(context.Background(), namespace) Expect(err).ToNot(HaveOccurred()) @@ -213,8 +215,8 @@ var _ = It("shouldn't validate the so creation when there is another so", func() so2Name := "test-so2" namespaceName := "managed-hpa" namespace := createNamespace(namespaceName) - so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment") - so2 := createScaledObject(so2Name, namespaceName, "apps/v1", "Deployment") + so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment", false) + so2 := createScaledObject(so2Name, namespaceName, "apps/v1", "Deployment", false) err := k8sClient.Create(context.Background(), namespace) Expect(err).ToNot(HaveOccurred()) @@ -231,7 +233,7 @@ var _ = It("shouldn't validate the so creation when there is another hpa with cu hpaName := "test-custom-hpa" namespaceName := "custom-apis" namespace := createNamespace(namespaceName) - so := createScaledObject(soName, namespaceName, "custom-api", "custom-kind") + so := createScaledObject(soName, namespaceName, "custom-api", "custom-kind", false) hpa := createHpa(hpaName, namespaceName, "custom-api", "custom-kind", nil) err := k8sClient.Create(context.Background(), namespace) @@ -244,6 +246,121 @@ var _ = It("shouldn't validate the so creation when there is another hpa with cu Expect(err).To(HaveOccurred()) }) +var _ = It("should validate the so creation with cpu and memory when deployment has requests", func() { + + namespaceName := "deployment-has-requests" + namespace := createNamespace(namespaceName) + workload := createDeployment(namespaceName, true, true) + so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment", true) + + err := k8sClient.Create(context.Background(), namespace) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), workload) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), so) + Expect(err).ToNot(HaveOccurred()) +}) + +var _ = It("shouldn't validate the so creation with cpu and memory when deployment hasn't got memory request", func() { + + namespaceName := "deployment-no-memory-request" + namespace := createNamespace(namespaceName) + workload := createDeployment(namespaceName, true, false) + so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment", true) + + err := k8sClient.Create(context.Background(), namespace) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), workload) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), so) + Expect(err).To(HaveOccurred()) +}) + +var _ = It("shouldn't validate the so creation with cpu and memory when deployment hasn't got cpu request", func() { + + namespaceName := "deployment-no-cpu-request" + namespace := createNamespace(namespaceName) + workload := createDeployment(namespaceName, false, true) + so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment", true) + + err := k8sClient.Create(context.Background(), namespace) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), workload) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), so) + Expect(err).To(HaveOccurred()) +}) + +var _ = It("should validate the so creation with cpu and memory when statefulset has requests", func() { + + namespaceName := "statefulset-has-requests" + namespace := createNamespace(namespaceName) + workload := createStatefulSet(namespaceName, true, true) + so := createScaledObject(soName, namespaceName, "apps/v1", "StatefulSet", true) + + err := k8sClient.Create(context.Background(), namespace) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), workload) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), so) + Expect(err).ToNot(HaveOccurred()) +}) + +var _ = It("shouldn't validate the so creation with cpu and memory when statefulset hasn't got memory request", func() { + + namespaceName := "statefulset-no-memory-request" + namespace := createNamespace(namespaceName) + workload := createStatefulSet(namespaceName, true, false) + so := createScaledObject(soName, namespaceName, "apps/v1", "StatefulSet", true) + + err := k8sClient.Create(context.Background(), namespace) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), workload) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), so) + Expect(err).To(HaveOccurred()) +}) + +var _ = It("shouldn't validate the so creation with cpu and memory when statefulset hasn't got cpu request", func() { + + namespaceName := "statefulset-no-cpu-request" + namespace := createNamespace(namespaceName) + workload := createStatefulSet(namespaceName, false, true) + so := createScaledObject(soName, namespaceName, "apps/v1", "StatefulSet", true) + + err := k8sClient.Create(context.Background(), namespace) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), workload) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), so) + Expect(err).To(HaveOccurred()) +}) + +var _ = It("should validate the so creation without cpu and memory when custom resources", func() { + + namespaceName := "crd-not-resources" + namespace := createNamespace(namespaceName) + so := createScaledObject(soName, namespaceName, "custom-api", "StatefulSet", true) + + err := k8sClient.Create(context.Background(), namespace) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), so) + Expect(err).ToNot(HaveOccurred()) +}) + var _ = AfterSuite(func() { cancel() By("tearing down the test environment") @@ -257,7 +374,36 @@ func createNamespace(name string) *v1.Namespace { } } -func createScaledObject(name, namespace, targetAPI, targetKind string) *ScaledObject { +func createScaledObject(name, namespace, targetAPI, targetKind string, hasCpuAndMemory bool) *ScaledObject { + triggers := []ScaleTriggers{ + { + Type: "cron", + Metadata: map[string]string{ + "timezone": "UTC", + "start": "0 * * * *", + "end": "1 * * * *", + "desiredReplicas": "1", + }, + }, + } + + if hasCpuAndMemory { + cpuTrigger := ScaleTriggers{ + Type: "cpu", + Metadata: map[string]string{ + "value": "10", + }, + } + triggers = append(triggers, cpuTrigger) + memoryTrigger := ScaleTriggers{ + Type: "memory", + Metadata: map[string]string{ + "value": "10", + }, + } + triggers = append(triggers, memoryTrigger) + } + return &ScaledObject{ ObjectMeta: metav1.ObjectMeta{ Name: name, @@ -270,26 +416,17 @@ func createScaledObject(name, namespace, targetAPI, targetKind string) *ScaledOb }, Spec: ScaledObjectSpec{ ScaleTargetRef: &ScaleTarget{ - Name: deploymentName, + Name: workloadName, APIVersion: targetAPI, Kind: targetKind, }, IdleReplicaCount: pointer.Int32(1), MinReplicaCount: pointer.Int32(5), MaxReplicaCount: pointer.Int32(10), - Triggers: []ScaleTriggers{ - { - Type: "cron", - Metadata: map[string]string{ - "timezone": "UTC", - "start": "0 * * * *", - "end": "1 * * * *", - "desiredReplicas": "1", - }, - }, - }, + Triggers: triggers, }, } + } func createHpa(name, namespace, targetAPI, targetKind string, owner *ScaledObject) *v2.HorizontalPodAutoscaler { @@ -297,7 +434,7 @@ func createHpa(name, namespace, targetAPI, targetKind string, owner *ScaledObjec ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, Spec: v2.HorizontalPodAutoscalerSpec{ ScaleTargetRef: v2.CrossVersionObjectReference{ - Name: deploymentName, + Name: workloadName, APIVersion: targetAPI, Kind: targetKind, }, @@ -329,3 +466,93 @@ func createHpa(name, namespace, targetAPI, targetKind string, owner *ScaledObjec return hpa } + +func createDeployment(namespace string, hasCpu, hasMemory bool) *appsv1.Deployment { + cpu := 0 + if hasCpu { + cpu = 100 + } + memory := 0 + if hasMemory { + memory = 100 + } + + return &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: workloadName, Namespace: namespace}, + Spec: appsv1.DeploymentSpec{ + Replicas: pointer.Int32(1), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "test": "test", + }, + }, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Name: workloadName, + Labels: map[string]string{ + "test": "test", + }, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "test", + Image: "test", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: *resource.NewMilliQuantity(int64(cpu), resource.DecimalSI), + v1.ResourceMemory: *resource.NewMilliQuantity(int64(memory), resource.DecimalSI), + }, + }, + }, + }, + }, + }, + }, + } +} + +func createStatefulSet(namespace string, hasCpu, hasMemory bool) *appsv1.StatefulSet { + cpu := 0 + if hasCpu { + cpu = 100 + } + memory := 0 + if hasMemory { + memory = 100 + } + + return &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Name: workloadName, Namespace: namespace}, + Spec: appsv1.StatefulSetSpec{ + Replicas: pointer.Int32(1), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "test": "test", + }, + }, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Name: workloadName, + Labels: map[string]string{ + "test": "test", + }, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "test", + Image: "test", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceCPU: *resource.NewMilliQuantity(int64(cpu), resource.DecimalSI), + v1.ResourceMemory: *resource.NewMilliQuantity(int64(memory), resource.DecimalSI), + }, + }, + }, + }, + }, + }, + }, + } +} diff --git a/cmd/webhooks/main.go b/cmd/webhooks/main.go index f8187c74e4f..425ba7a114a 100644 --- a/cmd/webhooks/main.go +++ b/cmd/webhooks/main.go @@ -47,7 +47,7 @@ import ( var webhooks = []rotator.WebhookInfo{ { - Name: "keda-validating-webhooks", + Name: "keda-admission", Type: rotator.Validating, }, } diff --git a/tests/internals/prometheus_metrics/prometheus_metrics_test.go b/tests/internals/prometheus_metrics/prometheus_metrics_test.go index a4694251555..46e14b5a9dd 100644 --- a/tests/internals/prometheus_metrics/prometheus_metrics_test.go +++ b/tests/internals/prometheus_metrics/prometheus_metrics_test.go @@ -483,7 +483,7 @@ func checkWebhookValues(t *testing.T, families map[string]*promModel.MetricFamil continue } } - metricValue = *metric.Counter.Value + metricValue += *metric.Counter.Value } assert.GreaterOrEqual(t, metricValue, 1.0, "keda_webhook_scaled_object_validation_errors has to be greater than 0") @@ -502,7 +502,7 @@ func checkWebhookValues(t *testing.T, families map[string]*promModel.MetricFamil continue } } - metricValue = *metric.Counter.Value + metricValue += *metric.Counter.Value } assert.GreaterOrEqual(t, metricValue, 1.0, "keda_webhook_scaled_object_validation_total has to be greater than 0") } From c4677cabeacc12a3834cb20b82aec6917054a3da Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Thu, 5 Jan 2023 13:11:51 +0100 Subject: [PATCH 29/41] solve styles Signed-off-by: Jorge Turrado --- apis/keda/v1alpha1/scaledobject_webhook.go | 14 +++++++++----- apis/keda/v1alpha1/scaledobject_webhook_test.go | 13 ++++++------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/apis/keda/v1alpha1/scaledobject_webhook.go b/apis/keda/v1alpha1/scaledobject_webhook.go index 2ff5ca9a0d8..a3044aafc9e 100644 --- a/apis/keda/v1alpha1/scaledobject_webhook.go +++ b/apis/keda/v1alpha1/scaledobject_webhook.go @@ -188,23 +188,27 @@ func verifyCpuMemoryScalers(incomingSo *ScaledObject, action string) error { Namespace: incomingSo.Namespace, Name: incomingSo.Spec.ScaleTargetRef.Name, } - if incomingSo.Spec.ScaleTargetRef.APIVersion == "apps/v1" && - incomingSo.Spec.ScaleTargetRef.Kind == "Deployment" { + + if incomingSo.Spec.ScaleTargetRef.APIVersion != "apps/v1" { + return nil + } + + switch incomingSo.Spec.ScaleTargetRef.APIVersion { + case "Deployment": deployment := &appsv1.Deployment{} err := kc.Get(context.Background(), key, deployment, &client.GetOptions{}) if err != nil { return err } podSpec = &deployment.Spec.Template.Spec - } else if incomingSo.Spec.ScaleTargetRef.APIVersion == "apps/v1" && - incomingSo.Spec.ScaleTargetRef.Kind == "StatefulSet" { + case "StatefulSet": statefulset := &appsv1.StatefulSet{} err := kc.Get(context.Background(), key, statefulset, &client.GetOptions{}) if err != nil { return err } podSpec = &statefulset.Spec.Template.Spec - } else { + default: return nil } } diff --git a/apis/keda/v1alpha1/scaledobject_webhook_test.go b/apis/keda/v1alpha1/scaledobject_webhook_test.go index 8303f3b08bf..81e7ec30b76 100644 --- a/apis/keda/v1alpha1/scaledobject_webhook_test.go +++ b/apis/keda/v1alpha1/scaledobject_webhook_test.go @@ -374,7 +374,7 @@ func createNamespace(name string) *v1.Namespace { } } -func createScaledObject(name, namespace, targetAPI, targetKind string, hasCpuAndMemory bool) *ScaledObject { +func createScaledObject(name, namespace, targetAPI, targetKind string, hasCPUAndMemory bool) *ScaledObject { triggers := []ScaleTriggers{ { Type: "cron", @@ -387,7 +387,7 @@ func createScaledObject(name, namespace, targetAPI, targetKind string, hasCpuAnd }, } - if hasCpuAndMemory { + if hasCPUAndMemory { cpuTrigger := ScaleTriggers{ Type: "cpu", Metadata: map[string]string{ @@ -426,7 +426,6 @@ func createScaledObject(name, namespace, targetAPI, targetKind string, hasCpuAnd Triggers: triggers, }, } - } func createHpa(name, namespace, targetAPI, targetKind string, owner *ScaledObject) *v2.HorizontalPodAutoscaler { @@ -467,9 +466,9 @@ func createHpa(name, namespace, targetAPI, targetKind string, owner *ScaledObjec return hpa } -func createDeployment(namespace string, hasCpu, hasMemory bool) *appsv1.Deployment { +func createDeployment(namespace string, hasCPU, hasMemory bool) *appsv1.Deployment { cpu := 0 - if hasCpu { + if hasCPU { cpu = 100 } memory := 0 @@ -512,9 +511,9 @@ func createDeployment(namespace string, hasCpu, hasMemory bool) *appsv1.Deployme } } -func createStatefulSet(namespace string, hasCpu, hasMemory bool) *appsv1.StatefulSet { +func createStatefulSet(namespace string, hasCPU, hasMemory bool) *appsv1.StatefulSet { cpu := 0 - if hasCpu { + if hasCPU { cpu = 100 } memory := 0 From 9476f6688c2588e27a07e87918f39f78a710134f Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Thu, 5 Jan 2023 14:03:15 +0100 Subject: [PATCH 30/41] fix errors Signed-off-by: Jorge Turrado --- apis/keda/v1alpha1/scaledobject_webhook.go | 17 ++--- .../scaled_object_validation_test.go | 62 +++++++++++++++++-- 2 files changed, 66 insertions(+), 13 deletions(-) diff --git a/apis/keda/v1alpha1/scaledobject_webhook.go b/apis/keda/v1alpha1/scaledobject_webhook.go index a3044aafc9e..b8c9c6dccd6 100644 --- a/apis/keda/v1alpha1/scaledobject_webhook.go +++ b/apis/keda/v1alpha1/scaledobject_webhook.go @@ -73,7 +73,7 @@ func (so *ScaledObject) ValidateDelete() error { func validateWorkload(so *ScaledObject, action string) error { prommetrics.RecordScaledObjectValidatingTotal(so.Namespace, action) - err := verifyCpuMemoryScalers(so, action) + err := verifyCPUMemoryScalers(so, action) if err != nil { return err } @@ -179,7 +179,7 @@ func verifyScaledObjects(incomingSo *ScaledObject, action string) error { return nil } -func verifyCpuMemoryScalers(incomingSo *ScaledObject, action string) error { +func verifyCPUMemoryScalers(incomingSo *ScaledObject, action string) error { var podSpec *corev1.PodSpec for _, trigger := range incomingSo.Spec.Triggers { if trigger.Type == "cpu" || trigger.Type == "memory" { @@ -188,20 +188,21 @@ func verifyCpuMemoryScalers(incomingSo *ScaledObject, action string) error { Namespace: incomingSo.Namespace, Name: incomingSo.Spec.ScaleTargetRef.Name, } - - if incomingSo.Spec.ScaleTargetRef.APIVersion != "apps/v1" { - return nil + incomingSoGckr, err := ParseGVKR(restMapper, incomingSo.Spec.ScaleTargetRef.APIVersion, incomingSo.Spec.ScaleTargetRef.Kind) + if err != nil { + scaledobjectlog.Error(err, "Failed to parse Group, Version, Kind, Resource from incoming ScaledObject", "apiVersion", incomingSo.Spec.ScaleTargetRef.APIVersion, "kind", incomingSo.Spec.ScaleTargetRef.Kind) + return err } - switch incomingSo.Spec.ScaleTargetRef.APIVersion { - case "Deployment": + switch incomingSoGckr.GVKString() { + case "apps/v1.Deployment": deployment := &appsv1.Deployment{} err := kc.Get(context.Background(), key, deployment, &client.GetOptions{}) if err != nil { return err } podSpec = &deployment.Spec.Template.Spec - case "StatefulSet": + case "apps/v1.StatefulSet": statefulset := &appsv1.StatefulSet{} err := kc.Get(context.Background(), key, statefulset, &client.GetOptions{}) if err != nil { diff --git a/tests/internals/scaled_object_validation/scaled_object_validation_test.go b/tests/internals/scaled_object_validation/scaled_object_validation_test.go index 963c2d23682..4a80328e529 100644 --- a/tests/internals/scaled_object_validation/scaled_object_validation_test.go +++ b/tests/internals/scaled_object_validation/scaled_object_validation_test.go @@ -53,14 +53,29 @@ spec: containers: - name: {{.DeploymentName}} image: nginx - resources: - requests: - cpu: 10m ` scaledObjectTemplate = ` apiVersion: keda.sh/v1alpha1 kind: ScaledObject +metadata: + name: {{.ScaledObjectName}} + namespace: {{.TestNamespace}} +spec: + scaleTargetRef: + name: {{.DeploymentName}} + triggers: + - type: cron + metadata: + timezone: Etc/UTC + start: 0 * * * * + end: 1 * * * * + desiredReplicas: '1' +` + + cpuScaledObjectTemplate = ` +apiVersion: keda.sh/v1alpha1 +kind: ScaledObject metadata: name: {{.ScaledObjectName}} namespace: {{.TestNamespace}} @@ -72,6 +87,21 @@ spec: metadata: type: Utilization value: "50" +` + memoryScaledObjectTemplate = ` +apiVersion: keda.sh/v1alpha1 +kind: ScaledObject +metadata: + name: {{.ScaledObjectName}} + namespace: {{.TestNamespace}} +spec: + scaleTargetRef: + name: {{.DeploymentName}} + triggers: + - type: memory + metadata: + type: Utilization + value: "50" ` hpaTemplate = ` apiVersion: autoscaling/v2 @@ -111,6 +141,10 @@ func TestScaledObjectValidations(t *testing.T) { testScaledWorkloadByOtherHpa(t, data) + testMissingCPU(t, data) + + testMissingMemory(t, data) + DeleteKubernetesResources(t, kc, testNamespace, data, templates) } @@ -134,7 +168,7 @@ func testScaledWorkloadByOtherScaledObject(t *testing.T, data templateData) { data.ScaledObjectName = scaledObject2Name err = KubectlApplyWithErrors(t, data, "scaledObjectTemplate", scaledObjectTemplate) assert.Errorf(t, err, "can deploy the scaledObject - %s", err) - assert.Contains(t, err.Error(), fmt.Sprintf("the workload '%s' of type 'apps/v1/Deployment' is already managed by the ScaledObject '%s", deploymentName, scaledObject1Name)) + assert.Contains(t, err.Error(), fmt.Sprintf("the workload '%s' of type 'apps/v1.Deployment' is already managed by the ScaledObject '%s", deploymentName, scaledObject1Name)) data.ScaledObjectName = scaledObject1Name KubectlDeleteWithTemplate(t, data, "scaledObjectTemplate", scaledObjectTemplate) @@ -150,11 +184,29 @@ func testScaledWorkloadByOtherHpa(t *testing.T, data templateData) { data.ScaledObjectName = scaledObject1Name err = KubectlApplyWithErrors(t, data, "scaledObjectTemplate", scaledObjectTemplate) assert.Errorf(t, err, "can deploy the scaledObject - %s", err) - assert.Contains(t, err.Error(), fmt.Sprintf("the workload '%s' of type 'apps/v1/Deployment' is already managed by the hpa '%s", deploymentName, hpaName)) + assert.Contains(t, err.Error(), fmt.Sprintf("the workload '%s' of type 'apps/v1.Deployment' is already managed by the hpa '%s", deploymentName, hpaName)) KubectlDeleteWithTemplate(t, data, "hpaTemplate", hpaTemplate) } +func testMissingCPU(t *testing.T, data templateData) { + t.Log("--- missing cpu resource ---") + + data.ScaledObjectName = scaledObject1Name + err := KubectlApplyWithErrors(t, data, "scaledObjectTemplate", cpuScaledObjectTemplate) + assert.Errorf(t, err, "can deploy the scaledObject - %s", err) + assert.Contains(t, err.Error(), fmt.Sprintf("the scaledobject has a cpu trigger but the container %s doesn't have the cpu request defined", deploymentName)) +} + +func testMissingMemory(t *testing.T, data templateData) { + t.Log("--- missing memory resource ---") + + data.ScaledObjectName = scaledObject1Name + err := KubectlApplyWithErrors(t, data, "scaledObjectTemplate", memoryScaledObjectTemplate) + assert.Errorf(t, err, "can deploy the scaledObject - %s", err) + assert.Contains(t, err.Error(), fmt.Sprintf("the scaledobject has a memory trigger but the container %s doesn't have the memory request defined", deploymentName)) +} + func getTemplateData() (templateData, []Template) { return templateData{ TestNamespace: testNamespace, From 281cdab8e2a78ad52b132052b2b7a3c4bcb4f948 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Fri, 6 Jan 2023 23:30:17 +0100 Subject: [PATCH 31/41] update arch picture Signed-off-by: Jorge Turrado --- images/keda-arch.png | Bin 99764 -> 118859 bytes images/keda-architecture.pptx | Bin 49225 -> 49793 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/images/keda-arch.png b/images/keda-arch.png index ab574853c51e4f73e7510a6bca35f208722bb7fd..7f083450c48899ab5633e167a214e202a73b4239 100644 GIT binary patch literal 118859 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 literal 99764 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

y>b`}jtl#wg(azyv7?>^3 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

eG|u@P&`EDNhP+*_!rf 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 diff --git a/images/keda-architecture.pptx b/images/keda-architecture.pptx index 75543edf91b4b595485efcc553721b3eb70ffd5f..ca431af02d500836a78e73da3aa71afd747d1d12 100644 GIT binary patch delta 33292 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
+-F5u+S-V*9l*#E@wC1mjVDw`sb zAe29Uq}TZ3w71D+EM_b9bKftKrQ`C~!3Jz`%d-Rgba9RX^Ko~sYp0Msk~F-`DB5}o z`-eOw$nr91o9`J0X{8WS3;odL5r6}_W4H&3bo}wB){O(#BVq?PGoQD-%LKsiJ8GOT zjQ0&2=L7vByzd^^9_6h%P14X>qAu%cvo%8=FOeDm&{o8P z;kiPH^TYQfez@Smd}!mLY<7+}*dz8@jvO+W7$hdoqXt!XcM5OaLwc~gJ+`!{*}F;T zoYgJ6_qxejcI*{IIp7`Td{7LqdI%0RSHx8Hw(+cPg(H}{6)>b9(DJm1U4p_-7655NYyW&Yl203-oA2q%e+c>gA9~Lp?R<%kMwfs@waZO& z1l#vzF_dKuE3dEFciumT+SaYN>gY4jt)8)3jfRskuvdinG+kuOPpH+dh7fn?R#P7K zVk+;dSb!R5t8%*vKn`7moc|~{>j%mFLW>rw$dWd9{e#HjrQH4RgGb0%8fd24wy$(f zJ4YN+KW!L2_u4@A>qX(M7SvU*bB!XrMC_&z-TMZsWEpP!=w`Bu&lY8RPPf_`vdApO&9}S78PZxV}m$gt5bb%;K9ud$l#yFt3VLU`a~$iKhqn?whb)EYKpzR zGpnnGshgssoxRgv=7f>To$}pmsIAoJ7)mD-uWU?Jk1!ECgJ7oMZ|TpQGI+wateqb` zS89k;H#!O{pVls`kAC|s&sZan)pFC0m#K_(NEO!E5Feiv>^*1i2~rGxxJk@~Lo*Zj^fVdMQ2BgD{}@+M z<1bOuG>M>yB}D~s(o|l7j*3y!eX#l~fwL0r26*RV%l=JpcaWf|iiPU*wFjBZphr7; zM6LQ6&X8#-Jg0e}&LV!f8XZZ*dsE3*xdnqpuPzthA{yx{Os|~G_|19-rxw6f59^CN zJ&r-wO;f?OwCSI5#%``s(SBqahZNPBT;X*q^8Nl*JC@JJ11ifv%kGq*PG27NG%zmD zo}pcrm^Q!U8y|bJm@5k9FBJ36aJc7Ue*HV7!-$L&v2o0})@E-&Q98{j3FUwt?ru3N1;|xp+pUV6rezIF1luk1@ELsaB z>jwlsN5J;&AMEl5nJ%tk3;ci)k4|)kFHcXUbr8a25!v(1ZmI&aEC>Lh!Rm-_QcO>s*5#;<07xeX%CRn z7ARc&;X;puV+^RCX9ae})8o*#>P-vT}M&yOJ)v_d!fvKHb=RCAV?T9=ifk z$e*9gv6Uamt+c(Zra+GwpZTpT03`Bj?dj{fgN+iQ&;ZA%OqAE`5jfy0<_S^*>5ZhI7qifE6=cQ4+UmGM`K z9{k~TQ<+?pmgaJb#(MV~Jtptdr8z6lm8HkG-)9E*KN?5+oM*nqY>=|90kD&Tc%^RF zs9GQJc$Pf#{20FFUK7|qkjobbEU9(n`_d6`-Gy+=iSil0^cz1Mkr7=cyV^<{kk`A3 z8W%e3o+(GI8YhHQN5YxBTDZ9ErP2}o!dN`3HTOJ4_hkKqLdk}$1CYT4bkZW5~M@b6~D(%}BjD&cDx z)4BO;&^BdFyT`~lz@IMMTGa{zdDk})v=9QAuAZIb@3(zJf$;v|FnvWoQUA`%E(jKF6DIlFiRoXi(EmLo z2tjgklKhV=Ka76@=EWcYGO%w09?9RXx&8^Vko*H0g@FF{H}g->hU_2EnIIgP3*r<0 zZ*LR-1Wqdb0a`WUk^Ik=W0Ze_NHza}njoOR_bUDq#H;%Ugw%vb^7nqJe}bM2{(v+g zpudgq{|P!V{R7H}fd0o46jE1I|6Z4%3M&YR9DEA_{fD*Ie}D|^|9})e!GUF)F~Of& zh@eGX{yf-GZ;12R_BYTHu78rm9YlYw%DVkYBDei3sqFqIS<%V`t?BV6D$&9Pt?&6K z`m31>+QjQmGy`(h(i_a)Dg(}|M~8Osft;lUpIReB+T9yycV9@7l}S+s3Yr=8|7Uyw z0^$Ehu>k3|2@bkL9)C;dynurW2UFl4o`F*Gw}XR)_6`_~P@slG)2)(XLC zV5JTcus{Pc*t?SmIxH9bqm!28pYqDzf9`W0SgVs7{MZB(5pTQEs+JDJ5 ve+P>qFn$*`$^TF~WD5nvKR_->+=5t3G?Qc From 232e18f1e5aa7ff834135ea59a1097c6712e1383 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Sat, 7 Jan 2023 20:03:55 +0100 Subject: [PATCH 32/41] use my own fork temporally Signed-off-by: Jorge Turrado --- go.mod | 3 +++ go.sum | 4 ++-- .../cert-controller/pkg/rotator/rotator.go | 7 ++++--- vendor/modules.txt | 3 ++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 3c6de54adf3..c5ad8678701 100644 --- a/go.mod +++ b/go.mod @@ -99,6 +99,9 @@ replace ( // https://nvd.nist.gov/vuln/detail/CVE-2022-1996 github.com/emicklei/go-restful => github.com/emicklei/go-restful v2.16.0+incompatible + // Needed for certificate generation till the PR is merged: https://github.com/open-policy-agent/cert-controller/pull/52 + github.com/open-policy-agent/cert-controller => github.com/jorturfer/cert-controller v0.5.0 + // https://avd.aquasec.com/nvd/2022/cve-2022-27191/ golang.org/x/crypto => golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 diff --git a/go.sum b/go.sum index feed6eca86c..fa470063637 100644 --- a/go.sum +++ b/go.sum @@ -589,6 +589,8 @@ github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqx github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= +github.com/jorturfer/cert-controller v0.5.0 h1:NuII7efZ14UsCn6a4vPbsQ+POvcmJ0S/UFPIkkXpRFw= +github.com/jorturfer/cert-controller v0.5.0/go.mod h1:uOQW+2tMU51vSxy1Yt162oVUTMdqLuotC0aObQxrh6k= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= @@ -735,8 +737,6 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= -github.com/open-policy-agent/cert-controller v0.5.0 h1:j8WiSh+UYv2GdxlcxgfXv+QxZQYwdbXV3KsZ4fZsM5A= -github.com/open-policy-agent/cert-controller v0.5.0/go.mod h1:uOQW+2tMU51vSxy1Yt162oVUTMdqLuotC0aObQxrh6k= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/otiai10/copy v1.0.2/go.mod h1:c7RpqBkwMom4bYTSkLSym4VSJz/XtncWRAj/J4PEIMY= github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE= diff --git a/vendor/github.com/open-policy-agent/cert-controller/pkg/rotator/rotator.go b/vendor/github.com/open-policy-agent/cert-controller/pkg/rotator/rotator.go index a8f9ee8afe6..396ffb9247c 100644 --- a/vendor/github.com/open-policy-agent/cert-controller/pkg/rotator/rotator.go +++ b/vendor/github.com/open-policy-agent/cert-controller/pkg/rotator/rotator.go @@ -168,6 +168,7 @@ type CertRotator struct { CAName string CAOrganization string DNSName string + ExtraDNSNames []string IsReady chan struct{} Webhooks []WebhookInfo RestartOnSecretRefresh bool @@ -475,14 +476,14 @@ func (cr *CertRotator) CreateCACert(begin, end time.Time) (*KeyPairArtifacts, er // CreateCertPEM takes the results of CreateCACert and uses it to create the // PEM-encoded public certificate and private key, respectively func (cr *CertRotator) CreateCertPEM(ca *KeyPairArtifacts, begin, end time.Time) ([]byte, []byte, error) { + dnsNames := cr.ExtraDNSNames + dnsNames = append(dnsNames, cr.DNSName) templ := &x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{ CommonName: cr.DNSName, }, - DNSNames: []string{ - cr.DNSName, - }, + DNSNames: dnsNames, NotBefore: begin, NotAfter: end, KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, diff --git a/vendor/modules.txt b/vendor/modules.txt index eea426f63ea..3ae741fe676 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -930,7 +930,7 @@ github.com/onsi/gomega/matchers/support/goraph/edge github.com/onsi/gomega/matchers/support/goraph/node github.com/onsi/gomega/matchers/support/goraph/util github.com/onsi/gomega/types -# github.com/open-policy-agent/cert-controller v0.5.0 +# github.com/open-policy-agent/cert-controller v0.5.0 => github.com/jorturfer/cert-controller v0.5.0 ## explicit; go 1.17 github.com/open-policy-agent/cert-controller/pkg/rotator # github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 @@ -2411,6 +2411,7 @@ sigs.k8s.io/yaml # github.com/chzyer/logex => github.com/chzyer/logex v1.2.1 # github.com/dgrijalva/jwt-go => github.com/golang-jwt/jwt/v4 v4.1.0 # github.com/emicklei/go-restful => github.com/emicklei/go-restful v2.16.0+incompatible +# github.com/open-policy-agent/cert-controller => github.com/jorturfer/cert-controller v0.5.0 # golang.org/x/crypto => golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 # golang.org/x/net => golang.org/x/net v0.4.0 # golang.org/x/text => golang.org/x/text v0.4.0 From 6f7839d0183b987c672cf8e6132f53dcd2c72e0e Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Sat, 7 Jan 2023 21:05:54 +0100 Subject: [PATCH 33/41] move to the operator the cert generation Signed-off-by: Jorge Turrado --- Makefile | 12 +- apis/keda/v1alpha1/scaledobject_webhook.go | 2 - cmd/operator/main.go | 123 ++++++++++++++++++++- cmd/webhooks/main.go | 107 ++---------------- config/manager/manager.yaml | 11 ++ config/metrics-server/deployment.yaml | 7 ++ config/rbac/role.yaml | 30 +++++ config/rbac/role_binding.yaml | 14 +++ config/webhooks/kustomization.yaml | 2 - config/webhooks/role.yaml | 37 ------- config/webhooks/role_binding.yaml | 26 ----- config/webhooks/webhooks.yaml | 4 +- 12 files changed, 193 insertions(+), 182 deletions(-) delete mode 100644 config/webhooks/role.yaml delete mode 100644 config/webhooks/role_binding.yaml diff --git a/Makefile b/Makefile index c954f3ddbf7..5e209a6636f 100644 --- a/Makefile +++ b/Makefile @@ -117,21 +117,13 @@ smoke-test: ## Run e2e tests against Kubernetes cluster configured in ~/.kube/co ##@ Development -manifests: controller-gen core-crd-manifests core-rbac-manifests webhook-rbac-manifests - -core-crd-manifests: ## Generate CustomResourceDefinition objects for core componenets. - $(CONTROLLER_GEN) crd:crdVersions=v1 paths="./..." output:crd:artifacts:config=config/crd/bases +manifests: ## Generate CustomResourceDefinition objects for core componenets. + $(CONTROLLER_GEN) crd:crdVersions=v1 rbac:roleName=keda-operator paths="./..." output:crd:artifacts:config=config/crd/bases # withTriggers is only used for duck typing so we only need the deepcopy methods # However operator-sdk generate doesn't appear to have an option for that # until this issue is fixed: https://github.com/kubernetes-sigs/controller-tools/issues/398 rm config/crd/bases/keda.sh_withtriggers.yaml -core-rbac-manifests: ## Generate ClusterRole objects for core componenets. - $(CONTROLLER_GEN) rbac:roleName=keda-operator paths="./controllers/..." - -webhook-rbac-manifests: ## Generate Role for webhooks. - $(CONTROLLER_GEN) rbac:roleName=keda-admission-webhooks paths="./apis/keda/v1alpha1/..." output:dir=config/webhooks - generate: controller-gen mockgen-gen proto-gen ## Generate code containing DeepCopy, DeepCopyInto, DeepCopyObject method implementations (API), mocks and proto. $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." diff --git a/apis/keda/v1alpha1/scaledobject_webhook.go b/apis/keda/v1alpha1/scaledobject_webhook.go index b8c9c6dccd6..6a59c110d28 100644 --- a/apis/keda/v1alpha1/scaledobject_webhook.go +++ b/apis/keda/v1alpha1/scaledobject_webhook.go @@ -49,8 +49,6 @@ func (so *ScaledObject) SetupWebhookWithManager(mgr ctrl.Manager) error { } // +kubebuilder:webhook:path=/validate-keda-sh-v1alpha1-scaledobject,mutating=false,failurePolicy=ignore,sideEffects=None,groups=keda.sh,resources=scaledobjects,verbs=create;update,versions=v1alpha1,name=vscaledobject.kb.io,admissionReviewVersions=v1 -// +kubebuilder:rbac:groups=admissionregistration.k8s.io,resources=validatingwebhookconfigurations,verbs=get;list;watch;update;patch -// +kubebuilder:rbac:groups="",namespace=keda,resources=secrets,verbs=get;list;watch;create;update;patch;delete var _ webhook.Validator = &ScaledObject{} diff --git a/cmd/operator/main.go b/cmd/operator/main.go index d4e31ee389f..8e9c700eae7 100644 --- a/cmd/operator/main.go +++ b/cmd/operator/main.go @@ -17,14 +17,19 @@ limitations under the License. package main import ( + "context" "flag" "fmt" "os" "runtime" "time" + "github.com/open-policy-agent/cert-controller/pkg/rotator" "github.com/spf13/pflag" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" apimachineryruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" utilruntime "k8s.io/apimachinery/pkg/util/runtime" kubeinformers "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" @@ -32,9 +37,11 @@ import ( _ "k8s.io/client-go/plugin/pkg/client/auth" "k8s.io/client-go/tools/cache" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/manager" kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1" kedacontrollers "github.com/kedacore/keda/v2/controllers/keda" @@ -76,6 +83,13 @@ func main() { var adapterClientRequestQPS float32 var adapterClientRequestBurst int var disableCompression bool + var certSecretName string + var certDir string + var operatorServiceName string + var metricsServerServiceName string + var webhooksServiceName string + + var enableCertRotation bool pflag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") pflag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") pflag.StringVar(&metricsServiceAddr, "metrics-service-bind-address", ":9666", "The address the gRPRC Metrics Service endpoint binds to.") @@ -85,6 +99,13 @@ func main() { pflag.Float32Var(&adapterClientRequestQPS, "kube-api-qps", 20.0, "Set the QPS rate for throttling requests sent to the apiserver") pflag.IntVar(&adapterClientRequestBurst, "kube-api-burst", 30, "Set the burst for throttling requests sent to the apiserver") pflag.BoolVar(&disableCompression, "disable-compression", true, "Disable response compression for k8s restAPI in client-go. ") + pflag.StringVar(&certSecretName, "cert-secret-name", "kedaorg-certs", "KEDA certificates secret name. Defaults to kedaorg-certs") + pflag.StringVar(&certDir, "cert-dir", "/certs", "Webhook certificates dir to use. Defaults to /certs") + pflag.StringVar(&operatorServiceName, "operator-service-name", "keda-operator", "Operator service name. Defaults to keda-operator") + pflag.StringVar(&metricsServerServiceName, "metrics-server-service-name", "keda-metrics-apiserver", "Metrics server service name. Defaults to keda-metrics-apiserver") + pflag.StringVar(&webhooksServiceName, "webhooks-service-name", "keda-admission-webhooks", "Webhook service name. Defaults to keda-admission-webhooks") + + pflag.BoolVar(&enableCertRotation, "enable-cert-rotation", false, "enable automatic generation and rotation of TLS certificates/keys") opts := zap.Options{} opts.BindFlags(flag.CommandLine) @@ -92,7 +113,7 @@ func main() { pflag.Parse() ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) - + ctx := ctrl.SetupSignalHandler() namespace, err := getWatchNamespace() if err != nil { setupLog.Error(err, "failed to get watch namespace") @@ -230,6 +251,10 @@ func main() { os.Exit(1) } + if enableCertRotation { + addCertificateRotation(ctx, mgr, certSecretName, certDir, operatorServiceName, metricsServerServiceName, webhooksServiceName) + } + grpcServer := metricsservice.NewGrpcServer(&scaledHandler, metricsServiceAddr) if err := mgr.Add(&grpcServer); err != nil { setupLog.Error(err, "unable to set up Metrics Service gRPC server") @@ -243,7 +268,6 @@ func main() { setupLog.Info(fmt.Sprintf("Go OS/Arch: %s/%s", runtime.GOOS, runtime.GOARCH)) setupLog.Info(fmt.Sprintf("Running on Kubernetes %s", kubeVersion.PrettyVersion), "version", kubeVersion.Version) - ctx := ctrl.SetupSignalHandler() kubeInformerFactory.Start(ctx.Done()) if ok := cache.WaitForCacheSync(ctx.Done(), secretInformer.Informer().HasSynced); !ok { @@ -256,3 +280,98 @@ func main() { os.Exit(1) } } + +// +kubebuilder:rbac:groups=admissionregistration.k8s.io,resources=validatingwebhookconfigurations,verbs=get;list;watch;update;patch +// +kubebuilder:rbac:groups="",namespace=keda,resources=secrets,verbs=get;list;watch;create;update;patch;delete + +func addCertificateRotation(ctx context.Context, mgr manager.Manager, secretName, certDir, operatorServiceName, metricsServerServiceName, webhooksServiceName string) { + caName := "kedaorg-ca" + caOrganization := "kedaorg" + + var rotatorHooks = []rotator.WebhookInfo{ + { + Name: "keda-admission", + Type: rotator.Validating, + }, + } + + // Make sure certs are generated and valid if cert rotation is enabled. + setupFinished := make(chan struct{}) + ensureSecret(ctx, mgr, secretName) + extraDNSNames := []string{} + extraDNSNames = append(extraDNSNames, getDnsNames(operatorServiceName)...) + extraDNSNames = append(extraDNSNames, getDnsNames(webhooksServiceName)...) + extraDNSNames = append(extraDNSNames, getDnsNames(metricsServerServiceName)...) + + setupLog.V(1).Info("setting up cert rotation") + if err := rotator.AddRotator(mgr, &rotator.CertRotator{ + SecretKey: types.NamespacedName{ + Namespace: kedautil.GetPodNamespace(), + Name: secretName, + }, + CertDir: certDir, + CAName: caName, + CAOrganization: caOrganization, + DNSName: extraDNSNames[0], + ExtraDNSNames: extraDNSNames, + IsReady: setupFinished, + Webhooks: rotatorHooks, + RestartOnSecretRefresh: true, + RequireLeaderElection: true, + }); err != nil { + setupLog.Error(err, "unable to set up cert rotation") + os.Exit(1) + } +} + +func getDnsNames(service string) []string { + namespace := kedautil.GetPodNamespace() + return []string{ + service, + fmt.Sprintf("%s.%s", service, namespace), + fmt.Sprintf("%s.%s.svc", service, namespace), + fmt.Sprintf("%s.%s.svc.local", service, namespace), + } +} + +func ensureSecret(ctx context.Context, mgr manager.Manager, secretName string) { + secrets := &corev1.SecretList{} + kedaNamespace := kedautil.GetPodNamespace() + opt := &client.ListOptions{ + Namespace: kedaNamespace, + } + + err := mgr.GetAPIReader().List(ctx, secrets, opt) + if err != nil { + setupLog.Error(err, "unable to check secrets") + os.Exit(1) + } + + exists := false + for _, secret := range secrets.Items { + if secret.Name == secretName { + exists = true + break + } + } + if !exists { + secret := &corev1.Secret{ + ObjectMeta: v1.ObjectMeta{ + Name: secretName, + Namespace: kedaNamespace, + Labels: map[string]string{ + "app": "keda-operator", + "app.kubernetes.io/name": "keda-operator", + "app.kubernetes.io/component": "keda-operator", + "app.kubernetes.io/part-of": "keda", + }, + }, + } + err = mgr.GetClient().Create(ctx, secret) + if err != nil { + setupLog.Error(err, "unable to create certificates secret") + os.Exit(1) + } + setupLog.V(1).Info(fmt.Sprintf("created the secret %s to store cert-controller certificates", secretName)) + } +} diff --git a/cmd/webhooks/main.go b/cmd/webhooks/main.go index 425ba7a114a..796417082fb 100644 --- a/cmd/webhooks/main.go +++ b/cmd/webhooks/main.go @@ -17,49 +17,30 @@ limitations under the License. package main import ( - "context" "flag" "fmt" "os" "runtime" - "github.com/open-policy-agent/cert-controller/pkg/rotator" "github.com/spf13/pflag" - corev1 "k8s.io/api/core/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" apimachineryruntime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" _ "k8s.io/client-go/plugin/pkg/client/auth" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/manager" kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1" "github.com/kedacore/keda/v2/pkg/k8s" - kedautil "github.com/kedacore/keda/v2/pkg/util" "github.com/kedacore/keda/v2/version" //+kubebuilder:scaffold:imports ) -var webhooks = []rotator.WebhookInfo{ - { - Name: "keda-admission", - Type: rotator.Validating, - }, -} - var ( - scheme = apimachineryruntime.NewScheme() - setupLog = ctrl.Log.WithName("setup") - serviceName = "keda-admission-webhooks" - caName = "kedaorg-ca" - caOrganization = "kedaorg" - // DNSName is ..svc - dnsName = fmt.Sprintf("%s.%s.svc", serviceName, kedautil.GetPodNamespace()) + scheme = apimachineryruntime.NewScheme() + setupLog = ctrl.Log.WithName("setup") ) func init() { @@ -74,17 +55,13 @@ func main() { var probeAddr string var webhooksClientRequestQPS float32 var webhooksClientRequestBurst int - var webhookCertDir string - var webhookSecretName string - var enableCertRotation bool + var certDir string var tlsMinVersion string pflag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") pflag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") pflag.Float32Var(&webhooksClientRequestQPS, "kube-api-qps", 20.0, "Set the QPS rate for throttling requests sent to the apiserver") pflag.IntVar(&webhooksClientRequestBurst, "kube-api-burst", 30, "Set the burst for throttling requests sent to the apiserver") - pflag.StringVar(&webhookCertDir, "webhooks-cert-dir", "/certs", "Webhook certificates dir to use. Defaults to /certs") - pflag.StringVar(&webhookSecretName, "webhooks-cert-secret-name", "kedaorg-admission-webhooks-certs", "Webhook certificates secret name. Defaults to kedaorg-admission-webhooks-certs") - pflag.BoolVar(&enableCertRotation, "enable-cert-rotation", false, "enable automatic generation and rotation of webhook TLS certificates/keys") + pflag.StringVar(&certDir, "cert-dir", "/certs", "Webhook certificates dir to use. Defaults to /certs") pflag.StringVar(&tlsMinVersion, "tls-min-version", "1.3", "Minimum TLS version") opts := zap.Options{} @@ -106,38 +83,13 @@ func main() { MetricsBindAddress: metricsAddr, Port: 9443, HealthProbeBindAddress: probeAddr, - CertDir: webhookCertDir, + CertDir: certDir, }) if err != nil { setupLog.Error(err, "unable to start admission webhooks") os.Exit(1) } - // Make sure certs are generated and valid if cert rotation is enabled. - setupFinished := make(chan struct{}) - if enableCertRotation { - ensureSecret(ctx, mgr, webhookSecretName) - setupLog.V(1).Info("setting up cert rotation") - if err := rotator.AddRotator(mgr, &rotator.CertRotator{ - SecretKey: types.NamespacedName{ - Namespace: kedautil.GetPodNamespace(), - Name: webhookSecretName, - }, - CertDir: webhookCertDir, - CAName: caName, - CAOrganization: caOrganization, - DNSName: dnsName, - IsReady: setupFinished, - Webhooks: webhooks, - RestartOnSecretRefresh: true, - }); err != nil { - setupLog.Error(err, "unable to set up cert rotation") - os.Exit(1) - } - } else { - close(setupFinished) - } - //+kubebuilder:scaffold:builder _, kubeVersion, err := k8s.InitScaleClient(mgr) @@ -153,7 +105,7 @@ func main() { setupLog.Info(fmt.Sprintf("Go OS/Arch: %s/%s", runtime.GOOS, runtime.GOARCH)) setupLog.Info(fmt.Sprintf("Running on Kubernetes %s", kubeVersion.PrettyVersion), "version", kubeVersion.Version) - go setupWebhook(mgr, tlsMinVersion, setupFinished) + setupWebhook(mgr, tlsMinVersion) if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up health check") @@ -170,52 +122,7 @@ func main() { } } -func ensureSecret(ctx context.Context, mgr manager.Manager, secretName string) { - secrets := &corev1.SecretList{} - kedaNamespace := kedautil.GetPodNamespace() - opt := &client.ListOptions{ - Namespace: kedaNamespace, - } - - err := mgr.GetAPIReader().List(ctx, secrets, opt) - if err != nil { - setupLog.Error(err, "unable to check secrets") - os.Exit(1) - } - - exists := false - for _, secret := range secrets.Items { - if secret.Name == secretName { - exists = true - break - } - } - if !exists { - secret := &corev1.Secret{ - ObjectMeta: v1.ObjectMeta{ - Name: secretName, - Namespace: kedaNamespace, - Labels: map[string]string{ - "app": "keda-admission-webhooks", - "app.kubernetes.io/name": "keda-admission-webhooks", - "app.kubernetes.io/component": "admission-webhooks", - "app.kubernetes.io/part-of": "keda", - }, - }, - } - err = mgr.GetClient().Create(ctx, secret) - if err != nil { - setupLog.Error(err, "unable to create certificates secret") - os.Exit(1) - } - setupLog.V(1).Info(fmt.Sprintf("created the secret %s to store cert-controller certificates", secretName)) - } -} - -func setupWebhook(mgr manager.Manager, tlsMinVersion string, setupFinished chan struct{}) { - // Block until the setup (certificate generation) finishes. - <-setupFinished - +func setupWebhook(mgr manager.Manager, tlsMinVersion string) { // setup webhooks if err := (&kedav1alpha1.ScaledObject{}).SetupWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "ScaledObject") diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index dd2da83dd25..707a5370e83 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -35,6 +35,7 @@ spec: - --zap-log-level=info - --zap-encoder=console - --zap-time-encoding=rfc3339 + - --enable-cert-rotation=true imagePullPolicy: Always resources: requests: @@ -71,6 +72,16 @@ spec: readOnlyRootFilesystem: true seccompProfile: type: RuntimeDefault + volumeMounts: + - mountPath: /certs + name: certificates + readOnly: true terminationGracePeriodSeconds: 10 nodeSelector: kubernetes.io/os: linux + volumes: + - name: certificates + secret: + defaultMode: 420 + secretName: kedaorg-certs + optional: true diff --git a/config/metrics-server/deployment.yaml b/config/metrics-server/deployment.yaml index 945c9ff0cb5..874ebf0c615 100644 --- a/config/metrics-server/deployment.yaml +++ b/config/metrics-server/deployment.yaml @@ -71,6 +71,9 @@ spec: name: temp-vol - mountPath: /apiserver.local.config/certificates name: certs + - mountPath: /certs + name: certificates + readOnly: true securityContext: runAsNonRoot: true capabilities: @@ -87,3 +90,7 @@ spec: emptyDir: {} - name: certs emptyDir: {} + - name: certificates + secret: + defaultMode: 420 + secretName: kedaorg-certs diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 1846281cdbf..50fdd338b85 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -43,6 +43,16 @@ rules: - '*/scale' verbs: - '*' +- apiGroups: + - admissionregistration.k8s.io + resources: + - validatingwebhookconfigurations + verbs: + - get + - list + - patch + - update + - watch - apiGroups: - apps resources: @@ -99,3 +109,23 @@ rules: - triggerauthentications/status verbs: - '*' +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + creationTimestamp: null + name: keda-operator + namespace: keda +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch diff --git a/config/rbac/role_binding.yaml b/config/rbac/role_binding.yaml index 5df22274191..dea3cb846cc 100644 --- a/config/rbac/role_binding.yaml +++ b/config/rbac/role_binding.yaml @@ -10,3 +10,17 @@ subjects: - kind: ServiceAccount name: keda-operator namespace: keda +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: keda-operator + namespace: keda +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: keda-operator +subjects: +- kind: ServiceAccount + name: keda-operator + namespace: keda \ No newline at end of file diff --git a/config/webhooks/kustomization.yaml b/config/webhooks/kustomization.yaml index 1af6db63d41..bdd46dc282e 100644 --- a/config/webhooks/kustomization.yaml +++ b/config/webhooks/kustomization.yaml @@ -1,6 +1,4 @@ resources: -- role.yaml -- role_binding.yaml - webhooks.yaml - service.yaml - validation_webhooks.yaml diff --git a/config/webhooks/role.yaml b/config/webhooks/role.yaml deleted file mode 100644 index fe2e134dea3..00000000000 --- a/config/webhooks/role.yaml +++ /dev/null @@ -1,37 +0,0 @@ ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - creationTimestamp: null - name: keda-admission-webhooks -rules: -- apiGroups: - - admissionregistration.k8s.io - resources: - - validatingwebhookconfigurations - verbs: - - get - - list - - patch - - update - - watch ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - creationTimestamp: null - name: keda-admission-webhooks - namespace: keda -rules: -- apiGroups: - - "" - resources: - - secrets - verbs: - - create - - delete - - get - - list - - patch - - update - - watch diff --git a/config/webhooks/role_binding.yaml b/config/webhooks/role_binding.yaml deleted file mode 100644 index 657dff91c1c..00000000000 --- a/config/webhooks/role_binding.yaml +++ /dev/null @@ -1,26 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: keda-admission-webhooks -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: keda-admission-webhooks -subjects: -- kind: ServiceAccount - name: keda-operator - namespace: keda ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: keda-admission-webhooks - namespace: keda -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: keda-admission-webhooks -subjects: -- kind: ServiceAccount - name: keda-operator - namespace: keda diff --git a/config/webhooks/webhooks.yaml b/config/webhooks/webhooks.yaml index 89a620e25df..1ef944ccb53 100644 --- a/config/webhooks/webhooks.yaml +++ b/config/webhooks/webhooks.yaml @@ -34,7 +34,6 @@ spec: - --zap-log-level=info - --zap-encoder=console - --zap-time-encoding=rfc3339 - - --enable-cert-rotation=true imagePullPolicy: Always resources: requests: @@ -89,5 +88,4 @@ spec: - name: certificates secret: defaultMode: 420 - secretName: kedaorg-admission-webhooks-certs - optional: true + secretName: kedaorg-certs From d557d744689dc2ac73cc2b02d5e4a09e52c8f705 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Sat, 7 Jan 2023 21:41:56 +0100 Subject: [PATCH 34/41] fix influx test Signed-off-by: Jorge Turrado --- cmd/operator/main.go | 3 + tests/scalers/influxdb/influxdb_test.go | 144 ++++++------------------ 2 files changed, 36 insertions(+), 111 deletions(-) diff --git a/cmd/operator/main.go b/cmd/operator/main.go index 8e9c700eae7..622191d344e 100644 --- a/cmd/operator/main.go +++ b/cmd/operator/main.go @@ -284,6 +284,7 @@ func main() { // +kubebuilder:rbac:groups=admissionregistration.k8s.io,resources=validatingwebhookconfigurations,verbs=get;list;watch;update;patch // +kubebuilder:rbac:groups="",namespace=keda,resources=secrets,verbs=get;list;watch;create;update;patch;delete +// addCertificateRotation registers all needed services to generate the certificates and patches needed resources with the caBundle func addCertificateRotation(ctx context.Context, mgr manager.Manager, secretName, certDir, operatorServiceName, metricsServerServiceName, webhooksServiceName string) { caName := "kedaorg-ca" caOrganization := "kedaorg" @@ -324,6 +325,7 @@ func addCertificateRotation(ctx context.Context, mgr manager.Manager, secretName } } +// getDnsNames creates all the possible DNS names for a given service func getDnsNames(service string) []string { namespace := kedautil.GetPodNamespace() return []string{ @@ -334,6 +336,7 @@ func getDnsNames(service string) []string { } } +// ensureSecret ensures that the secret used for storing TLS certificates exists func ensureSecret(ctx context.Context, mgr manager.Manager, secretName string) { secrets := &corev1.SecretList{} kedaNamespace := kedautil.GetPodNamespace() diff --git a/tests/scalers/influxdb/influxdb_test.go b/tests/scalers/influxdb/influxdb_test.go index 8b99c16d259..3a92b071ba1 100644 --- a/tests/scalers/influxdb/influxdb_test.go +++ b/tests/scalers/influxdb/influxdb_test.go @@ -20,35 +20,28 @@ import ( var _ = godotenv.Load("../../.env") const ( - testName = "influx-db-test" - influxdbJobName = "influx-client-job" - basicDeploymentName = "nginx-deployment" - label = "job-name=influx-client-job" + testName = "influx-db-test" + influxdbJobName = "influx-client-job" + deploymentName = "nginx-deployment" + label = "job-name=influx-client-job" ) var ( - testNamespace = fmt.Sprintf("%s-ns", testName) - influxdbStatefulsetName = fmt.Sprintf("%s-deployment", testName) - scaledObjectFloatName = fmt.Sprintf("%s-so-float", testName) - scaledObjectIntName = fmt.Sprintf("%s-so-int", testName) - scaledObjectActivationName = fmt.Sprintf("%s-so-activation", testName) - basicDeploymentIntName = fmt.Sprintf("%s-int", basicDeploymentName) - basicDeploymentFloatName = fmt.Sprintf("%s-float", basicDeploymentName) - authToken = "" - orgName = "" + testNamespace = fmt.Sprintf("%s-ns", testName) + influxdbStatefulsetName = fmt.Sprintf("%s-deployment", testName) + scaledObjectName = fmt.Sprintf("%s-so", testName) + authToken = "" + orgName = "" ) type templateData struct { - TestNamespace string - InfluxdbStatefulsetName string - InfluxdbWriteJobName string - ScaledObjectIntName string - ScaledObjectFloatName string - ScaledObjectActivationName string - BasicDeploymentIntName string - BasicDeploymentFloatName string - AuthToken string - OrgName string + TestNamespace string + InfluxdbStatefulsetName string + InfluxdbWriteJobName string + ScaledObjectName string + DeploymentName string + AuthToken string + OrgName string } const ( @@ -102,11 +95,11 @@ spec: apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: - name: {{.ScaledObjectActivationName}} + name: {{.ScaledObjectName}} namespace: {{.TestNamespace}} spec: scaleTargetRef: - name: {{.BasicDeploymentFloatName}} + name: {{.DeploymentName}} maxReplicaCount: 2 triggers: - type: influxdb @@ -126,11 +119,11 @@ spec: apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: - name: {{.ScaledObjectFloatName}} + name: {{.ScaledObjectName}} namespace: {{.TestNamespace}} spec: scaleTargetRef: - name: {{.BasicDeploymentFloatName}} + name: {{.DeploymentName}} maxReplicaCount: 2 triggers: - type: influxdb @@ -146,30 +139,6 @@ spec: |> map(fn: (r) => ({r with _value: float(v: r._value)})) ` - scaledObjectTemplateInt = ` -apiVersion: keda.sh/v1alpha1 -kind: ScaledObject -metadata: - name: {{.ScaledObjectIntName}} - namespace: {{.TestNamespace}} -spec: - scaleTargetRef: - name: {{.BasicDeploymentIntName}} - maxReplicaCount: 2 - triggers: - - type: influxdb - metadata: - authToken: {{.AuthToken}} - organizationName: {{.OrgName}} - serverURL: http://influxdb.{{.TestNamespace}}.svc:8086 - thresholdValue: "3" - query: | - from(bucket:"bucket") - |> range(start: -1h) - |> filter(fn: (r) => r._measurement == "stat") - |> map(fn: (r) => ({r with _value: int(v: r._value)})) -` - influxdbWriteJobTemplate = ` apiVersion: batch/v1 kind: Job @@ -188,35 +157,11 @@ spec: restartPolicy: OnFailure ` - basicDeploymentFloatTemplate = ` -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{.BasicDeploymentFloatName}} - namespace: {{.TestNamespace}} - labels: - app: nginx-deployment -spec: - replicas: 0 - selector: - matchLabels: - app: nginx-deployment - template: - metadata: - labels: - app: nginx-deployment - spec: - containers: - - name: nginx-deployment - image: nginx:1.14.2 - ports: - - containerPort: 80 -` - basicDeploymentIntTemplate = ` + deploymentTemplate = ` apiVersion: apps/v1 kind: Deployment metadata: - name: {{.BasicDeploymentIntName}} + name: {{.DeploymentFloatName}} namespace: {{.TestNamespace}} labels: app: nginx-deployment @@ -246,7 +191,7 @@ func TestScaler(t *testing.T) { kc := GetKubernetesClient(t) data, templates := getTemplateData() - CreateKubernetesResources(t, kc, testNamespace, data, []Template{{Name: "influxdbStatefulsetTemplate", Config: influxdbStatefulsetTemplate}}) + CreateKubernetesResources(t, kc, testNamespace, data, templates) assert.True(t, WaitForStatefulsetReplicaReadyCount(t, kc, influxdbStatefulsetName, testNamespace, 1, 60, 1), "replica count should be 0 after a minute") @@ -255,7 +200,6 @@ func TestScaler(t *testing.T) { testActivation(t, kc) // test scaling testScaleFloat(t, kc) - testScaleInt(t, kc) // cleanup DeleteKubernetesResources(t, kc, testNamespace, data, templates) @@ -282,34 +226,25 @@ func runWriteJob(t *testing.T, kc *kubernetes.Clientset) templateData { func getTemplateData() (templateData, []Template) { return templateData{ - TestNamespace: testNamespace, - InfluxdbStatefulsetName: influxdbStatefulsetName, - InfluxdbWriteJobName: influxdbJobName, - ScaledObjectIntName: scaledObjectIntName, - ScaledObjectFloatName: scaledObjectFloatName, - ScaledObjectActivationName: scaledObjectActivationName, - BasicDeploymentFloatName: basicDeploymentFloatName, - BasicDeploymentIntName: basicDeploymentIntName, - AuthToken: authToken, - OrgName: orgName, + TestNamespace: testNamespace, + InfluxdbStatefulsetName: influxdbStatefulsetName, + InfluxdbWriteJobName: influxdbJobName, + ScaledObjectName: scaledObjectName, + DeploymentName: deploymentName, + AuthToken: authToken, + OrgName: orgName, }, []Template{ {Name: "influxdbStatefulsetTemplate", Config: influxdbStatefulsetTemplate}, - {Name: "scaledObjectTemplateFloat", Config: scaledObjectTemplateFloat}, - {Name: "scaledObjectTemplateInt", Config: scaledObjectTemplateInt}, - {Name: "scaledObjectActivationTemplate", Config: scaledObjectActivationTemplate}, - {Name: "basicDeploymentFloatTemplate", Config: basicDeploymentFloatTemplate}, - {Name: "basicDeploymentIntTemplate", Config: basicDeploymentIntTemplate}, - {Name: "influxdbWriteJobTemplate", Config: influxdbWriteJobTemplate}, + {Name: "deploymentTemplate", Config: deploymentTemplate}, } } func testActivation(t *testing.T, kc *kubernetes.Clientset) { t.Log("--- testing activation---") data := runWriteJob(t, kc) - KubectlApplyWithTemplate(t, data, "basicDeploymentFloatTemplate", basicDeploymentFloatTemplate) KubectlApplyWithTemplate(t, data, "scaledObjectActivationTemplate", scaledObjectActivationTemplate) - AssertReplicaCountNotChangeDuringTimePeriod(t, kc, basicDeploymentFloatName, testNamespace, 0, 30) + AssertReplicaCountNotChangeDuringTimePeriod(t, kc, deploymentName, testNamespace, 0, 30) } func testScaleFloat(t *testing.T, kc *kubernetes.Clientset) { @@ -317,19 +252,6 @@ func testScaleFloat(t *testing.T, kc *kubernetes.Clientset) { data := runWriteJob(t, kc) KubectlApplyWithTemplate(t, data, "scaledObjectTemplateFloat", scaledObjectTemplateFloat) - assert.True(t, WaitForDeploymentReplicaReadyCount(t, kc, basicDeploymentFloatName, testNamespace, 2, 60, 1), - "replica count should be 2 after a minute") -} - -func testScaleInt(t *testing.T, kc *kubernetes.Clientset) { - t.Log("--- testing scale out with int ---") - data := runWriteJob(t, kc) - KubectlApplyWithTemplate(t, data, "basicDeploymentIntTemplate", basicDeploymentIntTemplate) - - assert.True(t, WaitForDeploymentReplicaReadyCount(t, kc, basicDeploymentIntName, testNamespace, 0, 60, 1), - "replica count should be 1 after a minute") - - KubectlApplyWithTemplate(t, data, "scaledObjectTemplateInt", scaledObjectTemplateInt) - assert.True(t, WaitForDeploymentReplicaReadyCount(t, kc, basicDeploymentIntName, testNamespace, 2, 60, 1), + assert.True(t, WaitForDeploymentReplicaReadyCount(t, kc, deploymentName, testNamespace, 2, 60, 1), "replica count should be 2 after a minute") } From d24c8b8cb1fc90a782ab90b5fecbede51712582f Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Sat, 7 Jan 2023 21:52:25 +0100 Subject: [PATCH 35/41] fix influx test Signed-off-by: Jorge Turrado --- cmd/operator/main.go | 10 +++++----- config/rbac/role_binding.yaml | 2 +- tests/scalers/influxdb/influxdb_test.go | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cmd/operator/main.go b/cmd/operator/main.go index 622191d344e..44c2889baaf 100644 --- a/cmd/operator/main.go +++ b/cmd/operator/main.go @@ -300,9 +300,9 @@ func addCertificateRotation(ctx context.Context, mgr manager.Manager, secretName setupFinished := make(chan struct{}) ensureSecret(ctx, mgr, secretName) extraDNSNames := []string{} - extraDNSNames = append(extraDNSNames, getDnsNames(operatorServiceName)...) - extraDNSNames = append(extraDNSNames, getDnsNames(webhooksServiceName)...) - extraDNSNames = append(extraDNSNames, getDnsNames(metricsServerServiceName)...) + extraDNSNames = append(extraDNSNames, getDNSNames(operatorServiceName)...) + extraDNSNames = append(extraDNSNames, getDNSNames(webhooksServiceName)...) + extraDNSNames = append(extraDNSNames, getDNSNames(metricsServerServiceName)...) setupLog.V(1).Info("setting up cert rotation") if err := rotator.AddRotator(mgr, &rotator.CertRotator{ @@ -325,8 +325,8 @@ func addCertificateRotation(ctx context.Context, mgr manager.Manager, secretName } } -// getDnsNames creates all the possible DNS names for a given service -func getDnsNames(service string) []string { +// getDNSNames creates all the possible DNS names for a given service +func getDNSNames(service string) []string { namespace := kedautil.GetPodNamespace() return []string{ service, diff --git a/config/rbac/role_binding.yaml b/config/rbac/role_binding.yaml index dea3cb846cc..c43c8f78abf 100644 --- a/config/rbac/role_binding.yaml +++ b/config/rbac/role_binding.yaml @@ -23,4 +23,4 @@ roleRef: subjects: - kind: ServiceAccount name: keda-operator - namespace: keda \ No newline at end of file + namespace: keda diff --git a/tests/scalers/influxdb/influxdb_test.go b/tests/scalers/influxdb/influxdb_test.go index 3a92b071ba1..1fd85615178 100644 --- a/tests/scalers/influxdb/influxdb_test.go +++ b/tests/scalers/influxdb/influxdb_test.go @@ -161,7 +161,7 @@ spec: apiVersion: apps/v1 kind: Deployment metadata: - name: {{.DeploymentFloatName}} + name: {{.DeploymentName}} namespace: {{.TestNamespace}} labels: app: nginx-deployment @@ -183,7 +183,7 @@ spec: ` ) -func TestScaler(t *testing.T) { +func TestInfluxScaler(t *testing.T) { // setup t.Log("--- setting up ---") From f1f3b995a8725bfc168ca46e94f551525575bd0b Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Sun, 8 Jan 2023 00:07:10 +0100 Subject: [PATCH 36/41] fix matching errors Signed-off-by: Jorge Turrado --- apis/keda/v1alpha1/scaledobject_webhook.go | 6 +- .../v1alpha1/scaledobject_webhook_test.go | 78 +++++++++++++------ tests/scalers/selenium/selenium_test.go | 2 +- 3 files changed, 61 insertions(+), 25 deletions(-) diff --git a/apis/keda/v1alpha1/scaledobject_webhook.go b/apis/keda/v1alpha1/scaledobject_webhook.go index 6a59c110d28..3f6177db06a 100644 --- a/apis/keda/v1alpha1/scaledobject_webhook.go +++ b/apis/keda/v1alpha1/scaledobject_webhook.go @@ -115,7 +115,8 @@ func verifyHpas(incomingSo *ScaledObject, action string) error { return err } - if hpaGckr.GVKString() == incomingSoGckr.GVKString() { + if hpaGckr.GVKString() == incomingSoGckr.GVKString() && + hpa.Spec.ScaleTargetRef.Name == incomingSo.Spec.ScaleTargetRef.Name { owned := false for _, owner := range hpa.OwnerReferences { if owner.Kind == incomingSo.Kind { @@ -166,7 +167,8 @@ func verifyScaledObjects(incomingSo *ScaledObject, action string) error { return err } - if soGckr.GVKString() == incomingSoGckr.GVKString() { + if soGckr.GVKString() == incomingSoGckr.GVKString() && + so.Spec.ScaleTargetRef.Name == incomingSo.Spec.ScaleTargetRef.Name { err = fmt.Errorf("the workload '%s' of type '%s' is already managed by the ScaledObject '%s'", so.Spec.ScaleTargetRef.Name, incomingSoGckr.GVKString(), so.Name) scaledobjectlog.Error(err, "validation error") prommetrics.RecordScaledObjectValidatingErrors(incomingSo.Namespace, action, "other-scaled-object") diff --git a/apis/keda/v1alpha1/scaledobject_webhook_test.go b/apis/keda/v1alpha1/scaledobject_webhook_test.go index 81e7ec30b76..6437eac4a1b 100644 --- a/apis/keda/v1alpha1/scaledobject_webhook_test.go +++ b/apis/keda/v1alpha1/scaledobject_webhook_test.go @@ -143,7 +143,7 @@ var _ = It("should validate the so creation when there isn't any hpa", func() { namespaceName := "valid" namespace := createNamespace(namespaceName) - so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment", false) + so := createScaledObject(soName, namespaceName, workloadName, "apps/v1", "Deployment", false) err := k8sClient.Create(context.Background(), namespace) Expect(err).ToNot(HaveOccurred()) @@ -152,13 +152,47 @@ var _ = It("should validate the so creation when there isn't any hpa", func() { Expect(err).ToNot(HaveOccurred()) }) +var _ = It("should validate the so creation when there are other SO for other workloads", func() { + + namespaceName := "valid-multiple-so" + namespace := createNamespace(namespaceName) + so1 := createScaledObject(soName, namespaceName, workloadName, "apps/v1", "Deployment", false) + so2 := createScaledObject("other-so-name", namespaceName, "other-workload", "apps/v1", "Deployment", false) + + err := k8sClient.Create(context.Background(), namespace) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), so1) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), so2) + Expect(err).ToNot(HaveOccurred()) +}) + +var _ = It("should validate the so creation when there are other HPA for other workloads", func() { + + namespaceName := "valid-other-hpa" + namespace := createNamespace(namespaceName) + so := createScaledObject(soName, namespaceName, workloadName, "apps/v1", "Deployment", false) + hpa := createHpa("other-hpa-name", namespaceName, "other-workload", "apps/v1", "Deployment", nil) + + err := k8sClient.Create(context.Background(), namespace) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), hpa) + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(context.Background(), so) + Expect(err).ToNot(HaveOccurred()) +}) + var _ = It("should validate the so creation when it's own hpa is already generated", func() { hpaName := "test-so-hpa" namespaceName := "own-hpa" namespace := createNamespace(namespaceName) - so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment", false) - hpa := createHpa(hpaName, namespaceName, "apps/v1", "Deployment", so) + so := createScaledObject(soName, namespaceName, workloadName, "apps/v1", "Deployment", false) + hpa := createHpa(hpaName, namespaceName, workloadName, "apps/v1", "Deployment", so) err := k8sClient.Create(context.Background(), namespace) Expect(err).ToNot(HaveOccurred()) @@ -175,8 +209,8 @@ var _ = It("should validate the so update when it's own hpa is already generated hpaName := "test-so-hpa" namespaceName := "update-so" namespace := createNamespace(namespaceName) - so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment", false) - hpa := createHpa(hpaName, namespaceName, "apps/v1", "Deployment", so) + so := createScaledObject(soName, namespaceName, workloadName, "apps/v1", "Deployment", false) + hpa := createHpa(hpaName, namespaceName, workloadName, "apps/v1", "Deployment", so) err := k8sClient.Create(context.Background(), namespace) Expect(err).ToNot(HaveOccurred()) @@ -197,8 +231,8 @@ var _ = It("shouldn't validate the so creation when there is another unmanaged h hpaName := "test-unmanaged-hpa" namespaceName := "unmanaged-hpa" namespace := createNamespace(namespaceName) - hpa := createHpa(hpaName, namespaceName, "apps/v1", "Deployment", nil) - so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment", false) + hpa := createHpa(hpaName, namespaceName, workloadName, "apps/v1", "Deployment", nil) + so := createScaledObject(soName, namespaceName, workloadName, "apps/v1", "Deployment", false) err := k8sClient.Create(context.Background(), namespace) Expect(err).ToNot(HaveOccurred()) @@ -215,8 +249,8 @@ var _ = It("shouldn't validate the so creation when there is another so", func() so2Name := "test-so2" namespaceName := "managed-hpa" namespace := createNamespace(namespaceName) - so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment", false) - so2 := createScaledObject(so2Name, namespaceName, "apps/v1", "Deployment", false) + so := createScaledObject(soName, namespaceName, workloadName, "apps/v1", "Deployment", false) + so2 := createScaledObject(so2Name, namespaceName, workloadName, "apps/v1", "Deployment", false) err := k8sClient.Create(context.Background(), namespace) Expect(err).ToNot(HaveOccurred()) @@ -233,8 +267,8 @@ var _ = It("shouldn't validate the so creation when there is another hpa with cu hpaName := "test-custom-hpa" namespaceName := "custom-apis" namespace := createNamespace(namespaceName) - so := createScaledObject(soName, namespaceName, "custom-api", "custom-kind", false) - hpa := createHpa(hpaName, namespaceName, "custom-api", "custom-kind", nil) + so := createScaledObject(soName, namespaceName, workloadName, "custom-api", "custom-kind", false) + hpa := createHpa(hpaName, namespaceName, workloadName, "custom-api", "custom-kind", nil) err := k8sClient.Create(context.Background(), namespace) Expect(err).ToNot(HaveOccurred()) @@ -251,7 +285,7 @@ var _ = It("should validate the so creation with cpu and memory when deployment namespaceName := "deployment-has-requests" namespace := createNamespace(namespaceName) workload := createDeployment(namespaceName, true, true) - so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment", true) + so := createScaledObject(soName, namespaceName, workloadName, "apps/v1", "Deployment", true) err := k8sClient.Create(context.Background(), namespace) Expect(err).ToNot(HaveOccurred()) @@ -268,7 +302,7 @@ var _ = It("shouldn't validate the so creation with cpu and memory when deployme namespaceName := "deployment-no-memory-request" namespace := createNamespace(namespaceName) workload := createDeployment(namespaceName, true, false) - so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment", true) + so := createScaledObject(soName, namespaceName, workloadName, "apps/v1", "Deployment", true) err := k8sClient.Create(context.Background(), namespace) Expect(err).ToNot(HaveOccurred()) @@ -285,7 +319,7 @@ var _ = It("shouldn't validate the so creation with cpu and memory when deployme namespaceName := "deployment-no-cpu-request" namespace := createNamespace(namespaceName) workload := createDeployment(namespaceName, false, true) - so := createScaledObject(soName, namespaceName, "apps/v1", "Deployment", true) + so := createScaledObject(soName, namespaceName, workloadName, "apps/v1", "Deployment", true) err := k8sClient.Create(context.Background(), namespace) Expect(err).ToNot(HaveOccurred()) @@ -302,7 +336,7 @@ var _ = It("should validate the so creation with cpu and memory when statefulset namespaceName := "statefulset-has-requests" namespace := createNamespace(namespaceName) workload := createStatefulSet(namespaceName, true, true) - so := createScaledObject(soName, namespaceName, "apps/v1", "StatefulSet", true) + so := createScaledObject(soName, namespaceName, workloadName, "apps/v1", "StatefulSet", true) err := k8sClient.Create(context.Background(), namespace) Expect(err).ToNot(HaveOccurred()) @@ -319,7 +353,7 @@ var _ = It("shouldn't validate the so creation with cpu and memory when stateful namespaceName := "statefulset-no-memory-request" namespace := createNamespace(namespaceName) workload := createStatefulSet(namespaceName, true, false) - so := createScaledObject(soName, namespaceName, "apps/v1", "StatefulSet", true) + so := createScaledObject(soName, namespaceName, workloadName, "apps/v1", "StatefulSet", true) err := k8sClient.Create(context.Background(), namespace) Expect(err).ToNot(HaveOccurred()) @@ -336,7 +370,7 @@ var _ = It("shouldn't validate the so creation with cpu and memory when stateful namespaceName := "statefulset-no-cpu-request" namespace := createNamespace(namespaceName) workload := createStatefulSet(namespaceName, false, true) - so := createScaledObject(soName, namespaceName, "apps/v1", "StatefulSet", true) + so := createScaledObject(soName, namespaceName, workloadName, "apps/v1", "StatefulSet", true) err := k8sClient.Create(context.Background(), namespace) Expect(err).ToNot(HaveOccurred()) @@ -352,7 +386,7 @@ var _ = It("should validate the so creation without cpu and memory when custom r namespaceName := "crd-not-resources" namespace := createNamespace(namespaceName) - so := createScaledObject(soName, namespaceName, "custom-api", "StatefulSet", true) + so := createScaledObject(soName, namespaceName, workloadName, "custom-api", "StatefulSet", true) err := k8sClient.Create(context.Background(), namespace) Expect(err).ToNot(HaveOccurred()) @@ -374,7 +408,7 @@ func createNamespace(name string) *v1.Namespace { } } -func createScaledObject(name, namespace, targetAPI, targetKind string, hasCPUAndMemory bool) *ScaledObject { +func createScaledObject(name, namespace, targetName, targetAPI, targetKind string, hasCPUAndMemory bool) *ScaledObject { triggers := []ScaleTriggers{ { Type: "cron", @@ -416,7 +450,7 @@ func createScaledObject(name, namespace, targetAPI, targetKind string, hasCPUAnd }, Spec: ScaledObjectSpec{ ScaleTargetRef: &ScaleTarget{ - Name: workloadName, + Name: targetName, APIVersion: targetAPI, Kind: targetKind, }, @@ -428,12 +462,12 @@ func createScaledObject(name, namespace, targetAPI, targetKind string, hasCPUAnd } } -func createHpa(name, namespace, targetAPI, targetKind string, owner *ScaledObject) *v2.HorizontalPodAutoscaler { +func createHpa(name, namespace, targetName, targetAPI, targetKind string, owner *ScaledObject) *v2.HorizontalPodAutoscaler { hpa := &v2.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, Spec: v2.HorizontalPodAutoscalerSpec{ ScaleTargetRef: v2.CrossVersionObjectReference{ - Name: workloadName, + Name: targetName, APIVersion: targetAPI, Kind: targetKind, }, diff --git a/tests/scalers/selenium/selenium_test.go b/tests/scalers/selenium/selenium_test.go index 0e7ab5074e2..44d59ec3820 100644 --- a/tests/scalers/selenium/selenium_test.go +++ b/tests/scalers/selenium/selenium_test.go @@ -457,7 +457,7 @@ spec: ` ) -func TestScaler(t *testing.T) { +func TestSeleniumScaler(t *testing.T) { // Create kubernetes resources kc := GetKubernetesClient(t) data, templates := getTemplateData() From 925ab2c0da47220bd6bb43457a78f20eab373563 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Sun, 8 Jan 2023 01:54:15 +0100 Subject: [PATCH 37/41] undo incorrect change Signed-off-by: Jorge Turrado --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5e209a6636f..9b10be3d71a 100644 --- a/Makefile +++ b/Makefile @@ -117,7 +117,7 @@ smoke-test: ## Run e2e tests against Kubernetes cluster configured in ~/.kube/co ##@ Development -manifests: ## Generate CustomResourceDefinition objects for core componenets. +manifests: controller-gen ## Generate ClusterRole and CustomResourceDefinition objects. $(CONTROLLER_GEN) crd:crdVersions=v1 rbac:roleName=keda-operator paths="./..." output:crd:artifacts:config=config/crd/bases # withTriggers is only used for duck typing so we only need the deepcopy methods # However operator-sdk generate doesn't appear to have an option for that From 2da02f583eb939068725636af1f337af81b6502e Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Mon, 9 Jan 2023 08:56:05 +0100 Subject: [PATCH 38/41] remove keda-architecture ppt in favour of keda-docs schematics.pptx Signed-off-by: Jorge Turrado --- images/keda-architecture.pptx | Bin 49793 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 images/keda-architecture.pptx diff --git a/images/keda-architecture.pptx b/images/keda-architecture.pptx deleted file mode 100644 index ca431af02d500836a78e73da3aa71afd747d1d12..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49793 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 Date: Mon, 9 Jan 2023 11:17:51 +0100 Subject: [PATCH 39/41] Update Makefile Signed-off-by: Jorge Turrado Ferrero --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9b10be3d71a..3afda6377ae 100644 --- a/Makefile +++ b/Makefile @@ -117,7 +117,7 @@ smoke-test: ## Run e2e tests against Kubernetes cluster configured in ~/.kube/co ##@ Development -manifests: controller-gen ## Generate ClusterRole and CustomResourceDefinition objects. +manifests: controller-gen ## Generate ClusterRole and CustomResourceDefinition objects. $(CONTROLLER_GEN) crd:crdVersions=v1 rbac:roleName=keda-operator paths="./..." output:crd:artifacts:config=config/crd/bases # withTriggers is only used for duck typing so we only need the deepcopy methods # However operator-sdk generate doesn't appear to have an option for that From 04a24f43397e719eb4c5badf58153c580aa750b0 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Mon, 9 Jan 2023 11:36:23 +0100 Subject: [PATCH 40/41] apply feedback Signed-off-by: Jorge Turrado --- CHANGELOG.md | 2 +- cmd/operator/main.go | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5841a6f9cf..8bb02407246 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,8 +48,8 @@ To learn more about active deprecations, we recommend checking [GitHub Discussio Here is an overview of all **stable** additions: -- **General**: Introduce new ArangoDB Scaler ([#4000](https://github.com/kedacore/keda/issues/4000)) - **General**: Introduce admission webhooks to automatically validate resource changes to prevent misconfiguration and enforce best practices. ([#3755](https://github.com/kedacore/keda/issues/3755)) +- **General**: Introduce new ArangoDB Scaler ([#4000](https://github.com/kedacore/keda/issues/4000)) Here is an overview of all new **experimental** features: diff --git a/cmd/operator/main.go b/cmd/operator/main.go index 44c2889baaf..8068e9c1536 100644 --- a/cmd/operator/main.go +++ b/cmd/operator/main.go @@ -252,7 +252,10 @@ func main() { } if enableCertRotation { - addCertificateRotation(ctx, mgr, certSecretName, certDir, operatorServiceName, metricsServerServiceName, webhooksServiceName) + if err := addCertificateRotation(ctx, mgr, certSecretName, certDir, operatorServiceName, metricsServerServiceName, webhooksServiceName); err != nil { + setupLog.Error(err, "unable to set up cert rotation") + os.Exit(1) + } } grpcServer := metricsservice.NewGrpcServer(&scaledHandler, metricsServiceAddr) @@ -285,7 +288,7 @@ func main() { // +kubebuilder:rbac:groups="",namespace=keda,resources=secrets,verbs=get;list;watch;create;update;patch;delete // addCertificateRotation registers all needed services to generate the certificates and patches needed resources with the caBundle -func addCertificateRotation(ctx context.Context, mgr manager.Manager, secretName, certDir, operatorServiceName, metricsServerServiceName, webhooksServiceName string) { +func addCertificateRotation(ctx context.Context, mgr manager.Manager, secretName, certDir, operatorServiceName, metricsServerServiceName, webhooksServiceName string) error { caName := "kedaorg-ca" caOrganization := "kedaorg" @@ -320,9 +323,9 @@ func addCertificateRotation(ctx context.Context, mgr manager.Manager, secretName RestartOnSecretRefresh: true, RequireLeaderElection: true, }); err != nil { - setupLog.Error(err, "unable to set up cert rotation") - os.Exit(1) + return err } + return nil } // getDNSNames creates all the possible DNS names for a given service From d3a3efa793a3031191aa31ad3ae8c9027c3bfcf0 Mon Sep 17 00:00:00 2001 From: Jorge Turrado Date: Mon, 9 Jan 2023 11:38:22 +0100 Subject: [PATCH 41/41] add a deletion note in release yaml Signed-off-by: Jorge Turrado --- .github/workflows/release-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index ba8dae8abd7..458a64f9a11 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -103,7 +103,7 @@ jobs: asset_name: keda-${{ steps.get_version.outputs.VERSION }}.yaml asset_content_type: application/x-yaml - # Upload core deployment YAML file to GitHub release + # Upload core deployment YAML file to GitHub release (TO BE DELETED FOR v2.12) - name: Upload Deployment YAML file id: upload-deployment-yaml uses: actions/upload-release-asset@v1