diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 8be0ac7a32..8e30dc9db6 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -5,9 +5,27 @@ on: branches: - main +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: kubernetes-test: - runs-on: large-runner + runs-on: warp-ubuntu-latest-x64-8x-spot + strategy: + fail-fast: false + matrix: + kube-version: + - "1.23" + - "1.30" + test-scenario: + - "multi-apps" + - "helm-chart" + include: + - kube-version: "1.23" + kind-image: "kindest/node:v1.23.17@sha256:14d0a9a892b943866d7e6be119a06871291c517d279aedb816a4b4bc0ec0a5b3" + - kube-version: "1.30" + kind-image: "kindest/node:v1.30.0@sha256:047357ac0cfea04663786a612ba1eaba9702bef25227a794b52890dd8bcd692e" steps: - name: Checkout uses: actions/checkout@v4 @@ -18,16 +36,25 @@ jobs: with: go-version: "~1.22" check-latest: true + cache: true + cache-dependency-path: | + **/go.sum - name: Set up Helm uses: azure/setup-helm@v4 with: version: v3.9.0 - - name: Setup BATS - uses: mig4/setup-bats@v1 + - name: Install chainsaw + uses: kyverno/action-install-chainsaw@v0.2.4 - name: Create Kind Cluster uses: helm/kind-action@v1.10.0 with: + node_image: ${{ matrix.kind-image }} + version: "v0.23.0" cluster_name: kind + - name: Install FE + run: | + cd frontend/webapp + yarn install - name: Build CLI run: | cd cli @@ -35,64 +62,6 @@ jobs: - name: Build and Load Odigos Images run: | TAG=e2e-test make build-images load-to-kind - - name: Install Odigos - run: | - cli/odigos install --version e2e-test - - name: Install Collector - Add Dependencies - shell: bash - run: | - helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts - - uses: actions/checkout@v4 - with: - repository: 'open-telemetry/opentelemetry-helm-charts' - path: opentelemetry-helm-charts - - name: Install Collector - Helm install - run: helm install test -f .github/workflows/e2e/collector-helm-values.yaml opentelemetry-helm-charts/charts/opentelemetry-collector --namespace traces --create-namespace - - name: Wait for Collector to be ready - run: | - kubectl wait --for=condition=Ready --timeout=60s -n traces pod/test-opentelemetry-collector-0 - - name: Install KV Shop - run: | - kubectl create ns kvshop - kubectl apply -f .github/workflows/e2e/kv-shop.yaml -n kvshop - - name: Wait for KV Shop to be ready - run: | - kubectl wait --for=condition=Ready --timeout=100s -n kvshop pods --all - - name: Select kvshop namespace for instrumentation - run: | - kubectl label namespace kvshop odigos-instrumentation=enabled - - name: Connect to Jaeger destination - run: | - kubectl create -f .github/workflows/e2e/jaeger-dest.yaml - - name: Wait for Odigos to bring up collectors - run: | - while [[ $(kubectl get daemonset odigos-data-collection -n odigos-system -o jsonpath='{.status.numberReady}') != 1 ]]; - do - echo "Waiting for odigos-data-collection daemonset to be created" && sleep 3; - done - while [[ $(kubectl get deployment odigos-gateway -n odigos-system -o jsonpath='{.status.readyReplicas}') != 1 ]]; - do - echo "Waiting for odigos-data-collection deployment to be created" && sleep 3; - done - while [[ $(kubectl get pods --output=jsonpath='{range .items[*]}{.status.phase}{"\n"}{end}' -n kvshop | grep -v Running | wc -l) != 0 ]]; - do - echo "Waiting for kvshop pods to be running" && sleep 3; - done - sleep 10 - kubectl get pods -A - kubectl get svc -A - - name: Start bot job - run: | - kubectl create -f .github/workflows/e2e/buybot-job.yaml -n kvshop - - name: Wait for bot job to complete - run: | - kubectl wait --for=condition=Complete --timeout=60s job/buybot-job -n kvshop - - name: Copy trace output - run: | - echo "Sleeping for 10 seconds to allow traces to be collected" - sleep 10 - kubectl cp -c filecp traces/test-opentelemetry-collector-0:tmp/trace.json ./.github/workflows/e2e/bats/traces-orig.json - cat ./.github/workflows/e2e/bats/traces-orig.json - - name: Verify output trace + - name: Run E2E Tests run: | - bats .github/workflows/e2e/bats/verify.bats + chainsaw test tests/e2e/${{ matrix.test-scenario }} diff --git a/.github/workflows/e2e/bats/utilities.bash b/.github/workflows/e2e/bats/utilities.bash deleted file mode 100644 index c53a9ca397..0000000000 --- a/.github/workflows/e2e/bats/utilities.bash +++ /dev/null @@ -1,166 +0,0 @@ -# DATA RETRIEVERS - -# Returns a list of span names emitted by a given library/scope - # $1 - library/scope name -span_names_for() { - spans_from_scope_named $1 | jq '.name' -} - -# Returns a list of server span names emitted by a given library/scope - # $1 - library/scope name -server_span_names_for() { - server_spans_from_scope_named $1 | jq '.name' -} - -# Returns a list of client span names emitted by a given library/scope - # $1 - library/scope name -client_span_names_for() { - client_spans_from_scope_named $1 | jq '.name' -} - -# Returns a list of attributes emitted by a given library/scope -span_attributes_for() { - # $1 - library/scope name - - spans_from_scope_named $1 | \ - jq ".attributes[]" -} - -# Returns a list of attributes emitted by a given library/scope on server spans. -server_span_attributes_for() { - # $1 - library/scope name - - server_spans_from_scope_named $1 | \ - jq ".attributes[]" -} - -# Returns a list of attributes emitted by a given library/scope on clinet_spans. -client_span_attributes_for() { - # $1 - library/scope name - - client_spans_from_scope_named $1 | \ - jq ".attributes[]" -} - -# Returns a list of all resource attributes -resource_attributes_received() { - spans_received | jq ".resource.attributes[]?" -} - -# Returns an array of all spans emitted by a given library/scope - # $1 - library/scope name -spans_from_scope_named() { - spans_received | jq ".scopeSpans[] | select(.scope.name == \"$1\").spans[]" -} - -# Returns an array of all server spans emitted by a given library/scope - # $1 - library/scope name -server_spans_from_scope_named() { - spans_from_scope_named $1 | jq "select(.kind == 2)" -} - -# Returns an array of all client spans emitted by a given library/scope - # $1 - library/scope name -client_spans_from_scope_named() { - spans_from_scope_named $1 | jq "select(.kind == 3)" -} - -# Returns an array of all spans received -spans_received() { - json_output | jq ".resourceSpans[]?" -} - -# Returns the content of the log file produced by a collector -# and located in the same directory as the BATS test file -# loading this helper script. -json_output() { - cat "${BATS_TEST_DIRNAME}/traces-orig.json" -} - -redact_json() { - json_output | \ - jq --sort-keys ' - del( - .resourceSpans[].scopeSpans[].spans[].startTimeUnixNano, - .resourceSpans[].scopeSpans[].spans[].endTimeUnixNano - ) - | .resourceSpans[].scopeSpans[].spans[].traceId|= (if - . // "" | test("^[A-Fa-f0-9]{32}$") then "xxxxx" else (. + "<-INVALID") - end) - | .resourceSpans[].scopeSpans[].spans[].spanId|= (if - . // "" | test("^[A-Fa-f0-9]{16}$") then "xxxxx" else (. + "<-INVALID") - end) - | .resourceSpans[].scopeSpans[].spans[].parentSpanId|= (if - . // "" | test("^[A-Fa-f0-9]{16}$") then "xxxxx" else (. + "") - end) - | .resourceSpans[].scopeSpans|=sort_by(.scope.name) - | .resourceSpans[].scopeSpans[].spans|=sort_by(.kind) - ' > ${BATS_TEST_DIRNAME}/traces.json -} - -# ASSERTION HELPERS - -# expect a 32-digit hexadecimal string (in quotes) -MATCH_A_TRACE_ID=^"\"[A-Fa-f0-9]{32}\"$" - -# expect a 16-digit hexadecimal string (in quotes) -MATCH_A_SPAN_ID=^"\"[A-Fa-f0-9]{16}\"$" - -# Fail and display details if the expected and actual values do not -# equal. Details include both values. -# -# Inspired by bats-assert * bats-support, but dramatically simplified -assert_equal() { - if [[ $1 != "$2" ]]; then - { - echo - echo "-- πŸ’₯ values are not equal πŸ’₯ --" - echo "expected : $2" - echo "actual : $1" - echo "--" - echo - } >&2 # output error to STDERR - return 1 - fi -} - -assert_ge() { - if [[ $1 -lt $2 ]]; then - { - echo - echo "-- πŸ’₯ Assertion failed: value is not greater than or equal to expected πŸ’₯ --" - echo "expected to be greater than or equal to: $2" - echo "actual: $1" - echo "--" - echo - } >&2 # output error to STDERR - return 1 - fi -} - -assert_regex() { - if ! [[ $1 =~ $2 ]]; then - { - echo - echo "-- πŸ’₯ value does not match regular expression πŸ’₯ --" - echo "value : $1" - echo "pattern : $2" - echo "--" - echo - } >&2 # output error to STDERR - return 1 - fi -} - -assert_not_empty() { - if [[ -z "$1" ]]; then - { - echo - echo "-- πŸ’₯ value is empty πŸ’₯ --" - echo "value : $1" - echo "--" - echo - } >&2 # output error to STDERR - return 1 - fi -} diff --git a/.github/workflows/e2e/bats/verify.bats b/.github/workflows/e2e/bats/verify.bats deleted file mode 100644 index 342ae02b0d..0000000000 --- a/.github/workflows/e2e/bats/verify.bats +++ /dev/null @@ -1,165 +0,0 @@ -#!/usr/bin/env bats - -load utilities - -GO_SCOPE="go.opentelemetry.io/auto/net/http" -JAVA_SCOPE="io.opentelemetry.tomcat-7.0" -JAVA_CLIENT_SCOPE="io.opentelemetry.http-url-connection" -JS_SCOPE="@opentelemetry/instrumentation-http" - -@test "all :: includes service.name in resource attributes" { - result=$(resource_attributes_received | jq "select(.key == \"service.name\").value.stringValue" | sort | uniq) - result_separated=$(echo $result | sed 's/\n/,/g') - assert_equal "$result_separated" '"coupon" "frontend" "inventory" "membership" "pricing"' -} - -@test "all :: includes odigos.version in resource attributes" { - result=$(resource_attributes_received | jq -r "select(.key == \"odigos.version\").value.stringValue") - - # Count occurrences of "e2e-test" - e2e_test_count=$(echo "$result" | grep -Fx "e2e-test" | wc -l | xargs) - - # Ensure all values match "e2e-test" by comparing counts - total_count=$(echo "$result" | wc -l | xargs) - - assert_equal "$e2e_test_count" "$total_count" - - # Ensure there are at least 5 elements in the array (currently 5 services) - assert_ge "$total_count" 5 -} - -@test "go :: emits a span name '{http.method}' (per semconv)" { - result=$(server_span_names_for ${GO_SCOPE}) - assert_equal "$result" '"GET"' -} - -@test "java :: emits a span name '{http.method} {http.route}''" { - result=$(server_span_names_for ${JAVA_SCOPE} | sort) - result_separated=$(echo $result | sed 's/\n/,/g') - assert_equal "$result_separated" '"GET /price" "POST /buy"' -} - -@test "js :: emits a span name '{http.method}' (per semconv)" { - result=$(server_span_names_for ${JS_SCOPE}) - assert_equal "$result" '"POST"' -} - -@test "go :: includes http.request.method attribute" { - result=$(server_span_attributes_for ${GO_SCOPE} | jq "select(.key == \"http.request.method\").value.stringValue") - assert_equal "$result" '"GET"' -} - -@test "java :: includes http.request.method attribute" { - result=$(server_span_attributes_for ${JAVA_SCOPE} | jq "select(.key == \"http.request.method\").value.stringValue" | sort) - result_separated=$(echo $result | sed 's/\n/,/g') - assert_equal "$result_separated" '"GET" "POST"' -} - -@test "js :: includes http.method attribute" { - result=$(server_span_attributes_for ${JS_SCOPE} | jq "select(.key == \"http.method\").value.stringValue") - assert_equal "$result" '"POST"' -} - -@test "go :: includes url.path attribute" { - result=$(server_span_attributes_for ${GO_SCOPE} | jq "select(.key == \"url.path\").value.stringValue") - assert_equal "$result" '"/isMember"' -} - -@test "java :: includes url.path attributes" { - result=$(server_span_attributes_for ${JAVA_SCOPE} | jq "select(.key == \"url.path\").value.stringValue" | sort) - result_separated=$(echo $result | sed 's/\n/,/g') - assert_equal "$result_separated" '"/buy" "/price"' -} - -@test "js :: includes http.target attribute" { - result=$(server_span_attributes_for ${JS_SCOPE} | jq "select(.key == \"http.target\").value.stringValue") - assert_equal "$result" '"/apply-coupon"' -} - -@test "go :: includes http.response.status_code attribute" { - result=$(server_span_attributes_for ${GO_SCOPE} | jq "select(.key == \"http.response.status_code\").value.intValue") - assert_equal "$result" '"200"' -} - -@test "java :: includes http.response.status_code attribute" { - result=$(server_span_attributes_for ${JAVA_SCOPE} | jq "select(.key == \"http.response.status_code\").value.intValue" | sort) - result_separated=$(echo $result | sed 's/\n/,/g') - assert_equal "$result_separated" '"200" "200"' -} - -@test "js :: includes http.status_code attribute" { - result=$(server_span_attributes_for ${JS_SCOPE} | jq "select(.key == \"http.status_code\").value.intValue") - assert_equal "$result" '"200"' -} - -@test "client :: includes http.response.status_code attribute" { - result=$(client_span_attributes_for ${JAVA_CLIENT_SCOPE} | jq "select(.key == \"http.response.status_code\").value.intValue" | sort) - result_separated=$(echo $result | sed 's/\n/,/g') - assert_equal "$result_separated" '"200" "200" "200"' -} - -@test "server :: trace ID present and valid in all spans" { - trace_id=$(server_spans_from_scope_named ${GO_SCOPE} | jq ".traceId") - assert_regex "$trace_id" ${MATCH_A_TRACE_ID} - trace_ids=$(server_spans_from_scope_named ${JAVA_SCOPE} | jq ".traceId") - while read -r line; do - assert_regex "$line" ${MATCH_A_TRACE_ID} - done <<< "$trace_ids" - trace_ids=$(server_spans_from_scope_named ${JS_SCOPE} | jq ".traceId") - while read -r line; do - assert_regex "$line" ${MATCH_A_TRACE_ID} - done <<< "$trace_ids" -} - -@test "server :: span ID present and valid in all spans" { - span_id=$(server_spans_from_scope_named ${GO_SCOPE} | jq ".spanId") - assert_regex "$span_id" ${MATCH_A_SPAN_ID} - span_ids=$(server_spans_from_scope_named ${JAVA_SCOPE} | jq ".spanId") - while read -r line; do - assert_regex "$line" ${MATCH_A_SPAN_ID} - done <<< "$span_ids" - span_ids=$(server_spans_from_scope_named ${JS_SCOPE} | jq ".spanId") - while read -r line; do - assert_regex "$line" ${MATCH_A_SPAN_ID} - done <<< "$span_ids" -} - -@test "server :: parent span ID present and valid in all spans" { - parent_span_id=$(server_spans_from_scope_named ${GO_SCOPE} | jq ".parentSpanId") - assert_regex "$parent_span_id" ${MATCH_A_SPAN_ID} - parent_span_ids=$(server_spans_from_scope_named ${JAVA_SCOPE} | jq ".parentSpanId" | sort) - while read -r line; do - assert_regex "$line" ${MATCH_A_SPAN_ID} - done <<< "$parent_span_ids" - parent_span_ids=$(server_spans_from_scope_named ${JS_SCOPE} | jq ".parentSpanId" | sort) - while read -r line; do - assert_regex "$line" ${MATCH_A_SPAN_ID} - done <<< "$parent_span_ids" -} - -@test "client, server :: spans have same trace ID" { - client_trace_id=$(client_spans_from_scope_named ${JAVA_CLIENT_SCOPE} | jq ".traceId" | uniq) - assert_not_empty "$client_trace_id" - server_trace_id=$(server_spans_from_scope_named ${JAVA_SCOPE} | jq ".traceId" | uniq) - assert_not_empty "$server_trace_id" - assert_equal "$server_trace_id" "$client_trace_id" -} - -@test "client, server :: server span has client span as parent" { - server_parent_span_ids=$(server_spans_from_scope_named ${JAVA_SCOPE} | jq ".parentSpanId" | sort) - client_span_ids=$(client_spans_from_scope_named ${JAVA_CLIENT_SCOPE} | jq ".spanId" | sort) - # Verify client_span_ids is contained in server_parent_span_ids - while read -r line; do - if [[ "$client_span_ids" != *"$line"* ]]; then - echo "client span ID $line not found in server parent span IDs" - exit 1 - fi - done <<< "$server_parent_span_ids" - - # Verify Go server span has JS client span as parent - go_parent_span_id=$(server_spans_from_scope_named ${GO_SCOPE} | jq ".parentSpanId") - assert_not_empty "$go_parent_span_id" - js_client_span_id=$(client_spans_from_scope_named ${JS_SCOPE} | jq ".spanId") - assert_not_empty "$js_client_span_id" - assert_equal "$go_parent_span_id" "$js_client_span_id" -} \ No newline at end of file diff --git a/.github/workflows/e2e/collector-helm-values.yaml b/.github/workflows/e2e/collector-helm-values.yaml deleted file mode 100644 index 5b3f81a2ce..0000000000 --- a/.github/workflows/e2e/collector-helm-values.yaml +++ /dev/null @@ -1,49 +0,0 @@ -mode: "statefulset" - -config: - receivers: - otlp: - protocols: - http: - endpoint: ${env:MY_POD_IP}:4318 - - exporters: - debug: {} - file/trace: - path: /tmp/trace.json - rotation: - - service: - telemetry: - logs: - level: "debug" - pipelines: - traces: - receivers: - - otlp - exporters: - - file/trace - - debug - - -image: - repository: otel/opentelemetry-collector-contrib - tag: "latest" - -command: - name: otelcol-contrib - -extraVolumes: - - name: filevolume - emptyDir: {} -extraVolumeMounts: - - mountPath: /tmp - name: filevolume - -extraContainers: - - name: filecp - image: busybox - command: ["sh", "-c", "sleep 36000"] - volumeMounts: - - name: filevolume - mountPath: /tmp \ No newline at end of file diff --git a/.github/workflows/e2e/jaeger-dest.yaml b/.github/workflows/e2e/jaeger-dest.yaml deleted file mode 100644 index aa2e5bd93d..0000000000 --- a/.github/workflows/e2e/jaeger-dest.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: odigos.io/v1alpha1 -kind: Destination -metadata: - generateName: odigos.io.dest.jaeger- - namespace: odigos-system -spec: - data: - JAEGER_URL: test-opentelemetry-collector.traces:4317 - destinationName: collector - signals: - - TRACES - type: jaeger \ No newline at end of file diff --git a/.github/workflows/publish-modules.yml b/.github/workflows/publish-modules.yml index 408c45b6b2..2b2a4e3e00 100644 --- a/.github/workflows/publish-modules.yml +++ b/.github/workflows/publish-modules.yml @@ -137,7 +137,9 @@ jobs: with: push: true tags: keyval/odigos-${{ matrix.service }}:${{ steps.extract_tag.outputs.tag }} - build-args: SERVICE_NAME=${{ matrix.service }} + build-args: | + SERVICE_NAME=${{ matrix.service }} + ODIGOS_VERSION=${{ steps.extract_tag.outputs.tag }} platforms: linux/amd64,linux/arm64 file: >- ${{ diff --git a/.gitignore b/.gitignore index dca09e6103..9f48db9cfc 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,4 @@ dist/ node_modules .DS_Store go.work.sum -opentelemetry-helm-charts/ -odigos-e2e-test -.github/workflows/e2e/bats/traces-orig.json -.github/workflows/e2e/bats/traces.json cli/odigos diff --git a/Makefile b/Makefile index b5633f3797..2548838593 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ build-odiglet-with-agents: docker build -t $(ORG)/odigos-odiglet:$(TAG) . -f odiglet/Dockerfile --build-arg ODIGOS_VERSION=$(TAG) --build-context nodejs-agent-native-community-src=../opentelemetry-node .PHONY: build-autoscaler -build-autoscaler: +build-autoscaler: docker build -t $(ORG)/odigos-autoscaler:$(TAG) . --build-arg SERVICE_NAME=autoscaler .PHONY: build-instrumentor @@ -39,12 +39,7 @@ build-ui: .PHONY: build-images build-images: - make build-autoscaler TAG=$(TAG) - make build-scheduler TAG=$(TAG) - make build-odiglet TAG=$(TAG) - make build-instrumentor TAG=$(TAG) - make build-collector TAG=$(TAG) - make build-ui TAG=$(TAG) + make -j 3 build-autoscaler build-scheduler build-odiglet build-instrumentor build-collector build-ui TAG=$(TAG) .PHONY: push-odiglet push-odiglet: @@ -100,13 +95,8 @@ load-to-kind-scheduler: .PHONY: load-to-kind load-to-kind: - make load-to-kind-autoscaler TAG=$(TAG) - make load-to-kind-scheduler TAG=$(TAG) - make load-to-kind-odiglet TAG=$(TAG) - kind load docker-image $(ORG)/odigos-instrumentor:$(TAG) - make load-to-kind-collector TAG=$(TAG) - make load-to-kind-ui TAG=$(TAG) - make load-to-kind-scheduler TAG=$(TAG) + make -j 6 load-to-kind-instrumentor load-to-kind-autoscaler load-to-kind-scheduler load-to-kind-odiglet load-to-kind-collector load-to-kind-ui TAG=$(TAG) + .PHONY: restart-ui restart-ui: diff --git a/agents/python/configurator/__init__.py b/agents/python/configurator/__init__.py index 1969eb6893..1fdc74683e 100644 --- a/agents/python/configurator/__init__.py +++ b/agents/python/configurator/__init__.py @@ -1,18 +1,20 @@ -# my_otel_configurator/__init__.py -import opentelemetry.sdk._configuration as sdk_config import threading import atexit import os +import opentelemetry.sdk._configuration as sdk_config from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.resources import ProcessResourceDetector, OTELResourceDetector +from .lib_handling import reorder_python_path, reload_distro_modules from .version import VERSION from opamp.http_client import OpAMPHTTPClient class OdigosPythonConfigurator(sdk_config._BaseConfigurator): + def _configure(self, **kwargs): _initialize_components() - -def _initialize_components(): + + +def _initialize_components(): trace_exporters, metric_exporters, log_exporters = sdk_config._import_exporters( sdk_config._get_exporter_names("traces"), sdk_config._get_exporter_names("metrics"), @@ -42,6 +44,12 @@ def _initialize_components(): initialize_logging_if_enabled(log_exporters, resource) + # Reorder the python sys.path to ensure that the user application's dependencies take precedence over the agent's dependencies. + # This is necessary because the user application's dependencies may be incompatible with those used by the agent. + reorder_python_path() + # Reload distro modules to ensure the new path is used. + reload_distro_modules() + def initialize_traces_if_enabled(trace_exporters, resource): traces_enabled = os.getenv(sdk_config.OTEL_TRACES_EXPORTER, "none").strip().lower() if traces_enabled != "none": diff --git a/agents/python/configurator/lib_handling.py b/agents/python/configurator/lib_handling.py new file mode 100644 index 0000000000..68f95cb500 --- /dev/null +++ b/agents/python/configurator/lib_handling.py @@ -0,0 +1,34 @@ +import sys + +def reorder_python_path(): + paths_to_move = [path for path in sys.path if path.startswith('/var/odigos/python')] + + for path in paths_to_move: + sys.path.remove(path) + sys.path.append(path) + + +def reload_distro_modules() -> None: + # Delete distro modules and their sub-modules, as they have been imported before the path was reordered. + # The distro modules will be re-imported from the new path. + needed_module_prefixes = [ + 'google.protobuf', + 'requests', + 'charset_normalizer', + 'certifi', + 'asgiref' + 'idna', + 'deprecated', + 'importlib_metadata', + 'packaging', + 'psutil', + 'zipp', + 'urllib3', + 'uuid_extensions.uuid7', + 'typing_extensions', + ] + + for module in list(sys.modules): + # Check if the module starts with any of the needed prefixes + if any(module.startswith(prefix) for prefix in needed_module_prefixes): + del sys.modules[module] diff --git a/api/config/crd/bases/odigos.io_instrumentationinstances.yaml b/api/config/crd/bases/odigos.io_instrumentationinstances.yaml index 495df4ab6e..673e5da1bd 100644 --- a/api/config/crd/bases/odigos.io_instrumentationinstances.yaml +++ b/api/config/crd/bases/odigos.io_instrumentationinstances.yaml @@ -38,6 +38,14 @@ spec: metadata: type: object spec: + properties: + containerName: + description: |- + stores the name of the container in the pod where the SDK is running. + The pod details can be found as the owner reference on the CR. + type: string + required: + - containerName type: object status: description: |- @@ -120,6 +128,10 @@ spec: type: object type: array healthy: + description: |- + Healthy true means that the odigos agent has started the SDK, and there are no errors. User can expect telemetry to be generated. + Healthy false means that the agent has stopped and telemetry data is not expected to be generated. + Healthy nil means that the agent did not report any health status yet (prefer to always report health status). type: boolean identifyingAttributes: description: |- diff --git a/api/generated/odigos/applyconfiguration/odigos/v1alpha1/instrumentationinstance.go b/api/generated/odigos/applyconfiguration/odigos/v1alpha1/instrumentationinstance.go index e0b7c1f3b9..969675f1ae 100644 --- a/api/generated/odigos/applyconfiguration/odigos/v1alpha1/instrumentationinstance.go +++ b/api/generated/odigos/applyconfiguration/odigos/v1alpha1/instrumentationinstance.go @@ -18,7 +18,6 @@ limitations under the License. package v1alpha1 import ( - v1alpha1 "github.com/odigos-io/odigos/api/odigos/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" types "k8s.io/apimachinery/pkg/types" v1 "k8s.io/client-go/applyconfigurations/meta/v1" @@ -29,7 +28,7 @@ import ( type InstrumentationInstanceApplyConfiguration struct { v1.TypeMetaApplyConfiguration `json:",inline"` *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` - Spec *v1alpha1.InstrumentationInstanceSpec `json:"spec,omitempty"` + Spec *InstrumentationInstanceSpecApplyConfiguration `json:"spec,omitempty"` Status *InstrumentationInstanceStatusApplyConfiguration `json:"status,omitempty"` } @@ -205,8 +204,8 @@ func (b *InstrumentationInstanceApplyConfiguration) ensureObjectMetaApplyConfigu // WithSpec sets the Spec field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Spec field is set to the value of the last call. -func (b *InstrumentationInstanceApplyConfiguration) WithSpec(value v1alpha1.InstrumentationInstanceSpec) *InstrumentationInstanceApplyConfiguration { - b.Spec = &value +func (b *InstrumentationInstanceApplyConfiguration) WithSpec(value *InstrumentationInstanceSpecApplyConfiguration) *InstrumentationInstanceApplyConfiguration { + b.Spec = value return b } diff --git a/api/generated/odigos/applyconfiguration/odigos/v1alpha1/instrumentationinstancespec.go b/api/generated/odigos/applyconfiguration/odigos/v1alpha1/instrumentationinstancespec.go new file mode 100644 index 0000000000..e12b1cce02 --- /dev/null +++ b/api/generated/odigos/applyconfiguration/odigos/v1alpha1/instrumentationinstancespec.go @@ -0,0 +1,38 @@ +/* +Copyright 2022. + +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. +*/ +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// InstrumentationInstanceSpecApplyConfiguration represents an declarative configuration of the InstrumentationInstanceSpec type for use +// with apply. +type InstrumentationInstanceSpecApplyConfiguration struct { + ContainerName *string `json:"containerName,omitempty"` +} + +// InstrumentationInstanceSpecApplyConfiguration constructs an declarative configuration of the InstrumentationInstanceSpec type for use with +// apply. +func InstrumentationInstanceSpec() *InstrumentationInstanceSpecApplyConfiguration { + return &InstrumentationInstanceSpecApplyConfiguration{} +} + +// WithContainerName sets the ContainerName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ContainerName field is set to the value of the last call. +func (b *InstrumentationInstanceSpecApplyConfiguration) WithContainerName(value string) *InstrumentationInstanceSpecApplyConfiguration { + b.ContainerName = &value + return b +} diff --git a/api/generated/odigos/applyconfiguration/utils.go b/api/generated/odigos/applyconfiguration/utils.go index a9ee7510d2..a619c840c2 100644 --- a/api/generated/odigos/applyconfiguration/utils.go +++ b/api/generated/odigos/applyconfiguration/utils.go @@ -54,6 +54,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &odigosv1alpha1.InstrumentationConfigSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("InstrumentationInstance"): return &odigosv1alpha1.InstrumentationInstanceApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("InstrumentationInstanceSpec"): + return &odigosv1alpha1.InstrumentationInstanceSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("InstrumentationInstanceStatus"): return &odigosv1alpha1.InstrumentationInstanceStatusApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("InstrumentationLibrary"): diff --git a/api/odigos/v1alpha1/instrumentationinstance_types.go b/api/odigos/v1alpha1/instrumentationinstance_types.go index e19bfd9eb1..ba69cdeec7 100644 --- a/api/odigos/v1alpha1/instrumentationinstance_types.go +++ b/api/odigos/v1alpha1/instrumentationinstance_types.go @@ -21,6 +21,10 @@ import ( ) type InstrumentationInstanceSpec struct { + // +required + // stores the name of the container in the pod where the SDK is running. + // The pod details can be found as the owner reference on the CR. + ContainerName string `json:"containerName"` } // +kubebuilder:validation:Enum=instrumentation;sampler;exporter @@ -79,26 +83,36 @@ type InstrumentationLibraryStatus struct { // InstrumentationInstanceStatus defines the observed state of InstrumentationInstance // If the instrumentation is not active, this CR should be deleted type InstrumentationInstanceStatus struct { + // Attributes that identify the SDK and are reported as resource attributes in the generated telemetry. // One can identify if an arbitrary telemetry is generated by this SDK by checking those resource attributes. IdentifyingAttributes []Attribute `json:"identifyingAttributes,omitempty"` + // Attributes that are not reported as resource attributes but useful to describe characteristics of the SDK. NonIdentifyingAttributes []Attribute `json:"nonIdentifyingAttributes,omitempty"` - Healthy *bool `json:"healthy,omitempty"` + + // Healthy true means that the odigos agent has started the SDK, and there are no errors. User can expect telemetry to be generated. + // Healthy false means that the agent has stopped and telemetry data is not expected to be generated. + // Healthy nil means that the agent did not report any health status yet (prefer to always report health status). + Healthy *bool `json:"healthy,omitempty"` + // message is a human readable message indicating details about the SDK general health. // can be omitted if healthy is true // +kubebuilder:validation:MaxLength=32768 Message string `json:"message,omitempty"` + // reason contains a programmatic identifier indicating the reason for the component status. // Producers of specific condition types may define expected values and meanings for this field, // and whether the values are considered a guaranteed API. Reason string `json:"reason,omitempty"` + // +required // +kubebuilder:validation:Required // +kubebuilder:validation:Type=string // +kubebuilder:validation:Format=date-time - LastStatusTime metav1.Time `json:"lastStatusTime"` - Components []InstrumentationLibraryStatus `json:"components,omitempty"` + LastStatusTime metav1.Time `json:"lastStatusTime"` + + Components []InstrumentationLibraryStatus `json:"components,omitempty"` } //+genclient diff --git a/cli/cmd/describe.go b/cli/cmd/describe.go index 0374ee38c3..bd18c20583 100644 --- a/cli/cmd/describe.go +++ b/cli/cmd/describe.go @@ -12,21 +12,24 @@ import ( "github.com/odigos-io/odigos/k8sutils/pkg/envoverwrite" "github.com/odigos-io/odigos/k8sutils/pkg/workload" "github.com/spf13/cobra" - appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" ) var ( describeNamespaceFlag string ) +type K8sSourceObject struct { + metav1.ObjectMeta + Kind string + PodTemplateSpec *v1.PodTemplateSpec + LabelSelector *metav1.LabelSelector +} + func wrapTextInRed(text string) string { return "\033[31m" + text + "\033[0m" } @@ -43,55 +46,7 @@ func wrapTextSuccessOfFailure(text string, success bool) string { } } -func cmdKindToK8sGVR(kind string) (schema.GroupVersionResource, error) { - kind = strings.ToLower(kind) - if kind == "deployment" || kind == "deployments" || kind == "dep" { - return schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}, nil - } - if kind == "statefulset" || kind == "statefulsets" || kind == "sts" { - return schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "statefulsets"}, nil - } - if kind == "daemonset" || kind == "daemonsets" || kind == "ds" { - return schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "daemonsets"}, nil - } - - return schema.GroupVersionResource{}, fmt.Errorf("unsupported kind: %s", kind) -} - -func extractPodInfo(obj *unstructured.Unstructured) (*v1.PodTemplateSpec, string, error) { - gvk := obj.GroupVersionKind() - - switch gvk.Kind { - case "Deployment": - var deployment appsv1.Deployment - err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &deployment) - if err != nil { - return nil, "", fmt.Errorf("failed to cast to Deployment: %v", err) - } - return &deployment.Spec.Template, metav1.FormatLabelSelector(deployment.Spec.Selector), nil - - case "StatefulSet": - var statefulSet appsv1.StatefulSet - err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &statefulSet) - if err != nil { - return nil, "", fmt.Errorf("failed to cast to StatefulSet: %v", err) - } - return &statefulSet.Spec.Template, metav1.FormatLabelSelector(statefulSet.Spec.Selector), nil - - case "DaemonSet": - var daemonSet appsv1.DaemonSet - err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &daemonSet) - if err != nil { - return nil, "", fmt.Errorf("failed to cast to DaemonSet: %v", err) - } - return &daemonSet.Spec.Template, metav1.FormatLabelSelector(daemonSet.Spec.Selector), nil - - default: - return nil, "", fmt.Errorf("unsupported kind: %s", gvk.Kind) - } -} - -func getInstrumentationLabelTexts(workload *unstructured.Unstructured, ns *v1.Namespace) (workloadText, nsText, decisionText string, instrumented bool) { +func getInstrumentationLabelTexts(workload *K8sSourceObject, ns *v1.Namespace) (workloadText, nsText, decisionText string, instrumented bool) { workloadLabel, workloadFound := workload.GetLabels()[consts.OdigosInstrumentationLabel] nsLabel, nsFound := ns.GetLabels()[consts.OdigosInstrumentationLabel] @@ -110,17 +65,17 @@ func getInstrumentationLabelTexts(workload *unstructured.Unstructured, ns *v1.Na if workloadFound { instrumented = workloadLabel == consts.InstrumentationEnabled if instrumented { - decisionText = "Workload is instrumented because the " + workload.GetKind() + " contains the label '" + consts.OdigosInstrumentationLabel + "=" + workloadLabel + "'" + decisionText = "Workload is instrumented because the " + workload.Kind + " contains the label '" + consts.OdigosInstrumentationLabel + "=" + workloadLabel + "'" } else { - decisionText = "Workload is NOT instrumented because the " + workload.GetKind() + " contains the label '" + consts.OdigosInstrumentationLabel + "=" + workloadLabel + "'" + decisionText = "Workload is NOT instrumented because the " + workload.Kind + " contains the label '" + consts.OdigosInstrumentationLabel + "=" + workloadLabel + "'" } } else { instrumented = nsLabel == consts.InstrumentationEnabled if instrumented { - decisionText = "Workload is instrumented because the " + workload.GetKind() + " is not labeled, and the namespace is labeled with '" + consts.OdigosInstrumentationLabel + "=" + nsLabel + "'" + decisionText = "Workload is instrumented because the " + workload.Kind + " is not labeled, and the namespace is labeled with '" + consts.OdigosInstrumentationLabel + "=" + nsLabel + "'" } else { if nsFound { - decisionText = "Workload is NOT instrumented because the " + workload.GetKind() + " is not labeled, and the namespace is labeled with '" + consts.OdigosInstrumentationLabel + "=" + nsLabel + "'" + decisionText = "Workload is NOT instrumented because the " + workload.Kind + " is not labeled, and the namespace is labeled with '" + consts.OdigosInstrumentationLabel + "=" + nsLabel + "'" } else { decisionText = "Workload is NOT instrumented because neither the workload nor the namespace has the '" + consts.OdigosInstrumentationLabel + "' label set" } @@ -130,23 +85,15 @@ func getInstrumentationLabelTexts(workload *unstructured.Unstructured, ns *v1.Na return } -func getRelevantResources(ctx context.Context, client *kube.Client, ns string, kind string, name string) (workloadObj *unstructured.Unstructured, namespace *corev1.Namespace, instrumentationConfig *odigosv1.InstrumentationConfig, instrumentedApplication *odigosv1.InstrumentedApplication, podTemplate *corev1.PodTemplateSpec, instrumentationInstances *odigosv1.InstrumentationInstanceList, pods *corev1.PodList, err error) { - gvr, err := cmdKindToK8sGVR(kind) - if err != nil { - return - } - - workloadObj, err = client.Dynamic.Resource(gvr).Namespace(ns).Get(ctx, name, metav1.GetOptions{}) - if err != nil { - return - } +func getRelevantResources(ctx context.Context, client *kube.Client, workloadObj *K8sSourceObject) (namespace *corev1.Namespace, instrumentationConfig *odigosv1.InstrumentationConfig, instrumentedApplication *odigosv1.InstrumentedApplication, instrumentationInstances *odigosv1.InstrumentationInstanceList, pods *corev1.PodList, err error) { + ns := workloadObj.GetNamespace() namespace, err = client.CoreV1().Namespaces().Get(ctx, ns, metav1.GetOptions{}) if err != nil { return } - runtimeObjectName := workload.GetRuntimeObjectName(workloadObj.GetName(), workloadObj.GetKind()) + runtimeObjectName := workload.GetRuntimeObjectName(workloadObj.GetName(), workloadObj.Kind) instrumentationConfig, err = client.OdigosClient.InstrumentationConfigs(ns).Get(ctx, runtimeObjectName, metav1.GetOptions{}) if err != nil { if apierrors.IsNotFound(err) { @@ -178,8 +125,7 @@ func getRelevantResources(ctx context.Context, client *kube.Client, ns string, k return } - var podLabelSelector string - podTemplate, podLabelSelector, err = extractPodInfo(workloadObj) + podLabelSelector := metav1.FormatLabelSelector(workloadObj.LabelSelector) if err != nil { // if pod info cannot be extracted, it is an unrecoverable error return @@ -193,10 +139,9 @@ func getRelevantResources(ctx context.Context, client *kube.Client, ns string, k return } -func printWorkloadManifestInfo(workloadObj *unstructured.Unstructured, namespace *corev1.Namespace) bool { - fmt.Println("Showing details for Workload") +func printWorkloadManifestInfo(workloadObj *K8sSourceObject, namespace *corev1.Namespace) bool { fmt.Println("Name: ", workloadObj.GetName()) - fmt.Println("Kind: ", workloadObj.GetKind()) + fmt.Println("Kind: ", workloadObj.Kind) fmt.Println("Namespace: ", workloadObj.GetNamespace()) fmt.Println("") @@ -279,7 +224,7 @@ func printInstrumentedApplicationInfo(instrumentedApplication *odigosv1.Instrume } } -func printAppliedInstrumentationDeviceInfo(workloadObj *unstructured.Unstructured, instrumentedApplication *odigosv1.InstrumentedApplication, podTemplate *corev1.PodTemplateSpec, instrumented bool) map[string][]string { +func printAppliedInstrumentationDeviceInfo(workloadObj *K8sSourceObject, instrumentedApplication *odigosv1.InstrumentedApplication, instrumented bool) map[string][]string { appliedInstrumentationDeviceStatusMessage := "Unknown" if !instrumented { // if the workload is not instrumented, the instrumentation device expected @@ -303,7 +248,7 @@ func printAppliedInstrumentationDeviceInfo(workloadObj *unstructured.Unstructure fmt.Println("Instrumentation Device:") fmt.Println(" Status:", appliedInstrumentationDeviceStatusMessage) containerNameToExpectedDevices := make(map[string][]string) - for _, container := range podTemplate.Spec.Containers { + for _, container := range workloadObj.PodTemplateSpec.Spec.Containers { fmt.Println(" - Container Name:", container.Name) odigosDevices := make([]string, 0) for resourceName := range container.Resources.Limits { @@ -331,8 +276,12 @@ func printAppliedInstrumentationDeviceInfo(workloadObj *unstructured.Unstructure originalContainerEnvs := origWorkloadEnvValues.GetContainerStoredEnvs(container.Name) if originalContainerEnvs != nil && len(originalContainerEnvs) > 0 { fmt.Println(" Original Environment Variables:") - for _, envVarOriginalValue := range container.Env { - fmt.Println(" - ", envVarOriginalValue.Name, ":", envVarOriginalValue.Value) + for envName, envVarOriginalValue := range originalContainerEnvs { + if envVarOriginalValue == nil { + fmt.Println(" - " + envName + "=null (not set in manifest)") + } else { + fmt.Println(" - " + envName + "=" + *envVarOriginalValue) + } } } } @@ -362,6 +311,9 @@ func printPodContainerInstrumentationInstancesInfo(instances []*odigosv1.Instrum if instance.Status.Message != "" { fmt.Println(" Message:", instance.Status.Message) } + if instance.Status.Reason != "" { + fmt.Println(" Reason:", instance.Status.Reason) + } if unhealthy { fmt.Println(" Troubleshooting: https://docs.odigos.io/architecture/troubleshooting#7-instrumentation-instance-unhealthy") } @@ -405,6 +357,9 @@ func printPodContainerInfo(pod *corev1.Pod, container *corev1.Container, instrum if instance.OwnerReferences[0].Name != pod.GetName() { continue } + if instance.Spec.ContainerName != container.Name { + continue + } thisPodInstrumentationInstances = append(thisPodInstrumentationInstances, &instance) } printPodContainerInstrumentationInstancesInfo(thisPodInstrumentationInstances) @@ -438,44 +393,134 @@ func printPodsInfo(pods *corev1.PodList, instrumentationInstances *odigosv1.Inst } } -// installCmd represents the install command var describeCmd = &cobra.Command{ Use: "describe", Short: "Show details of a specific odigos entity", Long: `Print detailed description of a specific odigos entity, which can be used to troubleshoot issues`, +} + +var describeSourceCmd = &cobra.Command{ + Use: "source", + Short: "Show details of a specific odigos source", + Long: `Print detailed description of a specific odigos source, which can be used to troubleshoot issues`, +} + +func printDescribeSource(ctx context.Context, client *kube.Client, workloadObj *K8sSourceObject) { + namespace, instrumentationConfig, instrumentedApplication, instrumentationInstances, pods, err := getRelevantResources(ctx, client, workloadObj) + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + instrumented := printWorkloadManifestInfo(workloadObj, namespace) + printInstrumentationConfigInfo(instrumentationConfig, instrumented) + printInstrumentedApplicationInfo(instrumentedApplication, instrumented) + containerNameToExpectedDevices := printAppliedInstrumentationDeviceInfo(workloadObj, instrumentedApplication, instrumented) + printPodsInfo(pods, instrumentationInstances, containerNameToExpectedDevices) +} + +var describeSourceDeploymentCmd = &cobra.Command{ + Use: "deployment ", + Short: "Show details of a specific odigos source of type deployment", + Long: `Print detailed description of a specific odigos source of type deployment, which can be used to troubleshoot issues`, + Aliases: []string{"deploy", "deployments", "deploy.apps", "deployment.apps", "deployments.apps"}, + Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { client, err := kube.CreateClient(cmd) if err != nil { kube.PrintClientErrorAndExit(err) } + ctx := cmd.Context() + name := args[0] ns := cmd.Flag("namespace").Value.String() - - if len(args) != 2 { - fmt.Println("Usage: odigos describe ") + deployment, err := client.AppsV1().Deployments(ns).Get(ctx, name, metav1.GetOptions{}) + if err != nil { + fmt.Printf("Error: %v\n", err) return } + workloadObj := &K8sSourceObject{ + Kind: "deployment", + ObjectMeta: deployment.ObjectMeta, + PodTemplateSpec: &deployment.Spec.Template, + LabelSelector: deployment.Spec.Selector, + } + printDescribeSource(ctx, client, workloadObj) + }, +} - kind := args[0] - name := args[1] +var describeSourceDaemonSetCmd = &cobra.Command{ + Use: "daemonset ", + Short: "Show details of a specific odigos source of type daemonset", + Long: `Print detailed description of a specific odigos source of type daemonset, which can be used to troubleshoot issues`, + Aliases: []string{"ds", "daemonsets", "ds.apps", "daemonset.apps", "daemonsets.apps"}, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + client, err := kube.CreateClient(cmd) + if err != nil { + kube.PrintClientErrorAndExit(err) + } - workloadObj, namespace, instrumentationConfig, instrumentedApplication, podTemplate, instrumentationInstances, pods, err := getRelevantResources(ctx, client, ns, kind, name) + ctx := cmd.Context() + name := args[0] + ns := cmd.Flag("namespace").Value.String() + ds, err := client.AppsV1().DaemonSets(ns).Get(ctx, name, metav1.GetOptions{}) if err != nil { fmt.Printf("Error: %v\n", err) return } + workloadObj := &K8sSourceObject{ + Kind: "daemonset", + ObjectMeta: ds.ObjectMeta, + PodTemplateSpec: &ds.Spec.Template, + LabelSelector: ds.Spec.Selector, + } + printDescribeSource(ctx, client, workloadObj) + }, +} + +var describeSourceStatefulSetCmd = &cobra.Command{ + Use: "statefulset ", + Short: "Show details of a specific odigos source of type statefulset", + Long: `Print detailed description of a specific odigos source of type statefulset, which can be used to troubleshoot issues`, + Aliases: []string{"sts", "statefulsets", "sts.apps", "statefulset.apps", "statefulsets.apps"}, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + client, err := kube.CreateClient(cmd) + if err != nil { + kube.PrintClientErrorAndExit(err) + } - instrumented := printWorkloadManifestInfo(workloadObj, namespace) - printInstrumentationConfigInfo(instrumentationConfig, instrumented) - printInstrumentedApplicationInfo(instrumentedApplication, instrumented) - containerNameToExpectedDevices := printAppliedInstrumentationDeviceInfo(workloadObj, instrumentedApplication, podTemplate, instrumented) - printPodsInfo(pods, instrumentationInstances, containerNameToExpectedDevices) + ctx := cmd.Context() + name := args[0] + ns := cmd.Flag("namespace").Value.String() + sts, err := client.AppsV1().StatefulSets(ns).Get(ctx, name, metav1.GetOptions{}) + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + workloadObj := &K8sSourceObject{ + Kind: "statefulset", + ObjectMeta: sts.ObjectMeta, + PodTemplateSpec: &sts.Spec.Template, + LabelSelector: sts.Spec.Selector, + } + printDescribeSource(ctx, client, workloadObj) }, } func init() { + + // describe rootCmd.AddCommand(describeCmd) - describeCmd.Flags().StringVarP(&describeNamespaceFlag, "namespace", "n", "default", "namespace of the resource being described") + // source + describeCmd.AddCommand(describeSourceCmd) + describeSourceCmd.PersistentFlags().StringVarP(&describeNamespaceFlag, "namespace", "n", "default", "namespace of the source being described") + + // source kinds + describeSourceCmd.AddCommand(describeSourceDeploymentCmd) + describeSourceCmd.AddCommand(describeSourceDaemonSetCmd) + describeSourceCmd.AddCommand(describeSourceStatefulSetCmd) } diff --git a/cli/cmd/version.go b/cli/cmd/version.go index b518ccd7ed..ad06ce7332 100644 --- a/cli/cmd/version.go +++ b/cli/cmd/version.go @@ -108,5 +108,5 @@ func init() { rootCmd.AddCommand(versionCmd) versionCmd.Flags().Bool(cliFlag, false, "prints only the CLI version") - versionCmd.Flags().Bool(clusterFlag, false, "prints only the Cluster version") + versionCmd.Flags().Bool(clusterFlag, false, "prints only the version of odigos deployed in the cluster") } diff --git a/cli/go.mod b/cli/go.mod index ef08e4ec80..55a9d2f13d 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -10,10 +10,10 @@ require ( github.com/odigos-io/odigos/k8sutils v0.0.0 github.com/spf13/cobra v1.8.1 gopkg.in/yaml.v2 v2.4.0 - k8s.io/api v0.30.1 - k8s.io/apiextensions-apiserver v0.30.1 + k8s.io/api v0.30.3 + k8s.io/apiextensions-apiserver v0.30.3 k8s.io/apimachinery v0.30.3 - k8s.io/client-go v0.30.1 + k8s.io/client-go v0.30.3 sigs.k8s.io/controller-runtime v0.18.4 sigs.k8s.io/yaml v1.4.0 ) diff --git a/cli/go.sum b/cli/go.sum index f812cabbe4..56b0814af5 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -191,14 +191,14 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY= -k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM= -k8s.io/apiextensions-apiserver v0.30.1 h1:4fAJZ9985BmpJG6PkoxVRpXv9vmPUOVzl614xarePws= -k8s.io/apiextensions-apiserver v0.30.1/go.mod h1:R4GuSrlhgq43oRY9sF2IToFh7PVlF1JjfWdoG3pixk4= +k8s.io/api v0.30.3 h1:ImHwK9DCsPA9uoU3rVh4QHAHHK5dTSv1nxJUapx8hoQ= +k8s.io/api v0.30.3/go.mod h1:GPc8jlzoe5JG3pb0KJCSLX5oAFIW3/qNJITlDj8BH04= +k8s.io/apiextensions-apiserver v0.30.3 h1:oChu5li2vsZHx2IvnGP3ah8Nj3KyqG3kRSaKmijhB9U= +k8s.io/apiextensions-apiserver v0.30.3/go.mod h1:uhXxYDkMAvl6CJw4lrDN4CPbONkF3+XL9cacCT44kV4= k8s.io/apimachinery v0.30.3 h1:q1laaWCmrszyQuSQCfNB8cFgCuDAoPszKY4ucAjDwHc= k8s.io/apimachinery v0.30.3/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= -k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= -k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= +k8s.io/client-go v0.30.3 h1:bHrJu3xQZNXIi8/MoxYtZBBWQQXwy16zqJwloXXfD3k= +k8s.io/client-go v0.30.3/go.mod h1:8d4pf8vYu665/kUbsxWAQ/JDBNWqfFeZnvFiVdmx89U= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= diff --git a/collector/processors/odigossamplingprocessor/processor.go b/collector/processors/odigossamplingprocessor/processor.go index 2ab3155803..c82d823328 100644 --- a/collector/processors/odigossamplingprocessor/processor.go +++ b/collector/processors/odigossamplingprocessor/processor.go @@ -15,8 +15,13 @@ type samplingProcessor struct { } func (sp *samplingProcessor) processTraces(ctx context.Context, td ptrace.Traces) (ptrace.Traces, error) { + globalUnsatisfiedRatioSet := false globalUnsatisfiedRatio := 0.0 + + serviceUnsatisfiedRatioSet := false serviceUnsatisfiedRatio := 0.0 + + EndpointUnsatisfiedRatioSet := false EndpointUnsatisfiedRatio := 0.0 // Evaluate global rules first @@ -27,6 +32,7 @@ func (sp *samplingProcessor) processTraces(ctx context.Context, td ptrace.Traces return td, nil } else { globalUnsatisfiedRatio = max(globalUnsatisfiedRatio, r.FallbackSamplingRatio) + globalUnsatisfiedRatioSet = true } default: sp.logger.Error("Unknown global rule details type", zap.String("rule", rule.Name)) @@ -45,6 +51,7 @@ func (sp *samplingProcessor) processTraces(ctx context.Context, td ptrace.Traces return td, nil } else { EndpointUnsatisfiedRatio = max(EndpointUnsatisfiedRatio, r.FallbackSamplingRatio) + EndpointUnsatisfiedRatioSet = true } } default: @@ -54,13 +61,13 @@ func (sp *samplingProcessor) processTraces(ctx context.Context, td ptrace.Traces var finalUnsatisfiedRatio float64 // Evaluate against the most specific unsatisfied ratio - if EndpointUnsatisfiedRatio > 0.0 { + if EndpointUnsatisfiedRatioSet { finalUnsatisfiedRatio = EndpointUnsatisfiedRatio } else { - if serviceUnsatisfiedRatio > 0.0 { + if serviceUnsatisfiedRatioSet { finalUnsatisfiedRatio = serviceUnsatisfiedRatio } else { - if globalUnsatisfiedRatio > 0.0 { + if globalUnsatisfiedRatioSet { finalUnsatisfiedRatio = globalUnsatisfiedRatio } else { // None of the rules matched, trace is sampled by default @@ -78,8 +85,6 @@ func (sp *samplingProcessor) processTraces(ctx context.Context, td ptrace.Traces return td, nil } -//// - func max(a, b float64) float64 { if a > b { return a diff --git a/common/agent_health_status.go b/common/agent_health_status.go new file mode 100644 index 0000000000..9e90bdee1b --- /dev/null +++ b/common/agent_health_status.go @@ -0,0 +1,25 @@ +package common + +type AgentHealthStatus string + +const ( + // AgentHealthStatusHealthy represents the healthy status of an agent + // It started the OpenTelemetry SDK with no errors, processed any configuration and is ready to receive data. + AgentHealthStatusHealthy AgentHealthStatus = "Healthy" + + // AgentHealthStatusStarting represents that the agent is starting and there is still no health status available. + // Once the agent finishes starting, it should report an either healthy or unhealthy status depending on the result. + AgentHealthStatusStarting AgentHealthStatus = "Starting" + + // AgentHealthStatusUnsupportedRuntimeVersion represents that the agent is running on an unsupported runtime version + // For example: Otel sdk supports node.js >= 14 and workload is running with node.js 12 + AgentHealthStatusUnsupportedRuntimeVersion = "UnsupportedRuntimeVersion" + + // AgentHealthStatusNoHeartbeat is when the server did not receive a 3 heartbeats from the agent, thus it is considered unhealthy + AgentHealthStatusNoHeartbeat = "NoHeartbeat" + + // AgentHealthStatusProcessTerminated is when the agent process is terminated. + // The termination can be due to normal shutdown (e.g. event loop run out of work) + // due to explicit termination (e.g. code calls exit(), or OS signal), or due to an error (e.g. unhandled exception) + AgentHealthProcessTerminated = "ProcessTerminated" +) diff --git a/common/config/clickhouse.go b/common/config/clickhouse.go index 23ee4c756e..3ed3ad5f92 100644 --- a/common/config/clickhouse.go +++ b/common/config/clickhouse.go @@ -2,14 +2,21 @@ package config import ( "errors" + "net/url" + "strings" "github.com/odigos-io/odigos/common" ) const ( - clickhouseEndpoint = "CLICKHOUSE_ENDPOINT" - clickhouseUsername = "CLICKHOUSE_USERNAME" - clickhousePassword = "CLICKHOUSE_PASSWORD" + clickhouseEndpoint = "CLICKHOUSE_ENDPOINT" + clickhouseUsername = "CLICKHOUSE_USERNAME" + clickhousePassword = "${CLICKHOUSE_PASSWORD}" + clickhouseCreateSchema = "CLICKHOUSE_CREATE_SCHEME" + clickhouseDatabaseName = "CLICKHOUSE_DATABASE_NAME" + clickhouseTracesTable = "CLICKHOUSE_TRACES_TABLE" + clickhouseMetricsTable = "CLICKHOUSE_METRICS_TABLE" + clickhouseLogsTable = "CLICKHOUSE_LOGS_TABLE" ) type Clickhouse struct{} @@ -24,19 +31,53 @@ func (c *Clickhouse) ModifyConfig(dest ExporterConfigurer, currentConfig *Config return errors.New("clickhouse endpoint not specified, gateway will not be configured for Clickhouse") } - username, userExists := dest.GetConfig()[clickhouseUsername] - password, passExists := dest.GetConfig()[clickhousePassword] - if userExists != passExists { - return errors.New("clickhouse username and password must be both specified, or neither") + if !strings.Contains(endpoint, "://") { + endpoint = "tcp://" + endpoint + } + + parsedUrl, err := url.Parse(endpoint) + if err != nil { + return errors.New("clickhouse endpoint is not a valid URL") + } + + if parsedUrl.Port() == "" { + endpoint = strings.Replace(endpoint, parsedUrl.Host, parsedUrl.Host+":9000", 1) } + username, userExists := dest.GetConfig()[clickhouseUsername] + exporterName := "clickhouse/clickhouse-" + dest.GetID() exporterConfig := GenericMap{ "endpoint": endpoint, } if userExists { exporterConfig["username"] = username - exporterConfig["password"] = password + exporterConfig["password"] = clickhousePassword + } + + createSchema, exists := dest.GetConfig()[clickhouseCreateSchema] + createSchemaBoolValue := exists && strings.ToLower(createSchema) == "create" + exporterConfig["create_schema"] = createSchemaBoolValue + + dbName, exists := dest.GetConfig()[clickhouseDatabaseName] + if !exists { + return errors.New("clickhouse database name not specified, gateway will not be configured for Clickhouse") + } + exporterConfig["database"] = dbName + + tracesTable, exists := dest.GetConfig()[clickhouseTracesTable] + if exists { + exporterConfig["traces_table_name"] = tracesTable + } + + metricsTable, exists := dest.GetConfig()[clickhouseMetricsTable] + if exists { + exporterConfig["metrics_table_name"] = metricsTable + } + + logsTable, exists := dest.GetConfig()[clickhouseLogsTable] + if exists { + exporterConfig["logs_table_name"] = logsTable } currentConfig.Exporters[exporterName] = exporterConfig diff --git a/destinations/data/clickhouse.yaml b/destinations/data/clickhouse.yaml index 8b4542c812..5eac44faff 100644 --- a/destinations/data/clickhouse.yaml +++ b/destinations/data/clickhouse.yaml @@ -29,7 +29,44 @@ spec: - name: CLICKHOUSE_PASSWORD displayName: Password componentType: input + secret: true componentProps: type: password required: false - secret: true + - name: CLICKHOUSE_CREATE_SCHEME + displayName: Create Scheme + componentType: dropdown + componentProps: + values: + - Create + - Skip + required: true + initialValue: Create + - name: CLICKHOUSE_DATABASE_NAME + displayName: Database Name + componentType: input + componentProps: + type: text + required: true + initialValue: otel + - name: CLICKHOUSE_TRACES_TABLE + displayName: Traces Table + componentType: input + componentProps: + type: text + required: true + initialValue: otel_traces + - name: CLICKHOUSE_METRICS_TABLE + displayName: Metrics Table + componentType: input + componentProps: + type: text + required: true + initialValue: otel_metrics + - name: CLICKHOUSE_LOGS_TABLE + displayName: Logs Table + componentType: input + componentProps: + type: text + required: true + initialValue: otel_logs \ No newline at end of file diff --git a/docs/architecture/troubleshooting.mdx b/docs/architecture/troubleshooting.mdx index 6c4d3a14a6..31b5528c67 100644 --- a/docs/architecture/troubleshooting.mdx +++ b/docs/architecture/troubleshooting.mdx @@ -11,7 +11,7 @@ It is recommended to use the `odigos describe` command to get detailed informati The odigos describe command will show the status of the `odigos-instrumentation` label. ```bash -odigos describe deployment myservice -n default +odigos describe source deployment myservice -n default Labels: Instrumented: false @@ -94,7 +94,7 @@ If the programming language is not detected: The odigos describe command will show the instrumentation devices added to the pod spec for the workload. ```bash -odigos describe deployment myservice -n default +odigos describe source deployment myservice -n default Instrumentation Device: Status: Successfully applied instrumentation device to pod template @@ -124,7 +124,7 @@ kubectl get deployment myservice -o jsonpath='{.spec.template.spec.containers[*] The odigos describe command will show the pods for this workload and the instrumentation devices resource they have. ```bash -odigos describe deployment myservice -n default +odigos describe source deployment myservice -n default Pods (Total 1, Running 1): @@ -149,7 +149,7 @@ Instrumentation instances are currently available only for go, node.js, and pyth They describe the status of the OpenTelemetry SDK agent running in a pod container. ```bash -odigos describe deployment myservice -n default +odigos describe source deployment myservice -n default Pods (Total 1, Running 1): diff --git a/docs/cli/odigos.mdx b/docs/cli/odigos.mdx index 06fb124692..eefa29ba63 100644 --- a/docs/cli/odigos.mdx +++ b/docs/cli/odigos.mdx @@ -20,6 +20,7 @@ odigos [flags] ## SEE ALSO * [odigos cloud](/cli/odigos_cloud) - Manage the connection of the cluster to Odigos cloud +* [odigos describe](/cli/odigos_describe) - Describe a source in your kubernetes cluster * [odigos install](/cli/odigos_install) - Install Odigos in your kubernetes cluster * [odigos ui](/cli/odigos_ui) - Open the Odigos UI in your browser * [odigos uninstall](/cli/odigos_uninstall) - Uninstall Odigos from your kubernetes cluster diff --git a/docs/cli/odigos_describe.mdx b/docs/cli/odigos_describe.mdx new file mode 100644 index 0000000000..2ab6a66020 --- /dev/null +++ b/docs/cli/odigos_describe.mdx @@ -0,0 +1,98 @@ +--- +title: "odigos describe" +sidebarTitle: "odigos describe" +--- + +Show details of a specific odigos entity + +## Synopsis + +This command can be used for troubleshooting and observing odigos state for it's various entities. +It is similar to `kubectl describe` but for odigos entities. + +- odigos source of kind deployment: + +```bash +odigos describe source deployment [flags] +``` + +- odigos source of kind statefulset: + +```bash +odigos describe source statefulset [flags] +``` + +- odigos source of kind daemonset: + +```bash +odigos describe source daemonset [flags] +``` + + +## Examples + +``` + # Describe a source of kind deployment and name myservice in the default namespace + odigos describe source deployment myservice -n default +``` + +Output: + +``` +Name: myservice +Kind: deployment +Namespace: default + +Labels: + Instrumented: true + Workload: odigos-instrumentation=enabled + Namespace: odigos-instrumentation=enabled + Decision: Workload is instrumented because the deployment contains the label 'odigos-instrumentation=enabled' + Troubleshooting: https://docs.odigos.io/architecture/troubleshooting#1-odigos-instrumentation-label + +Instrumentation Config: + Created at 2024-07-30 19:00:40 +0300 IDT + +Runtime inspection details: + Created at 2024-07-30 19:00:40 +0300 IDT + Detected Containers: + - Container Name: myservice + Language: javascript + Relevant Environment Variables: + - NODE_OPTIONS : --require /var/odigos/nodejs/autoinstrumentation.js + +Instrumentation Device: + Status: Successfully applied instrumentation device to pod template + - Container Name: myservice + Instrumentation Devices: javascript-native-community + +Pods (Total 1, Running 1): + + Pod Name: myservice-ffd68d8c-qqmxl + Pod Phase: Running + Pod Node Name: kind-control-plane + Containers: + - Container Name: myservice + Instrumentation Devices: javascript-native-community + Instrumentation Instances: + - Healthy: true + Reason: Healthy +``` + +## Options + +``` + -n, --namespace namespace of the source being described (default "default") +``` + + +## Options inherited from parent commands + +``` + --kubeconfig string (optional) absolute path to the kubeconfig file +``` + +## SEE ALSO + +* [odigos](/cli/odigos) - odigos CLI +* [odigos version](/cli/odigos_version) - print version information diff --git a/docs/cli/odigos_version.mdx b/docs/cli/odigos_version.mdx index e26a919e93..c78e4ba019 100644 --- a/docs/cli/odigos_version.mdx +++ b/docs/cli/odigos_version.mdx @@ -25,12 +25,15 @@ odigos version [flags] ## Options -This command has not specific options. +``` + --cli print only the CLI version + --cluster prints only the version of odigos deployed in the cluster +``` ## Options inherited from parent commands ``` - --kubeconfig string (optional) absolute path to the kubeconfig file + --kubeconfig string (optional) absolute path to the kubeconfig file ``` ## SEE ALSO diff --git a/docs/instrumentations/golang/golang.mdx b/docs/instrumentations/golang/golang.mdx index 0088d1649e..d587221061 100644 --- a/docs/instrumentations/golang/golang.mdx +++ b/docs/instrumentations/golang/golang.mdx @@ -5,7 +5,7 @@ sidebarTitle: "Go" ## Supported Versions -Odigos uses the official [opentelemetry-go-instrumentation](https://github.com/open-telemetry/opentelemetry-go-instrumentation) OpenTelemetry Auto Instrumentation using eBPF, thus it supports the same Node.js versions as this project. +Odigos uses the official [opentelemetry-go-instrumentation](https://github.com/open-telemetry/opentelemetry-go-instrumentation) OpenTelemetry Auto Instrumentation using eBPF, thus it supports the same golang versions as this project. - Go runtime versions **1.17** and above are supported. diff --git a/docs/instrumentations/nodejs/enrichment.mdx b/docs/instrumentations/nodejs/enrichment.mdx index a4c29405f5..5033c37c0c 100644 --- a/docs/instrumentations/nodejs/enrichment.mdx +++ b/docs/instrumentations/nodejs/enrichment.mdx @@ -62,5 +62,17 @@ function my_function() { Make sure to replace `instrumentation-scope-name` and `instrumentation-scope-version` with the name and version of your instrumented file. +Important Notes: + +1. **Always End a span**: + Ensure that every span is ended to appear in your trace. Defer the End method of the span to guarantee that the span is always ended, even with multiple return paths in the function. +2. **Propagate and use a valid context object**: + When calling tracer.Start, use a valid context object instead of context.Background(). This makes the new span a child of the active span, ensuring it appears correctly in the trace. +3. **Pass the context object downstream**: + When calling downstream functions, pass the context object returned from tracer.Start() to ensure any spans created within these functions are children of the current span. This maintains the hierarchical relationship between spans and provides a clear trace of the request flow. +4. **Assign meaningful names to spans**: + Use descriptive names for spans, (such as the function name) to clearly describe the operations they represent. This helps anyone examining the trace to easily understand the span's purpose and context. +5. **Avoid dynamic, high cardinality data in span names**: + Do not include dynamic data such as IDs in the span name, as this can cause performance issues and make the trace harder to read. Instead, use static, descriptive names for spans and record dynamic information in span attributes. This ensures better performance and readability of the trace. diff --git a/docs/instrumentations/overview.mdx b/docs/instrumentations/overview.mdx index fb6ac6a7a2..cda02c2ef7 100644 --- a/docs/instrumentations/overview.mdx +++ b/docs/instrumentations/overview.mdx @@ -13,5 +13,5 @@ Odigos provides automatic instrumentations in kubernetes for the following runti - [Go](/instrumentations/golang/golang) versions >= 1.17 - Java versions >= 17 - [Node.js](/instrumentations/nodejs/nodejs) versions >= 14 -- Python versions >= 3.8 +- [Python](/instrumentations/python/python) versions >= 3.8 - .NET diff --git a/docs/instrumentations/python/enrichment.mdx b/docs/instrumentations/python/enrichment.mdx new file mode 100644 index 0000000000..2510ef09bf --- /dev/null +++ b/docs/instrumentations/python/enrichment.mdx @@ -0,0 +1,96 @@ + +--- +title: "Enriching Python OpenTelemetry Data" +sidebarTitle: "Enrichment" +--- + +Odigos will automatically instrument your Python services and record semantic spans from popular modules. +Many users find the automatic instrumentation data sufficient for their needs. However, if there is anything specific to your application that you want to record, you can enrich the data by adding custom spans to your code. + +This is sometimes referred to as "manual instrumentation" + +## Required dependencies + +Install the API from PyPI using pip: + +```bash +pip install opentelemetry-api +``` + +## Creating Spans + +To create a span, use the `tracer` object from the `opentelemetry.trace` module. The `tracer` object is a factory for creating spans. + +```python +from opentelemetry import trace + +tracer = trace.get_tracer(__name__) + +def my_function(): + with tracer.start_as_current_span("my_function") as span: + print("Hello world!") +``` + +Important Notes: + +1. **Assign meaningful names to spans**: + Use descriptive names for spans, (such as the function name) to clearly describe the operations they represent. This helps anyone examining the trace to easily understand the span's purpose and context. +2. **Avoid dynamic, high cardinality data in span names**: + Do not include dynamic data such as IDs in the span name, as this can cause performance issues and make the trace harder to read. Instead, use static, descriptive names for spans and record dynamic information in span attributes. This ensures better performance and readability of the trace. + + + +### Recording Span Attributes + +Span attributes are key-value pairs that record additional information about an operation, which can be useful for debugging, performance analysis, or troubleshooting + +Examples: + +- User ID, organization ID, Account ID or other identifiers. +- Inputs - the relevant parameters or configuration that influenced the operation. +- Outputs - the result of the operation. +- Entities - the entities or resources that the operation interacted with. + +Attribute names are lowercased strings in the form `my_application.my_attribute`, example: `my_service.user_id`. +Read more [here](https://opentelemetry.io/docs/specs/semconv/general/attribute-naming/) + +To record attributes, use the `set_attribute` method on the span object. + +``` python +def my_function(arg: str): + with tracer.start_as_current_span("my_function") as span: + span.set_attribute("argument_name", arg) +``` + +Important Notes: + +1. **Be cautious when recording data**: + Avoid including PII (personally identifiable information) or any data you do not wish to expose in your traces. +2. **Attribute cost considerations**: + Each attribute affects performance and processing time. Record only what is necessary and avoid superfluous data. +3. **Use static names for attributes**: + Avoid dynamic content such as IDs in attribute keys. Use static names and properly namespace them (scope.attribute_name) to provide clear context for downstream consumers. +4. **Adhere to OpenTelemetry semantic conventions**: + Prefer using namespaces and attribute names from the OpenTelemetry semantic conventions to enhance data interoperability and make it easier for others to understand. + + +## Recording Errors + +To easily identify and monitor errors in your traces, the Span object includes a status field that can be used to mark the span as an error. This helps in spotting errors in trace viewers, sampling, and setting up alerts. + +If an operation you are tracing fails, you can mark the span's status as an error and record the error details within the span. Here's how you can do it: + +- An exception has been raised to demonstrate an error that occurred in your code. + +``` python + +def my_function(): + with tracer.start_as_current_span("my_function") as span: + try: + print("Hello world!") + raise Exception("Some Exception") + except Exception as e: + span.record_exception(e) + span.set_status(trace.Status(trace.StatusCode.ERROR, str(e))) + +``` \ No newline at end of file diff --git a/docs/instrumentations/python/python.mdx b/docs/instrumentations/python/python.mdx new file mode 100644 index 0000000000..bb70d05c1d --- /dev/null +++ b/docs/instrumentations/python/python.mdx @@ -0,0 +1,88 @@ +--- +title: "Python Automatic Instrumentation" +sidebarTitle: "Python" +--- + +## Supported Versions + +Odigos uses the official [opentelemetry-python-instrumentation](https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation) OpenTelemetry Auto Instrumentation, thus it supports the same Python versions as this project. + +- In the enterprise version, Odigos leverages eBPF to enhance performance in the Python instrumentation process. + +- Python runtime versions 3.8 and above are supported. + + +## Traces + +Odigos will automatically instrument your Python services to record and collect spans for distributed tracing, by utilizing the OpenTelemetry Python official auto Instrumentation Libraries. + +## Instrumentation Libraries + +The following Python modules will be auto instrumented by Odigos: + +### Database Clients, ORMs, and Data Access Libraries +- [`aiopg`](https://pypi.org/project/aiopg/) versions `aiopg >= 0.13.0, < 2.0.0` +- [`dbapi`](https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/dbapi/dbapi.html) +- [`mysql`](https://pypi.org/project/mysql-connector-python/) version `mysql-connector-python >= 8.0.0, < 9.0.0` +- [`mysqlclient`](https://pypi.org/project/mysqlclient/) version `mysqlclient < 3.0.0` +- [`psycopg`](https://pypi.org/project/psycopg/) versions `psycopg >= 3.1.0` +- [`psycopg2`](https://pypi.org/project/psycopg2/) versions `psycopg2 >= 2.7.3.1` +- [`pymemcache`](https://pypi.org/project/pymemcache/) versions `pymemcache >= 1.3.5, < 5.0.0` +- [`pymongo`](https://pypi.org/project/pymongo/) versions `pymongo >= 3.1, < 5.0.0` +- [`pymysql`](https://pypi.org/project/PyMySQL/) versions `pymysql < 2.0.0` +- [`redis`](https://pypi.org/project/redis/) versions `redis >= 2.6` +- [`sqlalchemy`](https://pypi.org/project/SQLAlchemy/) +- [`sqlite3`](https://docs.python.org/3/library/sqlite3.html) +- [`tortoiseorm`](https://pypi.org/project/tortoise-orm/) versions `tortoise-orm >= 0.17.0`, `pydantic >= 1.10.2` +- [`cassandra`](https://pypi.org/project/cassandra-driver/) versions `cassandra-driver >= 3.25.0, < 4.0.0`, `scylla-driver >= 3.25.0, < 4.0.0` +- [`elasticsearch`](https://pypi.org/project/elasticsearch/) versions `elasticsearch >= 6.0.0` +- [`asyncpg`](https://pypi.org/project/asyncpg/) versions `asyncpg >= 0.12.0` + +### HTTP Frameworks +- [`asgi`](https://pypi.org/project/asgiref/) versions `asgiref >= 3.0.0, < 4.0.0` +- [`django`](https://pypi.org/project/Django/) versions `django >= 1.10.0` +- [`fastapi`](https://pypi.org/project/fastapi/) versions `fastapi >= 0.58.0, < 0.59.0`, `fastapi-slim >= 0.111.0, < 0.112.0` +- [`flask`](https://pypi.org/project/Flask/) versions `flask >= 1.0.0` +- [`pyramid`](https://pypi.org/project/pyramid/) versions `pyramid >= 1.7.0` +- [`starlette`](https://pypi.org/project/starlette/) versions `starlette >= 0.13.0, < 0.14.0` +- [`falcon`](https://pypi.org/project/falcon/) versions `falcon >= 1.4.1, < 3.1.2` +- [`tornado`](https://pypi.org/project/tornado/) versions `tornado >= 5.1.1` + +### HTTP Clients +- [`aiohttp-client`](https://pypi.org/project/aiohttp/) versions `aiohttp >= 3.0.0, < 4.0.0` +- [`httpx`](https://pypi.org/project/httpx/) versions `httpx >= 0.18.0` +- [`requests`](https://pypi.org/project/requests/) versions `requests >= 2.0.0, < 3.0.0` +- [`urllib`](https://docs.python.org/3/library/urllib.html) +- [`urllib3`](https://pypi.org/project/urllib3/) versions `urllib3 >= 1.0.0, < 3.0.0` + +### Messaging Systems Clients +- [`aio-pika`](https://pypi.org/project/aio-pika/) versions `aio_pika >= 7.2.0, < 10.0.0` +- [`celery`](https://pypi.org/project/celery/) versions `celery >= 4.0.0, < 6.0.0` +- [`confluent-kafka`](https://pypi.org/project/confluent-kafka/) versions `confluent-kafka >= 1.8.2, <= 2.4.0` +- [`kafka-python`](https://pypi.org/project/kafka-python/) versions `kafka-python >= 2.0.0` +- [`pika`](https://pypi.org/project/pika/) versions `pika >= 0.12.0` +- [`remoulade`](https://pypi.org/project/remoulade/) versions `remoulade >= 0.50.0` + +### RPC (Remote Procedure Call) +- [`grpc`](https://pypi.org/project/grpcio/) versions `grpcio >= 1.27.0, < 2.0.0` + +### Web Servers +- [`aiohttp-server`](https://pypi.org/project/aiohttp/) versions `aiohttp >= 3.0.0, < 4.0.0` +- [`wsgi`](https://docs.python.org/3/library/wsgiref.html) + +### Cloud Services and SDKs +- [`boto`](https://pypi.org/project/boto/) versions `boto >= 2.0.0, < 3.0.0` +- [`boto3sqs`](https://pypi.org/project/boto3/) versions `boto3 >= 1.0.0, < 2.0.0` +- [`botocore`](https://pypi.org/project/botocore/) versions `botocore >= 1.0.0, < 2.0.0` + +### Framework and Library Utilities +- [`jinja2`](https://pypi.org/project/Jinja2/) versions `jinja2 >= 2.7, < 4.0` + +### Other +- [`asyncio`](https://pypi.org/project/asyncio/) + +### Loggers + +Automatic injection of trace context (trace id and span id) into log records for the following loggers: + +- [`logging`](https://docs.python.org/3/library/logging.html) \ No newline at end of file diff --git a/docs/mint.json b/docs/mint.json index 65d2e526cd..12e677434d 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -79,6 +79,7 @@ "pages": [ "cli/odigos", "cli/odigos_cloud", + "cli/odigos_describe", "cli/odigos_install", "cli/odigos_ui", "cli/odigos_uninstall", @@ -105,7 +106,14 @@ "instrumentations/nodejs/nodejs", "instrumentations/nodejs/enrichment" ] - } + }, + { + "group": "Python", + "pages": [ + "instrumentations/python/python", + "instrumentations/python/enrichment" + ] + } ] }, { diff --git a/docs/pipeline/actions/attributes/piimasking.mdx b/docs/pipeline/actions/attributes/piimasking.mdx index 8eb616ea97..989ecc3013 100644 --- a/docs/pipeline/actions/attributes/piimasking.mdx +++ b/docs/pipeline/actions/attributes/piimasking.mdx @@ -1,6 +1,6 @@ --- -title: "Pii Masking" -sidebarTitle: "Pii Masking" +title: "PII Masking" +sidebarTitle: "PII Masking" --- The "PII Masking" Odigos Action can be used to mask PII data from span attribute values. diff --git a/docs/pipeline/actions/sampling/introduction.mdx b/docs/pipeline/actions/sampling/introduction.mdx index 845701cb19..d51e8a917a 100644 --- a/docs/pipeline/actions/sampling/introduction.mdx +++ b/docs/pipeline/actions/sampling/introduction.mdx @@ -2,8 +2,10 @@ title: "Sampling Actions Introduction" sidebarTitle: "Introduction" --- -> **Note:** -> This feature is in beta. It may be subject to changes and improvements based on user feedback. + + +This feature is in beta. It may be subject to changes and improvements based on user feedback. + Sampling Actions allow you to configure various types of sampling methods before exporting traces to your Odigos Destinations. diff --git a/frontend/endpoints/applications.go b/frontend/endpoints/applications.go index fd9f294a69..a29346cf09 100644 --- a/frontend/endpoints/applications.go +++ b/frontend/endpoints/applications.go @@ -4,6 +4,10 @@ import ( "context" "net/http" + appsv1 "k8s.io/api/apps/v1" + + "github.com/odigos-io/odigos/k8sutils/pkg/client" + "github.com/gin-gonic/gin" "github.com/odigos-io/odigos/frontend/kube" "golang.org/x/sync/errgroup" @@ -38,7 +42,7 @@ type GetApplicationItemInNamespace struct { type GetApplicationItem struct { // namespace is used when querying all the namespaces, the response can be grouped/filtered by namespace namespace string - nsItem GetApplicationItemInNamespace + nsItem GetApplicationItemInNamespace } func GetApplicationsInNamespace(c *gin.Context) { @@ -126,70 +130,76 @@ func getApplicationsInNamespace(ctx context.Context, nsName string, nsInstrument } func getDeployments(namespace string, ctx context.Context) ([]GetApplicationItem, error) { - deps, err := kube.DefaultClient.AppsV1().Deployments(namespace).List(ctx, metav1.ListOptions{}) + var response []GetApplicationItem + err := client.ListWithPages(client.DefaultPageSize, kube.DefaultClient.AppsV1().Deployments(namespace).List, ctx, metav1.ListOptions{}, func(deps *appsv1.DeploymentList) error { + for _, dep := range deps.Items { + appInstrumentationLabeled := isObjectLabeledForInstrumentation(dep.ObjectMeta) + response = append(response, GetApplicationItem{ + namespace: dep.Namespace, + nsItem: GetApplicationItemInNamespace{ + Name: dep.Name, + Kind: WorkloadKindDeployment, + Instances: int(dep.Status.AvailableReplicas), + AppInstrumentationLabeled: appInstrumentationLabeled, + }, + }) + } + return nil + }) + if err != nil { return nil, err } - response := make([]GetApplicationItem, len(deps.Items)) - for i, dep := range deps.Items { - appInstrumentationLabeled := isObjectLabeledForInstrumentation(dep.ObjectMeta) - response[i] = GetApplicationItem{ - namespace: dep.Namespace, - nsItem: GetApplicationItemInNamespace { - Name: dep.Name, - Kind: WorkloadKindDeployment, - Instances: int(dep.Status.AvailableReplicas), - AppInstrumentationLabeled: appInstrumentationLabeled, - }, - } - } - return response, nil } func getStatefulSets(namespace string, ctx context.Context) ([]GetApplicationItem, error) { - ss, err := kube.DefaultClient.AppsV1().StatefulSets(namespace).List(ctx, metav1.ListOptions{}) + var response []GetApplicationItem + err := client.ListWithPages(client.DefaultPageSize, kube.DefaultClient.AppsV1().StatefulSets(namespace).List, ctx, metav1.ListOptions{}, func(sss *appsv1.StatefulSetList) error { + for _, ss := range sss.Items { + appInstrumentationLabeled := isObjectLabeledForInstrumentation(ss.ObjectMeta) + response = append(response, GetApplicationItem{ + namespace: ss.Namespace, + nsItem: GetApplicationItemInNamespace{ + Name: ss.Name, + Kind: WorkloadKindStatefulSet, + Instances: int(ss.Status.ReadyReplicas), + AppInstrumentationLabeled: appInstrumentationLabeled, + }, + }) + } + return nil + }) + if err != nil { return nil, err } - response := make([]GetApplicationItem, len(ss.Items)) - for i, s := range ss.Items { - appInstrumentationLabeled := isObjectLabeledForInstrumentation(s.ObjectMeta) - response[i] = GetApplicationItem{ - namespace: s.Namespace, - nsItem: GetApplicationItemInNamespace { - Name: s.Name, - Kind: WorkloadKindStatefulSet, - Instances: int(s.Status.ReadyReplicas), - AppInstrumentationLabeled: appInstrumentationLabeled, - }, - } - } - return response, nil } func getDaemonSets(namespace string, ctx context.Context) ([]GetApplicationItem, error) { - dss, err := kube.DefaultClient.AppsV1().DaemonSets(namespace).List(ctx, metav1.ListOptions{}) + var response []GetApplicationItem + err := client.ListWithPages(client.DefaultPageSize, kube.DefaultClient.AppsV1().DaemonSets(namespace).List, ctx, metav1.ListOptions{}, func(dss *appsv1.DaemonSetList) error { + for _, ds := range dss.Items { + appInstrumentationLabeled := isObjectLabeledForInstrumentation(ds.ObjectMeta) + response = append(response, GetApplicationItem{ + namespace: ds.Namespace, + nsItem: GetApplicationItemInNamespace{ + Name: ds.Name, + Kind: WorkloadKindDaemonSet, + Instances: int(ds.Status.NumberReady), + AppInstrumentationLabeled: appInstrumentationLabeled, + }, + }) + } + return nil + }) + if err != nil { return nil, err } - response := make([]GetApplicationItem, len(dss.Items)) - for i, ds := range dss.Items { - appInstrumentationLabeled := isObjectLabeledForInstrumentation(ds.ObjectMeta) - response[i] = GetApplicationItem{ - namespace: ds.Namespace, - nsItem: GetApplicationItemInNamespace { - Name: ds.Name, - Kind: WorkloadKindDaemonSet, - Instances: int(ds.Status.NumberReady), - AppInstrumentationLabeled: appInstrumentationLabeled, - }, - } - } - return response, nil } diff --git a/frontend/endpoints/destination_recognition/destination_finder.go b/frontend/endpoints/destination_recognition/destination_finder.go new file mode 100644 index 0000000000..4e7f13ac06 --- /dev/null +++ b/frontend/endpoints/destination_recognition/destination_finder.go @@ -0,0 +1,65 @@ +package destination_recognition + +import ( + "github.com/gin-gonic/gin" + k8s "k8s.io/api/core/v1" +) + +type DestinationType string + +const ( + JaegerDestinationType DestinationType = "jaeger" +) + +var SupportedDestinationType = []DestinationType{JaegerDestinationType} + +type DestinationDetails struct { + Name string `json:"name"` + UrlString string `json:"urlString"` +} + +type IDestinationFinder interface { + isPotentialService(k8s.Service) bool + fetchDestinationDetails(k8s.Service) DestinationDetails +} + +type DestinationFinder struct { + destinationFinder IDestinationFinder +} + +func (d *DestinationFinder) isPotentialService(service k8s.Service) bool { + return d.destinationFinder.isPotentialService(service) +} + +func (d *DestinationFinder) fetchDestinationDetails(service k8s.Service) DestinationDetails { + return d.destinationFinder.fetchDestinationDetails(service) +} + +func GetAllPotentialDestinationDetails(ctx *gin.Context, namespaces []k8s.Namespace) ([]DestinationDetails, error) { + helmManagedServices := getAllHelmManagedServices(ctx, namespaces) + + var destinationFinder *DestinationFinder + var destinationDetails []DestinationDetails + for _, service := range helmManagedServices { + for _, destinationType := range SupportedDestinationType { + destinationFinder = getDestinationFinder(destinationType) + if destinationFinder.isPotentialService(service) { + destinationDetails = append(destinationDetails, destinationFinder.fetchDestinationDetails(service)) + break + } + } + } + + return destinationDetails, nil +} + +func getDestinationFinder(destinationType DestinationType) *DestinationFinder { + switch destinationType { + case JaegerDestinationType: + return &DestinationFinder{ + destinationFinder: &JaegerDestinationFinder{}, + } + } + + return nil +} diff --git a/frontend/endpoints/destination_recognition/jaeger.go b/frontend/endpoints/destination_recognition/jaeger.go new file mode 100644 index 0000000000..c6f6c77cef --- /dev/null +++ b/frontend/endpoints/destination_recognition/jaeger.go @@ -0,0 +1,35 @@ +package destination_recognition + +import ( + "fmt" + "github.com/odigos-io/odigos/common" + k8s "k8s.io/api/core/v1" + "strings" +) + +type JaegerDestinationFinder struct{} + +const JaegerGrpcOtlpPort int32 = 4317 +const JaegerGrpcUrlFormat = "%s.%s:%d" + +func (j *JaegerDestinationFinder) isPotentialService(service k8s.Service) bool { + for _, port := range service.Spec.Ports { + if isJaegerService(port.Port, service.Name) { + return true + } + } + + return false +} + +func isJaegerService(portNumber int32, name string) bool { + return portNumber == JaegerGrpcOtlpPort && strings.Contains(name, string(common.JaegerDestinationType)) +} + +func (j *JaegerDestinationFinder) fetchDestinationDetails(service k8s.Service) DestinationDetails { + urlString := fmt.Sprintf(JaegerGrpcUrlFormat, service.Name, service.Namespace, JaegerGrpcOtlpPort) + return DestinationDetails{ + Name: string(common.JaegerDestinationType), + UrlString: urlString, + } +} diff --git a/frontend/endpoints/destination_recognition/utils.go b/frontend/endpoints/destination_recognition/utils.go new file mode 100644 index 0000000000..12b227dc5d --- /dev/null +++ b/frontend/endpoints/destination_recognition/utils.go @@ -0,0 +1,34 @@ +package destination_recognition + +import ( + "github.com/gin-gonic/gin" + "github.com/odigos-io/odigos/frontend/kube" + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func getAllHelmManagedServices(ctx *gin.Context, namespaces []v1.Namespace) []v1.Service { + var helmManagedServices []v1.Service + for _, ns := range namespaces { + services, _ := kube.DefaultClient.CoreV1().Services(ns.Name).List(ctx, metav1.ListOptions{}) + + for _, service := range services.Items { + if isHelmManagedService(service) { + helmManagedServices = append(helmManagedServices, service) + } + } + } + + return helmManagedServices +} + +// isHelmManagedService checks if a Pod was created by Helm +func isHelmManagedService(service v1.Service) bool { + annotations := service.GetAnnotations() + labels := service.GetLabels() + + _, hasHelmReleaseName := annotations["meta.helm.sh/release-name"] + managedByHelm := labels["app.kubernetes.io/managed-by"] == "Helm" + + return hasHelmReleaseName && managedByHelm +} diff --git a/frontend/endpoints/destinations.go b/frontend/endpoints/destinations.go index 8e19da46c8..aea5c43758 100644 --- a/frontend/endpoints/destinations.go +++ b/frontend/endpoints/destinations.go @@ -4,6 +4,8 @@ import ( "context" "encoding/json" "fmt" + "github.com/odigos-io/odigos/frontend/endpoints/destination_recognition" + "github.com/odigos-io/odigos/k8sutils/pkg/env" "net/http" "github.com/gin-gonic/gin" @@ -634,3 +636,17 @@ func addDestinationOwnerReferenceToSecret(ctx context.Context, odigosns string, } return nil } + +func findPotentialDestinations(ctx *gin.Context) ([]destination_recognition.DestinationDetails, error) { + relevantNamespaces, err := getRelevantNameSpaces(ctx, env.GetCurrentNamespace()) + if err != nil { + return nil, err + } + + destinationDetails, err := destination_recognition.GetAllPotentialDestinationDetails(ctx, relevantNamespaces) + if err != nil { + return nil, err + } + + return destinationDetails, nil +} diff --git a/frontend/endpoints/namespaces.go b/frontend/endpoints/namespaces.go index 6edd874131..b6f660e28b 100644 --- a/frontend/endpoints/namespaces.go +++ b/frontend/endpoints/namespaces.go @@ -5,6 +5,10 @@ import ( "fmt" "net/http" + "github.com/odigos-io/odigos/k8sutils/pkg/client" + + "k8s.io/apimachinery/pkg/runtime/schema" + "golang.org/x/sync/errgroup" "github.com/odigos-io/odigos/api/odigos/v1alpha1" @@ -177,31 +181,25 @@ func syncWorkloadsInNamespace(ctx context.Context, nsName string, workloads []Pe // returns a map, where the key is a namespace name and the value is the // number of apps in this namespace (not necessarily instrumented) func CountAppsPerNamespace(ctx context.Context) (map[string]int, error) { + namespaceToAppsCount := make(map[string]int) - deps, err := kube.DefaultClient.AppsV1().Deployments("").List(ctx, metav1.ListOptions{}) - if err != nil { - return nil, err - } - - ss, err := kube.DefaultClient.AppsV1().StatefulSets("").List(ctx, metav1.ListOptions{}) - if err != nil { - return nil, err - } + resourceTypes := []string{"deployments", "statefulsets", "daemonsets"} - ds, err := kube.DefaultClient.AppsV1().DaemonSets("").List(ctx, metav1.ListOptions{}) - if err != nil { - return nil, err - } + for _, resourceType := range resourceTypes { + err := client.ListWithPages(client.DefaultPageSize, kube.DefaultClient.MetadataClient.Resource(schema.GroupVersionResource{ + Group: "apps", + Version: "v1", + Resource: resourceType, + }).List, ctx, metav1.ListOptions{}, func(list *metav1.PartialObjectMetadataList) error { + for _, item := range list.Items { + namespaceToAppsCount[item.Namespace]++ + } + return nil + }) - namespaceToAppsCount := make(map[string]int) - for _, dep := range deps.Items { - namespaceToAppsCount[dep.Namespace]++ - } - for _, st := range ss.Items { - namespaceToAppsCount[st.Namespace]++ - } - for _, d := range ds.Items { - namespaceToAppsCount[d.Namespace]++ + if err != nil { + return nil, fmt.Errorf("failed to count %s: %w", resourceType, err) + } } return namespaceToAppsCount, nil diff --git a/frontend/kube/client.go b/frontend/kube/client.go index d1b3c8d03a..1d10d81f96 100644 --- a/frontend/kube/client.go +++ b/frontend/kube/client.go @@ -5,6 +5,7 @@ import ( odigosv1alpha1 "github.com/odigos-io/odigos/api/generated/odigos/clientset/versioned/typed/odigos/v1alpha1" k8sutils "github.com/odigos-io/odigos/k8sutils/pkg/client" "k8s.io/client-go/kubernetes" + "k8s.io/client-go/metadata" _ "k8s.io/client-go/plugin/pkg/client/auth" _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" ) @@ -26,8 +27,9 @@ const ( type Client struct { kubernetes.Interface - OdigosClient odigosv1alpha1.OdigosV1alpha1Interface - ActionsClient actionsv1alpha1.ActionsV1alpha1Interface + OdigosClient odigosv1alpha1.OdigosV1alpha1Interface + ActionsClient actionsv1alpha1.ActionsV1alpha1Interface + MetadataClient metadata.Interface } func CreateClient(kubeConfig string) (*Client, error) { @@ -54,9 +56,15 @@ func CreateClient(kubeConfig string) (*Client, error) { return nil, err } + metadataClient, err := metadata.NewForConfig(config) + if err != nil { + return nil, err + } + return &Client{ - Interface: clientset, - OdigosClient: odigosClient, - ActionsClient: actionsClient, + Interface: clientset, + OdigosClient: odigosClient, + ActionsClient: actionsClient, + MetadataClient: metadataClient, }, nil } diff --git a/frontend/webapp/components/overview/actions/actions.forms/add.cluster.info/index.tsx b/frontend/webapp/components/overview/actions/actions.forms/add.cluster.info/index.tsx index af81fd159c..ac594622bc 100644 --- a/frontend/webapp/components/overview/actions/actions.forms/add.cluster.info/index.tsx +++ b/frontend/webapp/components/overview/actions/actions.forms/add.cluster.info/index.tsx @@ -25,6 +25,7 @@ interface ClusterAttributes { interface AddClusterInfoFormProps { data: ClusterAttributes | null; onChange: (key: string, keyValues: ClusterAttributes | null) => void; + setIsFormValid?: (value: boolean) => void; } const ACTION_DATA_KEY = 'actionData'; @@ -32,6 +33,7 @@ const ACTION_DATA_KEY = 'actionData'; export function AddClusterInfoForm({ data, onChange, + setIsFormValid = () => {}, }: AddClusterInfoFormProps): React.JSX.Element { const [keyValuePairs, setKeyValuePairs] = React.useState([]); @@ -39,6 +41,10 @@ export function AddClusterInfoForm({ buildKeyValuePairs(); }, [data]); + useEffect(() => { + validateForm(); + }, [keyValuePairs]); + function handleKeyValuesChange(keyValues: KeyValue[]): void { const actionData = { clusterAttributes: keyValues.map((keyValue) => ({ @@ -56,6 +62,8 @@ export function AddClusterInfoForm({ } else { onChange(ACTION_DATA_KEY, actionData); } + + setKeyValuePairs(keyValues); // Update state with new key-value pairs } function buildKeyValuePairs() { @@ -72,6 +80,13 @@ export function AddClusterInfoForm({ setKeyValuePairs(values || DEFAULT_KEY_VALUE_PAIR); } + function validateForm() { + const isValid = keyValuePairs.every( + (pair) => pair.key.trim() !== '' && pair.value.trim() !== '' + ); + setIsFormValid(isValid); + } + return ( <> diff --git a/frontend/webapp/components/overview/actions/actions.forms/delete.attribute/index.tsx b/frontend/webapp/components/overview/actions/actions.forms/delete.attribute/index.tsx index 03ea03612c..f05e81c470 100644 --- a/frontend/webapp/components/overview/actions/actions.forms/delete.attribute/index.tsx +++ b/frontend/webapp/components/overview/actions/actions.forms/delete.attribute/index.tsx @@ -13,16 +13,33 @@ interface DeleteAttributes { interface DeleteAttributesProps { data: DeleteAttributes; onChange: (key: string, value: DeleteAttributes | null) => void; + setIsFormValid?: (value: boolean) => void; } const ACTION_DATA_KEY = 'actionData'; + export function DeleteAttributesForm({ data, onChange, + setIsFormValid = () => {}, }: DeleteAttributesProps): React.JSX.Element { + const [attributeNames, setAttributeNames] = React.useState( + data?.attributeNamesToDelete || [''] + ); + + useEffect(() => { + validateForm(); + }, [attributeNames]); + function handleOnChange(attributeNamesToDelete: string[]): void { onChange(ACTION_DATA_KEY, { attributeNamesToDelete, }); + setAttributeNames(attributeNamesToDelete); + } + + function validateForm() { + const isValid = attributeNames.every((name) => name.trim() !== ''); + setIsFormValid(isValid); } return ( @@ -32,11 +49,7 @@ export function DeleteAttributesForm({ placeholder="Add attribute names to delete" required title="Attribute Names to Delete" - values={ - data?.attributeNamesToDelete?.length > 0 - ? data.attributeNamesToDelete - : [''] - } + values={attributeNames.length > 0 ? attributeNames : ['']} onValuesChange={handleOnChange} /> diff --git a/frontend/webapp/components/overview/actions/actions.forms/dynamic.action.form/index.tsx b/frontend/webapp/components/overview/actions/actions.forms/dynamic.action.form/index.tsx index b9c67cc13e..f4d408cbcc 100644 --- a/frontend/webapp/components/overview/actions/actions.forms/dynamic.action.form/index.tsx +++ b/frontend/webapp/components/overview/actions/actions.forms/dynamic.action.form/index.tsx @@ -11,10 +11,10 @@ import { } from '../samplers'; import { PiiMaskingForm } from '../pii-masking'; -interface DynamicActionFormProps { - type: string | undefined; - data: any; - onChange: (key: string, value: any) => void; +interface DynamicActionFormProps { + type?: string; + data: T; + onChange: (key: string, value: T | null) => void; setIsFormValid?: (isValid: boolean) => void; } @@ -22,40 +22,31 @@ export function DynamicActionForm({ type, data, onChange, - setIsFormValid, + setIsFormValid = () => {}, }: DynamicActionFormProps): React.JSX.Element { - function renderCurrentAction() { - switch (type) { - case ActionsType.ADD_CLUSTER_INFO: - return ; - case ActionsType.DELETE_ATTRIBUTES: - return ; - case ActionsType.RENAME_ATTRIBUTES: - return ; - case ActionsType.ERROR_SAMPLER: - return ; - case ActionsType.PROBABILISTIC_SAMPLER: - return ; - case ActionsType.LATENCY_SAMPLER: - return ( - - ); - case ActionsType.PII_MASKING: - return ( - - ); - default: - return
; - } - } + const formComponents = { + [ActionsType.ADD_CLUSTER_INFO]: AddClusterInfoForm, + [ActionsType.DELETE_ATTRIBUTES]: DeleteAttributesForm, + [ActionsType.RENAME_ATTRIBUTES]: RenameAttributesForm, + [ActionsType.ERROR_SAMPLER]: ErrorSamplerForm, + [ActionsType.PROBABILISTIC_SAMPLER]: ProbabilisticSamplerForm, + [ActionsType.LATENCY_SAMPLER]: LatencySamplerForm, + [ActionsType.PII_MASKING]: PiiMaskingForm, + }; - return <>{renderCurrentAction()}; + const FormComponent = type ? formComponents[type] : null; + + return ( + <> + {FormComponent ? ( + + ) : ( +
No action form available
+ )} + + ); } diff --git a/frontend/webapp/components/overview/actions/actions.forms/rename.attributes/index.tsx b/frontend/webapp/components/overview/actions/actions.forms/rename.attributes/index.tsx index c444b2eabb..b9b62c1227 100644 --- a/frontend/webapp/components/overview/actions/actions.forms/rename.attributes/index.tsx +++ b/frontend/webapp/components/overview/actions/actions.forms/rename.attributes/index.tsx @@ -2,6 +2,7 @@ import React, { useEffect } from 'react'; import styled from 'styled-components'; import { KeyValuePair } from '@/design.system'; import { KeyValue } from '@keyval-dev/design-system'; + const FormWrapper = styled.div` width: 375px; `; @@ -12,10 +13,12 @@ interface RenameAttributes { }; } -interface DeleteAttributesProps { +interface RenameAttributesProps { data: RenameAttributes; onChange: (key: string, value: RenameAttributes) => void; + setIsFormValid?: (value: boolean) => void; } + const DEFAULT_KEY_VALUE_PAIR = [ { id: 0, @@ -25,16 +28,22 @@ const DEFAULT_KEY_VALUE_PAIR = [ ]; const ACTION_DATA_KEY = 'actionData'; + export function RenameAttributesForm({ data, onChange, -}: DeleteAttributesProps): React.JSX.Element { + setIsFormValid = () => {}, +}: RenameAttributesProps): React.JSX.Element { const [keyValuePairs, setKeyValuePairs] = React.useState([]); useEffect(() => { buildKeyValuePairs(); }, [data]); + useEffect(() => { + validateForm(); + }, [keyValuePairs]); + function handleKeyValuesChange(keyValues: KeyValue[]): void { const renames: { [key: string]: string; @@ -44,6 +53,7 @@ export function RenameAttributesForm({ }); onChange(ACTION_DATA_KEY, { renames }); + setKeyValuePairs(keyValues); // Update state with new key-value pairs } function buildKeyValuePairs() { @@ -61,6 +71,13 @@ export function RenameAttributesForm({ setKeyValuePairs(values || DEFAULT_KEY_VALUE_PAIR); } + function validateForm() { + const isValid = keyValuePairs.every( + (pair) => pair.key.trim() !== '' && pair.value.trim() !== '' + ); + setIsFormValid(isValid); + } + return ( <> diff --git a/frontend/webapp/components/overview/actions/actions.forms/samplers/error-sampler/index.tsx b/frontend/webapp/components/overview/actions/actions.forms/samplers/error-sampler/index.tsx index 3c71a61dda..acd15e8cff 100644 --- a/frontend/webapp/components/overview/actions/actions.forms/samplers/error-sampler/index.tsx +++ b/frontend/webapp/components/overview/actions/actions.forms/samplers/error-sampler/index.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import styled from 'styled-components'; import { KeyvalInput } from '@/design.system'; @@ -13,18 +13,35 @@ interface ErrorSampler { interface ErrorSamplerFormProps { data: ErrorSampler; onChange: (key: string, value: ErrorSampler | null) => void; + setIsFormValid?: (value: boolean) => void; } + const ACTION_DATA_KEY = 'actionData'; + export function ErrorSamplerForm({ data, onChange, + setIsFormValid = () => {}, }: ErrorSamplerFormProps): React.JSX.Element { + useEffect(() => { + validateForm(); + }, [data?.fallback_sampling_ratio]); + function handleOnChange(fallback_sampling_ratio: number): void { onChange(ACTION_DATA_KEY, { fallback_sampling_ratio, }); } + function validateForm() { + const isValid = + !isNaN(data?.fallback_sampling_ratio) && + data?.fallback_sampling_ratio >= 0 && + data?.fallback_sampling_ratio <= 100; + + setIsFormValid(isValid); + } + return ( <> diff --git a/frontend/webapp/components/overview/actions/actions.forms/samplers/latency-action/index.tsx b/frontend/webapp/components/overview/actions/actions.forms/samplers/latency-action/index.tsx index ce7088ad8c..740605a1e1 100644 --- a/frontend/webapp/components/overview/actions/actions.forms/samplers/latency-action/index.tsx +++ b/frontend/webapp/components/overview/actions/actions.forms/samplers/latency-action/index.tsx @@ -74,20 +74,11 @@ export function LatencySamplerForm({ }, [filters]); const memoizedSources = React.useMemo(() => { - let instrumentsSources = sources; - if (data) { - instrumentsSources = sources.filter((source) => { - return data.endpoints_filters.every( - (filter) => filter.service_name !== source.name - ); - }); - } - - return instrumentsSources.map((source, index) => ({ + return sources?.map((source, index) => ({ id: index, label: source.name, })); - }, [sources, data]); + }, [sources]); function handleOnChange(index: number, key: string, value: any): void { const updatedFilters = filters.map((filter, i) => diff --git a/frontend/webapp/components/overview/actions/actions.forms/samplers/probabilistic-sampler/index.tsx b/frontend/webapp/components/overview/actions/actions.forms/samplers/probabilistic-sampler/index.tsx index 0a11a050ac..9eb1c1f599 100644 --- a/frontend/webapp/components/overview/actions/actions.forms/samplers/probabilistic-sampler/index.tsx +++ b/frontend/webapp/components/overview/actions/actions.forms/samplers/probabilistic-sampler/index.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import styled from 'styled-components'; import { KeyvalInput } from '@/design.system'; @@ -13,13 +13,18 @@ interface ProbabilisticSampler { interface ProbabilisticSamplerProps { data: ProbabilisticSampler; onChange: (key: string, value: ProbabilisticSampler | null) => void; + setIsFormValid?: (value: boolean) => void; } const ACTION_DATA_KEY = 'actionData'; + export function ProbabilisticSamplerForm({ data, onChange, + setIsFormValid = () => {}, }: ProbabilisticSamplerProps): React.JSX.Element { - console.log({ data }); + useEffect(() => { + validateForm(); + }, [data?.sampling_percentage]); function handleOnChange(sampling_percentage: string): void { onChange(ACTION_DATA_KEY, { @@ -27,6 +32,12 @@ export function ProbabilisticSamplerForm({ }); } + function validateForm() { + const percentage = parseFloat(data?.sampling_percentage); + const isValid = !isNaN(percentage) && percentage >= 0 && percentage <= 100; + setIsFormValid(isValid); + } + return ( <> @@ -39,7 +50,7 @@ export function ProbabilisticSamplerForm({ min={0} max={100} error={ - +data?.sampling_percentage > 100 + parseFloat(data?.sampling_percentage) > 100 ? 'Value must be less than 100' : '' } diff --git a/frontend/webapp/containers/main/actions/edit-action/index.tsx b/frontend/webapp/containers/main/actions/edit-action/index.tsx index 3d4a45a2c3..f6aaa0722c 100644 --- a/frontend/webapp/containers/main/actions/edit-action/index.tsx +++ b/frontend/webapp/containers/main/actions/edit-action/index.tsx @@ -77,7 +77,7 @@ export function EditActionContainer(): React.JSX.Element { {ACTIONS[type].TITLE} - + diff --git a/frontend/webapp/containers/main/actions/edit-action/styled.ts b/frontend/webapp/containers/main/actions/edit-action/styled.ts index 3647a64ad4..eca9d876c5 100644 --- a/frontend/webapp/containers/main/actions/edit-action/styled.ts +++ b/frontend/webapp/containers/main/actions/edit-action/styled.ts @@ -25,12 +25,18 @@ export const FormFieldsWrapper = styled.div<{ disabled: boolean }>` pointer-events: ${({ disabled }) => (disabled ? 'none' : 'auto')}; `; -export const SwitchWrapper = styled.div<{ disabled: boolean }>` +export const SwitchWrapper = styled.div<{ + disabled: boolean; + isValid: boolean; +}>` p { color: ${({ disabled }) => disabled ? theme.colors.orange_brown : theme.colors.success}; font-weight: 600; } + + opacity: ${({ isValid }) => (!isValid ? 0.3 : 1)}; + pointer-events: ${({ isValid }) => (!isValid ? 'none' : 'auto')}; `; export const KeyvalInputWrapper = styled.div` diff --git a/frontend/webapp/cypress.config.ts b/frontend/webapp/cypress.config.ts new file mode 100644 index 0000000000..579ad00cb0 --- /dev/null +++ b/frontend/webapp/cypress.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'cypress'; + +export default defineConfig({ + projectId: 'aydhz4', + e2e: { + setupNodeEvents(on, config) { + // implement node event listeners here + }, + }, +}); diff --git a/frontend/webapp/cypress/e2e/firstTest.cy.ts b/frontend/webapp/cypress/e2e/firstTest.cy.ts new file mode 100644 index 0000000000..5c7536e302 --- /dev/null +++ b/frontend/webapp/cypress/e2e/firstTest.cy.ts @@ -0,0 +1,6 @@ + +describe('Basic UI Tests', () => { + it('Main page loads', () => { + cy.visit('localhost:3000') + }) +}) diff --git a/frontend/webapp/cypress/fixtures/example.json b/frontend/webapp/cypress/fixtures/example.json new file mode 100644 index 0000000000..02e4254378 --- /dev/null +++ b/frontend/webapp/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/frontend/webapp/cypress/support/commands.ts b/frontend/webapp/cypress/support/commands.ts new file mode 100644 index 0000000000..698b01a42c --- /dev/null +++ b/frontend/webapp/cypress/support/commands.ts @@ -0,0 +1,37 @@ +/// +// *********************************************** +// This example commands.ts shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) +// +// declare global { +// namespace Cypress { +// interface Chainable { +// login(email: string, password: string): Chainable +// drag(subject: string, options?: Partial): Chainable +// dismiss(subject: string, options?: Partial): Chainable +// visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable +// } +// } +// } \ No newline at end of file diff --git a/frontend/webapp/cypress/support/e2e.ts b/frontend/webapp/cypress/support/e2e.ts new file mode 100644 index 0000000000..f80f74f8e1 --- /dev/null +++ b/frontend/webapp/cypress/support/e2e.ts @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/e2e.ts is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands' + +// Alternatively you can use CommonJS syntax: +// require('./commands') \ No newline at end of file diff --git a/frontend/webapp/package.json b/frontend/webapp/package.json index 3d37333908..ac7dcc6ba8 100644 --- a/frontend/webapp/package.json +++ b/frontend/webapp/package.json @@ -34,6 +34,7 @@ "typescript": "5.1.3" }, "devDependencies": { - "babel-plugin-styled-components": "^2.1.4" + "babel-plugin-styled-components": "^2.1.4", + "cypress": "^13.13.1" } } diff --git a/frontend/webapp/yarn.lock b/frontend/webapp/yarn.lock index fa1c7f596e..f554ce166b 100644 --- a/frontend/webapp/yarn.lock +++ b/frontend/webapp/yarn.lock @@ -1328,6 +1328,43 @@ style-mod "^4.0.0" w3c-keyname "^2.2.4" +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== + +"@cypress/request@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-3.0.1.tgz#72d7d5425236a2413bd3d8bb66d02d9dc3168960" + integrity sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + http-signature "~1.3.6" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + performance-now "^2.1.0" + qs "6.10.4" + safe-buffer "^5.1.2" + tough-cookie "^4.1.3" + tunnel-agent "^0.6.0" + uuid "^8.3.2" + +"@cypress/xvfb@^1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@cypress/xvfb/-/xvfb-1.2.4.tgz#2daf42e8275b39f4aa53c14214e557bd14e7748a" + integrity sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q== + dependencies: + debug "^3.1.0" + lodash.once "^4.1.1" + "@emotion/is-prop-valid@^1.2.1": version "1.2.1" resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz" @@ -2173,6 +2210,13 @@ resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/node@*": + version "20.14.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.11.tgz#09b300423343460455043ddd4d0ded6ac579b74b" + integrity sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA== + dependencies: + undici-types "~5.26.4" + "@types/node@20.3.3": version "20.3.3" resolved "https://registry.npmjs.org/@types/node/-/node-20.3.3.tgz" @@ -2230,6 +2274,16 @@ resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz" integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== +"@types/sinonjs__fake-timers@8.1.1": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" + integrity sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g== + +"@types/sizzle@^2.3.2": + version "2.3.8" + resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.8.tgz#518609aefb797da19bf222feb199e8f653ff7627" + integrity sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg== + "@types/stylis@^4.0.2": version "4.2.0" resolved "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz" @@ -2240,6 +2294,13 @@ resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== +"@types/yauzl@^2.9.1": + version "2.10.3" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.3.tgz#e9b2808b4f109504a03cda958259876f61017999" + integrity sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q== + dependencies: + "@types/node" "*" + "@typescript-eslint/parser@^5.42.0": version "5.59.9" resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.9.tgz" @@ -2294,6 +2355,14 @@ acorn@^8.8.0: resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz" integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + ajv@^6.10.0, ajv@^6.12.4: version "6.12.6" resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" @@ -2304,6 +2373,18 @@ ajv@^6.10.0, ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ansi-colors@^4.1.1: + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== + +ansi-escapes@^4.3.0: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" @@ -2316,7 +2397,7 @@ ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" -ansi-styles@^4.1.0: +ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== @@ -2331,6 +2412,11 @@ anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" +arch@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" + integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== + argparse@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" @@ -2398,16 +2484,43 @@ array.prototype.tosorted@^1.1.1: es-shim-unscopables "^1.0.0" get-intrinsic "^1.1.3" +asn1@~0.2.3: + version "0.2.6" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== + ast-types-flow@^0.0.7: version "0.0.7" resolved "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz" integrity sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag== +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +async@^3.2.0: + version "3.2.5" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66" + integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + autoprefixer@^10.4.14: version "10.4.14" resolved "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz" @@ -2425,6 +2538,16 @@ available-typed-arrays@^1.0.5: resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== + +aws4@^1.8.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.13.0.tgz#d9b802e9bb9c248d7be5f7f5ef178dc3684e9dcc" + integrity sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g== + axe-core@^4.6.2: version "4.7.2" resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.7.2.tgz" @@ -2486,6 +2609,18 @@ balanced-match@^1.0.0: resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== + dependencies: + tweetnacl "^0.14.3" + big-integer@^1.6.16, big-integer@^1.6.44: version "1.6.51" resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz" @@ -2496,6 +2631,16 @@ binary-extensions@^2.0.0: resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +blob-util@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb" + integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ== + +bluebird@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + boolbase@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz" @@ -2557,6 +2702,19 @@ browserslist@^4.21.9: node-releases "^2.0.12" update-browserslist-db "^1.0.11" +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== + +buffer@^5.7.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + bundle-name@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz" @@ -2571,6 +2729,11 @@ busboy@1.6.0: dependencies: streamsearch "^1.1.0" +cachedir@^2.3.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.4.0.tgz#7fef9cf7367233d7c88068fe6e34ed0d355a610d" + integrity sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ== + call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" @@ -2604,6 +2767,11 @@ caniuse-lite@^1.0.30001503: resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001512.tgz" integrity sha512-2S9nK0G/mE+jasCUsMPlARhRCts1ebcp2Ji8Y8PWi4NDE1iRdLCnEPHkEfeBrGC45L4isBx5ur3IQ6yTE2mRZw== +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== + chalk@^2.0.0: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" @@ -2613,7 +2781,7 @@ chalk@^2.0.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0: +chalk@^4.0.0, chalk@^4.1.0: version "4.1.2" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -2621,6 +2789,11 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +check-more-types@^2.24.0: + version "2.24.0" + resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600" + integrity sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA== + chokidar@^3.4.0: version "3.5.3" resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz" @@ -2636,11 +2809,45 @@ chokidar@^3.4.0: optionalDependencies: fsevents "~2.3.2" +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + classcat@^5.0.3, classcat@^5.0.4: version "5.0.4" resolved "https://registry.npmjs.org/classcat/-/classcat-5.0.4.tgz" integrity sha512-sbpkOw6z413p+HDGcBENe498WM9woqWHiJxCq7nvmxe9WmrUmqfAcxpIwAiMtM5Q3AhYkzXcNQHqsWq0mND51g== +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-table3@~0.6.1: + version "0.6.5" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.5.tgz#013b91351762739c16a9567c21a04632e449bf2f" + integrity sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ== + dependencies: + string-width "^4.2.0" + optionalDependencies: + "@colors/colors" "1.5.0" + +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + client-only@0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz" @@ -2670,7 +2877,12 @@ color-name@~1.1.4: resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -combined-stream@^1.0.8: +colorette@^2.0.16: + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -2682,11 +2894,21 @@ commander@^4.0.1: resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== +commander@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + commander@^7.2.0: version "7.2.0" resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== +common-tags@^1.8.0: + version "1.8.2" + resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6" + integrity sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" @@ -2704,6 +2926,11 @@ core-js-compat@^3.30.1, core-js-compat@^3.31.0: dependencies: browserslist "^4.21.5" +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== + cosmiconfig@^7.0.1: version "7.1.0" resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz" @@ -2730,7 +2957,7 @@ crelt@^1.0.5: resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72" integrity sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g== -cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -2823,6 +3050,54 @@ csstype@^3.0.2, csstype@^3.1.2: resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz" integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== +cypress@^13.13.1: + version "13.13.1" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.13.1.tgz#860c1142a6e58ea412a764f0ce6ad07567721129" + integrity sha512-8F9UjL5MDUdgC/S5hr8CGLHbS5gGht5UOV184qc2pFny43fnkoaKxlzH/U6//zmGu/xRTaKimNfjknLT8+UDFg== + dependencies: + "@cypress/request" "^3.0.0" + "@cypress/xvfb" "^1.2.4" + "@types/sinonjs__fake-timers" "8.1.1" + "@types/sizzle" "^2.3.2" + arch "^2.2.0" + blob-util "^2.0.2" + bluebird "^3.7.2" + buffer "^5.7.1" + cachedir "^2.3.0" + chalk "^4.1.0" + check-more-types "^2.24.0" + cli-cursor "^3.1.0" + cli-table3 "~0.6.1" + commander "^6.2.1" + common-tags "^1.8.0" + dayjs "^1.10.4" + debug "^4.3.4" + enquirer "^2.3.6" + eventemitter2 "6.4.7" + execa "4.1.0" + executable "^4.1.1" + extract-zip "2.0.1" + figures "^3.2.0" + fs-extra "^9.1.0" + getos "^3.2.1" + is-ci "^3.0.1" + is-installed-globally "~0.4.0" + lazy-ass "^1.6.0" + listr2 "^3.8.3" + lodash "^4.17.21" + log-symbols "^4.0.0" + minimist "^1.2.8" + ospath "^1.2.2" + pretty-bytes "^5.6.0" + process "^0.11.10" + proxy-from-env "1.0.0" + request-progress "^3.0.0" + semver "^7.5.3" + supports-color "^8.1.1" + tmp "~0.2.3" + untildify "^4.0.0" + yauzl "^2.10.0" + "d3-color@1 - 3": version "3.1.0" resolved "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz" @@ -2890,7 +3165,19 @@ damerau-levenshtein@^1.0.8: resolved "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz" integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== -debug@^3.2.7: +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== + dependencies: + assert-plus "^1.0.0" + +dayjs@^1.10.4: + version "1.11.11" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.11.tgz#dfe0e9d54c5f8b68ccf8ca5f72ac603e7e5ed59e" + integrity sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg== + +debug@^3.1.0, debug@^3.2.7: version "3.2.7" resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== @@ -3063,6 +3350,14 @@ dot-case@^3.0.4: no-case "^3.0.4" tslib "^2.0.3" +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + electron-to-chromium@^1.4.411: version "1.4.427" resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.427.tgz" @@ -3073,11 +3368,23 @@ electron-to-chromium@^1.4.431: resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.450.tgz" integrity sha512-BLG5HxSELlrMx7dJ2s+8SFlsCtJp37Zpk2VAxyC6CZtbc+9AJeZHfYHbrlSgdXp6saQ8StMqOTEDaBKgA7u1sw== +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + emoji-regex@^9.2.2: version "9.2.2" resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + enhanced-resolve@^5.12.0: version "5.14.1" resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.1.tgz" @@ -3086,6 +3393,14 @@ enhanced-resolve@^5.12.0: graceful-fs "^4.2.4" tapable "^2.2.0" +enquirer@^2.3.6: + version "2.4.1" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.4.1.tgz#93334b3fbd74fc7097b224ab4a8fb7e40bf4ae56" + integrity sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ== + dependencies: + ansi-colors "^4.1.1" + strip-ansi "^6.0.1" + entities@^2.0.0: version "2.2.0" resolved "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz" @@ -3408,6 +3723,26 @@ esutils@^2.0.2: resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +eventemitter2@6.4.7: + version "6.4.7" + resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.7.tgz#a7f6c4d7abf28a14c1ef3442f21cb306a054271d" + integrity sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg== + +execa@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" + integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + execa@^5.0.0: version "5.1.1" resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz" @@ -3438,6 +3773,39 @@ execa@^7.1.1: signal-exit "^3.0.7" strip-final-newline "^3.0.0" +executable@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c" + integrity sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg== + dependencies: + pify "^2.2.0" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extract-zip@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" + integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== + dependencies: + debug "^4.1.1" + get-stream "^5.1.0" + yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== + +extsprintf@^1.2.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" + integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" @@ -3471,6 +3839,20 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== + dependencies: + pend "~1.2.0" + +figures@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" @@ -3518,6 +3900,11 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== + form-data@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz" @@ -3527,11 +3914,30 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + fraction.js@^4.2.0: version "4.2.0" resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz" integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA== +fs-extra@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-readdir-recursive@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz" @@ -3582,6 +3988,13 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@ has-proto "^1.0.1" has-symbols "^1.0.3" +get-stream@^5.0.0, get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + get-stream@^6.0.0, get-stream@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" @@ -3602,6 +4015,20 @@ get-tsconfig@^4.5.0: dependencies: resolve-pkg-maps "^1.0.0" +getos@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/getos/-/getos-3.2.1.tgz#0134d1f4e00eb46144c5a9c0ac4dc087cbb27dc5" + integrity sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q== + dependencies: + async "^3.2.0" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== + dependencies: + assert-plus "^1.0.0" + glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" @@ -3645,6 +4072,13 @@ glob@^7.1.3, glob@^7.2.0: once "^1.3.0" path-is-absolute "^1.0.0" +global-dirs@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.1.tgz#0c488971f066baceda21447aecb1a8b911d22485" + integrity sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA== + dependencies: + ini "2.0.0" + globals@^11.1.0: version "11.12.0" resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" @@ -3694,7 +4128,7 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -graceful-fs@^4.1.2, graceful-fs@^4.2.4: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: version "4.2.11" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -3750,6 +4184,20 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +http-signature@~1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9" + integrity sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw== + dependencies: + assert-plus "^1.0.0" + jsprim "^2.0.2" + sshpk "^1.14.1" + +human-signals@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" + integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== + human-signals@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz" @@ -3760,6 +4208,11 @@ human-signals@^4.3.0: resolved "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz" integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ignore@^5.2.0: version "5.2.4" resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz" @@ -3783,6 +4236,11 @@ imurmurhash@^0.1.4: resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" @@ -3796,6 +4254,11 @@ inherits@2: resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +ini@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + internal-slot@^1.0.3, internal-slot@^1.0.4, internal-slot@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz" @@ -3854,6 +4317,13 @@ is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== +is-ci@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.1.tgz#db6ecbed1bd659c43dac0f45661e7674103d1867" + integrity sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ== + dependencies: + ci-info "^3.2.0" + is-core-module@^2.11.0, is-core-module@^2.9.0: version "2.12.1" resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz" @@ -3883,6 +4353,11 @@ is-extglob@^2.1.1: resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" @@ -3897,6 +4372,14 @@ is-inside-container@^1.0.0: dependencies: is-docker "^3.0.0" +is-installed-globally@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" + integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== + dependencies: + global-dirs "^3.0.0" + is-path-inside "^3.0.2" + is-map@^2.0.1, is-map@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz" @@ -3919,7 +4402,7 @@ is-number@^7.0.0: resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-path-inside@^3.0.3: +is-path-inside@^3.0.2, is-path-inside@^3.0.3: version "3.0.3" resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== @@ -3979,6 +4462,16 @@ is-typed-array@^1.1.10, is-typed-array@^1.1.9: gopd "^1.0.1" has-tostringtag "^1.0.0" +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + is-weakmap@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz" @@ -4016,6 +4509,11 @@ isexe@^2.0.0: resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== + js-sha3@0.8.0: version "0.8.0" resolved "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz" @@ -4033,6 +4531,11 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" @@ -4053,11 +4556,21 @@ json-schema-traverse@^0.4.1: resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== + json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + json5@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz" @@ -4070,6 +4583,25 @@ json5@^2.2.2: resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +jsprim@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d" + integrity sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ== + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.4.0" + verror "1.10.0" + "jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.3: version "3.3.3" resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz" @@ -4090,6 +4622,11 @@ language-tags@=1.0.5: dependencies: language-subtag-registry "~0.3.2" +lazy-ass@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" + integrity sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw== + levn@^0.4.1: version "0.4.1" resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" @@ -4103,6 +4640,20 @@ lines-and-columns@^1.1.6: resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +listr2@^3.8.3: + version "3.14.0" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.14.0.tgz#23101cc62e1375fd5836b248276d1d2b51fdbe9e" + integrity sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g== + dependencies: + cli-truncate "^2.1.0" + colorette "^2.0.16" + log-update "^4.0.0" + p-map "^4.0.0" + rfdc "^1.3.0" + rxjs "^7.5.1" + through "^2.3.8" + wrap-ansi "^7.0.0" + locate-path@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" @@ -4120,11 +4671,34 @@ lodash.merge@^4.6.2: resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.once@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + lodash@^4.17.21: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +log-symbols@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +log-update@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" + integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== + dependencies: + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" + loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" @@ -4212,7 +4786,7 @@ mime-db@1.52.0: resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12: +mime-types@^2.1.12, mime-types@~2.1.19: version "2.1.35" resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -4236,7 +4810,7 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimist@^1.2.0, minimist@^1.2.6: +minimist@^1.2.0, minimist@^1.2.6, minimist@^1.2.8: version "1.2.8" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -4338,7 +4912,7 @@ normalize-range@^0.1.2: resolved "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz" integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== -npm-run-path@^4.0.1: +npm-run-path@^4.0.0, npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz" integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== @@ -4432,14 +5006,14 @@ oblivious-set@1.0.0: resolved "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz" integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw== -once@^1.3.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" -onetime@^5.1.2: +onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== @@ -4475,6 +5049,11 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" +ospath@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b" + integrity sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA== + p-limit@^3.0.2: version "3.1.0" resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" @@ -4489,6 +5068,13 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" @@ -4536,6 +5122,16 @@ path-type@^4.0.0: resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== + picocolors@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" @@ -4546,6 +5142,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1: resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pify@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + pify@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz" @@ -4588,6 +5189,16 @@ prelude-ls@^1.2.1: resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +pretty-bytes@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" + integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + prop-types@^15.8.1: version "15.8.1" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" @@ -4597,16 +5208,51 @@ prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" +proxy-from-env@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" + integrity sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A== + proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== +psl@^1.1.33: + version "1.9.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" + integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + punycode@^2.1.0: version "2.3.0" resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz" integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== +punycode@^2.1.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +qs@6.10.4: + version "6.10.4" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.4.tgz#6a3003755add91c0ec9eacdc5f878b034e73f9e7" + integrity sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g== + dependencies: + side-channel "^1.0.4" + +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" @@ -4735,6 +5381,18 @@ remove-accents@0.5.0: resolved "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz" integrity sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A== +request-progress@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-3.0.0.tgz#4ca754081c7fec63f505e4faa825aa06cd669dbe" + integrity sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg== + dependencies: + throttleit "^1.0.0" + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + reselect@^5.0.1: version "5.1.0" resolved "https://registry.yarnpkg.com/reselect/-/reselect-5.1.0.tgz#c479139ab9dd91be4d9c764a7f3868210ef8cd21" @@ -4768,11 +5426,24 @@ resolve@^2.0.0-next.4: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + reusify@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rfdc@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" + integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== + rimraf@3.0.2, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" @@ -4803,6 +5474,18 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +rxjs@^7.5.1: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + +safe-buffer@^5.0.1, safe-buffer@^5.1.2: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + safe-regex-test@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz" @@ -4812,6 +5495,11 @@ safe-regex-test@^1.0.0: get-intrinsic "^1.1.3" is-regex "^1.1.4" +safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + scheduler@^0.23.0: version "0.23.0" resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz" @@ -4836,6 +5524,11 @@ semver@^7.3.7: dependencies: lru-cache "^6.0.0" +semver@^7.5.3: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + shallowequal@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz" @@ -4862,7 +5555,7 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -signal-exit@^3.0.3, signal-exit@^3.0.7: +signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== @@ -4882,6 +5575,24 @@ slash@^4.0.0: resolved "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz" integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + snake-case@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz" @@ -4900,6 +5611,21 @@ source-map@^0.6.1: resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +sshpk@^1.14.1: + version "1.18.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.18.0.tgz#1663e55cddf4d688b86a46b77f0d5fe363aba028" + integrity sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + stable@^0.1.8: version "0.1.8" resolved "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz" @@ -4917,6 +5643,15 @@ streamsearch@^1.1.0: resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz" integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string.prototype.matchall@^4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz" @@ -4958,7 +5693,7 @@ string.prototype.trimstart@^1.0.6: define-properties "^1.1.4" es-abstract "^1.20.4" -strip-ansi@^6.0.1: +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -5041,6 +5776,13 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-color@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" @@ -5102,11 +5844,26 @@ text-table@^0.2.0: resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +throttleit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.1.tgz#304ec51631c3b770c65c6c6f76938b384000f4d5" + integrity sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ== + +through@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + titleize@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz" integrity sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ== +tmp@~0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.3.tgz#eb783cc22bc1e8bebd0671476d46ea4eb32a79ae" + integrity sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w== + to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz" @@ -5119,6 +5876,16 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +tough-cookie@^4.1.3: + version "4.1.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.4.tgz#945f1461b45b5a8c76821c33ea49c3ac192c1b36" + integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.2.0" + url-parse "^1.5.3" + tsconfig-paths@^3.14.1: version "3.14.2" resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz" @@ -5139,6 +5906,11 @@ tslib@^2.0.3, tslib@^2.4.0, tslib@^2.5.0: resolved "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz" integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w== +tslib@^2.1.0: + version "2.6.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" + integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz" @@ -5146,6 +5918,18 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" @@ -5158,6 +5942,11 @@ type-fest@^0.20.2: resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + typed-array-length@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz" @@ -5187,6 +5976,11 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz" @@ -5210,6 +6004,16 @@ unicode-property-aliases-ecmascript@^2.0.0: resolved "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz" integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== +universalify@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" + integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== + +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + unload@2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz" @@ -5238,11 +6042,33 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +url-parse@^1.5.3: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + use-sync-external-store@1.2.0, use-sync-external-store@^1.0.0, use-sync-external-store@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz" integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + w3c-keyname@^2.2.4: version "2.2.8" resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5" @@ -5301,6 +6127,24 @@ word-wrap@^1.2.3: resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" @@ -5321,6 +6165,14 @@ yaml@^1.10.0: resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +yauzl@^2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" diff --git a/k8sutils/pkg/client/pager.go b/k8sutils/pkg/client/pager.go new file mode 100644 index 0000000000..5561788d20 --- /dev/null +++ b/k8sutils/pkg/client/pager.go @@ -0,0 +1,30 @@ +package client + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const DefaultPageSize = 500 + +type listFunc[T metav1.ListInterface] func(context.Context, metav1.ListOptions) (T, error) + +func ListWithPages[T metav1.ListInterface](pageSize int, list listFunc[T], ctx context.Context, opts metav1.ListOptions, handler func(obj T) error) error { + opts.Limit = int64(pageSize) + opts.Continue = "" + for { + obj, err := list(ctx, opts) + if err != nil { + return err + } + if err := handler(obj); err != nil { + return err + } + if obj.GetContinue() == "" { + break + } + opts.Continue = obj.GetContinue() + } + return nil +} diff --git a/k8sutils/pkg/instrumentation_instance/status.go b/k8sutils/pkg/instrumentation_instance/status.go index 03aa75594f..111cb6a56d 100644 --- a/k8sutils/pkg/instrumentation_instance/status.go +++ b/k8sutils/pkg/instrumentation_instance/status.go @@ -13,84 +13,51 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) -type InstrumentationInstanceConfig struct { - healthy *bool - identifyingAttributes []odigosv1.Attribute - nonIdentifyingAttributes []odigosv1.Attribute - message string - reason string -} - type InstrumentationInstanceOption interface { - applyInstrumentationInstance(InstrumentationInstanceConfig) InstrumentationInstanceConfig -} - -type fnOpt func(InstrumentationInstanceConfig) InstrumentationInstanceConfig - -func (o fnOpt) applyInstrumentationInstance(c InstrumentationInstanceConfig) InstrumentationInstanceConfig { - return o(c) + applyInstrumentationInstance(odigosv1.InstrumentationInstanceStatus) odigosv1.InstrumentationInstanceStatus } -func WithHealthy(healthy *bool) InstrumentationInstanceOption { - return fnOpt(func(c InstrumentationInstanceConfig) InstrumentationInstanceConfig { - c.healthy = healthy - return c - }) -} - -func WithIdentifyingAttributes(attributes []odigosv1.Attribute) InstrumentationInstanceOption { - return fnOpt(func(c InstrumentationInstanceConfig) InstrumentationInstanceConfig { - c.identifyingAttributes = attributes - return c - }) -} +type updateInstrumentationInstanceStatusOpt func(odigosv1.InstrumentationInstanceStatus) odigosv1.InstrumentationInstanceStatus -func WithNonIdentifyingAttributes(attributes []odigosv1.Attribute) InstrumentationInstanceOption { - return fnOpt(func(c InstrumentationInstanceConfig) InstrumentationInstanceConfig { - c.nonIdentifyingAttributes = attributes - return c - }) +func (o updateInstrumentationInstanceStatusOpt) applyInstrumentationInstance(s odigosv1.InstrumentationInstanceStatus) odigosv1.InstrumentationInstanceStatus { + return o(s) } -func WithMessage(message string) InstrumentationInstanceOption { - return fnOpt(func(c InstrumentationInstanceConfig) InstrumentationInstanceConfig { - c.message = message - return c +// set Healthy and related fields in InstrumentationInstanceStatus +func WithHealthy(healthy *bool, reason string, message *string) InstrumentationInstanceOption { + return updateInstrumentationInstanceStatusOpt(func(s odigosv1.InstrumentationInstanceStatus) odigosv1.InstrumentationInstanceStatus { + s.Healthy = healthy + s.Reason = reason + if message != nil { + s.Message = *message + } else { + s.Message = "" + } + return s }) } -func WithReason(reason string) InstrumentationInstanceOption { - return fnOpt(func(c InstrumentationInstanceConfig) InstrumentationInstanceConfig { - c.reason = reason - return c +func WithAttributes(identifying []odigosv1.Attribute, nonIdentifying []odigosv1.Attribute) InstrumentationInstanceOption { + return updateInstrumentationInstanceStatusOpt(func(s odigosv1.InstrumentationInstanceStatus) odigosv1.InstrumentationInstanceStatus { + s.IdentifyingAttributes = identifying + s.NonIdentifyingAttributes = nonIdentifying + return s }) } -func newInstrumentationInstanceConfig(options ...InstrumentationInstanceOption) InstrumentationInstanceConfig { - var c InstrumentationInstanceConfig +func updateInstrumentationInstanceStatus(status odigosv1.InstrumentationInstanceStatus, options ...InstrumentationInstanceOption) odigosv1.InstrumentationInstanceStatus { for _, option := range options { - c = option.applyInstrumentationInstance(c) - } - return c -} - -func newInstrumentationInstanceStatus(options ...InstrumentationInstanceOption) *odigosv1.InstrumentationInstanceStatus { - c := newInstrumentationInstanceConfig(options...) - return &odigosv1.InstrumentationInstanceStatus{ - Healthy: c.healthy, - IdentifyingAttributes: c.identifyingAttributes, - NonIdentifyingAttributes: c.nonIdentifyingAttributes, - Message: c.message, - Reason: c.reason, - LastStatusTime: metav1.Now(), + status = option.applyInstrumentationInstance(status) } + status.LastStatusTime = metav1.Now() + return status } func InstrumentationInstanceName(owner client.Object, pid int) string { return fmt.Sprintf("%s-%d", owner.GetName(), pid) } -func PersistInstrumentationInstanceStatus(ctx context.Context, owner client.Object, kubeClient client.Client, instrumentedAppName string, pid int, scheme *runtime.Scheme, options ...InstrumentationInstanceOption) error { +func UpdateInstrumentationInstanceStatus(ctx context.Context, owner client.Object, containerName string, kubeClient client.Client, instrumentedAppName string, pid int, scheme *runtime.Scheme, options ...InstrumentationInstanceOption) error { instrumentationInstanceName := InstrumentationInstanceName(owner, pid) updatedInstance := &odigosv1.InstrumentationInstance{ TypeMeta: metav1.TypeMeta{ @@ -103,7 +70,11 @@ func PersistInstrumentationInstanceStatus(ctx context.Context, owner client.Obje Labels: map[string]string{ consts.InstrumentedAppNameLabel: instrumentedAppName, }, - }} + }, + Spec: odigosv1.InstrumentationInstanceSpec{ + ContainerName: containerName, + }, + } err := controllerutil.SetControllerReference(owner, updatedInstance, scheme) if err != nil { @@ -120,7 +91,7 @@ func PersistInstrumentationInstanceStatus(ctx context.Context, owner client.Obje } } - updatedInstance.Status = *newInstrumentationInstanceStatus(options...) + updatedInstance.Status = updateInstrumentationInstanceStatus(updatedInstance.Status, options...) err = kubeClient.Status().Update(ctx, updatedInstance) if err != nil { diff --git a/odiglet/Dockerfile b/odiglet/Dockerfile index 5bd6fb4051..f6462bf57b 100644 --- a/odiglet/Dockerfile +++ b/odiglet/Dockerfile @@ -54,11 +54,20 @@ RUN yarn compile FROM busybox AS dotnet-builder WORKDIR /dotnet-instrumentation -ARG DOTNET_OTEL_VERSION=v0.7.0 -ADD https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/releases/download/$DOTNET_OTEL_VERSION/opentelemetry-dotnet-instrumentation-linux-musl.zip . -RUN unzip opentelemetry-dotnet-instrumentation-linux-musl.zip && rm opentelemetry-dotnet-instrumentation-linux-musl.zip - -FROM --platform=$BUILDPLATFORM keyval/odiglet-base:v1.5 as builder +ARG DOTNET_OTEL_VERSION=v1.7.0 +ARG TARGETARCH +RUN if [ "$TARGETARCH" = "arm64" ]; then \ + echo "arm64" > /tmp/arch_suffix; \ + else \ + echo "x64" > /tmp/arch_suffix; \ + fi + +RUN ARCH_SUFFIX=$(cat /tmp/arch_suffix) && \ + wget https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/releases/download/${DOTNET_OTEL_VERSION}/opentelemetry-dotnet-instrumentation-linux-glibc-${ARCH_SUFFIX}.zip && \ + unzip opentelemetry-dotnet-instrumentation-linux-glibc-${ARCH_SUFFIX}.zip && \ + rm opentelemetry-dotnet-instrumentation-linux-glibc-${ARCH_SUFFIX}.zip + +FROM --platform=$BUILDPLATFORM keyval/odiglet-base:v1.5 AS builder WORKDIR /go/src/github.com/odigos-io/odigos # Copy local modules required by the build COPY api/ api/ @@ -77,7 +86,7 @@ RUN --mount=type=cache,target=/root/.cache/go-build \ WORKDIR /instrumentations # Java -ARG JAVA_OTEL_VERSION=v2.3.0 +ARG JAVA_OTEL_VERSION=v2.6.0 ADD https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/$JAVA_OTEL_VERSION/opentelemetry-javaagent.jar /instrumentations/java/javaagent.jar RUN chmod 644 /instrumentations/java/javaagent.jar diff --git a/odiglet/go.mod b/odiglet/go.mod index d6fa2beffc..7ab7a28c24 100644 --- a/odiglet/go.mod +++ b/odiglet/go.mod @@ -14,15 +14,15 @@ require ( github.com/odigos-io/odigos/opampserver v0.0.0 github.com/odigos-io/odigos/procdiscovery v0.0.0 github.com/odigos-io/opentelemetry-zap-bridge v0.0.5 - go.opentelemetry.io/auto v0.13.0-alpha.0.20240705154812-28b663b26905 - go.opentelemetry.io/otel v1.27.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 + go.opentelemetry.io/auto v0.14.0-alpha + go.opentelemetry.io/otel v1.28.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 go.uber.org/zap v1.27.0 google.golang.org/grpc v1.65.0 - k8s.io/api v0.30.2 + k8s.io/api v0.30.3 k8s.io/apimachinery v0.30.3 - k8s.io/client-go v0.30.2 - k8s.io/kubelet v0.30.2 + k8s.io/client-go v0.30.3 + k8s.io/kubelet v0.30.3 sigs.k8s.io/controller-runtime v0.18.4 ) @@ -63,36 +63,40 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.19.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.53.0 // indirect - github.com/prometheus/procfs v0.15.0 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/spf13/pflag v1.0.5 // indirect - go.opentelemetry.io/contrib/bridges/prometheus v0.52.0 // indirect - go.opentelemetry.io/contrib/exporters/autoexport v0.52.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.27.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect - go.opentelemetry.io/otel/exporters/prometheus v0.49.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.27.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.27.0 // indirect - go.opentelemetry.io/otel/metric v1.27.0 // indirect - go.opentelemetry.io/otel/sdk v1.27.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.27.0 // indirect - go.opentelemetry.io/otel/trace v1.27.0 // indirect - go.opentelemetry.io/proto/otlp v1.2.0 // indirect + go.opentelemetry.io/contrib/bridges/prometheus v0.53.0 // indirect + go.opentelemetry.io/contrib/exporters/autoexport v0.53.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.4.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.50.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.4.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0 // indirect + go.opentelemetry.io/otel/log v0.4.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/sdk v1.28.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.4.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 // indirect - golang.org/x/net v0.25.0 // indirect - golang.org/x/oauth2 v0.20.0 // indirect - golang.org/x/sys v0.21.0 // indirect - golang.org/x/term v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/oauth2 v0.21.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/term v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/odiglet/go.sum b/odiglet/go.sum index 71a8867c44..6b52b66a28 100644 --- a/odiglet/go.sum +++ b/odiglet/go.sum @@ -276,13 +276,13 @@ github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE= -github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.15.0 h1:A82kmvXJq2jTu5YUhSGNlYoxh85zLnKgPz4bMZgI5Ek= -github.com/prometheus/procfs v0.15.0/go.mod h1:Y0RJ/Y5g5wJpkTisOtqwDSo4HwhGmLB4VQSw2sQJLHk= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= @@ -311,42 +311,48 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opentelemetry.io/auto v0.13.0-alpha.0.20240705154812-28b663b26905 h1:jkn+mjs7Cpfzj/bjs8M9Ums2bxoABgq9ZtFI4aeL9M4= -go.opentelemetry.io/auto v0.13.0-alpha.0.20240705154812-28b663b26905/go.mod h1:7lDId8pdd0bm8odWRLh8xdA01d7oRtLMxwLCZAuibtc= -go.opentelemetry.io/collector/pdata v1.7.0 h1:/WNsBbE6KM3TTPUb9v/5B7IDqnDkgf8GyFhVJJqu7II= -go.opentelemetry.io/collector/pdata v1.7.0/go.mod h1:ehCBBA5GoFrMZkwyZAKGY/lAVSgZf6rzUt3p9mddmPU= -go.opentelemetry.io/contrib/bridges/prometheus v0.52.0 h1:NNkEjNcUXeNcxDTNLyyAmFHefByhj8YU1AojgcPqbfs= -go.opentelemetry.io/contrib/bridges/prometheus v0.52.0/go.mod h1:Dv7d2yUvusfblvi9qMQby+youF09GiUVWRWkdogrDtE= -go.opentelemetry.io/contrib/exporters/autoexport v0.52.0 h1:G/AGl5O78ZKHs63Rl65P1HyZfDnTyxjv8r7dbdZ9fB0= -go.opentelemetry.io/contrib/exporters/autoexport v0.52.0/go.mod h1:WoVWPZjJ7EB5Z9aROW1DZuRIoFEemxmhCdZJlcjY2AE= -go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= -go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.27.0 h1:bFgvUr3/O4PHj3VQcFEuYKvRZJX1SJDQ+11JXuSB3/w= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.27.0/go.mod h1:xJntEd2KL6Qdg5lwp97HMLQDVeAhrYxmzFseAMDPQ8I= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0 h1:CIHWikMsN3wO+wq1Tp5VGdVRTcON+DmOJSfDjXypKOc= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0/go.mod h1:TNupZ6cxqyFEpLXAZW7On+mLFL0/g0TE3unIYL91xWc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 h1:QY7/0NeRPKlzusf40ZE4t1VlMKbqSNT7cJRYzWuja0s= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0/go.mod h1:HVkSiDhTM9BoUJU8qE6j2eSWLLXvi1USXjyd2BXT8PY= -go.opentelemetry.io/otel/exporters/prometheus v0.49.0 h1:Er5I1g/YhfYv9Affk9nJLfH/+qCCVVg1f2R9AbJfqDQ= -go.opentelemetry.io/otel/exporters/prometheus v0.49.0/go.mod h1:KfQ1wpjf3zsHjzP149P4LyAwWRupc6c7t1ZJ9eXpKQM= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.27.0 h1:/jlt1Y8gXWiHG9FBx6cJaIC5hYx5Fe64nC8w5Cylt/0= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.27.0/go.mod h1:bmToOGOBZ4hA9ghphIc1PAf66VA8KOtsuy3+ScStG20= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.27.0 h1:/0YaXu3755A/cFbtXp+21lkXgI0QE5avTWA2HjU9/WE= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.27.0/go.mod h1:m7SFxp0/7IxmJPLIY3JhOcU9CoFzDaCPL6xxQIxhA+o= -go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= -go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= -go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= -go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= -go.opentelemetry.io/otel/sdk/metric v1.27.0 h1:5uGNOlpXi+Hbo/DRoI31BSb1v+OGcpv2NemcCrOL8gI= -go.opentelemetry.io/otel/sdk/metric v1.27.0/go.mod h1:we7jJVrYN2kh3mVBlswtPU22K0SA+769l93J6bsyvqw= -go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= -go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= -go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= -go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= +go.opentelemetry.io/auto v0.14.0-alpha h1:Dc8MoRawqVu/0EQxTpDjBmUHHRSQ6ATbKkjGoUTWaOw= +go.opentelemetry.io/auto v0.14.0-alpha/go.mod h1:OyuWH1KVgewc6YKakfDn48arE3giVB/IyQRtH/47oPI= +go.opentelemetry.io/contrib/bridges/prometheus v0.53.0 h1:BdkKDtcrHThgjcEia1737OUuFdP6xzBKAMx2sNZCkvE= +go.opentelemetry.io/contrib/bridges/prometheus v0.53.0/go.mod h1:ZkhVxcJgeXlL/lVyT/vxNHVFiSG5qOaDwYaSgD8IfZo= +go.opentelemetry.io/contrib/exporters/autoexport v0.53.0 h1:13K+tY7E8GJInkrvRiPAhC0gi/7vKjzDNhtmCf+QXG8= +go.opentelemetry.io/contrib/exporters/autoexport v0.53.0/go.mod h1:lyQF6xQ4iDnMg4sccNdFs1zf62xd79YI8vZqKjOTwMs= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.4.0 h1:zBPZAISA9NOc5cE8zydqDiS0itvg/P/0Hn9m72a5gvM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.4.0/go.mod h1:gcj2fFjEsqpV3fXuzAA+0Ze1p2/4MJ4T7d77AmkvueQ= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 h1:U2guen0GhqH8o/G2un8f/aG/y++OuW6MyCo6hT9prXk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0/go.mod h1:yeGZANgEcpdx/WK0IvvRFC+2oLiMS2u4L/0Rj2M2Qr0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 h1:aLmmtjRke7LPDQ3lvpFz+kNEH43faFhzW7v8BFIEydg= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0/go.mod h1:TC1pyCt6G9Sjb4bQpShH+P5R53pO6ZuGnHuuln9xMeE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 h1:j9+03ymgYhPKmeXGk5Zu+cIZOlVzd9Zv7QIiyItjFBU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0/go.mod h1:Y5+XiUG4Emn1hTfciPzGPJaSI+RpDts6BnCIir0SLqk= +go.opentelemetry.io/otel/exporters/prometheus v0.50.0 h1:2Ewsda6hejmbhGFyUvWZjUThC98Cf8Zy6g0zkIimOng= +go.opentelemetry.io/otel/exporters/prometheus v0.50.0/go.mod h1:pMm5PkUo5YwbLiuEf7t2xg4wbP0/eSJrMxIMxKosynY= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.4.0 h1:0MH3f8lZrflbUWXVxyBg/zviDFdGE062uKh5+fu8Vv0= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.4.0/go.mod h1:Vh68vYiHY5mPdekTr0ox0sALsqjoVy0w3Os278yX5SQ= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.28.0 h1:BJee2iLkfRfl9lc7aFmBwkWxY/RI1RDdXepSF6y8TPE= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.28.0/go.mod h1:DIzlHs3DRscCIBU3Y9YSzPfScwnYnzfnCd4g8zA7bZc= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0 h1:EVSnY9JbEEW92bEkIYOVMw4q1WJxIAGoFTrtYOzWuRQ= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0/go.mod h1:Ea1N1QQryNXpCD0I1fdLibBAIpQuBkznMmkdKrapk1Y= +go.opentelemetry.io/otel/log v0.4.0 h1:/vZ+3Utqh18e8TPjuc3ecg284078KWrR8BRz+PQAj3o= +go.opentelemetry.io/otel/log v0.4.0/go.mod h1:DhGnQvky7pHy82MIRV43iXh3FlKN8UUKftn0KbLOq6I= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/sdk/log v0.4.0 h1:1mMI22L82zLqf6KtkjrRy5BbagOTWdJsqMY/HSqILAA= +go.opentelemetry.io/otel/sdk/log v0.4.0/go.mod h1:AYJ9FVF0hNOgAVzUG/ybg/QttnXhUePWAupmCqtdESo= +go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= +go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= @@ -365,8 +371,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -410,14 +416,14 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= -golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -453,16 +459,16 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -491,8 +497,8 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= -golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 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= @@ -522,10 +528,10 @@ google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBr google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200916143405-f6a2fa72f0c4/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw= -google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= +google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -579,16 +585,16 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI= -k8s.io/api v0.30.2 h1:+ZhRj+28QT4UOH+BKznu4CBgPWgkXO7XAvMcMl0qKvI= -k8s.io/api v0.30.2/go.mod h1:ULg5g9JvOev2dG0u2hig4Z7tQ2hHIuS+m8MNZ+X6EmI= +k8s.io/api v0.30.3 h1:ImHwK9DCsPA9uoU3rVh4QHAHHK5dTSv1nxJUapx8hoQ= +k8s.io/api v0.30.3/go.mod h1:GPc8jlzoe5JG3pb0KJCSLX5oAFIW3/qNJITlDj8BH04= k8s.io/apiextensions-apiserver v0.30.1 h1:4fAJZ9985BmpJG6PkoxVRpXv9vmPUOVzl614xarePws= k8s.io/apiextensions-apiserver v0.30.1/go.mod h1:R4GuSrlhgq43oRY9sF2IToFh7PVlF1JjfWdoG3pixk4= k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= k8s.io/apimachinery v0.30.3 h1:q1laaWCmrszyQuSQCfNB8cFgCuDAoPszKY4ucAjDwHc= k8s.io/apimachinery v0.30.3/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= k8s.io/client-go v0.19.2/go.mod h1:S5wPhCqyDNAlzM9CnEdgTGV4OqhsW3jGO1UM1epwfJA= -k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50= -k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs= +k8s.io/client-go v0.30.3 h1:bHrJu3xQZNXIi8/MoxYtZBBWQQXwy16zqJwloXXfD3k= +k8s.io/client-go v0.30.3/go.mod h1:8d4pf8vYu665/kUbsxWAQ/JDBNWqfFeZnvFiVdmx89U= k8s.io/component-base v0.19.2/go.mod h1:g5LrsiTiabMLZ40AR6Hl45f088DevyGY+cCE2agEIVo= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= @@ -599,8 +605,8 @@ k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= k8s.io/kubelet v0.19.2/go.mod h1:FHHoByVWzh6kNaarXaDPAa751Oz6REcOVRyFT84L1Is= -k8s.io/kubelet v0.30.2 h1:Ck4E/pHndI20IzDXxS57dElhDGASPO5pzXF7BcKfmCY= -k8s.io/kubelet v0.30.2/go.mod h1:DSwwTbLQmdNkebAU7ypIALR4P9aXZNFwgRmedojUE94= +k8s.io/kubelet v0.30.3 h1:KvGWDdhzD0vEyDyGTCjsDc8D+0+lwRMw3fJbfQgF7ys= +k8s.io/kubelet v0.30.3/go.mod h1:D9or45Vkzcqg55CEiqZ8dVbwP3Ksj7DruEVRS9oq3Ys= k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= diff --git a/odiglet/pkg/ebpf/director.go b/odiglet/pkg/ebpf/director.go index c1400b94a6..750ca4f4b2 100644 --- a/odiglet/pkg/ebpf/director.go +++ b/odiglet/pkg/ebpf/director.go @@ -51,12 +51,13 @@ const ( ) type instrumentationStatus struct { - Workload common.PodWorkload - PodName types.NamespacedName - Healthy bool - Message string - Reason InstrumentationStatusReason - Pid int + Workload common.PodWorkload + PodName types.NamespacedName + ContainerName string + Healthy bool + Message string + Reason InstrumentationStatusReason + Pid int } type EbpfDirector[T OtelEbpfSdk] struct { @@ -140,10 +141,8 @@ func (d *EbpfDirector[T]) observeInstrumentations(ctx context.Context, scheme *r } instrumentedAppName := workload.GetRuntimeObjectName(status.Workload.Name, status.Workload.Kind) - err = inst.PersistInstrumentationInstanceStatus(ctx, &pod, d.client, instrumentedAppName, status.Pid, scheme, - inst.WithHealthy(&status.Healthy), - inst.WithMessage(status.Message), - inst.WithReason(string(status.Reason)), + err = inst.UpdateInstrumentationInstanceStatus(ctx, &pod, status.ContainerName, d.client, instrumentedAppName, status.Pid, scheme, + inst.WithHealthy(&status.Healthy, string(status.Reason), &status.Message), ) if err != nil { @@ -188,12 +187,13 @@ func (d *EbpfDirector[T]) Instrument(ctx context.Context, pid int, pod types.Nam return case <-loadedIndicator: d.instrumentationStatusChan <- instrumentationStatus{ - Healthy: true, - Message: "Successfully loaded eBPF probes to pod: " + pod.String(), - Workload: *podWorkload, - Reason: LoadedSuccessfully, - PodName: pod, - Pid: pid, + Healthy: true, + Message: "Successfully loaded eBPF probes to pod: " + pod.String(), + Workload: *podWorkload, + Reason: LoadedSuccessfully, + PodName: pod, + ContainerName: containerName, + Pid: pid, } } }() @@ -204,12 +204,13 @@ func (d *EbpfDirector[T]) Instrument(ctx context.Context, pid int, pod types.Nam inst, err := d.instrumentationFactory.CreateEbpfInstrumentation(ctx, pid, appName, podWorkload, containerName, pod.Name, loadedIndicator) if err != nil { d.instrumentationStatusChan <- instrumentationStatus{ - Healthy: false, - Message: err.Error(), - Workload: *podWorkload, - Reason: FailedToInitialize, - PodName: pod, - Pid: pid, + Healthy: false, + Message: err.Error(), + Workload: *podWorkload, + Reason: FailedToInitialize, + PodName: pod, + ContainerName: containerName, + Pid: pid, } return } @@ -234,12 +235,13 @@ func (d *EbpfDirector[T]) Instrument(ctx context.Context, pid int, pod types.Nam if err := inst.Run(context.Background()); err != nil { d.instrumentationStatusChan <- instrumentationStatus{ - Healthy: false, - Message: err.Error(), - Workload: *podWorkload, - Reason: FailedToLoad, - PodName: pod, - Pid: pid, + Healthy: false, + Message: err.Error(), + Workload: *podWorkload, + Reason: FailedToLoad, + PodName: pod, + ContainerName: containerName, + Pid: pid, } } }() diff --git a/odiglet/pkg/instrumentation/instrumentlang/dotnet.go b/odiglet/pkg/instrumentation/instrumentlang/dotnet.go index 950c82b1d7..f296d3d8db 100644 --- a/odiglet/pkg/instrumentation/instrumentlang/dotnet.go +++ b/odiglet/pkg/instrumentation/instrumentlang/dotnet.go @@ -2,6 +2,7 @@ package instrumentlang import ( "fmt" + "runtime" "github.com/odigos-io/odigos/common" "github.com/odigos-io/odigos/odiglet/pkg/env" @@ -14,7 +15,7 @@ const ( profilerEndVar = "CORECLR_PROFILER" profilerId = "{918728DD-259F-4A6A-AC2B-B85E1B658318}" profilerPathEnv = "CORECLR_PROFILER_PATH" - profilerPath = "/var/odigos/dotnet/OpenTelemetry.AutoInstrumentation.ClrProfiler.Native.so" + profilerPath = "/var/odigos/dotnet/linux-%s/OpenTelemetry.AutoInstrumentation.Native.so" serviceNameEnv = "OTEL_SERVICE_NAME" collectorUrlEnv = "OTEL_EXPORTER_OTLP_ENDPOINT" tracerHomeEnv = "OTEL_DOTNET_AUTO_HOME" @@ -34,7 +35,7 @@ func DotNet(deviceId string, uniqueDestinationSignals map[common.ObservabilitySi Envs: map[string]string{ enableProfilingEnvVar: "1", profilerEndVar: profilerId, - profilerPathEnv: profilerPath, + profilerPathEnv: fmt.Sprintf(profilerPath, getArch()), // TODO(edenfed): Support both musl and glibc. Requires improved language detection tracerHomeEnv: tracerHome, collectorUrlEnv: fmt.Sprintf("http://%s:%d", env.Current.NodeIP, consts.OTLPHttpPort), serviceNameEnv: deviceId, @@ -53,3 +54,11 @@ func DotNet(deviceId string, uniqueDestinationSignals map[common.ObservabilitySi }, } } + +func getArch() string { + if runtime.GOARCH == "arm64" { + return "arm64" + } + + return "x64" +} diff --git a/odiglet/pkg/instrumentation/instrumentlang/python.go b/odiglet/pkg/instrumentation/instrumentlang/python.go index 1d647982a6..8c6eec05d9 100644 --- a/odiglet/pkg/instrumentation/instrumentlang/python.go +++ b/odiglet/pkg/instrumentation/instrumentlang/python.go @@ -32,9 +32,6 @@ func Python(deviceId string, uniqueDestinationSignals map[common.ObservabilitySi metricsExporter := "none" tracesExporter := "none" - if _, ok := uniqueDestinationSignals[common.LogsObservabilitySignal]; ok { - logsExporter = "otlp" - } if _, ok := uniqueDestinationSignals[common.MetricsObservabilitySignal]; ok { metricsExporter = "otlp" } @@ -44,13 +41,15 @@ func Python(deviceId string, uniqueDestinationSignals map[common.ObservabilitySi return &v1beta1.ContainerAllocateResponse{ Envs: map[string]string{ - pythonOdigosDeviceId: deviceId, - pythonOdigosOpampServer: opampServerHost, - envLogCorrelation: "true", - envPythonPath: pythonpathVal, - "OTEL_EXPORTER_OTLP_ENDPOINT": otlpEndpoint, - envOtelTracesExporter: tracesExporter, - envOtelMetricsExporter: metricsExporter, + pythonOdigosDeviceId: deviceId, + pythonOdigosOpampServer: opampServerHost, + envLogCorrelation: "true", + envPythonPath: pythonpathVal, + "OTEL_EXPORTER_OTLP_ENDPOINT": otlpEndpoint, + envOtelTracesExporter: tracesExporter, + envOtelMetricsExporter: metricsExporter, + // Log exporter is currently set to "none" due to the data collection method, which collects logs from the file system. + // In the future, this will be changed to "otlp" to send logs directly from the agent to the gateway. envOtelLogsExporter: logsExporter, envOtelExporterOTLPTracesProtocol: httpProtobufProtocol, envOtelExporterOTLPMetricsProtocol: httpProtobufProtocol, diff --git a/opampserver/pkg/connection/conncache.go b/opampserver/pkg/connection/conncache.go index fc92b80112..9c9f36e95b 100644 --- a/opampserver/pkg/connection/conncache.go +++ b/opampserver/pkg/connection/conncache.go @@ -31,13 +31,13 @@ func NewConnectionsCache() *ConnectionsCache { } } -// GetConnection returns the connection information for the given device id. +// GetConnection returns the connection information for the given OpAMP instanceUid. // the returned object is a by-value copy of the connection information, so it can be safely used. // To change something in the connection information, use the functions below which are synced and safe. -func (c *ConnectionsCache) GetConnection(deviceId string) (*ConnectionInfo, bool) { +func (c *ConnectionsCache) GetConnection(instanceUid string) (*ConnectionInfo, bool) { c.mux.Lock() defer c.mux.Unlock() - conn, ok := c.liveConnections[deviceId] + conn, ok := c.liveConnections[instanceUid] if !ok || conn == nil { return nil, false } else { @@ -47,30 +47,30 @@ func (c *ConnectionsCache) GetConnection(deviceId string) (*ConnectionInfo, bool } } -func (c *ConnectionsCache) AddConnection(deviceId string, conn *ConnectionInfo) { +func (c *ConnectionsCache) AddConnection(instanceUid string, conn *ConnectionInfo) { // copy the conn object to avoid it being accessed concurrently connCopy := *conn c.mux.Lock() defer c.mux.Unlock() - c.liveConnections[deviceId] = &connCopy + c.liveConnections[instanceUid] = &connCopy } -func (c *ConnectionsCache) RemoveConnection(deviceId string) { +func (c *ConnectionsCache) RemoveConnection(instanceUid string) { c.mux.Lock() defer c.mux.Unlock() - delete(c.liveConnections, deviceId) + delete(c.liveConnections, instanceUid) } -func (c *ConnectionsCache) RecordMessageTime(deviceId string) { +func (c *ConnectionsCache) RecordMessageTime(instanceUid string) { c.mux.Lock() defer c.mux.Unlock() - conn, ok := c.liveConnections[deviceId] + conn, ok := c.liveConnections[instanceUid] if !ok { return } - conn.lastMessageTime = time.Now() - c.liveConnections[deviceId] = conn + conn.LastMessageTime = time.Now() + c.liveConnections[instanceUid] = conn } func (c *ConnectionsCache) CleanupStaleConnections() []ConnectionInfo { @@ -80,7 +80,7 @@ func (c *ConnectionsCache) CleanupStaleConnections() []ConnectionInfo { deadConnectionInfos := make([]ConnectionInfo, 0) for deviceId, conn := range c.liveConnections { - if time.Since(conn.lastMessageTime) > connectionStaleTime { + if time.Since(conn.LastMessageTime) > connectionStaleTime { delete(c.liveConnections, deviceId) deadConnectionInfos = append(deadConnectionInfos, *conn) } diff --git a/opampserver/pkg/connection/types.go b/opampserver/pkg/connection/types.go index f5ceb274ac..21605c85fe 100644 --- a/opampserver/pkg/connection/types.go +++ b/opampserver/pkg/connection/types.go @@ -13,9 +13,10 @@ type ConnectionInfo struct { DeviceId string Workload common.PodWorkload Pod *corev1.Pod + ContainerName string Pid int64 InstrumentedAppName string - lastMessageTime time.Time + LastMessageTime time.Time // config related fields // AgentRemoteConfig is the full remote config opamp message to send to the agent when needed diff --git a/opampserver/pkg/server/handlers.go b/opampserver/pkg/server/handlers.go index 18b6e2b414..2f20d0c78b 100644 --- a/opampserver/pkg/server/handlers.go +++ b/opampserver/pkg/server/handlers.go @@ -16,8 +16,6 @@ import ( "github.com/odigos-io/odigos/opampserver/pkg/sdkconfig/configresolvers" "github.com/odigos-io/odigos/opampserver/protobufs" semconv "go.opentelemetry.io/otel/semconv/v1.24.0" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -79,13 +77,13 @@ func (c *ConnectionHandlers) OnNewConnection(ctx context.Context, deviceId strin c.logger.Error(err, "failed to get full config", "k8sAttributes", k8sAttributes) return nil, nil, err } - c.logger.Info("new OpAMP client connected", "deviceId", deviceId, "namespace", k8sAttributes.Namespace, "podName", k8sAttributes.PodName, "instrumentedAppName", instrumentedAppName, "workloadKind", k8sAttributes.WorkloadKind, "workloadName", k8sAttributes.WorkloadName, "containerName", k8sAttributes.ContainerName, "otelServiceName", k8sAttributes.OtelServiceName) connectionInfo := &connection.ConnectionInfo{ DeviceId: deviceId, Workload: podWorkload, Pod: pod, + ContainerName: k8sAttributes.ContainerName, Pid: pid, InstrumentedAppName: instrumentedAppName, AgentRemoteConfig: fullRemoteConfig, @@ -117,25 +115,42 @@ func (c *ConnectionHandlers) OnAgentToServerMessage(ctx context.Context, request } func (c *ConnectionHandlers) OnConnectionClosed(ctx context.Context, connectionInfo *connection.ConnectionInfo) { - c.logger.Info("Connection closed for device", "deviceId", connectionInfo.DeviceId) - instrumentationInstanceName := instrumentation_instance.InstrumentationInstanceName(connectionInfo.Pod, int(connectionInfo.Pid)) - err := c.kubeclient.Delete(ctx, &odigosv1.InstrumentationInstance{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "odigos.io/v1alpha1", - Kind: "InstrumentationInstance", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: instrumentationInstanceName, - Namespace: connectionInfo.Pod.GetNamespace(), - }, - }) - - if err != nil && !apierrors.IsNotFound(err) { - c.logger.Error(err, "failed to delete instrumentation instance", "instrumentationInstanceName", instrumentationInstanceName) + // keep the instrumentation instance CR in unhealthy state so it can be used for troubleshooting +} + +func (c *ConnectionHandlers) OnConnectionNoHeartbeat(ctx context.Context, connectionInfo *connection.ConnectionInfo) error { + healthy := false + message := fmt.Sprintf("OpAMP server did not receive heartbeat from the agent, last message time: %s", connectionInfo.LastMessageTime.Format("2006-01-02 15:04:05 MST")) + // keep the instrumentation instance CR in unhealthy state so it can be used for troubleshooting + err := instrumentation_instance.UpdateInstrumentationInstanceStatus(ctx, connectionInfo.Pod, connectionInfo.ContainerName, c.kubeclient, connectionInfo.InstrumentedAppName, int(connectionInfo.Pid), c.scheme, + instrumentation_instance.WithHealthy(&healthy, common.AgentHealthStatusNoHeartbeat, &message), + ) + if err != nil { + return fmt.Errorf("failed to persist instrumentation instance health status on connection timedout: %w", err) } + + return nil } -func (c *ConnectionHandlers) PersistInstrumentationDeviceStatus(ctx context.Context, message *protobufs.AgentToServer, connectionInfo *connection.ConnectionInfo) error { +func (c *ConnectionHandlers) UpdateInstrumentationInstanceStatus(ctx context.Context, message *protobufs.AgentToServer, connectionInfo *connection.ConnectionInfo) error { + + isAgentDisconnect := message.AgentDisconnect != nil + hasHealth := message.Health != nil + // when agent disconnects, it need to report that it is unhealthy and disconnected + if isAgentDisconnect { + if !hasHealth { + return fmt.Errorf("missing health in agent disconnect message") + } + if message.Health.Healthy { + return fmt.Errorf("agent disconnect message with healthy status") + } + if message.Health.LastError == "" { + return fmt.Errorf("missing last error in unhealthy message") + } + } + + dynamicOptions := make([]instrumentation_instance.InstrumentationInstanceOption, 0) + if message.AgentDescription != nil { identifyingAttributes := make([]odigosv1.Attribute, 0, len(message.AgentDescription.IdentifyingAttributes)) for _, attr := range message.AgentDescription.IdentifyingAttributes { @@ -145,13 +160,18 @@ func (c *ConnectionHandlers) PersistInstrumentationDeviceStatus(ctx context.Cont Value: strValue, }) } + dynamicOptions = append(dynamicOptions, instrumentation_instance.WithAttributes(identifyingAttributes, []odigosv1.Attribute{})) + } + + // agent is only expected to send health status when it changes, so if found - persist it to CRD as new status + if hasHealth { + // always record healthy status into the CRD, to reflect the current state + healthy := message.Health.Healthy + dynamicOptions = append(dynamicOptions, instrumentation_instance.WithHealthy(&healthy, message.Health.Status, &message.Health.LastError)) + } - healthy := true // TODO: populate this field with real health status - err := instrumentation_instance.PersistInstrumentationInstanceStatus(ctx, connectionInfo.Pod, c.kubeclient, connectionInfo.InstrumentedAppName, int(connectionInfo.Pid), c.scheme, - instrumentation_instance.WithIdentifyingAttributes(identifyingAttributes), - instrumentation_instance.WithMessage("Agent connected"), - instrumentation_instance.WithHealthy(&healthy), - ) + if len(dynamicOptions) > 0 { + err := instrumentation_instance.UpdateInstrumentationInstanceStatus(ctx, connectionInfo.Pod, connectionInfo.ContainerName, c.kubeclient, connectionInfo.InstrumentedAppName, int(connectionInfo.Pid), c.scheme, dynamicOptions...) if err != nil { return fmt.Errorf("failed to persist instrumentation instance status: %w", err) } diff --git a/opampserver/pkg/server/server.go b/opampserver/pkg/server/server.go index 86bc9ab3f9..9132f6497c 100644 --- a/opampserver/pkg/server/server.go +++ b/opampserver/pkg/server/server.go @@ -65,6 +65,13 @@ func StartOpAmpServer(ctx context.Context, logger logr.Logger, mgr ctrl.Manager, return } + instanceUid := string(agentToServer.InstanceUid) + if instanceUid == "" { + logger.Error(err, "InstanceUid is missing") + w.WriteHeader(http.StatusBadRequest) + return + } + deviceId := req.Header.Get("X-Odigos-DeviceId") if deviceId == "" { logger.Error(err, "X-Odigos-DeviceId header is missing") @@ -72,8 +79,10 @@ func StartOpAmpServer(ctx context.Context, logger logr.Logger, mgr ctrl.Manager, return } + isAgentDisconnect := agentToServer.AgentDisconnect != nil + var serverToAgent *protobufs.ServerToAgent - connectionInfo, exists := connectionCache.GetConnection(deviceId) + connectionInfo, exists := connectionCache.GetConnection(instanceUid) if !exists { connectionInfo, serverToAgent, err = handlers.OnNewConnection(ctx, deviceId, &agentToServer) if err != nil { @@ -82,17 +91,10 @@ func StartOpAmpServer(ctx context.Context, logger logr.Logger, mgr ctrl.Manager, return } if connectionInfo != nil { - connectionCache.AddConnection(deviceId, connectionInfo) + connectionCache.AddConnection(instanceUid, connectionInfo) } } else { - - if agentToServer.AgentDisconnect != nil { - handlers.OnConnectionClosed(ctx, connectionInfo) - connectionCache.RemoveConnection(deviceId) - } - serverToAgent, err = handlers.OnAgentToServerMessage(ctx, &agentToServer, connectionInfo) - if err != nil { logger.Error(err, "Failed to process opamp message") w.WriteHeader(http.StatusInternalServerError) @@ -100,7 +102,7 @@ func StartOpAmpServer(ctx context.Context, logger logr.Logger, mgr ctrl.Manager, } } - err = handlers.PersistInstrumentationDeviceStatus(ctx, &agentToServer, connectionInfo) + err = handlers.UpdateInstrumentationInstanceStatus(ctx, &agentToServer, connectionInfo) if err != nil { logger.Error(err, "Failed to persist instrumentation device status") // still return the opamp response @@ -112,8 +114,15 @@ func StartOpAmpServer(ctx context.Context, logger logr.Logger, mgr ctrl.Manager, return } - // keep record in memory of last message time, to detect stale connections - connectionCache.RecordMessageTime(deviceId) + if isAgentDisconnect { + logger.Info("Agent disconnected", "workloadNamespace", connectionInfo.Workload.Namespace, "workloadName", connectionInfo.Workload.Name, "workloadKind", connectionInfo.Workload.Kind) + // if agent disconnects, remove the connection from the cache + // as it is not expected to send additional messages + connectionCache.RemoveConnection(instanceUid) + } else { + // keep record in memory of last message time, to detect stale connections + connectionCache.RecordMessageTime(instanceUid) + } serverToAgent.InstanceUid = agentToServer.InstanceUid @@ -156,7 +165,10 @@ func StartOpAmpServer(ctx context.Context, logger logr.Logger, mgr ctrl.Manager, // Clean up stale connections deadConnections := connectionCache.CleanupStaleConnections() for _, conn := range deadConnections { - handlers.OnConnectionClosed(ctx, &conn) + err := handlers.OnConnectionNoHeartbeat(ctx, &conn) + if err != nil { + logger.Error(err, "Failed to process connection with no heartbeat") + } } } } diff --git a/procdiscovery/pkg/process/process.go b/procdiscovery/pkg/process/process.go index 9063efda4b..8bb7ce069e 100644 --- a/procdiscovery/pkg/process/process.go +++ b/procdiscovery/pkg/process/process.go @@ -125,7 +125,7 @@ func getRelevantEnvVars(pid int) map[string]string { str = strings.TrimRight(str, "\x00") - envParts := strings.Split(str, "=") + envParts := strings.SplitN(str, "=", 2) if len(envParts) != 2 { continue } diff --git a/scheduler/go.mod b/scheduler/go.mod index 38ea52ab32..7926cde12b 100644 --- a/scheduler/go.mod +++ b/scheduler/go.mod @@ -10,7 +10,7 @@ require ( github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.33.1 k8s.io/apimachinery v0.30.3 - k8s.io/client-go v0.30.1 + k8s.io/client-go v0.30.3 sigs.k8s.io/controller-runtime v0.18.4 ) @@ -81,7 +81,7 @@ require ( gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.30.1 // indirect + k8s.io/api v0.30.3 // indirect k8s.io/apiextensions-apiserver v0.30.1 // indirect k8s.io/klog/v2 v2.120.1 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect diff --git a/scheduler/go.sum b/scheduler/go.sum index 8ef632e7d3..13b1ee80b3 100644 --- a/scheduler/go.sum +++ b/scheduler/go.sum @@ -268,14 +268,14 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY= -k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM= +k8s.io/api v0.30.3 h1:ImHwK9DCsPA9uoU3rVh4QHAHHK5dTSv1nxJUapx8hoQ= +k8s.io/api v0.30.3/go.mod h1:GPc8jlzoe5JG3pb0KJCSLX5oAFIW3/qNJITlDj8BH04= k8s.io/apiextensions-apiserver v0.30.1 h1:4fAJZ9985BmpJG6PkoxVRpXv9vmPUOVzl614xarePws= k8s.io/apiextensions-apiserver v0.30.1/go.mod h1:R4GuSrlhgq43oRY9sF2IToFh7PVlF1JjfWdoG3pixk4= k8s.io/apimachinery v0.30.3 h1:q1laaWCmrszyQuSQCfNB8cFgCuDAoPszKY4ucAjDwHc= k8s.io/apimachinery v0.30.3/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= -k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= -k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= +k8s.io/client-go v0.30.3 h1:bHrJu3xQZNXIi8/MoxYtZBBWQQXwy16zqJwloXXfD3k= +k8s.io/client-go v0.30.3/go.mod h1:8d4pf8vYu665/kUbsxWAQ/JDBNWqfFeZnvFiVdmx89U= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= diff --git a/tests/common/flush_traces.sh b/tests/common/flush_traces.sh new file mode 100755 index 0000000000..20b539d6fd --- /dev/null +++ b/tests/common/flush_traces.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Ensure the script fails if any command fails +set -e + +function flush_traces() { + local dest_namespace="traces" + local dest_service="e2e-tests-tempo" + local dest_port="tempo-prom-metrics" + kubectl get --raw /api/v1/namespaces/$dest_namespace/services/$dest_service:$dest_port/proxy/flush + # check if command succeeded + if [ $? -eq 0 ]; then + echo "Traces flushed successfully" + else + echo "Failed to flush traces" + exit 1 + fi +} + +flush_traces \ No newline at end of file diff --git a/tests/common/traceql_runner.sh b/tests/common/traceql_runner.sh new file mode 100755 index 0000000000..b36bd8e3c2 --- /dev/null +++ b/tests/common/traceql_runner.sh @@ -0,0 +1,74 @@ +#!/bin/bash + +# Ensure the script fails if any command fails +set -e + +# Function to verify the YAML schema +function verify_yaml_schema() { + local file=$1 + local query=$(yq e '.query' "$file") + local expected_count=$(yq e '.expected.count' "$file") + + if [ -z "$query" ] || [ "$expected_count" == "null" ] || [ -z "$expected_count" ]; then + echo "Invalid YAML schema in file: $file" + exit 1 + fi +} + +function urlencode() ( + local length="${#1}" + for (( i = 0; i < length; i++ )); do + local c="${1:i:1}" + case $c in + [a-zA-Z0-9.~_-]) printf "$c" ;; + *) printf '%%%02X' "'$c" ;; + esac + done +) + +# Function to process a YAML file +function process_yaml_file() { + local dest_namespace="traces" + local dest_service="e2e-tests-tempo" + local dest_port="tempo-prom-metrics" + + local file=$1 + file_name=$(basename "$file") + echo "Running test $file_name" + query=$(yq '.query' "$file") + encoded_query=$(urlencode "$query") + expected_count=$(yq e '.expected.count' "$file") + current_epoch=$(date +%s) + one_hour=3600 + start_epoch=$(($current_epoch - one_hour)) + end_epoch=$(($current_epoch + one_hour)) + response=$(kubectl get --raw /api/v1/namespaces/$dest_namespace/services/$dest_service:$dest_port/proxy/api/search\?end=$end_epoch\&start=$start_epoch\&q=$encoded_query) + num_of_traces=$(echo $response | jq '.traces | length') + # if num_of_traces not equal to expected_count + if [ "$num_of_traces" -ne "$expected_count" ]; then + echo "Test FAILED: expected $expected_count got $num_of_traces" + echo "$response" | jq + exit 1 + else + echo "Test PASSED" + exit 0 + fi +} + +# Check if the first argument is provided +if [ -z "$1" ]; then + echo "Usage: $0 " + exit 1 +fi + +# Test file path +TEST_FILE=$1 + +# Check if yq is installed +if ! command -v yq &> /dev/null; then + echo "yq command not found. Please install yq." + exit 1 +fi + +verify_yaml_schema $TEST_FILE +process_yaml_file $TEST_FILE diff --git a/tests/common/ui-tests/run_cypress_tests.sh b/tests/common/ui-tests/run_cypress_tests.sh new file mode 100755 index 0000000000..95dcaac627 --- /dev/null +++ b/tests/common/ui-tests/run_cypress_tests.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +echo "Running Cypress tests" +cd ../../../frontend/webapp || exit +npx cypress run + +status_cypress=$? +if [ $status_cypress -ne 0 ]; then + echo "Cypress tests failed" + + # Stop the background process + kill "$(cat odigos-ui.pid)" + rm odigos-ui.pid + rm ../../odigos-ui.log + + exit $status_cypress +else + echo "Cypress tests passed" +fi \ No newline at end of file diff --git a/tests/common/ui-tests/start_odigos_ui.sh b/tests/common/ui-tests/start_odigos_ui.sh new file mode 100755 index 0000000000..8af830620d --- /dev/null +++ b/tests/common/ui-tests/start_odigos_ui.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Ensure the script fails if any command fails +set -e + +echo "Running odigos UI setup" +cd ../../../frontend/webapp +yarn dev > ../../odigos-ui.log 2>&1 & + +# Capture the process ID +echo $! > odigos-ui.pid + +# Check the status of the process +sleep 5 +if ps -p $(cat odigos-ui.pid) > /dev/null +then + echo "Odigos UI started successfully" +else + echo "Failed to start Odigos UI" + # I want to print the log file to the console + cat ../../odigos-ui.log + exit 1 +fi \ No newline at end of file diff --git a/tests/common/ui-tests/stop_ui_and_clean.sh b/tests/common/ui-tests/stop_ui_and_clean.sh new file mode 100755 index 0000000000..7cd625bbcb --- /dev/null +++ b/tests/common/ui-tests/stop_ui_and_clean.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Ensure the script fails if any command fails +set -e + +echo "Killing Odigos UI process" +cd ../../../frontend/webapp +kill "$(cat odigos-ui.pid)" +rm odigos-ui.pid +rm ../../odigos-ui.log \ No newline at end of file diff --git a/tests/common/wait_for_dest.sh b/tests/common/wait_for_dest.sh new file mode 100755 index 0000000000..6179dffe66 --- /dev/null +++ b/tests/common/wait_for_dest.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# Ensure the script fails if any command fails +set -e + +# function to verify tempo is ready +# This is needed due to bug in tempo - It reports Ready before it is actually ready +# So we manually hit the health check endpoint to verify it is ready +function wait_for_ready() { + local dest_namespace="traces" + local dest_service="e2e-tests-tempo" + local dest_port="tempo-prom-metrics" + local response=$(kubectl get --raw /api/v1/namespaces/$dest_namespace/services/$dest_service:$dest_port/proxy/ready) + if [ "$response" != "ready" ]; then + echo "Tempo is not ready yet. Retrying in 2 seconds..." + sleep 2 + wait_for_ready + else + echo "Tempo is ready" + sleep 2 + fi +} + +wait_for_ready \ No newline at end of file diff --git a/tests/e2e/README.md b/tests/e2e/README.md new file mode 100644 index 0000000000..7a00951d48 --- /dev/null +++ b/tests/e2e/README.md @@ -0,0 +1,101 @@ +# Odigos End to End Testing +In addition to unit tests, Odigos has a suite of end-to-end tests that are run on every pull request. +These tests are installing multiple microservices, instrument with Odigos, generate traffic, and validate the results. + +## Tools +- [Kubernetes In Docker (KinD)](https://kind.sigs.k8s.io/) - a tool for running local Kubernetes clusters using Docker container β€œnodes”. +- [Chainsaw](https://kyverno.github.io/chainsaw/) - To orchestrate the different Kubernetes actions. +- [Tempo](https://github.com/grafana/tempo) - Distributed tracing backend. Chosen due to its query language that allows for easy querying of traces. + +## Running e2e locally +To run the end-to-end tests you need to have the following: +- kubectl configured to a fresh Kubernetes cluster. For local development, you can use KinD but also managed clusters like EKS should work. +- yq and jq installed. You can install it via: +```bash +brew install yq +brew install jq +``` +- Odigos cli compiled at `cli` folder. Compile via: +```bash +go build -tags=embed_manifests -o ./cli/odigos ./cli +``` +- Odigos images tagged with `e2e-test` preloaded to the cluster. If you are using KinD you can run: +```bash +TAG=e2e-test make build-images load-to-kind +``` +- Chainsaw binary, installed via one of the following methods: + - Hombrew: + ```bash + brew tap kyverno/chainsaw https://github.com/kyverno/chainsaw + brew install kyverno/chainsaw/chainsaw + ``` + - Go: + ```bash + go install github.com/kyverno/chainsaw@latest + ``` + +To run specific scenarios, for example `multi-apps` run from Odigos root directory: +```bash +chainsaw test tests/e2e/multi-apps +``` + +## Writing new scenarios +Every scenario should include some/all of the following: +- Install destination (usually Tempo) +- Install test applications +- Install Odigos +- Select apps for instrumentation and configure destination +- Generate traffic +- Validate traces + +Scenarios are written in yaml files called `chainsaw-test.yaml` according to the Chainsaw schema. + +See the [following document](https://kyverno.github.io/chainsaw/latest/test/) for more information on how to write scenarios. + +Scenarios should be placed in the `tests/e2e/` directory and TraceQL validations should be placed in the `tests/e2e//traceql` directory. + +After writing and testing new scenario, you should also add it to the GitHub Action file location at: +`.github/workflows/e2e.yaml` to run it on every pull request. + +## Working with TraceQL +TraceQL is a query language that allows you to query traces in Tempo. +It is used in the end-to-end tests to validate the traces generated by Odigos. + +### Connecting to Tempo +In order to run TraceQL queries, you need to connect to Tempo. +Tempo is installed automatically in the e2e test, so if you ran a scenario you can connect to it. +You can do this by port-forwarding the Tempo service: +```bash +kubectl port-forward svc/e2e-tests-tempo 3100:3100 -n traces +``` + +### Querying traces +Then you can execute TraceQL queries via: +```bash +curl -G -s http://localhost:3100/api/search --data-urlencode 'q={ resource.odigos.version = "e2e-test"}' +``` + +To get full individual trace you can use the following command: +```bash +curl -G -s http://localhost:3100/api/traces/3debdffae5920741a53d1bd015c62b29 +``` + +For both APIs it is recommended to pipe the results to `jq` for better readability and `less` for paging. + +### Writing queries +See [the following document](https://grafana.com/docs/tempo/latest/traceql/) for more information on how to write queries. +In order to add new traceql test, you need to add a new yaml file in the following schema: +```yaml +apiVersion: e2e.tests.odigos.io/v1 +kind: TraceTest +description: +query: | + +expected: + count: +``` + +Once you have the file, you can run the test via: +```bash +tests/e2e/common/traceql_runner.sh +``` \ No newline at end of file diff --git a/.github/workflows/e2e/kv-shop.yaml b/tests/e2e/helm-chart/02-install-simple-demo.yaml similarity index 70% rename from .github/workflows/e2e/kv-shop.yaml rename to tests/e2e/helm-chart/02-install-simple-demo.yaml index dc16d78eee..d12e8abd41 100644 --- a/.github/workflows/e2e/kv-shop.yaml +++ b/tests/e2e/helm-chart/02-install-simple-demo.yaml @@ -1,31 +1,37 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: membership + name: coupon + namespace: default labels: - app: membership + app: coupon spec: selector: matchLabels: - app: membership + app: coupon template: metadata: labels: - app: membership + app: coupon spec: containers: - - name: membership - image: keyval/kv-shop-membership:v0.2 + - name: coupon + image: keyval/odigos-demo-coupon:v0.1 + imagePullPolicy: IfNotPresent + env: + - name: MEMBERSHIP_SERVICE_HOST + value: "membership:8080" ports: - containerPort: 8080 --- kind: Service apiVersion: v1 metadata: - name: membership + name: coupon + namespace: default spec: selector: - app: membership + app: coupon ports: - protocol: TCP port: 8080 @@ -34,45 +40,55 @@ spec: apiVersion: apps/v1 kind: Deployment metadata: - name: coupon + name: frontend + namespace: default labels: - app: coupon - odigos-instrumentation: disabled + app: frontend spec: selector: matchLabels: - app: coupon + app: frontend template: metadata: labels: - app: coupon + app: frontend spec: containers: - - name: coupon - image: keyval/kv-shop-coupon:v0.2 + - name: frontend + image: keyval/odigos-demo-frontend:v0.2 + imagePullPolicy: IfNotPresent + securityContext: + runAsUser: 1000 env: - - name: NODE_IP - valueFrom: - fieldRef: - fieldPath: status.hostIP - - name: OTEL_TRACES_EXPORTER - value: otlp - - name: OTEL_EXPORTER_OTLP_ENDPOINT - value: "http://$(NODE_IP):4318" - - name: OTEL_SERVICE_NAME - value: coupon - - name: MEMBERSHIP_SERVICE_URL - value: "membership:8080" + - name: INVENTORY_SERVICE_HOST + value: inventory:8080 + - name: PRICING_SERVICE_HOST + value: pricing:8080 + - name: COUPON_SERVICE_HOST + value: coupon:8080 ports: - containerPort: 8080 + readinessProbe: + httpGet: + path: /actuator/health/readiness + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 5 + livenessProbe: + httpGet: + path: /actuator/health/liveness + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 5 --- kind: Service apiVersion: v1 metadata: - name: coupon + name: frontend + namespace: default spec: selector: - app: coupon + app: frontend ports: - protocol: TCP port: 8080 @@ -82,6 +98,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: inventory + namespace: default labels: app: inventory spec: @@ -95,7 +112,8 @@ spec: spec: containers: - name: inventory - image: keyval/kv-shop-inventory:v0.2 + image: keyval/odigos-demo-inventory:v0.1 + imagePullPolicy: IfNotPresent ports: - containerPort: 8080 --- @@ -103,6 +121,7 @@ kind: Service apiVersion: v1 metadata: name: inventory + namespace: default spec: selector: app: inventory @@ -114,31 +133,34 @@ spec: apiVersion: apps/v1 kind: Deployment metadata: - name: pricing + name: membership + namespace: default labels: - app: pricing + app: membership spec: selector: matchLabels: - app: pricing + app: membership template: metadata: labels: - app: pricing + app: membership spec: containers: - - name: pricing - image: keyval/kv-shop-pricing:v0.2 + - name: membership + image: keyval/odigos-demo-membership:v0.1 + imagePullPolicy: IfNotPresent ports: - containerPort: 8080 --- kind: Service apiVersion: v1 metadata: - name: pricing + name: membership + namespace: default spec: selector: - app: pricing + app: membership ports: - protocol: TCP port: 8080 @@ -147,38 +169,34 @@ spec: apiVersion: apps/v1 kind: Deployment metadata: - name: frontend + name: pricing + namespace: default labels: - app: frontend + app: pricing spec: selector: matchLabels: - app: frontend + app: pricing template: metadata: labels: - app: frontend + app: pricing spec: containers: - - name: frontend - image: keyval/kv-shop-frontend:v0.2 - env: - - name: INVENTORY_SERVICE_HOST - value: inventory:8080 - - name: PRICING_SERVICE_HOST - value: pricing:8080 - - name: COUPON_SERVICE_HOST - value: coupon:8080 + - name: pricing + image: keyval/odigos-demo-pricing:v0.1 + imagePullPolicy: IfNotPresent ports: - containerPort: 8080 --- kind: Service apiVersion: v1 metadata: - name: frontend + name: pricing + namespace: default spec: selector: - app: frontend + app: pricing ports: - protocol: TCP port: 8080 diff --git a/tests/e2e/helm-chart/03-instrument-ns.yaml b/tests/e2e/helm-chart/03-instrument-ns.yaml new file mode 100644 index 0000000000..6814c325fc --- /dev/null +++ b/tests/e2e/helm-chart/03-instrument-ns.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: default + labels: + odigos-instrumentation: enabled \ No newline at end of file diff --git a/tests/e2e/helm-chart/04-add-destination.yaml b/tests/e2e/helm-chart/04-add-destination.yaml new file mode 100644 index 0000000000..7a637f3f67 --- /dev/null +++ b/tests/e2e/helm-chart/04-add-destination.yaml @@ -0,0 +1,12 @@ +apiVersion: odigos.io/v1alpha1 +kind: Destination +metadata: + name: odigos.io.dest.tempo-123123 + namespace: odigos-test-ns +spec: + data: + TEMPO_URL: e2e-tests-tempo.traces:4317 + destinationName: e2e-tests + signals: + - TRACES + type: tempo \ No newline at end of file diff --git a/.github/workflows/e2e/buybot-job.yaml b/tests/e2e/helm-chart/05-generate-traffic.yaml similarity index 77% rename from .github/workflows/e2e/buybot-job.yaml rename to tests/e2e/helm-chart/05-generate-traffic.yaml index 6120a77094..fb94d0f538 100644 --- a/.github/workflows/e2e/buybot-job.yaml +++ b/tests/e2e/helm-chart/05-generate-traffic.yaml @@ -2,6 +2,7 @@ apiVersion: batch/v1 kind: Job metadata: name: buybot-job + namespace: default spec: template: metadata: @@ -18,5 +19,5 @@ spec: - name: curl image: curlimages/curl:8.4.0 imagePullPolicy: IfNotPresent - command: ["curl"] - args: ["-s","-X","POST","http://frontend:8080/buy?id=123"] + command: [ "curl" ] + args: [ "-s","-X","POST","http://frontend:8080/buy?id=123" ] diff --git a/tests/e2e/helm-chart/assert-apps-installed.yaml b/tests/e2e/helm-chart/assert-apps-installed.yaml new file mode 100644 index 0000000000..c78756927c --- /dev/null +++ b/tests/e2e/helm-chart/assert-apps-installed.yaml @@ -0,0 +1,69 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + app: frontend + namespace: default +status: + containerStatuses: + - name: frontend + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app: coupon + namespace: default +status: + containerStatuses: + - name: coupon + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app: inventory + namespace: default +status: + containerStatuses: + - name: inventory + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app: membership + namespace: default +status: + containerStatuses: + - name: membership + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app: pricing + namespace: default +status: + containerStatuses: + - name: pricing + ready: true + restartCount: 0 + started: true + phase: Running \ No newline at end of file diff --git a/tests/e2e/helm-chart/assert-instrumented-and-pipeline.yaml b/tests/e2e/helm-chart/assert-instrumented-and-pipeline.yaml new file mode 100644 index 0000000000..2cbe7980a5 --- /dev/null +++ b/tests/e2e/helm-chart/assert-instrumented-and-pipeline.yaml @@ -0,0 +1,321 @@ +apiVersion: odigos.io/v1alpha1 +kind: CollectorsGroup +metadata: + name: odigos-data-collection + namespace: odigos-test-ns +spec: + role: NODE_COLLECTOR +status: + ready: true +--- +apiVersion: odigos.io/v1alpha1 +kind: CollectorsGroup +metadata: + name: odigos-gateway + namespace: odigos-test-ns +spec: + role: CLUSTER_GATEWAY +status: + ready: true +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + odigos.io/collector: "true" + name: odigos-gateway + namespace: odigos-test-ns + ownerReferences: + - apiVersion: odigos.io/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CollectorsGroup + name: odigos-gateway +spec: + replicas: 1 + selector: + matchLabels: + odigos.io/collector: "true" + template: + metadata: + labels: + odigos.io/collector: "true" + spec: + containers: + - env: + - name: ODIGOS_VERSION + valueFrom: + configMapKeyRef: + key: ODIGOS_VERSION + name: odigos-deployment + - name: GOMEMLIMIT + (value != null): true + name: gateway + resources: + requests: + (memory != null): true + volumeMounts: + - mountPath: /conf + name: collector-conf + volumes: + - configMap: + defaultMode: 420 + items: + - key: collector-conf + path: collector-conf.yaml + name: odigos-gateway + name: collector-conf +status: + availableReplicas: 1 + readyReplicas: 1 + replicas: 1 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: odigos-gateway + namespace: odigos-test-ns + ownerReferences: + - apiVersion: odigos.io/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CollectorsGroup + name: odigos-gateway +(data != null): true +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: odigos-data-collection + namespace: odigos-test-ns + ownerReferences: + - apiVersion: odigos.io/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CollectorsGroup + name: odigos-data-collection +(data != null): true +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + labels: + odigos.io/data-collection: "true" + name: odigos-data-collection + namespace: odigos-test-ns + ownerReferences: + - apiVersion: odigos.io/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CollectorsGroup + name: odigos-data-collection +spec: + selector: + matchLabels: + odigos.io/data-collection: "true" + template: + metadata: + labels: + odigos.io/data-collection: "true" + spec: + containers: + - name: data-collection + securityContext: + privileged: true + volumeMounts: + - mountPath: /conf + name: conf + - mountPath: /var/lib/docker/containers + name: varlibdockercontainers + readOnly: true + - mountPath: /var/log + name: varlog + readOnly: true + - mountPath: /var/lib/kubelet/pod-resources + name: kubeletpodresources + readOnly: true + hostNetwork: true + nodeSelector: + kubernetes.io/os: linux + securityContext: {} + serviceAccount: odigos-data-collection + serviceAccountName: odigos-data-collection + volumes: + - configMap: + defaultMode: 420 + items: + - key: conf + path: conf.yaml + name: odigos-data-collection + name: conf + - hostPath: + path: /var/log + type: "" + name: varlog + - hostPath: + path: /var/lib/docker/containers + type: "" + name: varlibdockercontainers + - hostPath: + path: /var/lib/kubelet/pod-resources + type: "" + name: kubeletpodresources +status: + numberAvailable: 1 + numberReady: 1 +--- +apiVersion: v1 +kind: Pod +metadata: + namespace: default + labels: + app: frontend +spec: + containers: + - name: frontend + resources: + limits: + instrumentation.odigos.io/java-native-community: "1" + requests: + instrumentation.odigos.io/java-native-community: "1" +status: + containerStatuses: + - name: frontend + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + namespace: default + labels: + app: coupon +spec: + containers: + - name: coupon + resources: + limits: + instrumentation.odigos.io/javascript-native-community: "1" + requests: + instrumentation.odigos.io/javascript-native-community: "1" +status: + containerStatuses: + - name: coupon + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + namespace: default + labels: + app: inventory +spec: + containers: + - name: inventory + resources: + limits: + instrumentation.odigos.io/python-native-community: "1" + requests: + instrumentation.odigos.io/python-native-community: "1" +status: + containerStatuses: + - name: inventory + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + namespace: default + labels: + app: membership +spec: + containers: + - name: membership + resources: + limits: + instrumentation.odigos.io/go-ebpf-community: "1" + requests: + instrumentation.odigos.io/go-ebpf-community: "1" +status: + containerStatuses: + - name: membership + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + namespace: default + labels: + app: pricing +spec: + containers: + - name: pricing + resources: + limits: + instrumentation.odigos.io/dotnet-native-community: "1" + requests: + instrumentation.odigos.io/dotnet-native-community: "1" +status: + containerStatuses: + - name: pricing + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: odigos.io/v1alpha1 +kind: InstrumentationInstance +metadata: + namespace: default + labels: + instrumented-app: deployment-coupon +status: + healthy: true + identifyingAttributes: + - key: service.instance.id + (value != null): true + - key: telemetry.sdk.language + value: nodejs + - key: process.runtime.version + (value != null): true + - key: telemetry.distro.version + value: e2e-test + - key: process.pid + (value != null): true +--- +apiVersion: odigos.io/v1alpha1 +kind: InstrumentationInstance +metadata: + namespace: default + labels: + instrumented-app: deployment-inventory +status: + healthy: null + identifyingAttributes: + - key: service.instance.id + (value != null): true + - key: process.pid + (value != null): true + - key: telemetry.sdk.language + value: python +--- +apiVersion: odigos.io/v1alpha1 +kind: InstrumentationInstance +metadata: + namespace: default + labels: + instrumented-app: deployment-membership +status: + healthy: true + reason: LoadedSuccessfully \ No newline at end of file diff --git a/tests/e2e/helm-chart/assert-odigos-installed.yaml b/tests/e2e/helm-chart/assert-odigos-installed.yaml new file mode 100644 index 0000000000..09c944c442 --- /dev/null +++ b/tests/e2e/helm-chart/assert-odigos-installed.yaml @@ -0,0 +1,114 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: odigos-test-ns + labels: + odigos.io/system-object: "true" +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app.kubernetes.io/name: odigos-autoscaler + namespace: odigos-test-ns +status: + containerStatuses: + - name: manager + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app.kubernetes.io/name: odigos-scheduler + namespace: odigos-test-ns +status: + containerStatuses: + - name: manager + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app.kubernetes.io/name: odigos-instrumentor + namespace: odigos-test-ns +status: + containerStatuses: + - name: manager + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app.kubernetes.io/name: odiglet + namespace: odigos-test-ns + ownerReferences: + - apiVersion: apps/v1 + blockOwnerDeletion: true + controller: true + kind: DaemonSet + name: odiglet +spec: + containers: + - name: odiglet + resources: {} + securityContext: + capabilities: + add: + - SYS_PTRACE + privileged: true + hostNetwork: true + hostPID: true + nodeSelector: + kubernetes.io/os: linux + serviceAccount: odiglet + serviceAccountName: odiglet +status: + containerStatuses: + - name: odiglet + ready: true + restartCount: 0 + started: true +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + odigos.io/config: "1" + odigos.io/system-object: "true" + name: destinations.odigos.io +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + odigos.io/config: "1" + odigos.io/system-object: "true" + name: instrumentedapplications.odigos.io +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + odigos.io/config: "1" + odigos.io/system-object: "true" + name: odigosconfigurations.odigos.io +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + odigos.io/config: "1" + odigos.io/system-object: "true" + name: processors.odigos.io \ No newline at end of file diff --git a/tests/e2e/helm-chart/assert-runtime-detected.yaml b/tests/e2e/helm-chart/assert-runtime-detected.yaml new file mode 100644 index 0000000000..f0894f78a4 --- /dev/null +++ b/tests/e2e/helm-chart/assert-runtime-detected.yaml @@ -0,0 +1,79 @@ +apiVersion: odigos.io/v1alpha1 +kind: InstrumentedApplication +metadata: + name: deployment-coupon + namespace: default + ownerReferences: + - apiVersion: apps/v1 + blockOwnerDeletion: true + controller: true + kind: Deployment + name: coupon +spec: + runtimeDetails: + - containerName: coupon + language: javascript +--- +apiVersion: odigos.io/v1alpha1 +kind: InstrumentedApplication +metadata: + name: deployment-frontend + namespace: default + ownerReferences: + - apiVersion: apps/v1 + blockOwnerDeletion: true + controller: true + kind: Deployment + name: frontend +spec: + runtimeDetails: + - containerName: frontend + language: java +--- +apiVersion: odigos.io/v1alpha1 +kind: InstrumentedApplication +metadata: + name: deployment-inventory + namespace: default + ownerReferences: + - apiVersion: apps/v1 + blockOwnerDeletion: true + controller: true + kind: Deployment + name: inventory +spec: + runtimeDetails: + - containerName: inventory + language: python +--- +apiVersion: odigos.io/v1alpha1 +kind: InstrumentedApplication +metadata: + name: deployment-membership + namespace: default + ownerReferences: + - apiVersion: apps/v1 + blockOwnerDeletion: true + controller: true + kind: Deployment + name: membership +spec: + runtimeDetails: + - containerName: membership + language: go +--- +apiVersion: odigos.io/v1alpha1 +kind: InstrumentedApplication +metadata: + name: deployment-pricing + namespace: default + ownerReferences: + - apiVersion: apps/v1 + blockOwnerDeletion: true + controller: true + kind: Deployment + name: pricing +spec: + runtimeDetails: + - containerName: pricing + language: dotnet diff --git a/tests/e2e/helm-chart/assert-tempo-running.yaml b/tests/e2e/helm-chart/assert-tempo-running.yaml new file mode 100644 index 0000000000..f4653f4a37 --- /dev/null +++ b/tests/e2e/helm-chart/assert-tempo-running.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Pod +metadata: + name: e2e-tests-tempo-0 + namespace: traces +status: + phase: Running + containerStatuses: + - name: tempo + ready: true + restartCount: 0 \ No newline at end of file diff --git a/tests/e2e/helm-chart/assert-traffic-job-running.yaml b/tests/e2e/helm-chart/assert-traffic-job-running.yaml new file mode 100644 index 0000000000..0557b27420 --- /dev/null +++ b/tests/e2e/helm-chart/assert-traffic-job-running.yaml @@ -0,0 +1,10 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: buybot-job + namespace: default +status: + conditions: + - status: "True" + type: Complete + succeeded: 1 diff --git a/tests/e2e/helm-chart/chainsaw-test.yaml b/tests/e2e/helm-chart/chainsaw-test.yaml new file mode 100644 index 0000000000..dc66193c5f --- /dev/null +++ b/tests/e2e/helm-chart/chainsaw-test.yaml @@ -0,0 +1,152 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: helm-chart +spec: + description: This e2e test install Odigos via helm chart on custom namespace + skipDelete: true + steps: + - name: Prepare destination + try: + - script: + timeout: 60s + content: | + helm repo add grafana https://grafana.github.io/helm-charts + helm repo update + helm install e2e-tests grafana/tempo -n traces --create-namespace --set tempo.storage.trace.block.version=vParquet4 --version 1.10.1 + - assert: + file: assert-tempo-running.yaml + - name: Wait for destination to be ready + try: + - script: + timeout: 60s + content: ../../common/wait_for_dest.sh + - name: Install Odigos + try: + - script: + content: | + git clone https://github.com/odigos-io/odigos-charts.git /tmp/odigos-charts + # Retry to avoid flakiness (due to CRD race conditions in helm). Timeout after 60s. + while ! helm upgrade --install odigos /tmp/odigos-charts/charts/odigos --create-namespace --namespace odigos-test-ns --set image.tag=e2e-test; do + echo "Failed to install Odigos, retrying..." + sleep 1 + done + kubectl label namespace odigos-test-ns odigos.io/system-object="true" + rm -rf /tmp/odigos-charts + timeout: 60s + - name: Verify Odigos Installation + try: + - script: + content: | + export ACTUAL_VERSION=$(../../../cli/odigos version --cluster) + if [ "$ACTUAL_VERSION" != "e2e-test" ]; then + echo "Odigos version is not e2e-test, got $ACTUAL_VERSION" + exit 1 + fi + - assert: + file: assert-odigos-installed.yaml + - name: Install Demo App + try: + - script: + timeout: 100s + content: | + docker pull keyval/odigos-demo-inventory:v0.1 + docker pull keyval/odigos-demo-membership:v0.1 + docker pull keyval/odigos-demo-coupon:v0.1 + docker pull keyval/odigos-demo-inventory:v0.1 + docker pull keyval/odigos-demo-frontend:v0.2 + kind load docker-image keyval/odigos-demo-inventory:v0.1 + kind load docker-image keyval/odigos-demo-membership:v0.1 + kind load docker-image keyval/odigos-demo-coupon:v0.1 + kind load docker-image keyval/odigos-demo-inventory:v0.1 + kind load docker-image keyval/odigos-demo-frontend:v0.2 + - apply: + file: 02-install-simple-demo.yaml + - assert: + file: assert-apps-installed.yaml + - name: Detect Languages + try: + - apply: + file: 03-instrument-ns.yaml + - assert: + file: assert-runtime-detected.yaml + - name: Add Destination + try: + - apply: + file: 04-add-destination.yaml + - assert: + file: assert-instrumented-and-pipeline.yaml + - name: Generate Traffic + try: + - script: + timeout: 60s + content: | + while true; do + # Apply the job + kubectl apply -f 05-generate-traffic.yaml + + # Wait for the job to complete + job_name=$(kubectl get -f 05-generate-traffic.yaml -o=jsonpath='{.metadata.name}') + kubectl wait --for=condition=complete job/$job_name + + # Delete the job + kubectl delete -f 05-generate-traffic.yaml + + # Run the wait-for-trace script + ../../common/traceql_runner.sh tracesql/wait-for-trace.yaml + if [ $? -eq 0 ]; then + break + else + sleep 3 + ../../common/flush_traces.sh + fi + done + - name: Verify Trace - Context Propagation + try: + - script: + content: | + ../../common/traceql_runner.sh tracesql/context-propagation.yaml + catch: + - podLogs: + name: odiglet + namespace: odigos-system + - name: Verify Trace - Resource Attributes + try: + - script: + content: | + ../../common/traceql_runner.sh tracesql/resource-attributes.yaml + catch: + - podLogs: + name: odiglet + namespace: odigos-system + - name: Verify Trace - Span Attributes + try: + - script: + content: | + ../../common/traceql_runner.sh tracesql/span-attributes.yaml + catch: + - podLogs: + name: odiglet + namespace: odigos-system + + - name: Start Odigos UI in background + try: + - script: + timeout: 60s + content: | + ../../common/ui-tests/start_odigos_ui.sh + + - name: Run Cypress tests should pass + try: + - script: + timeout: 60s + content: | + ../../common/ui-tests/run_cypress_tests.sh + + - name: Stop Odigos UI + try: + - script: + timeout: 60s + content: | + ../../common/ui-tests/stop_ui_and_clean.sh + diff --git a/tests/e2e/helm-chart/tracesql/context-propagation.yaml b/tests/e2e/helm-chart/tracesql/context-propagation.yaml new file mode 100644 index 0000000000..9c463f9b3f --- /dev/null +++ b/tests/e2e/helm-chart/tracesql/context-propagation.yaml @@ -0,0 +1,13 @@ +apiVersion: e2e.tests.odigos.io/v1 +kind: TraceTest +description: This test checks if the context propagation is working correctly between different languages +query: | + { resource.service.name = "frontend" && resource.telemetry.sdk.language = "java" && + span.http.request.method = "POST" && span.http.route = "/buy" && span:kind = server } + >> ( + { resource.service.name = "pricing" && resource.telemetry.sdk.language = "dotnet" } && + { resource.service.name = "inventory" && resource.telemetry.sdk.language = "python" } && + ({ resource.service.name = "coupon" && resource.telemetry.sdk.language = "nodejs" } + >> { resource.service.name = "membership" && resource.telemetry.sdk.language = "go" })) +expected: + count: 1 \ No newline at end of file diff --git a/tests/e2e/helm-chart/tracesql/resource-attributes.yaml b/tests/e2e/helm-chart/tracesql/resource-attributes.yaml new file mode 100644 index 0000000000..934439b7ed --- /dev/null +++ b/tests/e2e/helm-chart/tracesql/resource-attributes.yaml @@ -0,0 +1,14 @@ +apiVersion: e2e.tests.odigos.io/v1 +kind: TraceTest +description: | + This test check the following resource attributes: + A. odigos.version attribute exists on all spans and has the correct value + B. Kubernetes attributes are correctly set on all spans + At the time of writing this test, TraceQL api does not support not equal to nil so we use regex instead. +query: | + { resource.odigos.version != "e2e-test" || + resource.k8s.deployment.name !~ ".*" || + resource.k8s.node.name !~ "kind-control-plane" || + resource.k8s.pod.name !~ ".*" } +expected: + count: 0 \ No newline at end of file diff --git a/tests/e2e/helm-chart/tracesql/span-attributes.yaml b/tests/e2e/helm-chart/tracesql/span-attributes.yaml new file mode 100644 index 0000000000..d508d4a39f --- /dev/null +++ b/tests/e2e/helm-chart/tracesql/span-attributes.yaml @@ -0,0 +1,18 @@ +apiVersion: e2e.tests.odigos.io/v1 +kind: TraceTest +description: | + This test checks the span attributes for a specific trace. + TODO - JS, Python and DotNet SDK are not generating data in latest semconv. add additional checks when they are updated. +query: | + { resource.service.name = "frontend" && resource.telemetry.sdk.language = "java" && + span.http.request.method = "POST" && span.http.route = "/buy" && span:kind = server && + span.http.response.status_code = 200 && span.url.query = "id=123" } + >> ( + { resource.service.name = "pricing" && resource.telemetry.sdk.language = "dotnet" && span:kind = server } && + { resource.service.name = "inventory" && resource.telemetry.sdk.language = "python" && span:kind = server } && + ({ resource.service.name = "coupon" && resource.telemetry.sdk.language = "nodejs" && span:kind = server } + >> { resource.service.name = "membership" && resource.telemetry.sdk.language = "go" && + span.http.request.method = "GET" && span:kind = server && + span.http.response.status_code = 200 && span.url.path = "/isMember" })) +expected: + count: 1 \ No newline at end of file diff --git a/tests/e2e/helm-chart/tracesql/wait-for-trace.yaml b/tests/e2e/helm-chart/tracesql/wait-for-trace.yaml new file mode 100644 index 0000000000..a88f58987c --- /dev/null +++ b/tests/e2e/helm-chart/tracesql/wait-for-trace.yaml @@ -0,0 +1,11 @@ +apiVersion: e2e.tests.odigos.io/v1 +kind: TraceTest +description: This test waits for a trace that goes from frontend to pricing, inventory, coupon, and membership services +query: | + { resource.service.name = "frontend" } && + { resource.service.name = "pricing" } && + { resource.service.name = "inventory" } && + { resource.service.name = "coupon" } && + { resource.service.name = "membership" } +expected: + count: 1 \ No newline at end of file diff --git a/tests/e2e/multi-apps/02-install-simple-demo.yaml b/tests/e2e/multi-apps/02-install-simple-demo.yaml new file mode 100644 index 0000000000..d12e8abd41 --- /dev/null +++ b/tests/e2e/multi-apps/02-install-simple-demo.yaml @@ -0,0 +1,203 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: coupon + namespace: default + labels: + app: coupon +spec: + selector: + matchLabels: + app: coupon + template: + metadata: + labels: + app: coupon + spec: + containers: + - name: coupon + image: keyval/odigos-demo-coupon:v0.1 + imagePullPolicy: IfNotPresent + env: + - name: MEMBERSHIP_SERVICE_HOST + value: "membership:8080" + ports: + - containerPort: 8080 +--- +kind: Service +apiVersion: v1 +metadata: + name: coupon + namespace: default +spec: + selector: + app: coupon + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend + namespace: default + labels: + app: frontend +spec: + selector: + matchLabels: + app: frontend + template: + metadata: + labels: + app: frontend + spec: + containers: + - name: frontend + image: keyval/odigos-demo-frontend:v0.2 + imagePullPolicy: IfNotPresent + securityContext: + runAsUser: 1000 + env: + - name: INVENTORY_SERVICE_HOST + value: inventory:8080 + - name: PRICING_SERVICE_HOST + value: pricing:8080 + - name: COUPON_SERVICE_HOST + value: coupon:8080 + ports: + - containerPort: 8080 + readinessProbe: + httpGet: + path: /actuator/health/readiness + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 5 + livenessProbe: + httpGet: + path: /actuator/health/liveness + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 5 +--- +kind: Service +apiVersion: v1 +metadata: + name: frontend + namespace: default +spec: + selector: + app: frontend + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: inventory + namespace: default + labels: + app: inventory +spec: + selector: + matchLabels: + app: inventory + template: + metadata: + labels: + app: inventory + spec: + containers: + - name: inventory + image: keyval/odigos-demo-inventory:v0.1 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8080 +--- +kind: Service +apiVersion: v1 +metadata: + name: inventory + namespace: default +spec: + selector: + app: inventory + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: membership + namespace: default + labels: + app: membership +spec: + selector: + matchLabels: + app: membership + template: + metadata: + labels: + app: membership + spec: + containers: + - name: membership + image: keyval/odigos-demo-membership:v0.1 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8080 +--- +kind: Service +apiVersion: v1 +metadata: + name: membership + namespace: default +spec: + selector: + app: membership + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: pricing + namespace: default + labels: + app: pricing +spec: + selector: + matchLabels: + app: pricing + template: + metadata: + labels: + app: pricing + spec: + containers: + - name: pricing + image: keyval/odigos-demo-pricing:v0.1 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8080 +--- +kind: Service +apiVersion: v1 +metadata: + name: pricing + namespace: default +spec: + selector: + app: pricing + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 \ No newline at end of file diff --git a/tests/e2e/multi-apps/03-instrument-ns.yaml b/tests/e2e/multi-apps/03-instrument-ns.yaml new file mode 100644 index 0000000000..6814c325fc --- /dev/null +++ b/tests/e2e/multi-apps/03-instrument-ns.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: default + labels: + odigos-instrumentation: enabled \ No newline at end of file diff --git a/tests/e2e/multi-apps/04-add-destination.yaml b/tests/e2e/multi-apps/04-add-destination.yaml new file mode 100644 index 0000000000..a847f9e449 --- /dev/null +++ b/tests/e2e/multi-apps/04-add-destination.yaml @@ -0,0 +1,12 @@ +apiVersion: odigos.io/v1alpha1 +kind: Destination +metadata: + name: odigos.io.dest.tempo-123123 + namespace: odigos-system +spec: + data: + TEMPO_URL: e2e-tests-tempo.traces:4317 + destinationName: e2e-tests + signals: + - TRACES + type: tempo \ No newline at end of file diff --git a/tests/e2e/multi-apps/05-generate-traffic.yaml b/tests/e2e/multi-apps/05-generate-traffic.yaml new file mode 100644 index 0000000000..fb94d0f538 --- /dev/null +++ b/tests/e2e/multi-apps/05-generate-traffic.yaml @@ -0,0 +1,23 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: buybot-job + namespace: default +spec: + template: + metadata: + annotations: + workload: job + labels: + app: buybot + spec: + restartPolicy: Never + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 + containers: + - name: curl + image: curlimages/curl:8.4.0 + imagePullPolicy: IfNotPresent + command: [ "curl" ] + args: [ "-s","-X","POST","http://frontend:8080/buy?id=123" ] diff --git a/tests/e2e/multi-apps/assert-apps-installed.yaml b/tests/e2e/multi-apps/assert-apps-installed.yaml new file mode 100644 index 0000000000..c78756927c --- /dev/null +++ b/tests/e2e/multi-apps/assert-apps-installed.yaml @@ -0,0 +1,69 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + app: frontend + namespace: default +status: + containerStatuses: + - name: frontend + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app: coupon + namespace: default +status: + containerStatuses: + - name: coupon + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app: inventory + namespace: default +status: + containerStatuses: + - name: inventory + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app: membership + namespace: default +status: + containerStatuses: + - name: membership + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app: pricing + namespace: default +status: + containerStatuses: + - name: pricing + ready: true + restartCount: 0 + started: true + phase: Running \ No newline at end of file diff --git a/tests/e2e/multi-apps/assert-instrumented-and-pipeline.yaml b/tests/e2e/multi-apps/assert-instrumented-and-pipeline.yaml new file mode 100644 index 0000000000..e87beb7c42 --- /dev/null +++ b/tests/e2e/multi-apps/assert-instrumented-and-pipeline.yaml @@ -0,0 +1,321 @@ +apiVersion: odigos.io/v1alpha1 +kind: CollectorsGroup +metadata: + name: odigos-data-collection + namespace: odigos-system +spec: + role: NODE_COLLECTOR +status: + ready: true +--- +apiVersion: odigos.io/v1alpha1 +kind: CollectorsGroup +metadata: + name: odigos-gateway + namespace: odigos-system +spec: + role: CLUSTER_GATEWAY +status: + ready: true +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + odigos.io/collector: "true" + name: odigos-gateway + namespace: odigos-system + ownerReferences: + - apiVersion: odigos.io/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CollectorsGroup + name: odigos-gateway +spec: + replicas: 1 + selector: + matchLabels: + odigos.io/collector: "true" + template: + metadata: + labels: + odigos.io/collector: "true" + spec: + containers: + - env: + - name: ODIGOS_VERSION + valueFrom: + configMapKeyRef: + key: ODIGOS_VERSION + name: odigos-deployment + - name: GOMEMLIMIT + (value != null): true + name: gateway + resources: + requests: + (memory != null): true + volumeMounts: + - mountPath: /conf + name: collector-conf + volumes: + - configMap: + defaultMode: 420 + items: + - key: collector-conf + path: collector-conf.yaml + name: odigos-gateway + name: collector-conf +status: + availableReplicas: 1 + readyReplicas: 1 + replicas: 1 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: odigos-gateway + namespace: odigos-system + ownerReferences: + - apiVersion: odigos.io/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CollectorsGroup + name: odigos-gateway +(data != null): true +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: odigos-data-collection + namespace: odigos-system + ownerReferences: + - apiVersion: odigos.io/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CollectorsGroup + name: odigos-data-collection +(data != null): true +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + labels: + odigos.io/data-collection: "true" + name: odigos-data-collection + namespace: odigos-system + ownerReferences: + - apiVersion: odigos.io/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: CollectorsGroup + name: odigos-data-collection +spec: + selector: + matchLabels: + odigos.io/data-collection: "true" + template: + metadata: + labels: + odigos.io/data-collection: "true" + spec: + containers: + - name: data-collection + securityContext: + privileged: true + volumeMounts: + - mountPath: /conf + name: conf + - mountPath: /var/lib/docker/containers + name: varlibdockercontainers + readOnly: true + - mountPath: /var/log + name: varlog + readOnly: true + - mountPath: /var/lib/kubelet/pod-resources + name: kubeletpodresources + readOnly: true + hostNetwork: true + nodeSelector: + kubernetes.io/os: linux + securityContext: {} + serviceAccount: odigos-data-collection + serviceAccountName: odigos-data-collection + volumes: + - configMap: + defaultMode: 420 + items: + - key: conf + path: conf.yaml + name: odigos-data-collection + name: conf + - hostPath: + path: /var/log + type: "" + name: varlog + - hostPath: + path: /var/lib/docker/containers + type: "" + name: varlibdockercontainers + - hostPath: + path: /var/lib/kubelet/pod-resources + type: "" + name: kubeletpodresources +status: + numberAvailable: 1 + numberReady: 1 +--- +apiVersion: v1 +kind: Pod +metadata: + namespace: default + labels: + app: frontend +spec: + containers: + - name: frontend + resources: + limits: + instrumentation.odigos.io/java-native-community: "1" + requests: + instrumentation.odigos.io/java-native-community: "1" +status: + containerStatuses: + - name: frontend + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + namespace: default + labels: + app: coupon +spec: + containers: + - name: coupon + resources: + limits: + instrumentation.odigos.io/javascript-native-community: "1" + requests: + instrumentation.odigos.io/javascript-native-community: "1" +status: + containerStatuses: + - name: coupon + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + namespace: default + labels: + app: inventory +spec: + containers: + - name: inventory + resources: + limits: + instrumentation.odigos.io/python-native-community: "1" + requests: + instrumentation.odigos.io/python-native-community: "1" +status: + containerStatuses: + - name: inventory + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + namespace: default + labels: + app: membership +spec: + containers: + - name: membership + resources: + limits: + instrumentation.odigos.io/go-ebpf-community: "1" + requests: + instrumentation.odigos.io/go-ebpf-community: "1" +status: + containerStatuses: + - name: membership + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + namespace: default + labels: + app: pricing +spec: + containers: + - name: pricing + resources: + limits: + instrumentation.odigos.io/dotnet-native-community: "1" + requests: + instrumentation.odigos.io/dotnet-native-community: "1" +status: + containerStatuses: + - name: pricing + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: odigos.io/v1alpha1 +kind: InstrumentationInstance +metadata: + namespace: default + labels: + instrumented-app: deployment-coupon +status: + healthy: true + identifyingAttributes: + - key: service.instance.id + (value != null): true + - key: telemetry.sdk.language + value: nodejs + - key: process.runtime.version + (value != null): true + - key: telemetry.distro.version + value: e2e-test + - key: process.pid + (value != null): true +--- +apiVersion: odigos.io/v1alpha1 +kind: InstrumentationInstance +metadata: + namespace: default + labels: + instrumented-app: deployment-inventory +status: + healthy: null + identifyingAttributes: + - key: service.instance.id + (value != null): true + - key: process.pid + (value != null): true + - key: telemetry.sdk.language + value: python +--- +apiVersion: odigos.io/v1alpha1 +kind: InstrumentationInstance +metadata: + namespace: default + labels: + instrumented-app: deployment-membership +status: + healthy: true + reason: LoadedSuccessfully \ No newline at end of file diff --git a/tests/e2e/multi-apps/assert-odigos-installed.yaml b/tests/e2e/multi-apps/assert-odigos-installed.yaml new file mode 100644 index 0000000000..5a4671e2bf --- /dev/null +++ b/tests/e2e/multi-apps/assert-odigos-installed.yaml @@ -0,0 +1,114 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: odigos-system + labels: + odigos.io/system-object: "true" +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app.kubernetes.io/name: odigos-autoscaler + namespace: odigos-system +status: + containerStatuses: + - name: manager + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app.kubernetes.io/name: odigos-scheduler + namespace: odigos-system +status: + containerStatuses: + - name: manager + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app.kubernetes.io/name: odigos-instrumentor + namespace: odigos-system +status: + containerStatuses: + - name: manager + ready: true + restartCount: 0 + started: true + phase: Running +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + app.kubernetes.io/name: odiglet + namespace: odigos-system + ownerReferences: + - apiVersion: apps/v1 + blockOwnerDeletion: true + controller: true + kind: DaemonSet + name: odiglet +spec: + containers: + - name: odiglet + resources: {} + securityContext: + capabilities: + add: + - SYS_PTRACE + privileged: true + hostNetwork: true + hostPID: true + nodeSelector: + kubernetes.io/os: linux + serviceAccount: odiglet + serviceAccountName: odiglet +status: + containerStatuses: + - name: odiglet + ready: true + restartCount: 0 + started: true +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + odigos.io/config: "1" + odigos.io/system-object: "true" + name: destinations.odigos.io +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + odigos.io/config: "1" + odigos.io/system-object: "true" + name: instrumentedapplications.odigos.io +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + odigos.io/config: "1" + odigos.io/system-object: "true" + name: odigosconfigurations.odigos.io +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + odigos.io/config: "1" + odigos.io/system-object: "true" + name: processors.odigos.io \ No newline at end of file diff --git a/tests/e2e/multi-apps/assert-runtime-detected.yaml b/tests/e2e/multi-apps/assert-runtime-detected.yaml new file mode 100644 index 0000000000..f0894f78a4 --- /dev/null +++ b/tests/e2e/multi-apps/assert-runtime-detected.yaml @@ -0,0 +1,79 @@ +apiVersion: odigos.io/v1alpha1 +kind: InstrumentedApplication +metadata: + name: deployment-coupon + namespace: default + ownerReferences: + - apiVersion: apps/v1 + blockOwnerDeletion: true + controller: true + kind: Deployment + name: coupon +spec: + runtimeDetails: + - containerName: coupon + language: javascript +--- +apiVersion: odigos.io/v1alpha1 +kind: InstrumentedApplication +metadata: + name: deployment-frontend + namespace: default + ownerReferences: + - apiVersion: apps/v1 + blockOwnerDeletion: true + controller: true + kind: Deployment + name: frontend +spec: + runtimeDetails: + - containerName: frontend + language: java +--- +apiVersion: odigos.io/v1alpha1 +kind: InstrumentedApplication +metadata: + name: deployment-inventory + namespace: default + ownerReferences: + - apiVersion: apps/v1 + blockOwnerDeletion: true + controller: true + kind: Deployment + name: inventory +spec: + runtimeDetails: + - containerName: inventory + language: python +--- +apiVersion: odigos.io/v1alpha1 +kind: InstrumentedApplication +metadata: + name: deployment-membership + namespace: default + ownerReferences: + - apiVersion: apps/v1 + blockOwnerDeletion: true + controller: true + kind: Deployment + name: membership +spec: + runtimeDetails: + - containerName: membership + language: go +--- +apiVersion: odigos.io/v1alpha1 +kind: InstrumentedApplication +metadata: + name: deployment-pricing + namespace: default + ownerReferences: + - apiVersion: apps/v1 + blockOwnerDeletion: true + controller: true + kind: Deployment + name: pricing +spec: + runtimeDetails: + - containerName: pricing + language: dotnet diff --git a/tests/e2e/multi-apps/assert-tempo-running.yaml b/tests/e2e/multi-apps/assert-tempo-running.yaml new file mode 100644 index 0000000000..f4653f4a37 --- /dev/null +++ b/tests/e2e/multi-apps/assert-tempo-running.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Pod +metadata: + name: e2e-tests-tempo-0 + namespace: traces +status: + phase: Running + containerStatuses: + - name: tempo + ready: true + restartCount: 0 \ No newline at end of file diff --git a/tests/e2e/multi-apps/assert-traffic-job-running.yaml b/tests/e2e/multi-apps/assert-traffic-job-running.yaml new file mode 100644 index 0000000000..0557b27420 --- /dev/null +++ b/tests/e2e/multi-apps/assert-traffic-job-running.yaml @@ -0,0 +1,10 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: buybot-job + namespace: default +status: + conditions: + - status: "True" + type: Complete + succeeded: 1 diff --git a/tests/e2e/multi-apps/chainsaw-test.yaml b/tests/e2e/multi-apps/chainsaw-test.yaml new file mode 100644 index 0000000000..bcd14cdf5c --- /dev/null +++ b/tests/e2e/multi-apps/chainsaw-test.yaml @@ -0,0 +1,135 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: multi-apps +spec: + description: This e2e test runs a multi-apps scenario + skipDelete: true + steps: + - name: Prepare destination + try: + - script: + timeout: 60s + content: | + helm repo add grafana https://grafana.github.io/helm-charts + helm repo update + helm install e2e-tests grafana/tempo -n traces --create-namespace --set tempo.storage.trace.block.version=vParquet4 --version 1.10.1 + - assert: + file: assert-tempo-running.yaml + - name: Wait for destination to be ready + try: + - script: + timeout: 60s + content: ../../common/wait_for_dest.sh + - name: Install Odigos + try: + - script: + content: ../../../cli/odigos install --version e2e-test + timeout: 60s + - assert: + file: assert-odigos-installed.yaml + - name: Install Demo App + try: + - script: + timeout: 100s + content: | + docker pull keyval/odigos-demo-inventory:v0.1 + docker pull keyval/odigos-demo-membership:v0.1 + docker pull keyval/odigos-demo-coupon:v0.1 + docker pull keyval/odigos-demo-inventory:v0.1 + docker pull keyval/odigos-demo-frontend:v0.2 + kind load docker-image keyval/odigos-demo-inventory:v0.1 + kind load docker-image keyval/odigos-demo-membership:v0.1 + kind load docker-image keyval/odigos-demo-coupon:v0.1 + kind load docker-image keyval/odigos-demo-inventory:v0.1 + kind load docker-image keyval/odigos-demo-frontend:v0.2 + - apply: + file: 02-install-simple-demo.yaml + - assert: + file: assert-apps-installed.yaml + - name: Detect Languages + try: + - apply: + file: 03-instrument-ns.yaml + - assert: + file: assert-runtime-detected.yaml + - name: Add Destination + try: + - apply: + file: 04-add-destination.yaml + - assert: + file: assert-instrumented-and-pipeline.yaml + - name: Generate Traffic + try: + - script: + timeout: 60s + content: | + while true; do + # Apply the job + kubectl apply -f 05-generate-traffic.yaml + + # Wait for the job to complete + job_name=$(kubectl get -f 05-generate-traffic.yaml -o=jsonpath='{.metadata.name}') + kubectl wait --for=condition=complete job/$job_name + + # Delete the job + kubectl delete -f 05-generate-traffic.yaml + + # Run the wait-for-trace script + ../../common/traceql_runner.sh tracesql/wait-for-trace.yaml + if [ $? -eq 0 ]; then + break + else + sleep 3 + ../../common/flush_traces.sh + fi + done + - name: Verify Trace - Context Propagation + try: + - script: + content: | + ../../common/traceql_runner.sh tracesql/context-propagation.yaml + catch: + - podLogs: + name: odiglet + namespace: odigos-system + - name: Verify Trace - Resource Attributes + try: + - script: + content: | + ../../common/traceql_runner.sh tracesql/resource-attributes.yaml + catch: + - podLogs: + name: odiglet + namespace: odigos-system + - name: Verify Trace - Span Attributes + try: + - script: + timeout: 60s + content: | + ../../common/traceql_runner.sh tracesql/span-attributes.yaml + catch: + - podLogs: + name: odiglet + namespace: odigos-system + + - name: Start Odigos UI in background + try: + - script: + timeout: 60s + content: | + ../../common/ui-tests/start_odigos_ui.sh + + - name: Run Cypress tests should pass + try: + - script: + timeout: 60s + content: | + ../../common/ui-tests/run_cypress_tests.sh + + - name: Stop Odigos UI + try: + - script: + timeout: 60s + content: | + ../../common/ui-tests/stop_ui_and_clean.sh diff --git a/tests/e2e/multi-apps/tracesql/context-propagation.yaml b/tests/e2e/multi-apps/tracesql/context-propagation.yaml new file mode 100644 index 0000000000..9c463f9b3f --- /dev/null +++ b/tests/e2e/multi-apps/tracesql/context-propagation.yaml @@ -0,0 +1,13 @@ +apiVersion: e2e.tests.odigos.io/v1 +kind: TraceTest +description: This test checks if the context propagation is working correctly between different languages +query: | + { resource.service.name = "frontend" && resource.telemetry.sdk.language = "java" && + span.http.request.method = "POST" && span.http.route = "/buy" && span:kind = server } + >> ( + { resource.service.name = "pricing" && resource.telemetry.sdk.language = "dotnet" } && + { resource.service.name = "inventory" && resource.telemetry.sdk.language = "python" } && + ({ resource.service.name = "coupon" && resource.telemetry.sdk.language = "nodejs" } + >> { resource.service.name = "membership" && resource.telemetry.sdk.language = "go" })) +expected: + count: 1 \ No newline at end of file diff --git a/tests/e2e/multi-apps/tracesql/resource-attributes.yaml b/tests/e2e/multi-apps/tracesql/resource-attributes.yaml new file mode 100644 index 0000000000..934439b7ed --- /dev/null +++ b/tests/e2e/multi-apps/tracesql/resource-attributes.yaml @@ -0,0 +1,14 @@ +apiVersion: e2e.tests.odigos.io/v1 +kind: TraceTest +description: | + This test check the following resource attributes: + A. odigos.version attribute exists on all spans and has the correct value + B. Kubernetes attributes are correctly set on all spans + At the time of writing this test, TraceQL api does not support not equal to nil so we use regex instead. +query: | + { resource.odigos.version != "e2e-test" || + resource.k8s.deployment.name !~ ".*" || + resource.k8s.node.name !~ "kind-control-plane" || + resource.k8s.pod.name !~ ".*" } +expected: + count: 0 \ No newline at end of file diff --git a/tests/e2e/multi-apps/tracesql/span-attributes.yaml b/tests/e2e/multi-apps/tracesql/span-attributes.yaml new file mode 100644 index 0000000000..d508d4a39f --- /dev/null +++ b/tests/e2e/multi-apps/tracesql/span-attributes.yaml @@ -0,0 +1,18 @@ +apiVersion: e2e.tests.odigos.io/v1 +kind: TraceTest +description: | + This test checks the span attributes for a specific trace. + TODO - JS, Python and DotNet SDK are not generating data in latest semconv. add additional checks when they are updated. +query: | + { resource.service.name = "frontend" && resource.telemetry.sdk.language = "java" && + span.http.request.method = "POST" && span.http.route = "/buy" && span:kind = server && + span.http.response.status_code = 200 && span.url.query = "id=123" } + >> ( + { resource.service.name = "pricing" && resource.telemetry.sdk.language = "dotnet" && span:kind = server } && + { resource.service.name = "inventory" && resource.telemetry.sdk.language = "python" && span:kind = server } && + ({ resource.service.name = "coupon" && resource.telemetry.sdk.language = "nodejs" && span:kind = server } + >> { resource.service.name = "membership" && resource.telemetry.sdk.language = "go" && + span.http.request.method = "GET" && span:kind = server && + span.http.response.status_code = 200 && span.url.path = "/isMember" })) +expected: + count: 1 \ No newline at end of file diff --git a/tests/e2e/multi-apps/tracesql/wait-for-trace.yaml b/tests/e2e/multi-apps/tracesql/wait-for-trace.yaml new file mode 100644 index 0000000000..a88f58987c --- /dev/null +++ b/tests/e2e/multi-apps/tracesql/wait-for-trace.yaml @@ -0,0 +1,11 @@ +apiVersion: e2e.tests.odigos.io/v1 +kind: TraceTest +description: This test waits for a trace that goes from frontend to pricing, inventory, coupon, and membership services +query: | + { resource.service.name = "frontend" } && + { resource.service.name = "pricing" } && + { resource.service.name = "inventory" } && + { resource.service.name = "coupon" } && + { resource.service.name = "membership" } +expected: + count: 1 \ No newline at end of file