From d48d8b73f899a2811f8afcd9cfd4545fa3474b30 Mon Sep 17 00:00:00 2001 From: Ash Davies <3853061+DrizzlyOwl@users.noreply.github.com> Date: Fri, 18 Oct 2024 12:18:30 +0100 Subject: [PATCH] Datadog Service Log Forwarder Lambda * The Datadog Forwarder is an AWS Lambda function that ships logs from AWS to Datadog --- .gitignore | 3 + README.md | 37 + datadog-forwarder-lambda.tf | 259 + lambdas/aws-dd-forwarder-3.127.0/.DS_Store | Bin 0 -> 18436 bytes .../Deprecated-1.2.14.dist-info/INSTALLER | 1 + .../Deprecated-1.2.14.dist-info/LICENSE.rst | 21 + .../Deprecated-1.2.14.dist-info/METADATA | 181 + .../Deprecated-1.2.14.dist-info/RECORD | 13 + .../Deprecated-1.2.14.dist-info/REQUESTED | 0 .../Deprecated-1.2.14.dist-info/WHEEL | 6 + .../Deprecated-1.2.14.dist-info/top_level.txt | 1 + .../META_INF/aws_signer_signature_v1.0.SF | 70 + lambdas/aws-dd-forwarder-3.127.0/__init__.py | 0 .../aws-dd-forwarder-3.127.0/attr/__init__.py | 103 + .../attr/__init__.pyi | 388 + lambdas/aws-dd-forwarder-3.127.0/attr/_cmp.py | 160 + .../aws-dd-forwarder-3.127.0/attr/_cmp.pyi | 13 + .../aws-dd-forwarder-3.127.0/attr/_compat.py | 103 + .../aws-dd-forwarder-3.127.0/attr/_config.py | 31 + .../aws-dd-forwarder-3.127.0/attr/_funcs.py | 522 + .../aws-dd-forwarder-3.127.0/attr/_make.py | 2960 ++++++ .../attr/_next_gen.py | 631 ++ .../attr/_typing_compat.pyi | 15 + .../attr/_version_info.py | 86 + .../attr/_version_info.pyi | 9 + .../attr/converters.py | 151 + .../attr/converters.pyi | 13 + .../attr/exceptions.py | 95 + .../attr/exceptions.pyi | 17 + .../aws-dd-forwarder-3.127.0/attr/filters.py | 72 + .../aws-dd-forwarder-3.127.0/attr/filters.pyi | 6 + .../aws-dd-forwarder-3.127.0/attr/py.typed | 0 .../aws-dd-forwarder-3.127.0/attr/setters.py | 79 + .../aws-dd-forwarder-3.127.0/attr/setters.pyi | 20 + .../attr/validators.py | 711 ++ .../attr/validators.pyi | 83 + .../attrs-24.2.0.dist-info/INSTALLER | 1 + .../attrs-24.2.0.dist-info/METADATA | 242 + .../attrs-24.2.0.dist-info/RECORD | 56 + .../attrs-24.2.0.dist-info/REQUESTED | 0 .../attrs-24.2.0.dist-info/WHEEL | 4 + .../attrs-24.2.0.dist-info/licenses/LICENSE | 21 + .../attrs/__init__.py | 67 + .../attrs/__init__.pyi | 252 + .../attrs/converters.py | 3 + .../attrs/exceptions.py | 3 + .../aws-dd-forwarder-3.127.0/attrs/filters.py | 3 + .../aws-dd-forwarder-3.127.0/attrs/py.typed | 0 .../aws-dd-forwarder-3.127.0/attrs/setters.py | 3 + .../attrs/validators.py | 3 + .../aws-dd-forwarder-3.127.0/bin/ddtrace-run | 8 + lambdas/aws-dd-forwarder-3.127.0/bin/dog | 8 + lambdas/aws-dd-forwarder-3.127.0/bin/dogshell | 8 + .../aws-dd-forwarder-3.127.0/bin/dogshellwrap | 8 + lambdas/aws-dd-forwarder-3.127.0/bin/dogwrap | 8 + .../aws-dd-forwarder-3.127.0/bin/normalizer | 8 + .../bytecode-0.15.1.dist-info/COPYING | 21 + .../bytecode-0.15.1.dist-info/INSTALLER | 1 + .../bytecode-0.15.1.dist-info/METADATA | 102 + .../bytecode-0.15.1.dist-info/RECORD | 22 + .../bytecode-0.15.1.dist-info/REQUESTED | 0 .../bytecode-0.15.1.dist-info/WHEEL | 5 + .../bytecode-0.15.1.dist-info/top_level.txt | 1 + .../bytecode/__init__.py | 218 + .../bytecode/bytecode.py | 330 + .../aws-dd-forwarder-3.127.0/bytecode/cfg.py | 1061 ++ .../bytecode/concrete.py | 1419 +++ .../bytecode/flags.py | 187 + .../bytecode/instr.py | 878 ++ .../bytecode/py.typed | 0 .../bytecode/version.py | 19 + .../caching/base_tags_cache.py | 172 + .../caching/cache_layer.py | 24 + .../caching/cloudwatch_log_group_cache.py | 194 + .../caching/common.py | 103 + .../caching/lambda_cache.py | 90 + .../caching/s3_tags_cache.py | 64 + .../caching/step_functions_cache.py | 144 + .../aws-dd-forwarder-3.127.0/cattr/.DS_Store | Bin 0 -> 6148 bytes .../cattr/__init__.py | 25 + .../cattr/converters.py | 8 + .../cattr/disambiguators.py | 3 + .../cattr/dispatch.py | 3 + .../aws-dd-forwarder-3.127.0/cattr/errors.py | 15 + lambdas/aws-dd-forwarder-3.127.0/cattr/gen.py | 21 + .../cattr/preconf/__init__.py | 3 + .../cattr/preconf/bson.py | 5 + .../cattr/preconf/json.py | 5 + .../cattr/preconf/msgpack.py | 5 + .../cattr/preconf/orjson.py | 5 + .../cattr/preconf/pyyaml.py | 5 + .../cattr/preconf/tomlkit.py | 5 + .../cattr/preconf/ujson.py | 5 + .../aws-dd-forwarder-3.127.0/cattr/py.typed | 0 .../cattrs-24.1.2.dist-info/INSTALLER | 1 + .../cattrs-24.1.2.dist-info/METADATA | 161 + .../cattrs-24.1.2.dist-info/RECORD | 96 + .../cattrs-24.1.2.dist-info/REQUESTED | 0 .../cattrs-24.1.2.dist-info/WHEEL | 4 + .../cattrs-24.1.2.dist-info/licenses/LICENSE | 11 + .../aws-dd-forwarder-3.127.0/cattrs/.DS_Store | Bin 0 -> 6148 bytes .../cattrs/__init__.py | 55 + .../cattrs/_compat.py | 578 + .../cattrs/_generics.py | 24 + .../aws-dd-forwarder-3.127.0/cattrs/cols.py | 289 + .../cattrs/converters.py | 1419 +++ .../cattrs/disambiguators.py | 205 + .../cattrs/dispatch.py | 194 + .../aws-dd-forwarder-3.127.0/cattrs/errors.py | 129 + .../aws-dd-forwarder-3.127.0/cattrs/fns.py | 22 + .../cattrs/gen/__init__.py | 1053 ++ .../cattrs/gen/_consts.py | 19 + .../cattrs/gen/_generics.py | 79 + .../cattrs/gen/_lc.py | 29 + .../cattrs/gen/_shared.py | 58 + .../cattrs/gen/typeddicts.py | 611 ++ .../cattrs/preconf/__init__.py | 27 + .../cattrs/preconf/bson.py | 106 + .../cattrs/preconf/cbor2.py | 50 + .../cattrs/preconf/json.py | 56 + .../cattrs/preconf/msgpack.py | 54 + .../cattrs/preconf/msgspec.py | 185 + .../cattrs/preconf/orjson.py | 95 + .../cattrs/preconf/pyyaml.py | 72 + .../cattrs/preconf/tomlkit.py | 87 + .../cattrs/preconf/ujson.py | 55 + .../aws-dd-forwarder-3.127.0/cattrs/py.typed | 0 .../cattrs/strategies/__init__.py | 12 + .../cattrs/strategies/_class_methods.py | 64 + .../cattrs/strategies/_subclasses.py | 238 + .../cattrs/strategies/_unions.py | 258 + lambdas/aws-dd-forwarder-3.127.0/cattrs/v.py | 112 + .../certifi-2024.8.30.dist-info/INSTALLER | 1 + .../certifi-2024.8.30.dist-info/LICENSE | 20 + .../certifi-2024.8.30.dist-info/METADATA | 67 + .../certifi-2024.8.30.dist-info/RECORD | 15 + .../certifi-2024.8.30.dist-info/REQUESTED | 0 .../certifi-2024.8.30.dist-info/WHEEL | 5 + .../certifi-2024.8.30.dist-info/top_level.txt | 1 + .../certifi/__init__.py | 4 + .../certifi/__main__.py | 12 + .../certifi/cacert.pem | 4929 +++++++++ .../aws-dd-forwarder-3.127.0/certifi/core.py | 114 + .../aws-dd-forwarder-3.127.0/certifi/py.typed | 0 .../INSTALLER | 1 + .../LICENSE | 21 + .../METADATA | 695 ++ .../charset_normalizer-3.4.0.dist-info/RECORD | 36 + .../REQUESTED | 0 .../charset_normalizer-3.4.0.dist-info/WHEEL | 6 + .../entry_points.txt | 2 + .../top_level.txt | 1 + .../charset_normalizer/.DS_Store | Bin 0 -> 6148 bytes .../charset_normalizer/__init__.py | 46 + .../charset_normalizer/__main__.py | 4 + .../charset_normalizer/api.py | 668 ++ .../charset_normalizer/cd.py | 395 + .../charset_normalizer/cli/__init__.py | 6 + .../charset_normalizer/cli/__main__.py | 320 + .../charset_normalizer/constant.py | 1997 ++++ .../charset_normalizer/legacy.py | 65 + .../md.cpython-311-x86_64-linux-gnu.so | Bin 0 -> 16064 bytes .../charset_normalizer/md.py | 628 ++ .../md__mypyc.cpython-311-x86_64-linux-gnu.so | Bin 0 -> 272696 bytes .../charset_normalizer/models.py | 359 + .../charset_normalizer/py.typed | 0 .../charset_normalizer/utils.py | 421 + .../charset_normalizer/version.py | 6 + .../customized_log_group.py | 38 + .../datadog-0.48.0.dist-info/INSTALLER | 1 + .../datadog-0.48.0.dist-info/METADATA | 228 + .../datadog-0.48.0.dist-info/RECORD | 161 + .../datadog-0.48.0.dist-info/REQUESTED | 0 .../datadog-0.48.0.dist-info/WHEEL | 5 + .../datadog-0.48.0.dist-info/entry_points.txt | 5 + .../datadog-0.48.0.dist-info/licenses/LICENSE | 26 + .../licenses/LICENSE-3rdparty.csv | 4 + .../datadog/.DS_Store | Bin 0 -> 6148 bytes .../datadog/__init__.py | 138 + .../datadog/api/__init__.py | 52 + .../datadog/api/api_client.py | 290 + .../datadog/api/aws_integration.py | 248 + .../datadog/api/aws_log_integration.py | 111 + .../datadog/api/azure_integration.py | 91 + .../datadog/api/comments.py | 12 + .../datadog/api/constants.py | 25 + .../datadog/api/dashboard_list_v2.py | 19 + .../datadog/api/dashboard_lists.py | 39 + .../datadog/api/dashboards.py | 20 + .../datadog/api/distributions.py | 45 + .../datadog/api/downtimes.py | 38 + .../datadog/api/events.py | 95 + .../datadog/api/exceptions.py | 105 + .../datadog/api/format.py | 44 + .../datadog/api/gcp_integration.py | 93 + .../datadog/api/graphs.py | 84 + .../datadog/api/hosts.py | 91 + .../datadog/api/http_client.py | 195 + .../datadog/api/infrastructure.py | 28 + .../datadog/api/logs.py | 22 + .../datadog/api/metadata.py | 64 + .../datadog/api/metrics.py | 147 + .../datadog/api/monitors.py | 157 + .../datadog/api/permissions.py | 27 + .../datadog/api/resources.py | 539 + .../datadog/api/roles.py | 71 + .../datadog/api/screenboards.py | 50 + .../datadog/api/service_checks.py | 45 + .../datadog/api/service_level_objectives.py | 213 + .../datadog/api/synthetics.py | 214 + .../datadog/api/tags.py | 54 + .../datadog/api/timeboards.py | 20 + .../datadog/api/users.py | 50 + .../datadog/dogshell/__init__.py | 113 + .../datadog/dogshell/comment.py | 152 + .../datadog/dogshell/common.py | 122 + .../datadog/dogshell/dashboard.py | 174 + .../datadog/dogshell/dashboard_list.py | 339 + .../datadog/dogshell/downtime.py | 132 + .../datadog/dogshell/event.py | 201 + .../datadog/dogshell/host.py | 61 + .../datadog/dogshell/metric.py | 72 + .../datadog/dogshell/monitor.py | 431 + .../datadog/dogshell/screenboard.py | 308 + .../datadog/dogshell/search.py | 43 + .../datadog/dogshell/service_check.py | 55 + .../dogshell/service_level_objective.py | 426 + .../datadog/dogshell/tag.py | 120 + .../datadog/dogshell/timeboard.py | 358 + .../datadog/dogshell/wrap.py | 520 + .../datadog/dogstatsd/__init__.py | 4 + .../datadog/dogstatsd/base.py | 1398 +++ .../datadog/dogstatsd/container.py | 57 + .../datadog/dogstatsd/context.py | 88 + .../datadog/dogstatsd/context_async.py | 52 + .../datadog/dogstatsd/route.py | 40 + .../aws-dd-forwarder-3.127.0/datadog/py.typed | 0 .../datadog/threadstats/__init__.py | 5 + .../datadog/threadstats/aws_lambda.py | 111 + .../datadog/threadstats/base.py | 511 + .../datadog/threadstats/constants.py | 18 + .../datadog/threadstats/events.py | 27 + .../datadog/threadstats/metrics.py | 203 + .../datadog/threadstats/periodic_timer.py | 36 + .../datadog/threadstats/reporters.py | 34 + .../datadog/util/__init__.py | 3 + .../datadog/util/cli.py | 152 + .../datadog/util/compat.py | 135 + .../datadog/util/config.py | 148 + .../datadog/util/deprecation.py | 24 + .../datadog/util/format.py | 42 + .../datadog/util/hostname.py | 305 + .../datadog/version.py | 1 + .../datadog_lambda-5.87.0.dist-info/INSTALLER | 1 + .../datadog_lambda-5.87.0.dist-info/LICENSE | 203 + .../datadog_lambda-5.87.0.dist-info/METADATA | 106 + .../datadog_lambda-5.87.0.dist-info/RECORD | 44 + .../datadog_lambda-5.87.0.dist-info/REQUESTED | 0 .../datadog_lambda-5.87.0.dist-info/WHEEL | 4 + .../datadog_lambda/__init__.py | 17 + .../datadog_lambda/api.py | 93 + .../datadog_lambda/cold_start.py | 252 + .../datadog_lambda/constants.py | 53 + .../datadog_lambda/dogstatsd.py | 143 + .../datadog_lambda/extension.py | 42 + .../datadog_lambda/handler.py | 31 + .../datadog_lambda/metric.py | 136 + .../datadog_lambda/module_name.py | 3 + .../datadog_lambda/patch.py | 159 + .../datadog_lambda/stats_writer.py | 9 + .../datadog_lambda/statsd_writer.py | 17 + .../datadog_lambda/tag_object.py | 68 + .../datadog_lambda/tags.py | 104 + .../datadog_lambda/thread_stats_writer.py | 65 + .../datadog_lambda/tracing.py | 1308 +++ .../datadog_lambda/trigger.py | 352 + .../datadog_lambda/wrapper.py | 395 + .../datadog_lambda/xray.py | 118 + .../ddsketch-2.0.4.dist-info/INSTALLER | 1 + .../ddsketch-2.0.4.dist-info/LICENSE | 13 + .../LICENSE-3rdparty.csv | 3 + .../ddsketch-2.0.4.dist-info/METADATA | 166 + .../ddsketch-2.0.4.dist-info/NOTICE | 4 + .../ddsketch-2.0.4.dist-info/RECORD | 30 + .../ddsketch-2.0.4.dist-info/REQUESTED | 0 .../ddsketch-2.0.4.dist-info/WHEEL | 5 + .../ddsketch-2.0.4.dist-info/top_level.txt | 1 + .../ddsketch/.DS_Store | Bin 0 -> 6148 bytes .../ddsketch/__init__.py | 24 + .../ddsketch/__version.py | 5 + .../ddsketch/_version.py | 17 + .../ddsketch/ddsketch.py | 316 + .../ddsketch/mapping.py | 216 + .../ddsketch/pb/__init__.py | 0 .../ddsketch/pb/ddsketch_pb2.py | 72 + .../ddsketch/pb/ddsketch_pre319_pb2.py | 283 + .../ddsketch/pb/proto.py | 104 + .../ddsketch/py.typed | 0 .../ddsketch/store.py | 504 + .../ddtrace-2.6.5.dist-info/INSTALLER | 1 + .../ddtrace-2.6.5.dist-info/LICENSE | 6 + .../ddtrace-2.6.5.dist-info/LICENSE.Apache | 200 + .../ddtrace-2.6.5.dist-info/LICENSE.BSD3 | 24 + .../ddtrace-2.6.5.dist-info/METADATA | 68 + .../ddtrace-2.6.5.dist-info/NOTICE | 4 + .../ddtrace-2.6.5.dist-info/RECORD | 1391 +++ .../ddtrace-2.6.5.dist-info/REQUESTED | 0 .../ddtrace-2.6.5.dist-info/WHEEL | 6 + .../ddtrace-2.6.5.dist-info/entry_points.txt | 10 + .../ddtrace-2.6.5.dist-info/top_level.txt | 1 + .../ddtrace/.DS_Store | Bin 0 -> 10244 bytes .../ddtrace/__init__.py | 58 + .../ddtrace/_hooks.py | 137 + .../ddtrace/_logger.py | 88 + .../ddtrace/_monkey.py | 273 + .../ddtrace/_trace/__init__.py | 0 .../ddtrace/_trace/_limits.py | 6 + .../ddtrace/_version.py | 16 + .../ddtrace/appsec/__init__.py | 0 .../ddtrace/appsec/_api_security/__init__.py | 0 .../appsec/_api_security/api_manager.py | 163 + .../ddtrace/appsec/_asm_request_context.py | 549 + .../ddtrace/appsec/_capabilities.py | 77 + .../ddtrace/appsec/_constants.py | 216 + .../ddtrace/appsec/_ddwaf/__init__.py | 214 + .../ddtrace/appsec/_ddwaf/ddwaf_types.py | 555 + .../_ddwaf/libddwaf/x86_64/lib/libddwaf.so | Bin 0 -> 2195976 bytes .../ddtrace/appsec/_deduplications.py | 32 + .../ddtrace/appsec/_handlers.py | 377 + .../ddtrace/appsec/_iast/__init__.py | 74 + .../ddtrace/appsec/_iast/_ast/__init__.py | 1 + .../ddtrace/appsec/_iast/_ast/ast_patching.py | 172 + .../ddtrace/appsec/_iast/_ast/visitor.py | 759 ++ .../ddtrace/appsec/_iast/_input_info.py | 13 + .../ddtrace/appsec/_iast/_loader.py | 29 + .../ddtrace/appsec/_iast/_metrics.py | 163 + .../appsec/_iast/_overhead_control_engine.py | 133 + .../ddtrace/appsec/_iast/_patch.py | 177 + .../ddtrace/appsec/_iast/_patch_modules.py | 27 + .../appsec/_iast/_patches/json_tainting.py | 82 + ...stacktrace.cpython-311-x86_64-linux-gnu.so | Bin 0 -> 16976 bytes .../ddtrace/appsec/_iast/_taint_dict.py | 21 + .../_iast/_taint_tracking/CMakeLists.txt | 76 + .../appsec/_iast/_taint_tracking/README.txt | 37 + .../appsec/_iast/_taint_tracking/__init__.py | 143 + .../_native.cpython-311-x86_64-linux-gnu.so | Bin 0 -> 633224 bytes .../_iast/_taint_tracking/_vendor/__init__.py | 0 .../_vendor/pybind11/CMakeLists.txt | 322 + .../_vendor/pybind11/__init__.py | 0 .../_vendor/pybind11/pybind11/__init__.py | 17 + .../_vendor/pybind11/pybind11/__main__.py | 62 + .../_vendor/pybind11/pybind11/_version.py | 12 + .../_vendor/pybind11/pybind11/commands.py | 37 + .../_vendor/pybind11/pybind11/py.typed | 0 .../pybind11/pybind11/setup_helpers.py | 498 + .../_vendor/pybind11/tools/FindCatch.cmake | 76 + .../_vendor/pybind11/tools/FindEigen3.cmake | 86 + .../pybind11/tools/FindPythonLibsNew.cmake | 287 + .../_vendor/pybind11/tools/JoinPaths.cmake | 23 + .../_vendor/pybind11/tools/__init__.py | 0 .../_vendor/pybind11/tools/check-style.sh | 44 + .../pybind11/tools/cmake_uninstall.cmake.in | 23 + .../codespell_ignore_lines_from_errors.py | 39 + .../_vendor/pybind11/tools/libsize.py | 36 + .../_vendor/pybind11/tools/make_changelog.py | 62 + .../_vendor/pybind11/tools/pybind11.pc.in | 7 + .../pybind11/tools/pybind11Common.cmake | 405 + .../pybind11/tools/pybind11Config.cmake.in | 233 + .../pybind11/tools/pybind11NewTools.cmake | 256 + .../pybind11/tools/pybind11Tools.cmake | 233 + .../_vendor/pybind11/tools/pyproject.toml | 3 + .../_vendor/pybind11/tools/setup_global.py.in | 63 + .../_vendor/pybind11/tools/setup_main.py.in | 44 + .../appsec/_iast/_taint_tracking/aspects.py | 942 ++ .../appsec/_iast/_taint_tracking/clean.sh | 6 + .../ddtrace/appsec/_iast/_taint_utils.py | 548 + .../ddtrace/appsec/_iast/_utils.py | 124 + .../ddtrace/appsec/_iast/constants.py | 89 + .../ddtrace/appsec/_iast/processor.py | 95 + .../ddtrace/appsec/_iast/reporter.py | 87 + .../appsec/_iast/taint_sinks/__init__.py | 8 + .../ddtrace/appsec/_iast/taint_sinks/_base.py | 314 + .../appsec/_iast/taint_sinks/ast_taint.py | 47 + .../_iast/taint_sinks/command_injection.py | 254 + .../_iast/taint_sinks/insecure_cookie.py | 72 + .../_iast/taint_sinks/path_traversal.py | 70 + .../appsec/_iast/taint_sinks/sql_injection.py | 44 + .../ddtrace/appsec/_iast/taint_sinks/ssrf.py | 175 + .../appsec/_iast/taint_sinks/weak_cipher.py | 166 + .../appsec/_iast/taint_sinks/weak_hash.py | 171 + .../_iast/taint_sinks/weak_randomness.py | 14 + .../ddtrace/appsec/_metrics.py | 124 + .../ddtrace/appsec/_processor.py | 409 + .../ddtrace/appsec/_python_info/__init__.py | 0 .../appsec/_python_info/stdlib/__init__.py | 21 + .../_python_info/stdlib/module_names_py310.py | 218 + .../_python_info/stdlib/module_names_py311.py | 218 + .../_python_info/stdlib/module_names_py36.py | 220 + .../_python_info/stdlib/module_names_py37.py | 221 + .../_python_info/stdlib/module_names_py38.py | 220 + .../_python_info/stdlib/module_names_py39.py | 220 + .../ddtrace/appsec/_remoteconfiguration.py | 248 + .../ddtrace/appsec/_trace_utils.py | 346 + .../ddtrace/appsec/_utils.py | 176 + .../ddtrace/appsec/iast/__init__.py | 1 + .../ddtrace/appsec/rules.json | 9320 +++++++++++++++++ .../ddtrace/appsec/trace_utils/__init__.py | 11 + .../aws-dd-forwarder-3.127.0/ddtrace/auto.py | 21 + .../ddtrace/bootstrap/__init__.py | 0 .../ddtrace/bootstrap/preload.py | 114 + .../ddtrace/bootstrap/sitecustomize.py | 171 + .../ddtrace/commands/__init__.py | 0 .../ddtrace/constants.py | 46 + .../ddtrace/context.py | 269 + .../ddtrace/contrib/__init__.py | 4 + .../ddtrace/contrib/aiobotocore/__init__.py | 38 + .../ddtrace/contrib/aiobotocore/patch.py | 180 + .../ddtrace/contrib/aiohttp/__init__.py | 99 + .../ddtrace/contrib/aiohttp/middlewares.py | 182 + .../ddtrace/contrib/aiohttp/patch.py | 158 + .../contrib/aiohttp_jinja2/__init__.py | 27 + .../ddtrace/contrib/aiohttp_jinja2/patch.py | 75 + .../ddtrace/contrib/aiomysql/__init__.py | 50 + .../ddtrace/contrib/aiomysql/patch.py | 161 + .../ddtrace/contrib/aiopg/__init__.py | 28 + .../ddtrace/contrib/aiopg/connection.py | 121 + .../ddtrace/contrib/aiopg/patch.py | 62 + .../ddtrace/contrib/aioredis/__init__.py | 83 + .../ddtrace/contrib/aioredis/patch.py | 233 + .../ddtrace/contrib/algoliasearch/__init__.py | 36 + .../ddtrace/contrib/algoliasearch/patch.py | 168 + .../ddtrace/contrib/aredis/__init__.py | 79 + .../ddtrace/contrib/aredis/patch.py | 86 + .../ddtrace/contrib/asgi/__init__.py | 75 + .../ddtrace/contrib/asgi/middleware.py | 286 + .../ddtrace/contrib/asgi/utils.py | 82 + .../ddtrace/contrib/asyncio/__init__.py | 65 + .../ddtrace/contrib/asyncio/compat.py | 15 + .../ddtrace/contrib/asyncio/helpers.py | 81 + .../ddtrace/contrib/asyncio/patch.py | 22 + .../ddtrace/contrib/asyncio/provider.py | 74 + .../ddtrace/contrib/asyncio/wrappers.py | 25 + .../ddtrace/contrib/asyncpg/__init__.py | 57 + .../ddtrace/contrib/asyncpg/patch.py | 163 + .../ddtrace/contrib/aws_lambda/__init__.py | 46 + .../ddtrace/contrib/aws_lambda/_cold_start.py | 18 + .../ddtrace/contrib/aws_lambda/patch.py | 272 + .../ddtrace/contrib/boto/__init__.py | 41 + .../ddtrace/contrib/boto/patch.py | 212 + .../ddtrace/contrib/botocore/__init__.py | 125 + .../ddtrace/contrib/botocore/patch.py | 273 + .../contrib/botocore/services/bedrock.py | 341 + .../contrib/botocore/services/kinesis.py | 182 + .../ddtrace/contrib/botocore/services/sqs.py | 227 + .../botocore/services/stepfunctions.py | 108 + .../ddtrace/contrib/botocore/utils.py | 248 + .../ddtrace/contrib/bottle/__init__.py | 47 + .../ddtrace/contrib/bottle/patch.py | 41 + .../ddtrace/contrib/bottle/trace.py | 107 + .../ddtrace/contrib/cassandra/__init__.py | 34 + .../ddtrace/contrib/cassandra/patch.py | 5 + .../ddtrace/contrib/cassandra/session.py | 294 + .../ddtrace/contrib/celery/__init__.py | 58 + .../ddtrace/contrib/celery/app.py | 99 + .../ddtrace/contrib/celery/constants.py | 20 + .../ddtrace/contrib/celery/patch.py | 41 + .../ddtrace/contrib/celery/signals.py | 225 + .../ddtrace/contrib/celery/utils.py | 136 + .../ddtrace/contrib/cherrypy/__init__.py | 66 + .../ddtrace/contrib/cherrypy/middleware.py | 159 + .../ddtrace/contrib/consul/__init__.py | 33 + .../ddtrace/contrib/consul/patch.py | 85 + .../ddtrace/contrib/coverage/__init__.py | 32 + .../ddtrace/contrib/coverage/constants.py | 1 + .../ddtrace/contrib/coverage/data.py | 7 + .../ddtrace/contrib/coverage/patch.py | 62 + .../ddtrace/contrib/coverage/utils.py | 26 + .../ddtrace/contrib/dbapi/__init__.py | 344 + .../ddtrace/contrib/dbapi_async/__init__.py | 258 + .../ddtrace/contrib/django/__init__.py | 192 + .../ddtrace/contrib/django/_asgi.py | 36 + .../ddtrace/contrib/django/compat.py | 31 + .../ddtrace/contrib/django/patch.py | 875 ++ .../ddtrace/contrib/django/restframework.py | 33 + .../ddtrace/contrib/django/utils.py | 421 + .../ddtrace/contrib/dogpile_cache/__init__.py | 50 + .../ddtrace/contrib/dogpile_cache/lock.py | 39 + .../ddtrace/contrib/dogpile_cache/patch.py | 52 + .../ddtrace/contrib/dogpile_cache/region.py | 55 + .../ddtrace/contrib/elasticsearch/__init__.py | 57 + .../ddtrace/contrib/elasticsearch/patch.py | 269 + .../ddtrace/contrib/elasticsearch/quantize.py | 35 + .../ddtrace/contrib/falcon/__init__.py | 58 + .../ddtrace/contrib/falcon/middleware.py | 123 + .../ddtrace/contrib/falcon/patch.py | 52 + .../ddtrace/contrib/fastapi/__init__.py | 72 + .../ddtrace/contrib/fastapi/patch.py | 100 + .../ddtrace/contrib/flask/__init__.py | 113 + .../ddtrace/contrib/flask/patch.py | 559 + .../ddtrace/contrib/flask/wrappers.py | 96 + .../ddtrace/contrib/flask_cache/__init__.py | 57 + .../ddtrace/contrib/flask_cache/tracers.py | 188 + .../ddtrace/contrib/flask_cache/utils.py | 62 + .../ddtrace/contrib/flask_login/__init__.py | 44 + .../ddtrace/contrib/flask_login/patch.py | 108 + .../ddtrace/contrib/futures/__init__.py | 34 + .../ddtrace/contrib/futures/patch.py | 43 + .../ddtrace/contrib/futures/threading.py | 44 + .../ddtrace/contrib/gevent/__init__.py | 72 + .../ddtrace/contrib/gevent/greenlet.py | 60 + .../ddtrace/contrib/gevent/patch.py | 80 + .../ddtrace/contrib/gevent/provider.py | 42 + .../ddtrace/contrib/graphql/__init__.py | 58 + .../ddtrace/contrib/graphql/patch.py | 321 + .../ddtrace/contrib/grpc/__init__.py | 89 + .../contrib/grpc/aio_client_interceptor.py | 252 + .../contrib/grpc/aio_server_interceptor.py | 301 + .../contrib/grpc/client_interceptor.py | 275 + .../ddtrace/contrib/grpc/constants.py | 26 + .../ddtrace/contrib/grpc/patch.py | 260 + .../contrib/grpc/server_interceptor.py | 135 + .../ddtrace/contrib/grpc/utils.py | 83 + .../ddtrace/contrib/gunicorn/__init__.py | 22 + .../ddtrace/contrib/httplib/__init__.py | 66 + .../ddtrace/contrib/httplib/patch.py | 229 + .../ddtrace/contrib/httpx/__init__.py | 91 + .../ddtrace/contrib/httpx/patch.py | 204 + .../ddtrace/contrib/jinja2/__init__.py | 41 + .../ddtrace/contrib/jinja2/constants.py | 1 + .../ddtrace/contrib/jinja2/patch.py | 110 + .../ddtrace/contrib/kafka/__init__.py | 55 + .../ddtrace/contrib/kafka/patch.py | 287 + .../ddtrace/contrib/kombu/__init__.py | 45 + .../ddtrace/contrib/kombu/constants.py | 1 + .../ddtrace/contrib/kombu/patch.py | 167 + .../ddtrace/contrib/kombu/utils.py | 49 + .../ddtrace/contrib/langchain/__init__.py | 207 + .../ddtrace/contrib/langchain/constants.py | 87 + .../ddtrace/contrib/langchain/patch.py | 825 ++ .../ddtrace/contrib/logbook/__init__.py | 62 + .../ddtrace/contrib/logbook/patch.py | 74 + .../ddtrace/contrib/logging/__init__.py | 75 + .../ddtrace/contrib/logging/constants.py | 7 + .../ddtrace/contrib/logging/patch.py | 145 + .../ddtrace/contrib/loguru/__init__.py | 77 + .../ddtrace/contrib/loguru/patch.py | 85 + .../ddtrace/contrib/mako/__init__.py | 24 + .../ddtrace/contrib/mako/constants.py | 1 + .../ddtrace/contrib/mako/patch.py | 68 + .../ddtrace/contrib/mariadb/__init__.py | 66 + .../ddtrace/contrib/mariadb/patch.py | 58 + .../ddtrace/contrib/molten/__init__.py | 50 + .../ddtrace/contrib/molten/patch.py | 180 + .../ddtrace/contrib/molten/wrappers.py | 124 + .../ddtrace/contrib/mongoengine/__init__.py | 30 + .../ddtrace/contrib/mongoengine/patch.py | 20 + .../ddtrace/contrib/mongoengine/trace.py | 34 + .../ddtrace/contrib/mysql/__init__.py | 76 + .../ddtrace/contrib/mysql/patch.py | 68 + .../ddtrace/contrib/mysqldb/__init__.py | 88 + .../ddtrace/contrib/mysqldb/patch.py | 111 + .../ddtrace/contrib/openai/__init__.py | 262 + .../ddtrace/contrib/openai/_endpoint_hooks.py | 908 ++ .../ddtrace/contrib/openai/patch.py | 387 + .../ddtrace/contrib/openai/utils.py | 122 + .../ddtrace/contrib/psycopg/__init__.py | 68 + .../contrib/psycopg/async_connection.py | 66 + .../ddtrace/contrib/psycopg/async_cursor.py | 26 + .../ddtrace/contrib/psycopg/connection.py | 109 + .../ddtrace/contrib/psycopg/cursor.py | 28 + .../ddtrace/contrib/psycopg/extensions.py | 180 + .../ddtrace/contrib/psycopg/patch.py | 214 + .../ddtrace/contrib/pylibmc/__init__.py | 33 + .../ddtrace/contrib/pylibmc/addrs.py | 14 + .../ddtrace/contrib/pylibmc/client.py | 189 + .../ddtrace/contrib/pylibmc/patch.py | 20 + .../ddtrace/contrib/pymemcache/__init__.py | 44 + .../ddtrace/contrib/pymemcache/client.py | 361 + .../ddtrace/contrib/pymemcache/patch.py | 49 + .../ddtrace/contrib/pymongo/__init__.py | 48 + .../ddtrace/contrib/pymongo/client.py | 351 + .../ddtrace/contrib/pymongo/parse.py | 204 + .../ddtrace/contrib/pymongo/patch.py | 94 + .../ddtrace/contrib/pymysql/__init__.py | 68 + .../ddtrace/contrib/pymysql/patch.py | 63 + .../ddtrace/contrib/pynamodb/__init__.py | 42 + .../ddtrace/contrib/pynamodb/patch.py | 108 + .../ddtrace/contrib/pyodbc/__init__.py | 66 + .../ddtrace/contrib/pyodbc/patch.py | 69 + .../ddtrace/contrib/pyramid/__init__.py | 56 + .../ddtrace/contrib/pyramid/constants.py | 6 + .../ddtrace/contrib/pyramid/patch.py | 103 + .../ddtrace/contrib/pyramid/trace.py | 138 + .../ddtrace/contrib/pytest/__init__.py | 86 + .../ddtrace/contrib/pytest/constants.py | 9 + .../ddtrace/contrib/pytest/newhooks.py | 26 + .../ddtrace/contrib/pytest/plugin.py | 927 ++ .../ddtrace/contrib/pytest_bdd/__init__.py | 47 + .../ddtrace/contrib/pytest_bdd/constants.py | 2 + .../ddtrace/contrib/pytest_bdd/plugin.py | 139 + .../contrib/pytest_benchmark/__init__.py | 0 .../contrib/pytest_benchmark/constants.py | 58 + .../contrib/pytest_benchmark/plugin.py | 33 + .../ddtrace/contrib/redis/__init__.py | 80 + .../ddtrace/contrib/redis/asyncio_patch.py | 40 + .../ddtrace/contrib/redis/patch.py | 164 + .../ddtrace/contrib/rediscluster/__init__.py | 61 + .../ddtrace/contrib/rediscluster/patch.py | 113 + .../ddtrace/contrib/requests/__init__.py | 87 + .../ddtrace/contrib/requests/connection.py | 146 + .../ddtrace/contrib/requests/constants.py | 1 + .../ddtrace/contrib/requests/patch.py | 51 + .../ddtrace/contrib/requests/session.py | 21 + .../ddtrace/contrib/rq/__init__.py | 283 + .../ddtrace/contrib/sanic/__init__.py | 75 + .../ddtrace/contrib/sanic/patch.py | 285 + .../ddtrace/contrib/snowflake/__init__.py | 72 + .../ddtrace/contrib/snowflake/patch.py | 99 + .../ddtrace/contrib/sqlalchemy/__init__.py | 34 + .../ddtrace/contrib/sqlalchemy/engine.py | 164 + .../ddtrace/contrib/sqlalchemy/patch.py | 29 + .../ddtrace/contrib/sqlite3/__init__.py | 66 + .../ddtrace/contrib/sqlite3/patch.py | 97 + .../ddtrace/contrib/starlette/__init__.py | 86 + .../ddtrace/contrib/starlette/patch.py | 184 + .../ddtrace/contrib/structlog/__init__.py | 52 + .../ddtrace/contrib/structlog/patch.py | 90 + .../ddtrace/contrib/subprocess/__init__.py | 33 + .../ddtrace/contrib/subprocess/constants.py | 18 + .../ddtrace/contrib/subprocess/patch.py | 410 + .../ddtrace/contrib/tornado/__init__.py | 130 + .../ddtrace/contrib/tornado/application.py | 59 + .../ddtrace/contrib/tornado/constants.py | 7 + .../ddtrace/contrib/tornado/decorators.py | 82 + .../ddtrace/contrib/tornado/handlers.py | 148 + .../ddtrace/contrib/tornado/patch.py | 72 + .../ddtrace/contrib/tornado/stack_context.py | 144 + .../ddtrace/contrib/tornado/template.py | 35 + .../ddtrace/contrib/trace_utils.py | 682 ++ .../ddtrace/contrib/trace_utils_async.py | 39 + .../ddtrace/contrib/trace_utils_redis.py | 184 + .../ddtrace/contrib/unittest/__init__.py | 47 + .../ddtrace/contrib/unittest/constants.py | 8 + .../ddtrace/contrib/unittest/patch.py | 858 ++ .../ddtrace/contrib/urllib3/__init__.py | 63 + .../ddtrace/contrib/urllib3/patch.py | 150 + .../ddtrace/contrib/vertica/__init__.py | 53 + .../ddtrace/contrib/vertica/patch.py | 263 + .../ddtrace/contrib/wsgi/__init__.py | 43 + .../ddtrace/contrib/wsgi/wsgi.py | 270 + .../ddtrace/contrib/yaaredis/__init__.py | 79 + .../ddtrace/contrib/yaaredis/patch.py | 83 + .../ddtrace/data_streams.py | 36 + .../ddtrace/debugging/__init__.py | 34 + .../ddtrace/debugging/_async.py | 27 + .../ddtrace/debugging/_config.py | 6 + .../ddtrace/debugging/_debugger.py | 730 ++ .../ddtrace/debugging/_encoding.py | 324 + .../ddtrace/debugging/_exception/__init__.py | 0 .../debugging/_exception/auto_instrument.py | 210 + .../ddtrace/debugging/_expressions.py | 365 + .../ddtrace/debugging/_function/__init__.py | 0 .../ddtrace/debugging/_function/discovery.py | 240 + .../ddtrace/debugging/_function/store.py | 111 + .../ddtrace/debugging/_metrics.py | 9 + .../ddtrace/debugging/_probe/__init__.py | 0 .../ddtrace/debugging/_probe/model.py | 290 + .../ddtrace/debugging/_probe/registry.py | 204 + .../ddtrace/debugging/_probe/remoteconfig.py | 341 + .../ddtrace/debugging/_probe/status.py | 147 + .../ddtrace/debugging/_redaction.py | 167 + .../ddtrace/debugging/_safety.py | 73 + .../ddtrace/debugging/_signal/__init__.py | 0 .../ddtrace/debugging/_signal/collector.py | 117 + .../debugging/_signal/metric_sample.py | 86 + .../ddtrace/debugging/_signal/model.py | 168 + .../ddtrace/debugging/_signal/snapshot.py | 249 + .../ddtrace/debugging/_signal/tracing.py | 149 + .../ddtrace/debugging/_signal/utils.py | 312 + .../ddtrace/debugging/_uploader.py | 97 + .../ddtrace/ext/__init__.py | 23 + .../ddtrace/ext/aws.py | 88 + .../ddtrace/ext/cassandra.py | 6 + .../ddtrace/ext/ci.py | 576 + .../ddtrace/ext/consul.py | 4 + .../ddtrace/ext/db.py | 6 + .../ddtrace/ext/elasticsearch.py | 9 + .../ddtrace/ext/git.py | 402 + .../ddtrace/ext/http.py | 22 + .../ddtrace/ext/kafka.py | 14 + .../ddtrace/ext/kombu.py | 12 + .../ddtrace/ext/memcached.py | 4 + .../ddtrace/ext/mongo.py | 4 + .../ddtrace/ext/net.py | 12 + .../ddtrace/ext/redis.py | 14 + .../ddtrace/ext/sql.py | 75 + .../ddtrace/ext/test.py | 93 + .../ddtrace/ext/user.py | 8 + .../ddtrace/filters.py | 72 + .../ddtrace/internal/README.md | 7 + .../ddtrace/internal/__init__.py | 0 .../_encoding.cpython-311-x86_64-linux-gnu.so | Bin 0 -> 357568 bytes .../ddtrace/internal/_encoding.pyi | 41 + .../_rand.cpython-311-x86_64-linux-gnu.so | Bin 0 -> 64144 bytes .../ddtrace/internal/_rand.pyi | 3 + .../_tagset.cpython-311-x86_64-linux-gnu.so | Bin 0 -> 118400 bytes .../ddtrace/internal/_tagset.pyi | 18 + .../ddtrace/internal/_utils.pxd | 2 + .../ddtrace/internal/agent.py | 106 + .../ddtrace/internal/assembly.py | 274 + .../ddtrace/internal/atexit.py | 79 + .../internal/ci_visibility/__init__.py | 11 + .../internal/ci_visibility/constants.py | 66 + .../internal/ci_visibility/coverage.py | 163 + .../ddtrace/internal/ci_visibility/encoder.py | 208 + .../ddtrace/internal/ci_visibility/filters.py | 40 + .../internal/ci_visibility/git_client.py | 481 + .../internal/ci_visibility/recorder.py | 600 ++ .../ci_visibility/telemetry/__init__.py | 0 .../ci_visibility/telemetry/constants.py | 44 + .../internal/ci_visibility/telemetry/git.py | 90 + .../internal/ci_visibility/telemetry/utils.py | 17 + .../ddtrace/internal/ci_visibility/utils.py | 141 + .../ddtrace/internal/ci_visibility/writer.py | 148 + .../ddtrace/internal/codeowners.py | 195 + .../ddtrace/internal/compat.py | 458 + .../ddtrace/internal/constants.py | 102 + .../ddtrace/internal/core/__init__.py | 322 + .../ddtrace/internal/core/event_hub.py | 134 + .../ddtrace/internal/datadog/__init__.py | 0 .../internal/datadog/profiling/__init__.py | 0 .../_ddup.cpython-311-x86_64-linux-gnu.so | Bin 0 -> 3667048 bytes .../internal/datadog/profiling/_ddup.pyi | 29 + .../internal/datadog/profiling/ddup.py | 90 + .../internal/datadog/profiling/utils.py | 31 + .../ddtrace/internal/datastreams/__init__.py | 27 + .../ddtrace/internal/datastreams/botocore.py | 186 + .../ddtrace/internal/datastreams/encoding.py | 45 + .../ddtrace/internal/datastreams/fnv.py | 39 + .../ddtrace/internal/datastreams/kafka.py | 117 + .../ddtrace/internal/datastreams/kombu.py | 55 + .../ddtrace/internal/datastreams/processor.py | 464 + .../ddtrace/internal/datastreams/utils.py | 18 + .../ddtrace/internal/debug.py | 268 + .../ddtrace/internal/dogstatsd.py | 29 + .../ddtrace/internal/encoding.py | 150 + .../ddtrace/internal/forksafe.py | 137 + .../ddtrace/internal/gitmetadata.py | 154 + .../ddtrace/internal/glob_matching.py | 51 + .../ddtrace/internal/hostname.py | 18 + .../ddtrace/internal/http.py | 36 + .../ddtrace/internal/injection.py | 211 + .../ddtrace/internal/llmobs/__init__.py | 4 + .../internal/llmobs/integrations/__init__.py | 7 + .../internal/llmobs/integrations/base.py | 227 + .../internal/llmobs/integrations/bedrock.py | 80 + .../internal/llmobs/integrations/langchain.py | 88 + .../internal/llmobs/integrations/openai.py | 215 + .../ddtrace/internal/llmobs/writer.py | 135 + .../ddtrace/internal/log_writer.py | 120 + .../ddtrace/internal/logger.py | 179 + .../ddtrace/internal/metrics.py | 87 + .../ddtrace/internal/module.py | 562 + .../ddtrace/internal/packages.py | 134 + .../ddtrace/internal/periodic.py | 174 + .../ddtrace/internal/processor/__init__.py | 77 + .../processor/endpoint_call_counter.py | 43 + .../ddtrace/internal/processor/stats.py | 255 + .../ddtrace/internal/processor/trace.py | 388 + .../ddtrace/internal/rate_limiter.py | 257 + .../ddtrace/internal/remoteconfig/__init__.py | 0 .../internal/remoteconfig/_connectors.py | 90 + .../internal/remoteconfig/_publishers.py | 120 + .../ddtrace/internal/remoteconfig/_pubsub.py | 113 + .../internal/remoteconfig/_subscribers.py | 59 + .../ddtrace/internal/remoteconfig/client.py | 593 ++ .../internal/remoteconfig/constants.py | 2 + .../ddtrace/internal/remoteconfig/utils.py | 6 + .../ddtrace/internal/remoteconfig/worker.py | 181 + .../ddtrace/internal/runtime/__init__.py | 46 + .../ddtrace/internal/runtime/collector.py | 89 + .../ddtrace/internal/runtime/constants.py | 27 + .../ddtrace/internal/runtime/container.py | 115 + .../internal/runtime/metric_collectors.py | 93 + .../internal/runtime/runtime_metrics.py | 166 + .../internal/runtime/tag_collectors.py | 73 + .../ddtrace/internal/safety.py | 129 + .../ddtrace/internal/sampling.py | 319 + .../ddtrace/internal/schema/__init__.py | 64 + .../internal/schema/span_attribute_schema.py | 115 + .../ddtrace/internal/serverless/__init__.py | 47 + .../ddtrace/internal/serverless/mini_agent.py | 48 + .../ddtrace/internal/service.py | 106 + .../ddtrace/internal/sma.py | 67 + .../ddtrace/internal/telemetry/__init__.py | 74 + .../ddtrace/internal/telemetry/constants.py | 69 + .../ddtrace/internal/telemetry/data.py | 127 + .../ddtrace/internal/telemetry/metrics.py | 144 + .../internal/telemetry/metrics_namespaces.py | 56 + .../ddtrace/internal/telemetry/writer.py | 760 ++ .../ddtrace/internal/tracemethods.py | 106 + .../ddtrace/internal/uds.py | 27 + .../ddtrace/internal/utils/__init__.py | 82 + .../ddtrace/internal/utils/attrdict.py | 42 + .../ddtrace/internal/utils/cache.py | 139 + .../ddtrace/internal/utils/config.py | 19 + .../ddtrace/internal/utils/deprecations.py | 6 + .../ddtrace/internal/utils/formats.py | 185 + .../ddtrace/internal/utils/http.py | 441 + .../ddtrace/internal/utils/importlib.py | 48 + .../ddtrace/internal/utils/inspection.py | 121 + .../ddtrace/internal/utils/retry.py | 63 + .../ddtrace/internal/utils/signals.py | 27 + .../ddtrace/internal/utils/time.py | 140 + .../ddtrace/internal/utils/version.py | 79 + .../ddtrace/internal/utils/wrappers.py | 23 + .../ddtrace/internal/uwsgi.py | 69 + .../ddtrace/internal/wrapping/__init__.py | 306 + .../ddtrace/internal/wrapping/asyncs.py | 646 ++ .../ddtrace/internal/wrapping/generators.py | 466 + .../ddtrace/internal/writer/__init__.py | 9 + .../ddtrace/internal/writer/writer.py | 630 ++ .../ddtrace/internal/writer/writer_client.py | 49 + .../ddtrace/opentelemetry/__init__.py | 114 + .../ddtrace/opentelemetry/_context.py | 87 + .../ddtrace/opentelemetry/_span.py | 308 + .../ddtrace/opentelemetry/_trace.py | 154 + .../ddtrace/opentracer/__init__.py | 8 + .../ddtrace/opentracer/helpers.py | 25 + .../opentracer/propagation/__init__.py | 6 + .../ddtrace/opentracer/propagation/binary.py | 0 .../ddtrace/opentracer/propagation/http.py | 74 + .../opentracer/propagation/propagator.py | 13 + .../ddtrace/opentracer/propagation/text.py | 0 .../ddtrace/opentracer/settings.py | 41 + .../ddtrace/opentracer/span.py | 197 + .../ddtrace/opentracer/span_context.py | 66 + .../ddtrace/opentracer/tags.py | 23 + .../ddtrace/opentracer/tracer.py | 376 + .../ddtrace/opentracer/utils.py | 60 + .../aws-dd-forwarder-3.127.0/ddtrace/pin.py | 205 + .../ddtrace/profiling/__init__.py | 21 + .../ddtrace/profiling/_asyncio.py | 65 + .../_build.cpython-311-x86_64-linux-gnu.so | Bin 0 -> 28336 bytes .../ddtrace/profiling/_build.pyi | 3 + ..._threading.cpython-311-x86_64-linux-gnu.so | Bin 0 -> 120872 bytes .../ddtrace/profiling/_threading.pyi | 11 + .../ddtrace/profiling/_traceback.py | 5 + .../ddtrace/profiling/auto.py | 9 + .../ddtrace/profiling/bootstrap/__init__.py | 0 .../profiling/bootstrap/sitecustomize.py | 15 + .../ddtrace/profiling/collector/__init__.py | 81 + .../ddtrace/profiling/collector/_lock.py | 230 + .../_memalloc.cpython-311-x86_64-linux-gnu.so | Bin 0 -> 28688 bytes .../ddtrace/profiling/collector/_memalloc.pyi | 15 + .../_task.cpython-311-x86_64-linux-gnu.so | Bin 0 -> 120712 bytes .../ddtrace/profiling/collector/_task.pyi | 7 + ..._traceback.cpython-311-x86_64-linux-gnu.so | Bin 0 -> 87768 bytes .../profiling/collector/_traceback.pyi | 9 + .../ddtrace/profiling/collector/asyncio.py | 49 + .../ddtrace/profiling/collector/memalloc.py | 206 + .../stack.cpython-311-x86_64-linux-gnu.so | Bin 0 -> 300672 bytes .../ddtrace/profiling/collector/stack.pyi | 7 + .../profiling/collector/stack_event.py | 22 + .../ddtrace/profiling/collector/threading.py | 41 + .../ddtrace/profiling/event.py | 76 + .../ddtrace/profiling/exporter/__init__.py | 46 + .../ddtrace/profiling/exporter/file.py | 39 + .../ddtrace/profiling/exporter/http.py | 261 + .../pprof.cpython-311-x86_64-linux-gnu.so | Bin 0 -> 516968 bytes .../ddtrace/profiling/exporter/pprof.proto | 212 + .../ddtrace/profiling/exporter/pprof.pyi | 138 + .../profiling/exporter/pprof_312_pb2.py | 620 ++ .../profiling/exporter/pprof_319_pb2.py | 105 + .../ddtrace/profiling/exporter/pprof_3_pb2.py | 615 ++ .../profiling/exporter/pprof_421_pb2.py | 40 + .../ddtrace/profiling/profiler.py | 383 + .../ddtrace/profiling/recorder.py | 95 + .../ddtrace/profiling/scheduler.py | 111 + .../ddtrace/propagation/__init__.py | 0 .../propagation/_database_monitoring.py | 97 + .../ddtrace/propagation/_utils.py | 31 + .../ddtrace/propagation/http.py | 981 ++ .../ddtrace/provider.py | 173 + .../aws-dd-forwarder-3.127.0/ddtrace/py.typed | 0 .../ddtrace/runtime/__init__.py | 58 + .../ddtrace/sampler.py | 337 + .../ddtrace/sampling_rule.py | 214 + .../ddtrace/settings/__init__.py | 17 + .../ddtrace/settings/_database_monitoring.py | 17 + .../ddtrace/settings/asm.py | 101 + .../ddtrace/settings/config.py | 797 ++ .../settings/dynamic_instrumentation.py | 133 + .../ddtrace/settings/exception_debugging.py | 16 + .../ddtrace/settings/exceptions.py | 6 + .../ddtrace/settings/http.py | 83 + .../ddtrace/settings/integration.py | 154 + .../ddtrace/settings/peer_service.py | 39 + .../ddtrace/settings/profiling.py | 241 + .../ddtrace/sourcecode/__init__.py | 36 + .../ddtrace/sourcecode/_utils.py | 73 + .../ddtrace/sourcecode/setuptools_auto.py | 33 + .../aws-dd-forwarder-3.127.0/ddtrace/span.py | 614 ++ .../ddtrace/tracer.py | 1104 ++ .../ddtrace/tracing/__init__.py | 0 .../ddtrace/tracing/_span_link.py | 109 + .../ddtrace/tracing/trace_handlers.py | 584 ++ .../ddtrace/vendor/__init__.py | 116 + .../ddtrace/vendor/contextvars/__init__.py | 160 + .../ddtrace/vendor/debtcollector/__init__.py | 45 + .../ddtrace/vendor/debtcollector/_utils.py | 180 + .../ddtrace/vendor/debtcollector/moves.py | 197 + .../ddtrace/vendor/debtcollector/removals.py | 334 + .../ddtrace/vendor/debtcollector/renames.py | 45 + .../ddtrace/vendor/debtcollector/updating.py | 68 + .../ddtrace/vendor/dogstatsd/__init__.py | 5 + .../ddtrace/vendor/dogstatsd/base.py | 1232 +++ .../ddtrace/vendor/dogstatsd/compat.py | 34 + .../ddtrace/vendor/dogstatsd/container.py | 57 + .../ddtrace/vendor/dogstatsd/context.py | 88 + .../ddtrace/vendor/dogstatsd/context_async.py | 52 + .../ddtrace/vendor/dogstatsd/format.py | 33 + .../ddtrace/vendor/dogstatsd/route.py | 40 + .../ddtrace/vendor/monotonic/__init__.py | 169 + .../ddtrace/vendor/packaging/__init__.py | 0 .../ddtrace/vendor/packaging/_structures.py | 70 + .../ddtrace/vendor/packaging/version.py | 441 + .../ddtrace/vendor/psutil/__init__.py | 2516 +++++ .../ddtrace/vendor/psutil/_common.py | 651 ++ .../ddtrace/vendor/psutil/_compat.py | 332 + .../ddtrace/vendor/psutil/_psaix.py | 554 + .../ddtrace/vendor/psutil/_psbsd.py | 905 ++ .../ddtrace/vendor/psutil/_pslinux.py | 2096 ++++ .../ddtrace/vendor/psutil/_psosx.py | 568 + .../ddtrace/vendor/psutil/_psposix.py | 179 + .../ddtrace/vendor/psutil/_pssunos.py | 720 ++ ...util_linux.cpython-311-x86_64-linux-gnu.so | Bin 0 -> 29376 bytes ...util_posix.cpython-311-x86_64-linux-gnu.so | Bin 0 -> 22696 bytes .../ddtrace/vendor/psutil/_pswindows.py | 1127 ++ .../ddtrace/vendor/psutil/setup.py | 227 + .../ddtrace/vendor/sqlcommenter/__init__.py | 60 + .../ddtrace/vendor/wrapt/LICENSE | 24 + .../ddtrace/vendor/wrapt/__init__.py | 27 + .../_wrappers.cpython-311-x86_64-linux-gnu.so | Bin 0 -> 65368 bytes .../ddtrace/vendor/wrapt/arguments.py | 38 + .../ddtrace/vendor/wrapt/decorators.py | 541 + .../ddtrace/vendor/wrapt/importer.py | 286 + .../ddtrace/vendor/wrapt/setup.py | 7 + .../ddtrace/vendor/wrapt/wrappers.py | 1001 ++ .../ddtrace/version.py | 17 + .../deprecated/__init__.py | 15 + .../deprecated/classic.py | 292 + .../deprecated/sphinx.py | 262 + .../enhanced_lambda_metrics.py | 376 + .../envier-0.5.2.dist-info/INSTALLER | 1 + .../envier-0.5.2.dist-info/METADATA | 137 + .../envier-0.5.2.dist-info/RECORD | 16 + .../envier-0.5.2.dist-info/REQUESTED | 0 .../envier-0.5.2.dist-info/WHEEL | 4 + .../envier-0.5.2.dist-info/licenses/LICENSE | 21 + .../envier/__init__.py | 5 + .../envier/_version.py | 16 + .../aws-dd-forwarder-3.127.0/envier/env.py | 478 + .../aws-dd-forwarder-3.127.0/envier/mypy.py | 105 + .../envier/validators.py | 30 + .../exceptiongroup-1.2.2.dist-info/INSTALLER | 1 + .../exceptiongroup-1.2.2.dist-info/LICENSE | 73 + .../exceptiongroup-1.2.2.dist-info/METADATA | 157 + .../exceptiongroup-1.2.2.dist-info/RECORD | 19 + .../exceptiongroup-1.2.2.dist-info/REQUESTED | 0 .../exceptiongroup-1.2.2.dist-info/WHEEL | 4 + .../exceptiongroup/__init__.py | 46 + .../exceptiongroup/_catch.py | 138 + .../exceptiongroup/_exceptions.py | 321 + .../exceptiongroup/_formatting.py | 601 ++ .../exceptiongroup/_suppress.py | 55 + .../exceptiongroup/_version.py | 16 + .../exceptiongroup/py.typed | 0 lambdas/aws-dd-forwarder-3.127.0/forwarder.py | 173 + .../aws-dd-forwarder-3.127.0/google/.DS_Store | Bin 0 -> 6148 bytes .../google/_upb/_message.abi3.so | Bin 0 -> 390752 bytes .../google/protobuf/__init__.py | 10 + .../google/protobuf/any_pb2.py | 37 + .../google/protobuf/api_pb2.py | 43 + .../google/protobuf/compiler/__init__.py | 0 .../google/protobuf/compiler/plugin_pb2.py | 46 + .../google/protobuf/descriptor.py | 1511 +++ .../google/protobuf/descriptor_database.py | 154 + .../google/protobuf/descriptor_pb2.py | 3169 ++++++ .../google/protobuf/descriptor_pool.py | 1355 +++ .../google/protobuf/duration_pb2.py | 37 + .../google/protobuf/empty_pb2.py | 37 + .../google/protobuf/field_mask_pb2.py | 37 + .../google/protobuf/internal/__init__.py | 7 + .../protobuf/internal/_parameterized.py | 420 + .../protobuf/internal/api_implementation.py | 142 + .../google/protobuf/internal/builder.py | 117 + .../google/protobuf/internal/containers.py | 677 ++ .../google/protobuf/internal/decoder.py | 1036 ++ .../google/protobuf/internal/encoder.py | 806 ++ .../protobuf/internal/enum_type_wrapper.py | 112 + .../protobuf/internal/extension_dict.py | 194 + .../google/protobuf/internal/field_mask.py | 310 + .../protobuf/internal/message_listener.py | 55 + .../internal/python_edition_defaults.py | 5 + .../protobuf/internal/python_message.py | 1580 +++ .../protobuf/internal/testing_refleaks.py | 119 + .../google/protobuf/internal/type_checkers.py | 408 + .../protobuf/internal/well_known_types.py | 678 ++ .../google/protobuf/internal/wire_format.py | 245 + .../google/protobuf/json_format.py | 1069 ++ .../google/protobuf/message.py | 422 + .../google/protobuf/message_factory.py | 237 + .../google/protobuf/proto.py | 116 + .../google/protobuf/proto_builder.py | 111 + .../google/protobuf/proto_json.py | 83 + .../google/protobuf/pyext/__init__.py | 0 .../google/protobuf/pyext/cpp_message.py | 49 + .../google/protobuf/reflection.py | 85 + .../google/protobuf/runtime_version.py | 123 + .../google/protobuf/service.py | 213 + .../google/protobuf/service_reflection.py | 272 + .../google/protobuf/source_context_pb2.py | 37 + .../google/protobuf/struct_pb2.py | 47 + .../google/protobuf/symbol_database.py | 197 + .../google/protobuf/testdata/__init__.py | 0 .../google/protobuf/text_encoding.py | 106 + .../google/protobuf/text_format.py | 1864 ++++ .../google/protobuf/timestamp_pb2.py | 37 + .../google/protobuf/type_pb2.py | 53 + .../google/protobuf/unknown_fields.py | 97 + .../google/protobuf/util/__init__.py | 0 .../google/protobuf/wrappers_pb2.py | 53 + .../idna-3.7.dist-info/INSTALLER | 1 + .../idna-3.7.dist-info/LICENSE.md | 31 + .../idna-3.7.dist-info/METADATA | 243 + .../idna-3.7.dist-info/RECORD | 23 + .../idna-3.7.dist-info/REQUESTED | 0 .../idna-3.7.dist-info/WHEEL | 4 + .../aws-dd-forwarder-3.127.0/idna/__init__.py | 44 + .../aws-dd-forwarder-3.127.0/idna/codec.py | 118 + .../aws-dd-forwarder-3.127.0/idna/compat.py | 13 + lambdas/aws-dd-forwarder-3.127.0/idna/core.py | 395 + .../aws-dd-forwarder-3.127.0/idna/idnadata.py | 4245 ++++++++ .../idna/intranges.py | 54 + .../idna/package_data.py | 2 + .../aws-dd-forwarder-3.127.0/idna/py.typed | 0 .../idna/uts46data.py | 8598 +++++++++++++++ .../INSTALLER | 1 + .../LICENSE | 202 + .../METADATA | 129 + .../importlib_metadata-8.4.0.dist-info/RECORD | 32 + .../REQUESTED | 0 .../importlib_metadata-8.4.0.dist-info/WHEEL | 5 + .../top_level.txt | 1 + .../importlib_metadata/.DS_Store | Bin 0 -> 6148 bytes .../importlib_metadata/__init__.py | 1120 ++ .../importlib_metadata/_adapters.py | 83 + .../importlib_metadata/_collections.py | 30 + .../importlib_metadata/_compat.py | 57 + .../importlib_metadata/_functools.py | 104 + .../importlib_metadata/_itertools.py | 171 + .../importlib_metadata/_meta.py | 67 + .../importlib_metadata/_text.py | 99 + .../importlib_metadata/compat/__init__.py | 0 .../importlib_metadata/compat/py311.py | 22 + .../importlib_metadata/compat/py39.py | 36 + .../importlib_metadata/diagnose.py | 21 + .../importlib_metadata/py.typed | 0 .../lambda_function.py | 136 + .../logs/datadog_batcher.py | 44 + .../logs/datadog_client.py | 37 + .../logs/datadog_http_client.py | 94 + .../logs/datadog_scrubber.py | 36 + .../logs/datadog_tcp_client.py | 72 + .../logs/exceptions.py | 12 + .../aws-dd-forwarder-3.127.0/logs/helpers.py | 93 + .../opentelemetry/.DS_Store | Bin 0 -> 8196 bytes .../opentelemetry/_events/__init__.py | 229 + .../opentelemetry/_logs/__init__.py | 59 + .../opentelemetry/_logs/_internal/__init__.py | 292 + .../opentelemetry/_logs/py.typed | 0 .../opentelemetry/_logs/severity/__init__.py | 115 + .../opentelemetry/attributes/__init__.py | 204 + .../opentelemetry/attributes/py.typed | 0 .../opentelemetry/baggage/__init__.py | 132 + .../baggage/propagation/__init__.py | 146 + .../opentelemetry/baggage/py.typed | 0 .../opentelemetry/context/__init__.py | 175 + .../opentelemetry/context/context.py | 53 + .../context/contextvars_context.py | 53 + .../opentelemetry/context/py.typed | 0 .../environment_variables/__init__.py | 88 + .../environment_variables/py.typed | 0 .../opentelemetry/metrics/__init__.py | 136 + .../metrics/_internal/__init__.py | 817 ++ .../metrics/_internal/instrument.py | 445 + .../metrics/_internal/observation.py | 52 + .../opentelemetry/metrics/py.typed | 0 .../opentelemetry/propagate/__init__.py | 168 + .../opentelemetry/propagate/py.typed | 0 .../opentelemetry/propagators/composite.py | 91 + .../opentelemetry/propagators/py.typed | 0 .../opentelemetry/propagators/textmap.py | 197 + .../opentelemetry/py.typed | 0 .../opentelemetry/trace/__init__.py | 646 ++ .../trace/propagation/__init__.py | 51 + .../trace/propagation/tracecontext.py | 118 + .../opentelemetry/trace/py.typed | 0 .../opentelemetry/trace/span.py | 609 ++ .../opentelemetry/trace/status.py | 82 + .../opentelemetry/util/_decorator.py | 82 + .../opentelemetry/util/_importlib_metadata.py | 29 + .../opentelemetry/util/_once.py | 47 + .../opentelemetry/util/_providers.py | 54 + .../opentelemetry/util/py.typed | 0 .../opentelemetry/util/re.py | 114 + .../opentelemetry/util/types.py | 44 + .../opentelemetry/version/__init__.py | 15 + .../opentelemetry/version/py.typed | 0 .../INSTALLER | 1 + .../METADATA | 44 + .../opentelemetry_api-1.27.0.dist-info/RECORD | 79 + .../REQUESTED | 0 .../opentelemetry_api-1.27.0.dist-info/WHEEL | 4 + .../entry_points.txt | 15 + .../licenses/LICENSE | 201 + .../protobuf-5.28.2.dist-info/INSTALLER | 1 + .../protobuf-5.28.2.dist-info/LICENSE | 32 + .../protobuf-5.28.2.dist-info/METADATA | 17 + .../protobuf-5.28.2.dist-info/RECORD | 111 + .../protobuf-5.28.2.dist-info/REQUESTED | 0 .../protobuf-5.28.2.dist-info/WHEEL | 4 + .../requests-2.32.3.dist-info/INSTALLER | 1 + .../requests-2.32.3.dist-info/LICENSE | 175 + .../requests-2.32.3.dist-info/METADATA | 119 + .../requests-2.32.3.dist-info/RECORD | 43 + .../requests-2.32.3.dist-info/REQUESTED | 0 .../requests-2.32.3.dist-info/WHEEL | 5 + .../requests-2.32.3.dist-info/top_level.txt | 1 + .../requests/__init__.py | 184 + .../requests/__version__.py | 14 + .../requests/_internal_utils.py | 50 + .../requests/adapters.py | 719 ++ .../aws-dd-forwarder-3.127.0/requests/api.py | 157 + .../aws-dd-forwarder-3.127.0/requests/auth.py | 314 + .../requests/certs.py | 17 + .../requests/compat.py | 94 + .../requests/cookies.py | 561 + .../requests/exceptions.py | 151 + .../aws-dd-forwarder-3.127.0/requests/help.py | 134 + .../requests/hooks.py | 33 + .../requests/models.py | 1037 ++ .../requests/packages.py | 23 + .../requests/sessions.py | 831 ++ .../requests/status_codes.py | 128 + .../requests/structures.py | 99 + .../requests/utils.py | 1096 ++ .../INSTALLER | 1 + .../requests_futures-1.0.1.dist-info/LICENSE | 13 + .../requests_futures-1.0.1.dist-info/METADATA | 356 + .../requests_futures-1.0.1.dist-info/RECORD | 11 + .../REQUESTED | 0 .../requests_futures-1.0.1.dist-info/WHEEL | 6 + .../top_level.txt | 1 + .../requests_futures/__init__.py | 31 + .../requests_futures/sessions.py | 197 + .../aws-dd-forwarder-3.127.0/requirements.txt | 24 + .../aws-dd-forwarder-3.127.0/retry/enums.py | 10 + .../aws-dd-forwarder-3.127.0/retry/storage.py | 87 + lambdas/aws-dd-forwarder-3.127.0/settings.py | 312 + lambdas/aws-dd-forwarder-3.127.0/setup.py | 24 + .../six-1.16.0.dist-info/INSTALLER | 1 + .../six-1.16.0.dist-info/LICENSE | 18 + .../six-1.16.0.dist-info/METADATA | 49 + .../six-1.16.0.dist-info/RECORD | 9 + .../six-1.16.0.dist-info/REQUESTED | 0 .../six-1.16.0.dist-info/WHEEL | 6 + .../six-1.16.0.dist-info/top_level.txt | 1 + lambdas/aws-dd-forwarder-3.127.0/six.py | 998 ++ .../aws-dd-forwarder-3.127.0/steps/common.py | 114 + .../steps/enrichment.py | 225 + .../aws-dd-forwarder-3.127.0/steps/enums.py | 164 + .../steps/handlers/aws_attributes.py | 53 + .../steps/handlers/awslogs_handler.py | 260 + .../steps/handlers/s3_handler.py | 331 + .../aws-dd-forwarder-3.127.0/steps/parsing.py | 185 + .../steps/splitting.py | 74 + .../steps/transformation.py | 195 + lambdas/aws-dd-forwarder-3.127.0/telemetry.py | 61 + .../aws-dd-forwarder-3.127.0/template.yaml | 997 ++ .../tests/__init__.py | 19 + .../tests/test_awslogs_handler.py | 290 + .../tests/test_caching.py | 95 + .../tests/test_cloudtrail_s3.py | 156 + .../tests/test_customized_log_group.py | 60 + .../tests/test_enhanced_lambda_metrics.py | 339 + .../tests/test_enrichment.py | 142 + .../tests/test_lambda_function.py | 266 + .../tests/test_logs.py | 105 + .../tests/test_parsing.py | 345 + .../tests/test_s3_handler.py | 330 + .../tests/test_splitting.py | 55 + .../tests/test_transformation.py | 315 + .../cache_test_lambda/handler.py | 7 + .../external_lambda/handler.py | 13 + .../integration_tests/recorder/pb/__init__.py | 7 + .../integration_tests/recorder/pb/span_pb2.py | 68 + .../recorder/pb/trace_payload_pb2.py | 39 + .../recorder/pb/trace_pb2.py | 38 + .../integration_tests/recorder/recorder.py | 84 + .../tester/test_snapshots.py | 189 + .../trace_forwarder/__init__.py | 4 + .../trace_forwarder/bin/trace-intake.h | 84 + .../trace_forwarder/bin/trace-intake.so | Bin 0 -> 20257000 bytes .../trace_forwarder/connection.py | 34 + .../INSTALLER | 1 + .../LICENSE | 279 + .../METADATA | 67 + .../typing_extensions-4.12.2.dist-info/RECORD | 8 + .../REQUESTED | 0 .../typing_extensions-4.12.2.dist-info/WHEEL | 4 + .../typing_extensions.py | 3641 +++++++ .../urllib3-2.0.7.dist-info/INSTALLER | 1 + .../urllib3-2.0.7.dist-info/METADATA | 158 + .../urllib3-2.0.7.dist-info/RECORD | 71 + .../urllib3-2.0.7.dist-info/REQUESTED | 0 .../urllib3-2.0.7.dist-info/WHEEL | 4 + .../licenses/LICENSE.txt | 21 + .../urllib3/.DS_Store | Bin 0 -> 6148 bytes .../urllib3/__init__.py | 166 + .../urllib3/_base_connection.py | 173 + .../urllib3/_collections.py | 481 + .../urllib3/_request_methods.py | 217 + .../urllib3/_version.py | 4 + .../urllib3/connection.py | 906 ++ .../urllib3/connectionpool.py | 1183 +++ .../urllib3/contrib/__init__.py | 0 .../contrib/_securetransport/__init__.py | 0 .../contrib/_securetransport/bindings.py | 430 + .../contrib/_securetransport/low_level.py | 474 + .../urllib3/contrib/pyopenssl.py | 548 + .../urllib3/contrib/securetransport.py | 913 ++ .../urllib3/contrib/socks.py | 233 + .../urllib3/exceptions.py | 318 + .../urllib3/fields.py | 345 + .../urllib3/filepost.py | 89 + .../urllib3/poolmanager.py | 637 ++ .../aws-dd-forwarder-3.127.0/urllib3/py.typed | 2 + .../urllib3/response.py | 1132 ++ .../urllib3/util/__init__.py | 44 + .../urllib3/util/connection.py | 137 + .../urllib3/util/proxy.py | 43 + .../urllib3/util/request.py | 256 + .../urllib3/util/response.py | 101 + .../urllib3/util/retry.py | 529 + .../urllib3/util/ssl_.py | 515 + .../urllib3/util/ssl_match_hostname.py | 159 + .../urllib3/util/ssltransport.py | 280 + .../urllib3/util/timeout.py | 279 + .../urllib3/util/url.py | 471 + .../urllib3/util/util.py | 42 + .../urllib3/util/wait.py | 124 + .../wrapt-1.16.0.dist-info/INSTALLER | 1 + .../wrapt-1.16.0.dist-info/LICENSE | 24 + .../wrapt-1.16.0.dist-info/METADATA | 170 + .../wrapt-1.16.0.dist-info/RECORD | 24 + .../wrapt-1.16.0.dist-info/REQUESTED | 0 .../wrapt-1.16.0.dist-info/WHEEL | 8 + .../wrapt-1.16.0.dist-info/top_level.txt | 1 + .../wrapt/__init__.py | 30 + .../wrapt/__wrapt__.py | 14 + .../_wrappers.cpython-311-x86_64-linux-gnu.so | Bin 0 -> 192160 bytes .../wrapt/arguments.py | 38 + .../wrapt/decorators.py | 541 + .../wrapt/importer.py | 295 + .../aws-dd-forwarder-3.127.0/wrapt/patches.py | 141 + .../wrapt/weakrefs.py | 98 + .../wrapt/wrappers.py | 784 ++ .../xmltodict-0.14.1.dist-info/INSTALLER | 1 + .../xmltodict-0.14.1.dist-info/LICENSE | 7 + .../xmltodict-0.14.1.dist-info/METADATA | 288 + .../xmltodict-0.14.1.dist-info/RECORD | 9 + .../xmltodict-0.14.1.dist-info/REQUESTED | 0 .../xmltodict-0.14.1.dist-info/WHEEL | 6 + .../xmltodict-0.14.1.dist-info/top_level.txt | 1 + lambdas/aws-dd-forwarder-3.127.0/xmltodict.py | 522 + .../zipp-3.20.2.dist-info/INSTALLER | 1 + .../zipp-3.20.2.dist-info/LICENSE | 17 + .../zipp-3.20.2.dist-info/METADATA | 106 + .../zipp-3.20.2.dist-info/RECORD | 17 + .../zipp-3.20.2.dist-info/REQUESTED | 0 .../zipp-3.20.2.dist-info/WHEEL | 5 + .../zipp-3.20.2.dist-info/top_level.txt | 1 + .../aws-dd-forwarder-3.127.0/zipp/.DS_Store | Bin 0 -> 6148 bytes .../aws-dd-forwarder-3.127.0/zipp/__init__.py | 452 + .../zipp/compat/__init__.py | 0 .../zipp/compat/overlay.py | 37 + .../zipp/compat/py310.py | 13 + lambdas/aws-dd-forwarder-3.127.0/zipp/glob.py | 114 + locals.tf | 21 +- outputs.tf | 12 + policies/datadog-forwarder.json.tpl | 13 + policies/kms-encrypt.json.tpl | 13 + policies/s3-object-read.json.tpl | 26 + policies/s3-object-rw.json.tpl | 21 + .../secrets-manager-get-secret-value.json.tpl | 12 + s3-datadog-forwarder-lambda.tf | 105 + variables.tf | 20 + 1309 files changed, 227635 insertions(+), 8 deletions(-) create mode 100644 datadog-forwarder-lambda.tf create mode 100644 lambdas/aws-dd-forwarder-3.127.0/.DS_Store create mode 100644 lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/INSTALLER create mode 100644 lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/LICENSE.rst create mode 100644 lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/METADATA create mode 100644 lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/RECORD create mode 100644 lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/REQUESTED create mode 100644 lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/WHEEL create mode 100644 lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/top_level.txt create mode 100644 lambdas/aws-dd-forwarder-3.127.0/META_INF/aws_signer_signature_v1.0.SF create mode 100644 lambdas/aws-dd-forwarder-3.127.0/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attr/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attr/__init__.pyi create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attr/_cmp.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attr/_cmp.pyi create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attr/_compat.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attr/_config.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attr/_funcs.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attr/_make.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attr/_next_gen.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attr/_typing_compat.pyi create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attr/_version_info.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attr/_version_info.pyi create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attr/converters.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attr/converters.pyi create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attr/exceptions.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attr/exceptions.pyi create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attr/filters.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attr/filters.pyi create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attr/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attr/setters.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attr/setters.pyi create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attr/validators.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attr/validators.pyi create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attrs-24.2.0.dist-info/INSTALLER create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attrs-24.2.0.dist-info/METADATA create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attrs-24.2.0.dist-info/RECORD create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attrs-24.2.0.dist-info/REQUESTED create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attrs-24.2.0.dist-info/WHEEL create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attrs-24.2.0.dist-info/licenses/LICENSE create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attrs/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attrs/__init__.pyi create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attrs/converters.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attrs/exceptions.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attrs/filters.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attrs/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attrs/setters.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/attrs/validators.py create mode 100755 lambdas/aws-dd-forwarder-3.127.0/bin/ddtrace-run create mode 100755 lambdas/aws-dd-forwarder-3.127.0/bin/dog create mode 100755 lambdas/aws-dd-forwarder-3.127.0/bin/dogshell create mode 100755 lambdas/aws-dd-forwarder-3.127.0/bin/dogshellwrap create mode 100755 lambdas/aws-dd-forwarder-3.127.0/bin/dogwrap create mode 100755 lambdas/aws-dd-forwarder-3.127.0/bin/normalizer create mode 100644 lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/COPYING create mode 100644 lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/INSTALLER create mode 100644 lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/METADATA create mode 100644 lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/RECORD create mode 100644 lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/REQUESTED create mode 100644 lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/WHEEL create mode 100644 lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/top_level.txt create mode 100644 lambdas/aws-dd-forwarder-3.127.0/bytecode/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/bytecode/bytecode.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/bytecode/cfg.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/bytecode/concrete.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/bytecode/flags.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/bytecode/instr.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/bytecode/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/bytecode/version.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/caching/base_tags_cache.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/caching/cache_layer.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/caching/cloudwatch_log_group_cache.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/caching/common.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/caching/lambda_cache.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/caching/s3_tags_cache.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/caching/step_functions_cache.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattr/.DS_Store create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattr/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattr/converters.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattr/disambiguators.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattr/dispatch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattr/errors.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattr/gen.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/bson.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/json.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/msgpack.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/orjson.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/pyyaml.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/tomlkit.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/ujson.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattr/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs-24.1.2.dist-info/INSTALLER create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs-24.1.2.dist-info/METADATA create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs-24.1.2.dist-info/RECORD create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs-24.1.2.dist-info/REQUESTED create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs-24.1.2.dist-info/WHEEL create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs-24.1.2.dist-info/licenses/LICENSE create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/.DS_Store create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/_compat.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/_generics.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/cols.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/converters.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/disambiguators.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/dispatch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/errors.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/fns.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/gen/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/gen/_consts.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/gen/_generics.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/gen/_lc.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/gen/_shared.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/gen/typeddicts.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/bson.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/cbor2.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/json.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/msgpack.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/msgspec.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/orjson.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/pyyaml.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/tomlkit.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/ujson.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/strategies/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/strategies/_class_methods.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/strategies/_subclasses.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/strategies/_unions.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/cattrs/v.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/INSTALLER create mode 100644 lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/LICENSE create mode 100644 lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/METADATA create mode 100644 lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/RECORD create mode 100644 lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/REQUESTED create mode 100644 lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/WHEEL create mode 100644 lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/top_level.txt create mode 100644 lambdas/aws-dd-forwarder-3.127.0/certifi/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/certifi/__main__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/certifi/cacert.pem create mode 100644 lambdas/aws-dd-forwarder-3.127.0/certifi/core.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/certifi/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/INSTALLER create mode 100644 lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/LICENSE create mode 100644 lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/METADATA create mode 100644 lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/RECORD create mode 100644 lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/REQUESTED create mode 100644 lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/WHEEL create mode 100644 lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/entry_points.txt create mode 100644 lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/top_level.txt create mode 100644 lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/.DS_Store create mode 100644 lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/__main__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/api.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/cd.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/cli/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/cli/__main__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/constant.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/legacy.py create mode 100755 lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/md.cpython-311-x86_64-linux-gnu.so create mode 100644 lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/md.py create mode 100755 lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/md__mypyc.cpython-311-x86_64-linux-gnu.so create mode 100644 lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/models.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/version.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/customized_log_group.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/INSTALLER create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/METADATA create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/RECORD create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/REQUESTED create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/WHEEL create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/entry_points.txt create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/licenses/LICENSE create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/licenses/LICENSE-3rdparty.csv create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/.DS_Store create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/api_client.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/aws_integration.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/aws_log_integration.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/azure_integration.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/comments.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/dashboard_list_v2.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/dashboard_lists.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/dashboards.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/distributions.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/downtimes.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/events.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/exceptions.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/format.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/gcp_integration.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/graphs.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/hosts.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/http_client.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/infrastructure.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/logs.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/metadata.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/metrics.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/monitors.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/permissions.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/resources.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/roles.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/screenboards.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/service_checks.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/service_level_objectives.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/synthetics.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/tags.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/timeboards.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/api/users.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/comment.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/common.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/dashboard.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/dashboard_list.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/downtime.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/event.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/host.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/metric.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/monitor.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/screenboard.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/search.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/service_check.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/service_level_objective.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/tag.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/timeboard.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/wrap.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/dogstatsd/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/dogstatsd/base.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/dogstatsd/container.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/dogstatsd/context.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/dogstatsd/context_async.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/dogstatsd/route.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/aws_lambda.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/base.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/events.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/metrics.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/periodic_timer.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/reporters.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/util/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/util/cli.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/util/compat.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/util/config.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/util/deprecation.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/util/format.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/util/hostname.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog/version.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog_lambda-5.87.0.dist-info/INSTALLER create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog_lambda-5.87.0.dist-info/LICENSE create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog_lambda-5.87.0.dist-info/METADATA create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog_lambda-5.87.0.dist-info/RECORD create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog_lambda-5.87.0.dist-info/REQUESTED create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog_lambda-5.87.0.dist-info/WHEEL create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/api.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/cold_start.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/dogstatsd.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/extension.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/handler.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/metric.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/module_name.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/stats_writer.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/statsd_writer.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/tag_object.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/tags.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/thread_stats_writer.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/tracing.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/trigger.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/wrapper.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/xray.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/INSTALLER create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/LICENSE create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/LICENSE-3rdparty.csv create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/METADATA create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/NOTICE create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/RECORD create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/REQUESTED create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/WHEEL create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/top_level.txt create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddsketch/.DS_Store create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddsketch/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddsketch/__version.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddsketch/_version.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddsketch/ddsketch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddsketch/mapping.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddsketch/pb/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddsketch/pb/ddsketch_pb2.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddsketch/pb/ddsketch_pre319_pb2.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddsketch/pb/proto.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddsketch/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddsketch/store.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/INSTALLER create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/LICENSE create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/LICENSE.Apache create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/LICENSE.BSD3 create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/METADATA create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/NOTICE create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/RECORD create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/REQUESTED create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/WHEEL create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/entry_points.txt create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/top_level.txt create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/.DS_Store create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/_hooks.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/_logger.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/_monkey.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/_trace/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/_trace/_limits.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/_version.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_api_security/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_api_security/api_manager.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_asm_request_context.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_capabilities.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_ddwaf/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_ddwaf/ddwaf_types.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_ddwaf/libddwaf/x86_64/lib/libddwaf.so create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_deduplications.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_handlers.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_ast/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_ast/ast_patching.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_ast/visitor.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_input_info.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_loader.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_metrics.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_overhead_control_engine.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_patch_modules.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_patches/json_tainting.py create mode 100755 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_stacktrace.cpython-311-x86_64-linux-gnu.so create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_dict.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/CMakeLists.txt create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/README.txt create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/__init__.py create mode 100755 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_native.cpython-311-x86_64-linux-gnu.so create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/CMakeLists.txt create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/__main__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/_version.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/commands.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/setup_helpers.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/FindCatch.cmake create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/FindEigen3.cmake create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/FindPythonLibsNew.cmake create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/JoinPaths.cmake create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/check-style.sh create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/cmake_uninstall.cmake.in create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/codespell_ignore_lines_from_errors.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/libsize.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/make_changelog.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pybind11.pc.in create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pybind11Common.cmake create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pybind11Config.cmake.in create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pybind11NewTools.cmake create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pybind11Tools.cmake create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pyproject.toml create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/setup_global.py.in create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/setup_main.py.in create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/aspects.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/clean.sh create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/processor.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/reporter.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/_base.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/ast_taint.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/command_injection.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/path_traversal.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/sql_injection.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/ssrf.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/weak_cipher.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/weak_hash.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/weak_randomness.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_metrics.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_processor.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/module_names_py310.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/module_names_py311.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/module_names_py36.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/module_names_py37.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/module_names_py38.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/module_names_py39.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_remoteconfiguration.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_trace_utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/iast/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/rules.json create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/trace_utils/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/auto.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/bootstrap/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/bootstrap/preload.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/bootstrap/sitecustomize.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/commands/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/context.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiobotocore/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiobotocore/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiohttp/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiohttp/middlewares.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiohttp/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiohttp_jinja2/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiohttp_jinja2/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiomysql/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiomysql/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiopg/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiopg/connection.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiopg/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aioredis/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aioredis/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/algoliasearch/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/algoliasearch/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aredis/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aredis/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asgi/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asgi/middleware.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asgi/utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncio/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncio/compat.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncio/helpers.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncio/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncio/provider.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncio/wrappers.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncpg/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncpg/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aws_lambda/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aws_lambda/_cold_start.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aws_lambda/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/boto/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/boto/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/services/bedrock.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/services/kinesis.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/services/sqs.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/services/stepfunctions.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/bottle/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/bottle/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/bottle/trace.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/cassandra/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/cassandra/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/cassandra/session.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/celery/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/celery/app.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/celery/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/celery/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/celery/signals.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/celery/utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/cherrypy/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/cherrypy/middleware.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/consul/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/consul/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/coverage/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/coverage/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/coverage/data.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/coverage/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/coverage/utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/dbapi/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/dbapi_async/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/django/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/django/_asgi.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/django/compat.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/django/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/django/restframework.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/django/utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/dogpile_cache/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/dogpile_cache/lock.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/dogpile_cache/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/dogpile_cache/region.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/elasticsearch/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/elasticsearch/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/elasticsearch/quantize.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/falcon/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/falcon/middleware.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/falcon/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/fastapi/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/fastapi/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask/wrappers.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask_cache/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask_cache/tracers.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask_cache/utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask_login/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask_login/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/futures/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/futures/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/futures/threading.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/gevent/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/gevent/greenlet.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/gevent/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/gevent/provider.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/graphql/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/graphql/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/aio_client_interceptor.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/aio_server_interceptor.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/client_interceptor.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/server_interceptor.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/gunicorn/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/httplib/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/httplib/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/httpx/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/httpx/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/jinja2/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/jinja2/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/jinja2/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/kafka/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/kafka/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/kombu/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/kombu/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/kombu/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/kombu/utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/langchain/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/langchain/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/langchain/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/logbook/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/logbook/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/logging/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/logging/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/logging/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/loguru/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/loguru/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mako/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mako/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mako/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mariadb/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mariadb/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/molten/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/molten/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/molten/wrappers.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mongoengine/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mongoengine/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mongoengine/trace.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mysql/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mysql/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mysqldb/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mysqldb/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/openai/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/openai/_endpoint_hooks.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/openai/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/openai/utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/async_connection.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/async_cursor.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/connection.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/cursor.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/extensions.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pylibmc/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pylibmc/addrs.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pylibmc/client.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pylibmc/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymemcache/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymemcache/client.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymemcache/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymongo/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymongo/client.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymongo/parse.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymongo/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymysql/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymysql/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pynamodb/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pynamodb/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pyodbc/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pyodbc/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pyramid/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pyramid/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pyramid/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pyramid/trace.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest/newhooks.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest/plugin.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest_bdd/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest_bdd/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest_bdd/plugin.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest_benchmark/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest_benchmark/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest_benchmark/plugin.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/redis/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/redis/asyncio_patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/redis/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/rediscluster/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/rediscluster/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/requests/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/requests/connection.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/requests/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/requests/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/requests/session.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/rq/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sanic/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sanic/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/snowflake/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/snowflake/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sqlalchemy/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sqlalchemy/engine.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sqlalchemy/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sqlite3/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sqlite3/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/starlette/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/starlette/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/structlog/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/structlog/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/subprocess/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/subprocess/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/subprocess/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/application.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/decorators.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/handlers.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/stack_context.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/template.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/trace_utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/trace_utils_async.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/trace_utils_redis.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/unittest/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/unittest/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/unittest/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/urllib3/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/urllib3/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/vertica/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/vertica/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/wsgi/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/wsgi/wsgi.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/yaaredis/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/yaaredis/patch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/data_streams.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_async.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_config.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_debugger.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_encoding.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_exception/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_exception/auto_instrument.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_expressions.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_function/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_function/discovery.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_function/store.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_metrics.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_probe/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_probe/model.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_probe/registry.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_probe/remoteconfig.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_probe/status.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_redaction.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_safety.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/collector.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/metric_sample.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/model.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/snapshot.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/tracing.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_uploader.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/aws.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/cassandra.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/ci.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/consul.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/db.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/elasticsearch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/git.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/http.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/kafka.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/kombu.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/memcached.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/mongo.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/net.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/redis.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/sql.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/test.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/user.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/filters.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/README.md create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/__init__.py create mode 100755 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/_encoding.cpython-311-x86_64-linux-gnu.so create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/_encoding.pyi create mode 100755 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/_rand.cpython-311-x86_64-linux-gnu.so create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/_rand.pyi create mode 100755 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/_tagset.cpython-311-x86_64-linux-gnu.so create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/_tagset.pyi create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/_utils.pxd create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/agent.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/assembly.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/atexit.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/coverage.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/encoder.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/filters.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/git_client.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/recorder.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/telemetry/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/telemetry/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/telemetry/git.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/telemetry/utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/writer.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/codeowners.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/compat.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/core/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/core/event_hub.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/datadog/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/datadog/profiling/__init__.py create mode 100755 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/datadog/profiling/_ddup.cpython-311-x86_64-linux-gnu.so create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/datadog/profiling/_ddup.pyi create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/datadog/profiling/ddup.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/datadog/profiling/utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/datastreams/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/datastreams/botocore.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/datastreams/encoding.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/datastreams/fnv.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/datastreams/kafka.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/datastreams/kombu.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/datastreams/processor.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/datastreams/utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/debug.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/dogstatsd.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/encoding.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/forksafe.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/gitmetadata.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/glob_matching.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/hostname.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/http.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/injection.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/llmobs/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/llmobs/integrations/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/llmobs/integrations/base.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/llmobs/integrations/bedrock.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/llmobs/integrations/langchain.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/llmobs/integrations/openai.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/llmobs/writer.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/log_writer.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/logger.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/metrics.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/module.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/packages.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/periodic.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/processor/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/processor/endpoint_call_counter.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/processor/stats.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/processor/trace.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/rate_limiter.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/remoteconfig/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/remoteconfig/_connectors.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/remoteconfig/_publishers.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/remoteconfig/_pubsub.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/remoteconfig/_subscribers.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/remoteconfig/client.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/remoteconfig/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/remoteconfig/utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/remoteconfig/worker.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/runtime/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/runtime/collector.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/runtime/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/runtime/container.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/runtime/metric_collectors.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/runtime/runtime_metrics.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/runtime/tag_collectors.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/safety.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/sampling.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/schema/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/schema/span_attribute_schema.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/serverless/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/serverless/mini_agent.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/service.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/sma.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/telemetry/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/telemetry/constants.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/telemetry/data.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/telemetry/metrics.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/telemetry/metrics_namespaces.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/telemetry/writer.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/tracemethods.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/uds.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/utils/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/utils/attrdict.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/utils/cache.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/utils/config.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/utils/deprecations.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/utils/formats.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/utils/http.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/utils/importlib.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/utils/inspection.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/utils/retry.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/utils/signals.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/utils/time.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/utils/version.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/utils/wrappers.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/uwsgi.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/wrapping/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/wrapping/asyncs.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/wrapping/generators.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/writer/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/writer/writer.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/writer/writer_client.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/opentelemetry/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/opentelemetry/_context.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/opentelemetry/_span.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/opentelemetry/_trace.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/opentracer/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/opentracer/helpers.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/opentracer/propagation/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/opentracer/propagation/binary.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/opentracer/propagation/http.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/opentracer/propagation/propagator.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/opentracer/propagation/text.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/opentracer/settings.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/opentracer/span.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/opentracer/span_context.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/opentracer/tags.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/opentracer/tracer.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/opentracer/utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/pin.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/_asyncio.py create mode 100755 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/_build.cpython-311-x86_64-linux-gnu.so create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/_build.pyi create mode 100755 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/_threading.cpython-311-x86_64-linux-gnu.so create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/_threading.pyi create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/_traceback.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/auto.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/bootstrap/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/bootstrap/sitecustomize.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/collector/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/collector/_lock.py create mode 100755 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/collector/_memalloc.cpython-311-x86_64-linux-gnu.so create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/collector/_memalloc.pyi create mode 100755 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/collector/_task.cpython-311-x86_64-linux-gnu.so create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/collector/_task.pyi create mode 100755 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/collector/_traceback.cpython-311-x86_64-linux-gnu.so create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/collector/_traceback.pyi create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/collector/asyncio.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/collector/memalloc.py create mode 100755 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/collector/stack.cpython-311-x86_64-linux-gnu.so create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/collector/stack.pyi create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/collector/stack_event.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/collector/threading.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/event.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/exporter/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/exporter/file.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/exporter/http.py create mode 100755 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/exporter/pprof.cpython-311-x86_64-linux-gnu.so create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/exporter/pprof.proto create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/exporter/pprof.pyi create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/exporter/pprof_312_pb2.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/exporter/pprof_319_pb2.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/exporter/pprof_3_pb2.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/exporter/pprof_421_pb2.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/profiler.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/recorder.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/profiling/scheduler.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/propagation/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/propagation/_database_monitoring.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/propagation/_utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/propagation/http.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/provider.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/runtime/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/sampler.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/sampling_rule.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/settings/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/settings/_database_monitoring.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/settings/asm.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/settings/config.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/settings/dynamic_instrumentation.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/settings/exception_debugging.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/settings/exceptions.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/settings/http.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/settings/integration.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/settings/peer_service.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/settings/profiling.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/sourcecode/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/sourcecode/_utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/sourcecode/setuptools_auto.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/span.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/tracer.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/tracing/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/tracing/_span_link.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/tracing/trace_handlers.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/contextvars/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/debtcollector/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/debtcollector/_utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/debtcollector/moves.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/debtcollector/removals.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/debtcollector/renames.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/debtcollector/updating.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/dogstatsd/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/dogstatsd/base.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/dogstatsd/compat.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/dogstatsd/container.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/dogstatsd/context.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/dogstatsd/context_async.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/dogstatsd/format.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/dogstatsd/route.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/monotonic/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/packaging/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/packaging/_structures.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/packaging/version.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/psutil/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/psutil/_common.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/psutil/_compat.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/psutil/_psaix.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/psutil/_psbsd.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/psutil/_pslinux.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/psutil/_psosx.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/psutil/_psposix.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/psutil/_pssunos.py create mode 100755 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/psutil/_psutil_linux.cpython-311-x86_64-linux-gnu.so create mode 100755 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/psutil/_psutil_posix.cpython-311-x86_64-linux-gnu.so create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/psutil/_pswindows.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/psutil/setup.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/sqlcommenter/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/wrapt/LICENSE create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/wrapt/__init__.py create mode 100755 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/wrapt/_wrappers.cpython-311-x86_64-linux-gnu.so create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/wrapt/arguments.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/wrapt/decorators.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/wrapt/importer.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/wrapt/setup.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/vendor/wrapt/wrappers.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/ddtrace/version.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/deprecated/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/deprecated/classic.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/deprecated/sphinx.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/enhanced_lambda_metrics.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/envier-0.5.2.dist-info/INSTALLER create mode 100644 lambdas/aws-dd-forwarder-3.127.0/envier-0.5.2.dist-info/METADATA create mode 100644 lambdas/aws-dd-forwarder-3.127.0/envier-0.5.2.dist-info/RECORD create mode 100644 lambdas/aws-dd-forwarder-3.127.0/envier-0.5.2.dist-info/REQUESTED create mode 100644 lambdas/aws-dd-forwarder-3.127.0/envier-0.5.2.dist-info/WHEEL create mode 100644 lambdas/aws-dd-forwarder-3.127.0/envier-0.5.2.dist-info/licenses/LICENSE create mode 100644 lambdas/aws-dd-forwarder-3.127.0/envier/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/envier/_version.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/envier/env.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/envier/mypy.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/envier/validators.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/exceptiongroup-1.2.2.dist-info/INSTALLER create mode 100644 lambdas/aws-dd-forwarder-3.127.0/exceptiongroup-1.2.2.dist-info/LICENSE create mode 100644 lambdas/aws-dd-forwarder-3.127.0/exceptiongroup-1.2.2.dist-info/METADATA create mode 100644 lambdas/aws-dd-forwarder-3.127.0/exceptiongroup-1.2.2.dist-info/RECORD create mode 100644 lambdas/aws-dd-forwarder-3.127.0/exceptiongroup-1.2.2.dist-info/REQUESTED create mode 100644 lambdas/aws-dd-forwarder-3.127.0/exceptiongroup-1.2.2.dist-info/WHEEL create mode 100644 lambdas/aws-dd-forwarder-3.127.0/exceptiongroup/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/exceptiongroup/_catch.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/exceptiongroup/_exceptions.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/exceptiongroup/_formatting.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/exceptiongroup/_suppress.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/exceptiongroup/_version.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/exceptiongroup/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/forwarder.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/.DS_Store create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/_upb/_message.abi3.so create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/any_pb2.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/api_pb2.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/compiler/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/compiler/plugin_pb2.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/descriptor.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/descriptor_database.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/descriptor_pb2.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/descriptor_pool.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/duration_pb2.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/empty_pb2.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/field_mask_pb2.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/internal/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/internal/_parameterized.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/internal/api_implementation.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/internal/builder.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/internal/containers.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/internal/decoder.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/internal/encoder.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/internal/enum_type_wrapper.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/internal/extension_dict.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/internal/field_mask.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/internal/message_listener.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/internal/python_edition_defaults.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/internal/python_message.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/internal/testing_refleaks.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/internal/type_checkers.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/internal/well_known_types.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/internal/wire_format.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/json_format.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/message.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/message_factory.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/proto.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/proto_builder.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/proto_json.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/pyext/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/pyext/cpp_message.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/reflection.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/runtime_version.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/service.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/service_reflection.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/source_context_pb2.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/struct_pb2.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/symbol_database.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/testdata/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/text_encoding.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/text_format.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/timestamp_pb2.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/type_pb2.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/unknown_fields.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/util/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/google/protobuf/wrappers_pb2.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/idna-3.7.dist-info/INSTALLER create mode 100644 lambdas/aws-dd-forwarder-3.127.0/idna-3.7.dist-info/LICENSE.md create mode 100644 lambdas/aws-dd-forwarder-3.127.0/idna-3.7.dist-info/METADATA create mode 100644 lambdas/aws-dd-forwarder-3.127.0/idna-3.7.dist-info/RECORD create mode 100644 lambdas/aws-dd-forwarder-3.127.0/idna-3.7.dist-info/REQUESTED create mode 100644 lambdas/aws-dd-forwarder-3.127.0/idna-3.7.dist-info/WHEEL create mode 100644 lambdas/aws-dd-forwarder-3.127.0/idna/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/idna/codec.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/idna/compat.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/idna/core.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/idna/idnadata.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/idna/intranges.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/idna/package_data.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/idna/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/idna/uts46data.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/importlib_metadata-8.4.0.dist-info/INSTALLER create mode 100644 lambdas/aws-dd-forwarder-3.127.0/importlib_metadata-8.4.0.dist-info/LICENSE create mode 100644 lambdas/aws-dd-forwarder-3.127.0/importlib_metadata-8.4.0.dist-info/METADATA create mode 100644 lambdas/aws-dd-forwarder-3.127.0/importlib_metadata-8.4.0.dist-info/RECORD create mode 100644 lambdas/aws-dd-forwarder-3.127.0/importlib_metadata-8.4.0.dist-info/REQUESTED create mode 100644 lambdas/aws-dd-forwarder-3.127.0/importlib_metadata-8.4.0.dist-info/WHEEL create mode 100644 lambdas/aws-dd-forwarder-3.127.0/importlib_metadata-8.4.0.dist-info/top_level.txt create mode 100644 lambdas/aws-dd-forwarder-3.127.0/importlib_metadata/.DS_Store create mode 100644 lambdas/aws-dd-forwarder-3.127.0/importlib_metadata/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/importlib_metadata/_adapters.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/importlib_metadata/_collections.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/importlib_metadata/_compat.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/importlib_metadata/_functools.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/importlib_metadata/_itertools.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/importlib_metadata/_meta.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/importlib_metadata/_text.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/importlib_metadata/compat/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/importlib_metadata/compat/py311.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/importlib_metadata/compat/py39.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/importlib_metadata/diagnose.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/importlib_metadata/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/lambda_function.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/logs/datadog_batcher.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/logs/datadog_client.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/logs/datadog_http_client.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/logs/datadog_scrubber.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/logs/datadog_tcp_client.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/logs/exceptions.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/logs/helpers.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/.DS_Store create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/_events/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/_logs/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/_logs/_internal/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/_logs/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/_logs/severity/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/attributes/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/attributes/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/baggage/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/baggage/propagation/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/baggage/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/context/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/context/context.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/context/contextvars_context.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/context/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/environment_variables/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/environment_variables/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/metrics/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/metrics/_internal/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/metrics/_internal/instrument.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/metrics/_internal/observation.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/metrics/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/propagate/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/propagate/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/propagators/composite.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/propagators/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/propagators/textmap.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/trace/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/trace/propagation/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/trace/propagation/tracecontext.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/trace/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/trace/span.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/trace/status.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/util/_decorator.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/util/_importlib_metadata.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/util/_once.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/util/_providers.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/util/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/util/re.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/util/types.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/version/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry/version/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry_api-1.27.0.dist-info/INSTALLER create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry_api-1.27.0.dist-info/METADATA create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry_api-1.27.0.dist-info/RECORD create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry_api-1.27.0.dist-info/REQUESTED create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry_api-1.27.0.dist-info/WHEEL create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry_api-1.27.0.dist-info/entry_points.txt create mode 100644 lambdas/aws-dd-forwarder-3.127.0/opentelemetry_api-1.27.0.dist-info/licenses/LICENSE create mode 100644 lambdas/aws-dd-forwarder-3.127.0/protobuf-5.28.2.dist-info/INSTALLER create mode 100644 lambdas/aws-dd-forwarder-3.127.0/protobuf-5.28.2.dist-info/LICENSE create mode 100644 lambdas/aws-dd-forwarder-3.127.0/protobuf-5.28.2.dist-info/METADATA create mode 100644 lambdas/aws-dd-forwarder-3.127.0/protobuf-5.28.2.dist-info/RECORD create mode 100644 lambdas/aws-dd-forwarder-3.127.0/protobuf-5.28.2.dist-info/REQUESTED create mode 100644 lambdas/aws-dd-forwarder-3.127.0/protobuf-5.28.2.dist-info/WHEEL create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests-2.32.3.dist-info/INSTALLER create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests-2.32.3.dist-info/LICENSE create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests-2.32.3.dist-info/METADATA create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests-2.32.3.dist-info/RECORD create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests-2.32.3.dist-info/REQUESTED create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests-2.32.3.dist-info/WHEEL create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests-2.32.3.dist-info/top_level.txt create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests/__version__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests/_internal_utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests/adapters.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests/api.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests/auth.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests/certs.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests/compat.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests/cookies.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests/exceptions.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests/help.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests/hooks.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests/models.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests/packages.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests/sessions.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests/status_codes.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests/structures.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests/utils.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests_futures-1.0.1.dist-info/INSTALLER create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests_futures-1.0.1.dist-info/LICENSE create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests_futures-1.0.1.dist-info/METADATA create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests_futures-1.0.1.dist-info/RECORD create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests_futures-1.0.1.dist-info/REQUESTED create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests_futures-1.0.1.dist-info/WHEEL create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests_futures-1.0.1.dist-info/top_level.txt create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests_futures/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requests_futures/sessions.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/requirements.txt create mode 100644 lambdas/aws-dd-forwarder-3.127.0/retry/enums.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/retry/storage.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/settings.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/setup.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/six-1.16.0.dist-info/INSTALLER create mode 100644 lambdas/aws-dd-forwarder-3.127.0/six-1.16.0.dist-info/LICENSE create mode 100644 lambdas/aws-dd-forwarder-3.127.0/six-1.16.0.dist-info/METADATA create mode 100644 lambdas/aws-dd-forwarder-3.127.0/six-1.16.0.dist-info/RECORD create mode 100644 lambdas/aws-dd-forwarder-3.127.0/six-1.16.0.dist-info/REQUESTED create mode 100644 lambdas/aws-dd-forwarder-3.127.0/six-1.16.0.dist-info/WHEEL create mode 100644 lambdas/aws-dd-forwarder-3.127.0/six-1.16.0.dist-info/top_level.txt create mode 100644 lambdas/aws-dd-forwarder-3.127.0/six.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/steps/common.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/steps/enrichment.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/steps/enums.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/steps/handlers/aws_attributes.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/steps/handlers/awslogs_handler.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/steps/handlers/s3_handler.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/steps/parsing.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/steps/splitting.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/steps/transformation.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/telemetry.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/template.yaml create mode 100644 lambdas/aws-dd-forwarder-3.127.0/tests/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/tests/test_awslogs_handler.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/tests/test_caching.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/tests/test_cloudtrail_s3.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/tests/test_customized_log_group.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/tests/test_enhanced_lambda_metrics.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/tests/test_enrichment.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/tests/test_lambda_function.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/tests/test_logs.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/tests/test_parsing.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/tests/test_s3_handler.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/tests/test_splitting.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/tests/test_transformation.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/tools/integration_tests/cache_test_lambda/handler.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/tools/integration_tests/external_lambda/handler.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/tools/integration_tests/recorder/pb/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/tools/integration_tests/recorder/pb/span_pb2.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/tools/integration_tests/recorder/pb/trace_payload_pb2.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/tools/integration_tests/recorder/pb/trace_pb2.py create mode 100755 lambdas/aws-dd-forwarder-3.127.0/tools/integration_tests/recorder/recorder.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/tools/integration_tests/tester/test_snapshots.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/trace_forwarder/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/trace_forwarder/bin/trace-intake.h create mode 100644 lambdas/aws-dd-forwarder-3.127.0/trace_forwarder/bin/trace-intake.so create mode 100644 lambdas/aws-dd-forwarder-3.127.0/trace_forwarder/connection.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/typing_extensions-4.12.2.dist-info/INSTALLER create mode 100644 lambdas/aws-dd-forwarder-3.127.0/typing_extensions-4.12.2.dist-info/LICENSE create mode 100644 lambdas/aws-dd-forwarder-3.127.0/typing_extensions-4.12.2.dist-info/METADATA create mode 100644 lambdas/aws-dd-forwarder-3.127.0/typing_extensions-4.12.2.dist-info/RECORD create mode 100644 lambdas/aws-dd-forwarder-3.127.0/typing_extensions-4.12.2.dist-info/REQUESTED create mode 100644 lambdas/aws-dd-forwarder-3.127.0/typing_extensions-4.12.2.dist-info/WHEEL create mode 100644 lambdas/aws-dd-forwarder-3.127.0/typing_extensions.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3-2.0.7.dist-info/INSTALLER create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3-2.0.7.dist-info/METADATA create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3-2.0.7.dist-info/RECORD create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3-2.0.7.dist-info/REQUESTED create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3-2.0.7.dist-info/WHEEL create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3-2.0.7.dist-info/licenses/LICENSE.txt create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/.DS_Store create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/_base_connection.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/_collections.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/_request_methods.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/_version.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/connection.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/connectionpool.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/contrib/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/contrib/_securetransport/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/contrib/_securetransport/bindings.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/contrib/_securetransport/low_level.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/contrib/pyopenssl.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/contrib/securetransport.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/contrib/socks.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/exceptions.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/fields.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/filepost.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/poolmanager.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/py.typed create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/response.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/util/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/util/connection.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/util/proxy.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/util/request.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/util/response.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/util/retry.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/util/ssl_.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/util/ssl_match_hostname.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/util/ssltransport.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/util/timeout.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/util/url.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/util/util.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/urllib3/util/wait.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/wrapt-1.16.0.dist-info/INSTALLER create mode 100644 lambdas/aws-dd-forwarder-3.127.0/wrapt-1.16.0.dist-info/LICENSE create mode 100644 lambdas/aws-dd-forwarder-3.127.0/wrapt-1.16.0.dist-info/METADATA create mode 100644 lambdas/aws-dd-forwarder-3.127.0/wrapt-1.16.0.dist-info/RECORD create mode 100644 lambdas/aws-dd-forwarder-3.127.0/wrapt-1.16.0.dist-info/REQUESTED create mode 100644 lambdas/aws-dd-forwarder-3.127.0/wrapt-1.16.0.dist-info/WHEEL create mode 100644 lambdas/aws-dd-forwarder-3.127.0/wrapt-1.16.0.dist-info/top_level.txt create mode 100644 lambdas/aws-dd-forwarder-3.127.0/wrapt/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/wrapt/__wrapt__.py create mode 100755 lambdas/aws-dd-forwarder-3.127.0/wrapt/_wrappers.cpython-311-x86_64-linux-gnu.so create mode 100644 lambdas/aws-dd-forwarder-3.127.0/wrapt/arguments.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/wrapt/decorators.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/wrapt/importer.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/wrapt/patches.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/wrapt/weakrefs.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/wrapt/wrappers.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/xmltodict-0.14.1.dist-info/INSTALLER create mode 100644 lambdas/aws-dd-forwarder-3.127.0/xmltodict-0.14.1.dist-info/LICENSE create mode 100644 lambdas/aws-dd-forwarder-3.127.0/xmltodict-0.14.1.dist-info/METADATA create mode 100644 lambdas/aws-dd-forwarder-3.127.0/xmltodict-0.14.1.dist-info/RECORD create mode 100644 lambdas/aws-dd-forwarder-3.127.0/xmltodict-0.14.1.dist-info/REQUESTED create mode 100644 lambdas/aws-dd-forwarder-3.127.0/xmltodict-0.14.1.dist-info/WHEEL create mode 100644 lambdas/aws-dd-forwarder-3.127.0/xmltodict-0.14.1.dist-info/top_level.txt create mode 100644 lambdas/aws-dd-forwarder-3.127.0/xmltodict.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/zipp-3.20.2.dist-info/INSTALLER create mode 100644 lambdas/aws-dd-forwarder-3.127.0/zipp-3.20.2.dist-info/LICENSE create mode 100644 lambdas/aws-dd-forwarder-3.127.0/zipp-3.20.2.dist-info/METADATA create mode 100644 lambdas/aws-dd-forwarder-3.127.0/zipp-3.20.2.dist-info/RECORD create mode 100644 lambdas/aws-dd-forwarder-3.127.0/zipp-3.20.2.dist-info/REQUESTED create mode 100644 lambdas/aws-dd-forwarder-3.127.0/zipp-3.20.2.dist-info/WHEEL create mode 100644 lambdas/aws-dd-forwarder-3.127.0/zipp-3.20.2.dist-info/top_level.txt create mode 100644 lambdas/aws-dd-forwarder-3.127.0/zipp/.DS_Store create mode 100644 lambdas/aws-dd-forwarder-3.127.0/zipp/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/zipp/compat/__init__.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/zipp/compat/overlay.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/zipp/compat/py310.py create mode 100644 lambdas/aws-dd-forwarder-3.127.0/zipp/glob.py create mode 100644 policies/datadog-forwarder.json.tpl create mode 100644 policies/kms-encrypt.json.tpl create mode 100644 policies/s3-object-read.json.tpl create mode 100644 policies/s3-object-rw.json.tpl create mode 100644 policies/secrets-manager-get-secret-value.json.tpl create mode 100644 s3-datadog-forwarder-lambda.tf diff --git a/.gitignore b/.gitignore index c9f841b..f7d387a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ backend.vars # caches lambdas/.zip-cache/* + +# Junk files +.DS_Store diff --git a/README.md b/README.md index 3bd2e38..c5fddb3 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ for dxw's Dalmatian hosting platform. | [aws_cloudtrail.cloudtrail](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudtrail) | resource | | [aws_cloudwatch_log_group.cloudtrail](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | | [aws_cloudwatch_log_group.cloudwatch_slack_alerts_lambda_log_group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_cloudwatch_log_group.datadog_forwarder_log_group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | | [aws_cloudwatch_log_group.delete_default_resources_lambda_log_group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | | [aws_codestarconnections_connection.connections](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/codestarconnections_connection) | resource | | [aws_glue_catalog_database.cloudtrail](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/glue_catalog_database) | resource | @@ -50,6 +51,13 @@ for dxw's Dalmatian hosting platform. | [aws_iam_policy.custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.datadog_aws_integration](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.datadog_aws_integration_resource_collection](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.datadog_forwarder](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.datadog_forwarder_kms_encrypt](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.datadog_forwarder_kms_encrypt_wildcard](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.datadog_forwarder_s3_object_read](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.datadog_forwarder_s3_object_rw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.datadog_forwarder_secret](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.datadog_forwarder_tags](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.delete_default_resources_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.delete_default_resources_vpc_delete_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.ssm_dhmc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | @@ -57,6 +65,7 @@ for dxw's Dalmatian hosting platform. | [aws_iam_role.cloudwatch_slack_alerts_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role.custom](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role.datadog_aws_integration](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role.datadog_forwarder](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role.delete_default_resources_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role.ssm_dhmc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role_policy_attachment.cloudtrail_cloudwatch_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | @@ -65,6 +74,13 @@ for dxw's Dalmatian hosting platform. | [aws_iam_role_policy_attachment.datadog_aws_integration](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.datadog_aws_integration_resource_collection](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.datadog_aws_integration_security_audit](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.datadog_forwarder](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.datadog_forwarder_kms_encrypt](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.datadog_forwarder_kms_encrypt_wildcard](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.datadog_forwarder_s3_object_read](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.datadog_forwarder_s3_object_rw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.datadog_forwarder_secret](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.datadog_forwarder_tags](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.delete_default_resources_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.delete_default_resources_vpc_delete_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.ssm_dhmc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | @@ -73,37 +89,51 @@ for dxw's Dalmatian hosting platform. | [aws_kms_alias.cloudwatch_opsgenie_alerts_sns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_alias) | resource | | [aws_kms_alias.cloudwatch_opsgenie_alerts_sns_us_east_1](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_alias) | resource | | [aws_kms_alias.cloudwatch_slack_alerts](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_alias) | resource | +| [aws_kms_alias.datadog_forwarder](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_alias) | resource | +| [aws_kms_alias.datadog_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_alias) | resource | | [aws_kms_alias.delete_default_resources_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_alias) | resource | | [aws_kms_key.athena_cloudtrail_output](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | | [aws_kms_key.cloudtrail_cloudwatch_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | | [aws_kms_key.cloudwatch_opsgenie_alerts_sns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | | [aws_kms_key.cloudwatch_opsgenie_alerts_sns_us_east_1](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | | [aws_kms_key.cloudwatch_slack_alerts](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | +| [aws_kms_key.datadog_forwarder](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | +| [aws_kms_key.datadog_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | | [aws_kms_key.delete_default_resources_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | | [aws_lambda_function.cloudwatch_slack_alerts](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function) | resource | +| [aws_lambda_function.datadog_service_log_forwarder](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function) | resource | | [aws_lambda_function.delete_default_resources](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function) | resource | | [aws_lambda_permission.cloudwatch_slack_alerts_sns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource | | [aws_route53_zone.root](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_zone) | resource | | [aws_s3_bucket.athena_cloudtrail_output](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | | [aws_s3_bucket.cloudtrail](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | +| [aws_s3_bucket.datadog_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | | [aws_s3_bucket.logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | | [aws_s3_bucket_lifecycle_configuration.athena_cloudtrail_output](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | | [aws_s3_bucket_lifecycle_configuration.cloudtrail](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | +| [aws_s3_bucket_lifecycle_configuration.datadog_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | | [aws_s3_bucket_lifecycle_configuration.logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | | [aws_s3_bucket_logging.athena_cloudtrail_output](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource | | [aws_s3_bucket_logging.cloudtrail](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource | +| [aws_s3_bucket_logging.datadog_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource | | [aws_s3_bucket_policy.athena_cloudtrail_output](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | | [aws_s3_bucket_policy.cloudtrail](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | +| [aws_s3_bucket_policy.datadog_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | | [aws_s3_bucket_policy.logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | | [aws_s3_bucket_public_access_block.athena_cloudtrail_output](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | | [aws_s3_bucket_public_access_block.cloudtrail](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | +| [aws_s3_bucket_public_access_block.datadog_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | | [aws_s3_bucket_public_access_block.logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | | [aws_s3_bucket_server_side_encryption_configuration.athena_cloudtrail_output](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | | [aws_s3_bucket_server_side_encryption_configuration.cloudtrail](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | +| [aws_s3_bucket_server_side_encryption_configuration.datadog_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | | [aws_s3_bucket_server_side_encryption_configuration.logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | | [aws_s3_bucket_versioning.athena_cloudtrail_output](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | | [aws_s3_bucket_versioning.cloudtrail](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | +| [aws_s3_bucket_versioning.datadog_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | | [aws_s3_bucket_versioning.logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | +| [aws_secretsmanager_secret.datadog_api_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret) | resource | +| [aws_secretsmanager_secret_version.datadog_api_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_version) | resource | | [aws_sns_topic.cloudwatch_opsgenie_alerts](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic) | resource | | [aws_sns_topic.cloudwatch_opsgenie_alerts_us_east_1](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic) | resource | | [aws_sns_topic.cloudwatch_slack_alerts](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic) | resource | @@ -115,7 +145,10 @@ for dxw's Dalmatian hosting platform. | [aws_sns_topic_subscription.cloudwatch_slack_alerts_lambda_subscription](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic_subscription) | resource | | [aws_ssm_service_setting.ssm_dhmc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_service_setting) | resource | | [datadog_integration_aws.aws](https://registry.terraform.io/providers/DataDog/datadog/latest/docs/resources/integration_aws) | resource | +| [datadog_integration_aws_lambda_arn.datadog_forwarder_arn](https://registry.terraform.io/providers/DataDog/datadog/latest/docs/resources/integration_aws_lambda_arn) | resource | +| [datadog_integration_aws_log_collection.datadog_forwarder](https://registry.terraform.io/providers/DataDog/datadog/latest/docs/resources/integration_aws_log_collection) | resource | | [archive_file.cloudwatch_slack_alerts_lambda](https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file) | data source | +| [archive_file.datadog_forwarder](https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file) | data source | | [archive_file.delete_default_resources_lambda](https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file) | data source | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | | [aws_regions.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/regions) | data source | @@ -142,6 +175,9 @@ for dxw's Dalmatian hosting platform. | [custom\_iam\_roles](#input\_custom\_iam\_roles) | Configure custom IAM roles/policies |
map(object({
description = string
policies = map(object({
description = string
Version = string
Statement = list(object({
Action = list(string)
Effect = string
Resource = string
}))
}))
assume_role_policy = object({
Version = string
Statement = list(object({
Action = list(string)
Effect = string
Principal = map(string)
}))
})
}))
| n/a | yes | | [datadog\_api\_key](#input\_datadog\_api\_key) | Datadog API key | `string` | n/a | yes | | [datadog\_app\_key](#input\_datadog\_app\_key) | Datadog App key | `string` | n/a | yes | +| [datadog\_forwarder\_enhanced\_metrics](#input\_datadog\_forwarder\_enhanced\_metrics) | Set the environment variable DD\_ENHANCED\_METRICS on the Forwarder. Set to false to stop the Forwarder from generating enhanced metrics itself, but it will still forward custom metrics from other lambdas. | `bool` | n/a | yes | +| [datadog\_forwarder\_log\_retention](#input\_datadog\_forwarder\_log\_retention) | Datadog Forwarder S3 bucket retention in days. Set to 0 to keep all logs. | `number` | n/a | yes | +| [datadog\_forwarder\_store\_failed\_events](#input\_datadog\_forwarder\_store\_failed\_events) | Set environment variable DD\_STORE\_FAILED\_EVENTS on the Forwarder. Set to true to enable the forwarder to also store event data in the S3 bucket | `bool` | n/a | yes | | [datadog\_region](#input\_datadog\_region) | Datadog region | `string` | n/a | yes | | [delete\_default\_resources\_lambda\_kms\_encryption](#input\_delete\_default\_resources\_lambda\_kms\_encryption) | Conditionally encrypt the Delete Default Resources Lambda logs with KMS | `bool` | n/a | yes | | [delete\_default\_resources\_log\_retention](#input\_delete\_default\_resources\_log\_retention) | Log retention for the Delete Default Resources Lambda | `number` | n/a | yes | @@ -149,6 +185,7 @@ for dxw's Dalmatian hosting platform. | [enable\_cloudwatch\_opsgenie\_alerts](#input\_enable\_cloudwatch\_opsgenie\_alerts) | Enable CloudWatch Opsgenie alerts. This creates an SNS topic to which alerts and pipelines can send messages, which are then sent to the Opsgenie SNS endpoint. | `bool` | n/a | yes | | [enable\_cloudwatch\_slack\_alerts](#input\_enable\_cloudwatch\_slack\_alerts) | Enable CloudWatch Slack alerts. This creates an SNS topic to which alerts and pipelines can send messages, which are then picked up by a Lambda function that forwards them to a Slack webhook. | `bool` | n/a | yes | | [enable\_datadog\_aws\_integration](#input\_enable\_datadog\_aws\_integration) | Conditionally create the datadog AWS integration role (https://docs.datadoghq.com/integrations/guide/aws-terraform-setup/) and configure the datadog integration | `bool` | n/a | yes | +| [enable\_datadog\_forwarder](#input\_enable\_datadog\_forwarder) | Conditionally launch Datadog AWS service log forwarder lambda | `bool` | n/a | yes | | [enable\_delete\_default\_resources](#input\_enable\_delete\_default\_resources) | Creates a Lambda function which deletes all default VPCs and resources within them. This only needs to be ran once, either through the AWS console or via the AWS CLI | `bool` | n/a | yes | | [enable\_route53\_root\_hosted\_zone](#input\_enable\_route53\_root\_hosted\_zone) | Conditionally create Route53 hosted zone, which will contain the DNS records for resources launched within the account. | `bool` | n/a | yes | | [enable\_s3\_tfvars](#input\_enable\_s3\_tfvars) | enable\_s3\_tfvars | `bool` | n/a | yes | diff --git a/datadog-forwarder-lambda.tf b/datadog-forwarder-lambda.tf new file mode 100644 index 0000000..c9c6577 --- /dev/null +++ b/datadog-forwarder-lambda.tf @@ -0,0 +1,259 @@ +resource "aws_kms_key" "datadog_forwarder" { + count = local.enable_datadog_forwarder ? 1 : 0 + + description = "This key is used to encrypt the DataDog Forwarder Lambda logs (${local.project_name})" + deletion_window_in_days = 10 + enable_key_rotation = true + policy = templatefile( + "${path.root}/policies/kms-key-policy.json.tpl", + { + statement = <qD^u6%rPToED zobP=1+~0SHh%!f)XZndoh^P}cr<*t8G5#qa?ZaKaIG=Cd)rKb-+&6LOP@d=chPc_YBP=w@ST8&(awxFUT}BX(M|Qd1OVOi`p((Tu&s$XLbkyuji}N9?p_rB-puSj8!0TjqreUm0T^ zX#xM>%8ZyK{YV)|83FY<5YMN=0Cbv}9sSr7JlpBve>QXi4aOTUZkci!}>CG2^6 zTI9_SWv;|d<;hW-civMqeP{_{R>nHD_u>B#rCeLL44%h%IOz|&eT0^Bx0$G=-2P;S#+lC^_;Uf##Cz($CXDb=5}47 zs;HY>$G0+9@xxVK!RV3@_H(8kZdcTC$o41}@DD?Y^3;qHN)wi!!@;K8bg9q4}+ z(Mq_N=6+@x-+JxY8NU^bFh|?{0r*IyN8u%*u0Vbg=?dT}M;F0Pg(@Oi>F|^A-N{q@ zapebMbn>O$B2?48b-SpQLDa{9shbF+Liee7r4{Tk>?`dshK2zDnSh?`-ekFJ!J_+f`nP2A71eZ;jg9*6o`Sl6#=~8HJEp(Ba>KFjjfR zhnPA7IgX`6@?b{CHq2QtLzMBE=MyJ(j>Jy&2y=4XsJyw-UJ**Mi!_C{!Sm2*GY-hp z6xm_kL-WZ<47GZE1<1M4=yC#A*6^_1i_00C_{2$svFZL#fINW~#kWM70=-Y5N$_Kh z-W^VVX4VrjUY2?0>5=gGRbM6gqy%O~O5(W!W9UD)fA#lV)b**%_28(-j;p-de>fJT z{WZ4VzHZ;sgSEZ`~pG1@7unHuUt-SBwB zr`QI~EGuNh*)p13lj2`OTuPKC)F$|%BJ*`O_?kn@UcxQ~pMIRcJ_YZ@EaDljurG4m ziCS%6{v~QTg^Nfw70)A;6@IqgVn2JL_=#xYnK;J^uirzW)63hyZEzvD_ zCc`tI-Q;m%5^0%cMdakKz5vEN5{mCWDWyLU)&!o;ajthh8|%Vj&ocIzz92#!pB_x2 zL;B>*(qdzgb(dU^;DtVBUb;NIOrW(-qSa6Fb5U!Vudjy5EB-dLq4?IYjPS#nM?Tod z57ai#CWp;VT!h*_c>|s{sZrsgX|3+~cOs!lt%6T$Jnsab;w-s{1-ikzB0d@EbFDtR z2#|Aoq00$enF34(UeDlZ?6dC*)&zP2eTl6{_-efw2|xetk&r$)(czPFBi5t@p6^zf z_-+#u>u+nU_x=2ykXu*dQ8L@{LwpU2KQVDAJxok3f{$#C`M484^Lsx&pJA<8N;D(e z$vCgj0_CJ{P8(2U-8I%&R^YVOkUMGAj}v`jeYMgb<<}t(ya7)VYEpbntSyj@gxUi5 zY6n|q={3xHy>nhVbrLLye8o49Odw(*BXL}U8SHhraM&{z>vuR|t&Mj47`MFQM=T7o zr8#jLONZp4$UJm`hx2AVhj&S+6@G_0i&j_08#m8x{ksRl~+EDyzSVsD>#(wN- z$F#ZoWP^XNn8cSbr;yE zm^lW&7eZfZw=M4p#O$?PV`+)i8TT9LS9*#DB?SJqD~!KK#}B6YD zPmCUgZ}xBNd|vWz?FzVyxcOqa8h^os@8+j?NO`2=fAEVSI>z7eY}`(ZobrWdH7mt8 lT>ttH0n_z=y8dr+KS7zFG__V&<|R$(l=G1yH#cWp{tw5sSYH4D literal 0 HcmV?d00001 diff --git a/lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/INSTALLER b/lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/LICENSE.rst b/lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/LICENSE.rst new file mode 100644 index 0000000..191ddaf --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/LICENSE.rst @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Laurent LAPORTE + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/METADATA b/lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/METADATA new file mode 100644 index 0000000..9ecede2 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/METADATA @@ -0,0 +1,181 @@ +Metadata-Version: 2.1 +Name: Deprecated +Version: 1.2.14 +Summary: Python @deprecated decorator to deprecate old python classes, functions or methods. +Home-page: https://github.com/tantale/deprecated +Author: Laurent LAPORTE +Author-email: tantale.solutions@gmail.com +License: MIT +Project-URL: Documentation, https://deprecated.readthedocs.io/en/latest/ +Project-URL: Source, https://github.com/tantale/deprecated +Project-URL: Bug Tracker, https://github.com/tantale/deprecated/issues +Keywords: deprecate,deprecated,deprecation,warning,warn,decorator +Platform: any +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* +Description-Content-Type: text/x-rst +License-File: LICENSE.rst +Requires-Dist: wrapt (<2,>=1.10) +Provides-Extra: dev +Requires-Dist: tox ; extra == 'dev' +Requires-Dist: PyTest ; extra == 'dev' +Requires-Dist: PyTest-Cov ; extra == 'dev' +Requires-Dist: bump2version (<1) ; extra == 'dev' +Requires-Dist: sphinx (<2) ; extra == 'dev' + + +Deprecated Library +------------------ + +Deprecated is Easy to Use +````````````````````````` + +If you need to mark a function or a method as deprecated, +you can use the ``@deprecated`` decorator: + +Save in a hello.py: + +.. code:: python + + from deprecated import deprecated + + + @deprecated(version='1.2.1', reason="You should use another function") + def some_old_function(x, y): + return x + y + + + class SomeClass(object): + @deprecated(version='1.3.0', reason="This method is deprecated") + def some_old_method(self, x, y): + return x + y + + + some_old_function(12, 34) + obj = SomeClass() + obj.some_old_method(5, 8) + + +And Easy to Setup +````````````````` + +And run it: + +.. code:: bash + + $ pip install Deprecated + $ python hello.py + hello.py:15: DeprecationWarning: Call to deprecated function (or staticmethod) some_old_function. + (You should use another function) -- Deprecated since version 1.2.0. + some_old_function(12, 34) + hello.py:17: DeprecationWarning: Call to deprecated method some_old_method. + (This method is deprecated) -- Deprecated since version 1.3.0. + obj.some_old_method(5, 8) + + +You can document your code +`````````````````````````` + +Have you ever wonder how to document that some functions, classes, methods, etc. are deprecated? +This is now possible with the integrated Sphinx directives: + +For instance, in hello_sphinx.py: + +.. code:: python + + from deprecated.sphinx import deprecated + from deprecated.sphinx import versionadded + from deprecated.sphinx import versionchanged + + + @versionadded(version='1.0', reason="This function is new") + def function_one(): + '''This is the function one''' + + + @versionchanged(version='1.0', reason="This function is modified") + def function_two(): + '''This is the function two''' + + + @deprecated(version='1.0', reason="This function will be removed soon") + def function_three(): + '''This is the function three''' + + + function_one() + function_two() + function_three() # warns + + help(function_one) + help(function_two) + help(function_three) + + +The result it immediate +``````````````````````` + +Run it: + +.. code:: bash + + $ python hello_sphinx.py + + hello_sphinx.py:23: DeprecationWarning: Call to deprecated function (or staticmethod) function_three. + (This function will be removed soon) -- Deprecated since version 1.0. + function_three() # warns + + Help on function function_one in module __main__: + + function_one() + This is the function one + + .. versionadded:: 1.0 + This function is new + + Help on function function_two in module __main__: + + function_two() + This is the function two + + .. versionchanged:: 1.0 + This function is modified + + Help on function function_three in module __main__: + + function_three() + This is the function three + + .. deprecated:: 1.0 + This function will be removed soon + + +Links +````` + +* `Python package index (PyPi) `_ +* `GitHub website `_ +* `Read The Docs `_ +* `EBook on Lulu.com `_ +* `StackOverFlow Q&A `_ +* `Development version + `_ + diff --git a/lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/RECORD b/lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/RECORD new file mode 100644 index 0000000..d8f67cb --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/RECORD @@ -0,0 +1,13 @@ +Deprecated-1.2.14.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +Deprecated-1.2.14.dist-info/LICENSE.rst,sha256=HoPt0VvkGbXVveNy4yXlJ_9PmRX1SOfHUxS0H2aZ6Dw,1081 +Deprecated-1.2.14.dist-info/METADATA,sha256=xQYvk5nwOfnkxxRD-VHkpE-sMu0IBHRZ8ayspypfkTs,5354 +Deprecated-1.2.14.dist-info/RECORD,, +Deprecated-1.2.14.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +Deprecated-1.2.14.dist-info/WHEEL,sha256=a-zpFRIJzOq5QfuhBzbhiA1eHTzNCJn8OdRvhdNX0Rk,110 +Deprecated-1.2.14.dist-info/top_level.txt,sha256=nHbOYawKPQQE5lQl-toUB1JBRJjUyn_m_Mb8RVJ0RjA,11 +deprecated/__init__.py,sha256=ZphiULqDVrESSB0mLV2WA88JyhQxZSK44zuDGbV5k-g,349 +deprecated/__pycache__/__init__.cpython-311.pyc,, +deprecated/__pycache__/classic.cpython-311.pyc,, +deprecated/__pycache__/sphinx.cpython-311.pyc,, +deprecated/classic.py,sha256=QugmUi7IhBvp2nDvMtyWqFDPRR43-9nfSZG1ZJSDpFM,9880 +deprecated/sphinx.py,sha256=NqQ0oKGcVn6yUe23iGbCieCgvWbEDQSPt9QelbXJnDU,10258 diff --git a/lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/REQUESTED b/lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/WHEEL b/lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/WHEEL new file mode 100644 index 0000000..f771c29 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.40.0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/top_level.txt b/lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/top_level.txt new file mode 100644 index 0000000..9f8d550 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/Deprecated-1.2.14.dist-info/top_level.txt @@ -0,0 +1 @@ +deprecated diff --git a/lambdas/aws-dd-forwarder-3.127.0/META_INF/aws_signer_signature_v1.0.SF b/lambdas/aws-dd-forwarder-3.127.0/META_INF/aws_signer_signature_v1.0.SF new file mode 100644 index 0000000..b55229a --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/META_INF/aws_signer_signature_v1.0.SF @@ -0,0 +1,70 @@ +-----BEGIN PKCS7----- +MIAGCSqGSIb3DQEHAqCAMIACAQExDTALBglghkgBZQMEAgIwCwYJKoZIhvcNAQcB +oIAwggJ7MIICAqADAgECAhEAgseesYiwj2v65uNdIxGWjDAKBggqhkjOPQQDAzBp +MQswCQYDVQQGEwJVUzEMMAoGA1UECgwDQVdTMRUwEwYDVQQLDAxDcnlwdG9ncmFw +aHkxCzAJBgNVBAgMAldBMSgwJgYDVQQDDB9TaWduZXIgdXMtZWFzdC0xIFNVQk9S +RElOQVRFIENBMB4XDTI0MTAxMTAzNTUwNloXDTI0MTAxNDA0NTUwNVowYjELMAkG +A1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0dGxlMQwwCgYDVQQK +DANBV1MxFTATBgNVBAsMDENyeXB0b2dyYXBoeTEPMA0GA1UEAwwGc2lnbmVyMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAENEJBApwU+6Uew83wGVUmaoQVqhH8OWJ8uhNK +pYeGe2aO+ltRW56QVdcbTV5bndUcW9nslQ946H5Zn1Un/j/WPxkBJjUfNZvKvwDL +kD2kbr6fpJnpTrylWuJC/JyRXP3Uo3UwczAJBgNVHRMEAjAAMB8GA1UdIwQYMBaA +FBW0Hd/bDRgmoXefjl93qtSKwJXVMB0GA1UdDgQWBBRcF773L+DbVZJg2nkGpbHH +SGoCJDAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwMwCgYI +KoZIzj0EAwMDZwAwZAIwEVaZHYn31KWcjTkpUDxyYtN2OZdFmDx05ZwyOjz74MbS +ZI1gWjpUz8fG5ZeZ2rzfAjAiJzHe0f6PnC29XXlT+XbaLCgX+93ma2BO0ssYugs6 +FMXwBljceZQwC+X4Dpp58agwggJzMIIB+qADAgECAhEAzX5q6bZNJ1qnMgY/JSNY +BDAKBggqhkjOPQQDAzBpMQswCQYDVQQGEwJVUzEMMAoGA1UECgwDQVdTMRUwEwYD +VQQLDAxDcnlwdG9ncmFwaHkxCzAJBgNVBAgMAldBMSgwJgYDVQQDDB9TaWduZXIg +dXMtd2VzdC0yIFNVQk9SRElOQVRFIENBMB4XDTI0MDgwMTE0MjIxNloXDTI1MDUw +MTE1MjIxNlowaTELMAkGA1UEBhMCVVMxDDAKBgNVBAoMA0FXUzEVMBMGA1UECwwM +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 +MTU1MDUzLjcwMVowIgYJKoZIhvcNAQkFMRUYEzIwMjQxMDExMTU1MDUzLjcwMVow +KAYJKoZIhvcNAQk0MRswGTALBglghkgBZQMEAgKhCgYIKoZIzj0EAwMwPwYJKoZI +hvcNAQkEMTIEMLjbYXVmR5oZlI5YUD9xABhD9seqVJ1fKmT3UkDiWBZK3RlBus8+ +xB7MlG/8YxEn/DBlBgcrgTuBCQECMVoMWGFybjphd3M6c2lnbmVyOnVzLWVhc3Qt +MTo0NjQ2MjI1MzIwMTI6L3NpZ25pbmctam9icy9jODlkOTE4NC1mY2FhLTRjYzUt +ODFmMC0zMjQyZjdmYjllMTUwawYHK4E7gQkBBDFgDF5hcm46YXdzOnNpZ25lcjp1 +cy1lYXN0LTE6NDY0NjIyNTMyMDEyOi9zaWduaW5nLXByb2ZpbGVzL0RhdGFkb2dM +YW1iZGFTaWduaW5nUHJvZmlsZS85dk1JOVpBR0xjMAoGCCqGSM49BAMDBGYwZAIw +M+knoFUHpSY1U+qmX1EUQCenrg4n+wc1fK5pv8K+LddOf9KHqrY28GkutH3sLF31 +AjBX0SUoW/3rEedtYJ/N9uGDlNn69Iw2ooboeBNjK9xb4QMTHsCPBR8PBFve33rq +ADYAAAAAAAA= +-----END PKCS7----- diff --git a/lambdas/aws-dd-forwarder-3.127.0/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/attr/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/attr/__init__.py new file mode 100644 index 0000000..51b1c25 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attr/__init__.py @@ -0,0 +1,103 @@ +# SPDX-License-Identifier: MIT + +""" +Classes Without Boilerplate +""" + +from functools import partial +from typing import Callable + +from . import converters, exceptions, filters, setters, validators +from ._cmp import cmp_using +from ._compat import Protocol +from ._config import get_run_validators, set_run_validators +from ._funcs import asdict, assoc, astuple, evolve, has, resolve_types +from ._make import ( + NOTHING, + Attribute, + Converter, + Factory, + attrib, + attrs, + fields, + fields_dict, + make_class, + validate, +) +from ._next_gen import define, field, frozen, mutable +from ._version_info import VersionInfo + + +s = attributes = attrs +ib = attr = attrib +dataclass = partial(attrs, auto_attribs=True) # happy Easter ;) + + +class AttrsInstance(Protocol): + pass + + +__all__ = [ + "Attribute", + "AttrsInstance", + "Converter", + "Factory", + "NOTHING", + "asdict", + "assoc", + "astuple", + "attr", + "attrib", + "attributes", + "attrs", + "cmp_using", + "converters", + "define", + "evolve", + "exceptions", + "field", + "fields", + "fields_dict", + "filters", + "frozen", + "get_run_validators", + "has", + "ib", + "make_class", + "mutable", + "resolve_types", + "s", + "set_run_validators", + "setters", + "validate", + "validators", +] + + +def _make_getattr(mod_name: str) -> Callable: + """ + Create a metadata proxy for packaging information that uses *mod_name* in + its warnings and errors. + """ + + def __getattr__(name: str) -> str: + if name not in ("__version__", "__version_info__"): + msg = f"module {mod_name} has no attribute {name}" + raise AttributeError(msg) + + try: + from importlib.metadata import metadata + except ImportError: + from importlib_metadata import metadata + + meta = metadata("attrs") + + if name == "__version_info__": + return VersionInfo._from_version_string(meta["version"]) + + return meta["version"] + + return __getattr__ + + +__getattr__ = _make_getattr(__name__) diff --git a/lambdas/aws-dd-forwarder-3.127.0/attr/__init__.pyi b/lambdas/aws-dd-forwarder-3.127.0/attr/__init__.pyi new file mode 100644 index 0000000..6ae0a83 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attr/__init__.pyi @@ -0,0 +1,388 @@ +import enum +import sys + +from typing import ( + Any, + Callable, + Generic, + Mapping, + Protocol, + Sequence, + TypeVar, + overload, +) + +# `import X as X` is required to make these public +from . import converters as converters +from . import exceptions as exceptions +from . import filters as filters +from . import setters as setters +from . import validators as validators +from ._cmp import cmp_using as cmp_using +from ._typing_compat import AttrsInstance_ +from ._version_info import VersionInfo +from attrs import ( + define as define, + field as field, + mutable as mutable, + frozen as frozen, + _EqOrderType, + _ValidatorType, + _ConverterType, + _ReprArgType, + _OnSetAttrType, + _OnSetAttrArgType, + _FieldTransformer, + _ValidatorArgType, +) + +if sys.version_info >= (3, 10): + from typing import TypeGuard +else: + from typing_extensions import TypeGuard + +if sys.version_info >= (3, 11): + from typing import dataclass_transform +else: + from typing_extensions import dataclass_transform + +__version__: str +__version_info__: VersionInfo +__title__: str +__description__: str +__url__: str +__uri__: str +__author__: str +__email__: str +__license__: str +__copyright__: str + +_T = TypeVar("_T") +_C = TypeVar("_C", bound=type) + +_FilterType = Callable[["Attribute[_T]", _T], bool] + +# We subclass this here to keep the protocol's qualified name clean. +class AttrsInstance(AttrsInstance_, Protocol): + pass + +_A = TypeVar("_A", bound=type[AttrsInstance]) + +class _Nothing(enum.Enum): + NOTHING = enum.auto() + +NOTHING = _Nothing.NOTHING + +# NOTE: Factory lies about its return type to make this possible: +# `x: List[int] # = Factory(list)` +# Work around mypy issue #4554 in the common case by using an overload. +if sys.version_info >= (3, 8): + from typing import Literal + @overload + def Factory(factory: Callable[[], _T]) -> _T: ... + @overload + def Factory( + factory: Callable[[Any], _T], + takes_self: Literal[True], + ) -> _T: ... + @overload + def Factory( + factory: Callable[[], _T], + takes_self: Literal[False], + ) -> _T: ... + +else: + @overload + def Factory(factory: Callable[[], _T]) -> _T: ... + @overload + def Factory( + factory: Union[Callable[[Any], _T], Callable[[], _T]], + takes_self: bool = ..., + ) -> _T: ... + +In = TypeVar("In") +Out = TypeVar("Out") + +class Converter(Generic[In, Out]): + @overload + def __init__(self, converter: Callable[[In], Out]) -> None: ... + @overload + def __init__( + self, + converter: Callable[[In, AttrsInstance, Attribute], Out], + *, + takes_self: Literal[True], + takes_field: Literal[True], + ) -> None: ... + @overload + def __init__( + self, + converter: Callable[[In, Attribute], Out], + *, + takes_field: Literal[True], + ) -> None: ... + @overload + def __init__( + self, + converter: Callable[[In, AttrsInstance], Out], + *, + takes_self: Literal[True], + ) -> None: ... + +class Attribute(Generic[_T]): + name: str + default: _T | None + validator: _ValidatorType[_T] | None + repr: _ReprArgType + cmp: _EqOrderType + eq: _EqOrderType + order: _EqOrderType + hash: bool | None + init: bool + converter: _ConverterType | Converter[Any, _T] | None + metadata: dict[Any, Any] + type: type[_T] | None + kw_only: bool + on_setattr: _OnSetAttrType + alias: str | None + + def evolve(self, **changes: Any) -> "Attribute[Any]": ... + +# NOTE: We had several choices for the annotation to use for type arg: +# 1) Type[_T] +# - Pros: Handles simple cases correctly +# - Cons: Might produce less informative errors in the case of conflicting +# TypeVars e.g. `attr.ib(default='bad', type=int)` +# 2) Callable[..., _T] +# - Pros: Better error messages than #1 for conflicting TypeVars +# - Cons: Terrible error messages for validator checks. +# e.g. attr.ib(type=int, validator=validate_str) +# -> error: Cannot infer function type argument +# 3) type (and do all of the work in the mypy plugin) +# - Pros: Simple here, and we could customize the plugin with our own errors. +# - Cons: Would need to write mypy plugin code to handle all the cases. +# We chose option #1. + +# `attr` lies about its return type to make the following possible: +# attr() -> Any +# attr(8) -> int +# attr(validator=) -> Whatever the callable expects. +# This makes this type of assignments possible: +# x: int = attr(8) +# +# This form catches explicit None or no default but with no other arguments +# returns Any. +@overload +def attrib( + default: None = ..., + validator: None = ..., + repr: _ReprArgType = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., + init: bool = ..., + metadata: Mapping[Any, Any] | None = ..., + type: None = ..., + converter: None = ..., + factory: None = ..., + kw_only: bool = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., +) -> Any: ... + +# This form catches an explicit None or no default and infers the type from the +# other arguments. +@overload +def attrib( + default: None = ..., + validator: _ValidatorArgType[_T] | None = ..., + repr: _ReprArgType = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., + init: bool = ..., + metadata: Mapping[Any, Any] | None = ..., + type: type[_T] | None = ..., + converter: _ConverterType | Converter[Any, _T] | None = ..., + factory: Callable[[], _T] | None = ..., + kw_only: bool = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., +) -> _T: ... + +# This form catches an explicit default argument. +@overload +def attrib( + default: _T, + validator: _ValidatorArgType[_T] | None = ..., + repr: _ReprArgType = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., + init: bool = ..., + metadata: Mapping[Any, Any] | None = ..., + type: type[_T] | None = ..., + converter: _ConverterType | Converter[Any, _T] | None = ..., + factory: Callable[[], _T] | None = ..., + kw_only: bool = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., +) -> _T: ... + +# This form covers type=non-Type: e.g. forward references (str), Any +@overload +def attrib( + default: _T | None = ..., + validator: _ValidatorArgType[_T] | None = ..., + repr: _ReprArgType = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., + init: bool = ..., + metadata: Mapping[Any, Any] | None = ..., + type: object = ..., + converter: _ConverterType | Converter[Any, _T] | None = ..., + factory: Callable[[], _T] | None = ..., + kw_only: bool = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., +) -> Any: ... +@overload +@dataclass_transform(order_default=True, field_specifiers=(attrib, field)) +def attrs( + maybe_cls: _C, + these: dict[str, Any] | None = ..., + repr_ns: str | None = ..., + repr: bool = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + auto_detect: bool = ..., + collect_by_mro: bool = ..., + getstate_setstate: bool | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + field_transformer: _FieldTransformer | None = ..., + match_args: bool = ..., + unsafe_hash: bool | None = ..., +) -> _C: ... +@overload +@dataclass_transform(order_default=True, field_specifiers=(attrib, field)) +def attrs( + maybe_cls: None = ..., + these: dict[str, Any] | None = ..., + repr_ns: str | None = ..., + repr: bool = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + auto_detect: bool = ..., + collect_by_mro: bool = ..., + getstate_setstate: bool | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + field_transformer: _FieldTransformer | None = ..., + match_args: bool = ..., + unsafe_hash: bool | None = ..., +) -> Callable[[_C], _C]: ... +def fields(cls: type[AttrsInstance]) -> Any: ... +def fields_dict(cls: type[AttrsInstance]) -> dict[str, Attribute[Any]]: ... +def validate(inst: AttrsInstance) -> None: ... +def resolve_types( + cls: _A, + globalns: dict[str, Any] | None = ..., + localns: dict[str, Any] | None = ..., + attribs: list[Attribute[Any]] | None = ..., + include_extras: bool = ..., +) -> _A: ... + +# TODO: add support for returning a proper attrs class from the mypy plugin +# we use Any instead of _CountingAttr so that e.g. `make_class('Foo', +# [attr.ib()])` is valid +def make_class( + name: str, + attrs: list[str] | tuple[str, ...] | dict[str, Any], + bases: tuple[type, ...] = ..., + class_body: dict[str, Any] | None = ..., + repr_ns: str | None = ..., + repr: bool = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + collect_by_mro: bool = ..., + on_setattr: _OnSetAttrArgType | None = ..., + field_transformer: _FieldTransformer | None = ..., +) -> type: ... + +# _funcs -- + +# TODO: add support for returning TypedDict from the mypy plugin +# FIXME: asdict/astuple do not honor their factory args. Waiting on one of +# these: +# https://github.com/python/mypy/issues/4236 +# https://github.com/python/typing/issues/253 +# XXX: remember to fix attrs.asdict/astuple too! +def asdict( + inst: AttrsInstance, + recurse: bool = ..., + filter: _FilterType[Any] | None = ..., + dict_factory: type[Mapping[Any, Any]] = ..., + retain_collection_types: bool = ..., + value_serializer: Callable[[type, Attribute[Any], Any], Any] | None = ..., + tuple_keys: bool | None = ..., +) -> dict[str, Any]: ... + +# TODO: add support for returning NamedTuple from the mypy plugin +def astuple( + inst: AttrsInstance, + recurse: bool = ..., + filter: _FilterType[Any] | None = ..., + tuple_factory: type[Sequence[Any]] = ..., + retain_collection_types: bool = ..., +) -> tuple[Any, ...]: ... +def has(cls: type) -> TypeGuard[type[AttrsInstance]]: ... +def assoc(inst: _T, **changes: Any) -> _T: ... +def evolve(inst: _T, **changes: Any) -> _T: ... + +# _config -- + +def set_run_validators(run: bool) -> None: ... +def get_run_validators() -> bool: ... + +# aliases -- + +s = attributes = attrs +ib = attr = attrib +dataclass = attrs # Technically, partial(attrs, auto_attribs=True) ;) diff --git a/lambdas/aws-dd-forwarder-3.127.0/attr/_cmp.py b/lambdas/aws-dd-forwarder-3.127.0/attr/_cmp.py new file mode 100644 index 0000000..f367bb3 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attr/_cmp.py @@ -0,0 +1,160 @@ +# SPDX-License-Identifier: MIT + + +import functools +import types + +from ._make import _make_ne + + +_operation_names = {"eq": "==", "lt": "<", "le": "<=", "gt": ">", "ge": ">="} + + +def cmp_using( + eq=None, + lt=None, + le=None, + gt=None, + ge=None, + require_same_type=True, + class_name="Comparable", +): + """ + Create a class that can be passed into `attrs.field`'s ``eq``, ``order``, + and ``cmp`` arguments to customize field comparison. + + The resulting class will have a full set of ordering methods if at least + one of ``{lt, le, gt, ge}`` and ``eq`` are provided. + + Args: + eq (typing.Callable | None): + Callable used to evaluate equality of two objects. + + lt (typing.Callable | None): + Callable used to evaluate whether one object is less than another + object. + + le (typing.Callable | None): + Callable used to evaluate whether one object is less than or equal + to another object. + + gt (typing.Callable | None): + Callable used to evaluate whether one object is greater than + another object. + + ge (typing.Callable | None): + Callable used to evaluate whether one object is greater than or + equal to another object. + + require_same_type (bool): + When `True`, equality and ordering methods will return + `NotImplemented` if objects are not of the same type. + + class_name (str | None): Name of class. Defaults to "Comparable". + + See `comparison` for more details. + + .. versionadded:: 21.1.0 + """ + + body = { + "__slots__": ["value"], + "__init__": _make_init(), + "_requirements": [], + "_is_comparable_to": _is_comparable_to, + } + + # Add operations. + num_order_functions = 0 + has_eq_function = False + + if eq is not None: + has_eq_function = True + body["__eq__"] = _make_operator("eq", eq) + body["__ne__"] = _make_ne() + + if lt is not None: + num_order_functions += 1 + body["__lt__"] = _make_operator("lt", lt) + + if le is not None: + num_order_functions += 1 + body["__le__"] = _make_operator("le", le) + + if gt is not None: + num_order_functions += 1 + body["__gt__"] = _make_operator("gt", gt) + + if ge is not None: + num_order_functions += 1 + body["__ge__"] = _make_operator("ge", ge) + + type_ = types.new_class( + class_name, (object,), {}, lambda ns: ns.update(body) + ) + + # Add same type requirement. + if require_same_type: + type_._requirements.append(_check_same_type) + + # Add total ordering if at least one operation was defined. + if 0 < num_order_functions < 4: + if not has_eq_function: + # functools.total_ordering requires __eq__ to be defined, + # so raise early error here to keep a nice stack. + msg = "eq must be define is order to complete ordering from lt, le, gt, ge." + raise ValueError(msg) + type_ = functools.total_ordering(type_) + + return type_ + + +def _make_init(): + """ + Create __init__ method. + """ + + def __init__(self, value): + """ + Initialize object with *value*. + """ + self.value = value + + return __init__ + + +def _make_operator(name, func): + """ + Create operator method. + """ + + def method(self, other): + if not self._is_comparable_to(other): + return NotImplemented + + result = func(self.value, other.value) + if result is NotImplemented: + return NotImplemented + + return result + + method.__name__ = f"__{name}__" + method.__doc__ = ( + f"Return a {_operation_names[name]} b. Computed by attrs." + ) + + return method + + +def _is_comparable_to(self, other): + """ + Check whether `other` is comparable to `self`. + """ + return all(func(self, other) for func in self._requirements) + + +def _check_same_type(self, other): + """ + Return True if *self* and *other* are of the same type, False otherwise. + """ + return other.value.__class__ is self.value.__class__ diff --git a/lambdas/aws-dd-forwarder-3.127.0/attr/_cmp.pyi b/lambdas/aws-dd-forwarder-3.127.0/attr/_cmp.pyi new file mode 100644 index 0000000..cc7893b --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attr/_cmp.pyi @@ -0,0 +1,13 @@ +from typing import Any, Callable + +_CompareWithType = Callable[[Any, Any], bool] + +def cmp_using( + eq: _CompareWithType | None = ..., + lt: _CompareWithType | None = ..., + le: _CompareWithType | None = ..., + gt: _CompareWithType | None = ..., + ge: _CompareWithType | None = ..., + require_same_type: bool = ..., + class_name: str = ..., +) -> type: ... diff --git a/lambdas/aws-dd-forwarder-3.127.0/attr/_compat.py b/lambdas/aws-dd-forwarder-3.127.0/attr/_compat.py new file mode 100644 index 0000000..104eeb0 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attr/_compat.py @@ -0,0 +1,103 @@ +# SPDX-License-Identifier: MIT + +import inspect +import platform +import sys +import threading + +from collections.abc import Mapping, Sequence # noqa: F401 +from typing import _GenericAlias + + +PYPY = platform.python_implementation() == "PyPy" +PY_3_8_PLUS = sys.version_info[:2] >= (3, 8) +PY_3_9_PLUS = sys.version_info[:2] >= (3, 9) +PY_3_10_PLUS = sys.version_info[:2] >= (3, 10) +PY_3_11_PLUS = sys.version_info[:2] >= (3, 11) +PY_3_12_PLUS = sys.version_info[:2] >= (3, 12) +PY_3_13_PLUS = sys.version_info[:2] >= (3, 13) +PY_3_14_PLUS = sys.version_info[:2] >= (3, 14) + + +if sys.version_info < (3, 8): + try: + from typing_extensions import Protocol + except ImportError: # pragma: no cover + Protocol = object +else: + from typing import Protocol # noqa: F401 + +if PY_3_14_PLUS: # pragma: no cover + import annotationlib + + _get_annotations = annotationlib.get_annotations + +else: + + def _get_annotations(cls): + """ + Get annotations for *cls*. + """ + return cls.__dict__.get("__annotations__", {}) + + +class _AnnotationExtractor: + """ + Extract type annotations from a callable, returning None whenever there + is none. + """ + + __slots__ = ["sig"] + + def __init__(self, callable): + try: + self.sig = inspect.signature(callable) + except (ValueError, TypeError): # inspect failed + self.sig = None + + def get_first_param_type(self): + """ + Return the type annotation of the first argument if it's not empty. + """ + if not self.sig: + return None + + params = list(self.sig.parameters.values()) + if params and params[0].annotation is not inspect.Parameter.empty: + return params[0].annotation + + return None + + def get_return_type(self): + """ + Return the return type if it's not empty. + """ + if ( + self.sig + and self.sig.return_annotation is not inspect.Signature.empty + ): + return self.sig.return_annotation + + return None + + +# Thread-local global to track attrs instances which are already being repr'd. +# This is needed because there is no other (thread-safe) way to pass info +# about the instances that are already being repr'd through the call stack +# in order to ensure we don't perform infinite recursion. +# +# For instance, if an instance contains a dict which contains that instance, +# we need to know that we're already repr'ing the outside instance from within +# the dict's repr() call. +# +# This lives here rather than in _make.py so that the functions in _make.py +# don't have a direct reference to the thread-local in their globals dict. +# If they have such a reference, it breaks cloudpickle. +repr_context = threading.local() + + +def get_generic_base(cl): + """If this is a generic class (A[str]), return the generic base for it.""" + if cl.__class__ is _GenericAlias: + return cl.__origin__ + return None diff --git a/lambdas/aws-dd-forwarder-3.127.0/attr/_config.py b/lambdas/aws-dd-forwarder-3.127.0/attr/_config.py new file mode 100644 index 0000000..9c245b1 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attr/_config.py @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: MIT + +__all__ = ["set_run_validators", "get_run_validators"] + +_run_validators = True + + +def set_run_validators(run): + """ + Set whether or not validators are run. By default, they are run. + + .. deprecated:: 21.3.0 It will not be removed, but it also will not be + moved to new ``attrs`` namespace. Use `attrs.validators.set_disabled()` + instead. + """ + if not isinstance(run, bool): + msg = "'run' must be bool." + raise TypeError(msg) + global _run_validators + _run_validators = run + + +def get_run_validators(): + """ + Return whether or not validators are run. + + .. deprecated:: 21.3.0 It will not be removed, but it also will not be + moved to new ``attrs`` namespace. Use `attrs.validators.get_disabled()` + instead. + """ + return _run_validators diff --git a/lambdas/aws-dd-forwarder-3.127.0/attr/_funcs.py b/lambdas/aws-dd-forwarder-3.127.0/attr/_funcs.py new file mode 100644 index 0000000..355cef4 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attr/_funcs.py @@ -0,0 +1,522 @@ +# SPDX-License-Identifier: MIT + + +import copy + +from ._compat import PY_3_9_PLUS, get_generic_base +from ._make import _OBJ_SETATTR, NOTHING, fields +from .exceptions import AttrsAttributeNotFoundError + + +def asdict( + inst, + recurse=True, + filter=None, + dict_factory=dict, + retain_collection_types=False, + value_serializer=None, +): + """ + Return the *attrs* attribute values of *inst* as a dict. + + Optionally recurse into other *attrs*-decorated classes. + + Args: + inst: Instance of an *attrs*-decorated class. + + recurse (bool): Recurse into classes that are also *attrs*-decorated. + + filter (~typing.Callable): + A callable whose return code determines whether an attribute or + element is included (`True`) or dropped (`False`). Is called with + the `attrs.Attribute` as the first argument and the value as the + second argument. + + dict_factory (~typing.Callable): + A callable to produce dictionaries from. For example, to produce + ordered dictionaries instead of normal Python dictionaries, pass in + ``collections.OrderedDict``. + + retain_collection_types (bool): + Do not convert to `list` when encountering an attribute whose type + is `tuple` or `set`. Only meaningful if *recurse* is `True`. + + value_serializer (typing.Callable | None): + A hook that is called for every attribute or dict key/value. It + receives the current instance, field and value and must return the + (updated) value. The hook is run *after* the optional *filter* has + been applied. + + Returns: + Return type of *dict_factory*. + + Raises: + attrs.exceptions.NotAnAttrsClassError: + If *cls* is not an *attrs* class. + + .. versionadded:: 16.0.0 *dict_factory* + .. versionadded:: 16.1.0 *retain_collection_types* + .. versionadded:: 20.3.0 *value_serializer* + .. versionadded:: 21.3.0 + If a dict has a collection for a key, it is serialized as a tuple. + """ + attrs = fields(inst.__class__) + rv = dict_factory() + for a in attrs: + v = getattr(inst, a.name) + if filter is not None and not filter(a, v): + continue + + if value_serializer is not None: + v = value_serializer(inst, a, v) + + if recurse is True: + if has(v.__class__): + rv[a.name] = asdict( + v, + recurse=True, + filter=filter, + dict_factory=dict_factory, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ) + elif isinstance(v, (tuple, list, set, frozenset)): + cf = v.__class__ if retain_collection_types is True else list + items = [ + _asdict_anything( + i, + is_key=False, + filter=filter, + dict_factory=dict_factory, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ) + for i in v + ] + try: + rv[a.name] = cf(items) + except TypeError: + if not issubclass(cf, tuple): + raise + # Workaround for TypeError: cf.__new__() missing 1 required + # positional argument (which appears, for a namedturle) + rv[a.name] = cf(*items) + elif isinstance(v, dict): + df = dict_factory + rv[a.name] = df( + ( + _asdict_anything( + kk, + is_key=True, + filter=filter, + dict_factory=df, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ), + _asdict_anything( + vv, + is_key=False, + filter=filter, + dict_factory=df, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ), + ) + for kk, vv in v.items() + ) + else: + rv[a.name] = v + else: + rv[a.name] = v + return rv + + +def _asdict_anything( + val, + is_key, + filter, + dict_factory, + retain_collection_types, + value_serializer, +): + """ + ``asdict`` only works on attrs instances, this works on anything. + """ + if getattr(val.__class__, "__attrs_attrs__", None) is not None: + # Attrs class. + rv = asdict( + val, + recurse=True, + filter=filter, + dict_factory=dict_factory, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ) + elif isinstance(val, (tuple, list, set, frozenset)): + if retain_collection_types is True: + cf = val.__class__ + elif is_key: + cf = tuple + else: + cf = list + + rv = cf( + [ + _asdict_anything( + i, + is_key=False, + filter=filter, + dict_factory=dict_factory, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ) + for i in val + ] + ) + elif isinstance(val, dict): + df = dict_factory + rv = df( + ( + _asdict_anything( + kk, + is_key=True, + filter=filter, + dict_factory=df, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ), + _asdict_anything( + vv, + is_key=False, + filter=filter, + dict_factory=df, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ), + ) + for kk, vv in val.items() + ) + else: + rv = val + if value_serializer is not None: + rv = value_serializer(None, None, rv) + + return rv + + +def astuple( + inst, + recurse=True, + filter=None, + tuple_factory=tuple, + retain_collection_types=False, +): + """ + Return the *attrs* attribute values of *inst* as a tuple. + + Optionally recurse into other *attrs*-decorated classes. + + Args: + inst: Instance of an *attrs*-decorated class. + + recurse (bool): + Recurse into classes that are also *attrs*-decorated. + + filter (~typing.Callable): + A callable whose return code determines whether an attribute or + element is included (`True`) or dropped (`False`). Is called with + the `attrs.Attribute` as the first argument and the value as the + second argument. + + tuple_factory (~typing.Callable): + A callable to produce tuples from. For example, to produce lists + instead of tuples. + + retain_collection_types (bool): + Do not convert to `list` or `dict` when encountering an attribute + which type is `tuple`, `dict` or `set`. Only meaningful if + *recurse* is `True`. + + Returns: + Return type of *tuple_factory* + + Raises: + attrs.exceptions.NotAnAttrsClassError: + If *cls* is not an *attrs* class. + + .. versionadded:: 16.2.0 + """ + attrs = fields(inst.__class__) + rv = [] + retain = retain_collection_types # Very long. :/ + for a in attrs: + v = getattr(inst, a.name) + if filter is not None and not filter(a, v): + continue + if recurse is True: + if has(v.__class__): + rv.append( + astuple( + v, + recurse=True, + filter=filter, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + ) + elif isinstance(v, (tuple, list, set, frozenset)): + cf = v.__class__ if retain is True else list + items = [ + ( + astuple( + j, + recurse=True, + filter=filter, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + if has(j.__class__) + else j + ) + for j in v + ] + try: + rv.append(cf(items)) + except TypeError: + if not issubclass(cf, tuple): + raise + # Workaround for TypeError: cf.__new__() missing 1 required + # positional argument (which appears, for a namedturle) + rv.append(cf(*items)) + elif isinstance(v, dict): + df = v.__class__ if retain is True else dict + rv.append( + df( + ( + ( + astuple( + kk, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + if has(kk.__class__) + else kk + ), + ( + astuple( + vv, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + if has(vv.__class__) + else vv + ), + ) + for kk, vv in v.items() + ) + ) + else: + rv.append(v) + else: + rv.append(v) + + return rv if tuple_factory is list else tuple_factory(rv) + + +def has(cls): + """ + Check whether *cls* is a class with *attrs* attributes. + + Args: + cls (type): Class to introspect. + + Raises: + TypeError: If *cls* is not a class. + + Returns: + bool: + """ + attrs = getattr(cls, "__attrs_attrs__", None) + if attrs is not None: + return True + + # No attrs, maybe it's a specialized generic (A[str])? + generic_base = get_generic_base(cls) + if generic_base is not None: + generic_attrs = getattr(generic_base, "__attrs_attrs__", None) + if generic_attrs is not None: + # Stick it on here for speed next time. + cls.__attrs_attrs__ = generic_attrs + return generic_attrs is not None + return False + + +def assoc(inst, **changes): + """ + Copy *inst* and apply *changes*. + + This is different from `evolve` that applies the changes to the arguments + that create the new instance. + + `evolve`'s behavior is preferable, but there are `edge cases`_ where it + doesn't work. Therefore `assoc` is deprecated, but will not be removed. + + .. _`edge cases`: https://github.com/python-attrs/attrs/issues/251 + + Args: + inst: Instance of a class with *attrs* attributes. + + changes: Keyword changes in the new copy. + + Returns: + A copy of inst with *changes* incorporated. + + Raises: + attrs.exceptions.AttrsAttributeNotFoundError: + If *attr_name* couldn't be found on *cls*. + + attrs.exceptions.NotAnAttrsClassError: + If *cls* is not an *attrs* class. + + .. deprecated:: 17.1.0 + Use `attrs.evolve` instead if you can. This function will not be + removed du to the slightly different approach compared to + `attrs.evolve`, though. + """ + new = copy.copy(inst) + attrs = fields(inst.__class__) + for k, v in changes.items(): + a = getattr(attrs, k, NOTHING) + if a is NOTHING: + msg = f"{k} is not an attrs attribute on {new.__class__}." + raise AttrsAttributeNotFoundError(msg) + _OBJ_SETATTR(new, k, v) + return new + + +def evolve(*args, **changes): + """ + Create a new instance, based on the first positional argument with + *changes* applied. + + Args: + + inst: + Instance of a class with *attrs* attributes. *inst* must be passed + as a positional argument. + + changes: + Keyword changes in the new copy. + + Returns: + A copy of inst with *changes* incorporated. + + Raises: + TypeError: + If *attr_name* couldn't be found in the class ``__init__``. + + attrs.exceptions.NotAnAttrsClassError: + If *cls* is not an *attrs* class. + + .. versionadded:: 17.1.0 + .. deprecated:: 23.1.0 + It is now deprecated to pass the instance using the keyword argument + *inst*. It will raise a warning until at least April 2024, after which + it will become an error. Always pass the instance as a positional + argument. + .. versionchanged:: 24.1.0 + *inst* can't be passed as a keyword argument anymore. + """ + try: + (inst,) = args + except ValueError: + msg = ( + f"evolve() takes 1 positional argument, but {len(args)} were given" + ) + raise TypeError(msg) from None + + cls = inst.__class__ + attrs = fields(cls) + for a in attrs: + if not a.init: + continue + attr_name = a.name # To deal with private attributes. + init_name = a.alias + if init_name not in changes: + changes[init_name] = getattr(inst, attr_name) + + return cls(**changes) + + +def resolve_types( + cls, globalns=None, localns=None, attribs=None, include_extras=True +): + """ + Resolve any strings and forward annotations in type annotations. + + This is only required if you need concrete types in :class:`Attribute`'s + *type* field. In other words, you don't need to resolve your types if you + only use them for static type checking. + + With no arguments, names will be looked up in the module in which the class + was created. If this is not what you want, for example, if the name only + exists inside a method, you may pass *globalns* or *localns* to specify + other dictionaries in which to look up these names. See the docs of + `typing.get_type_hints` for more details. + + Args: + cls (type): Class to resolve. + + globalns (dict | None): Dictionary containing global variables. + + localns (dict | None): Dictionary containing local variables. + + attribs (list | None): + List of attribs for the given class. This is necessary when calling + from inside a ``field_transformer`` since *cls* is not an *attrs* + class yet. + + include_extras (bool): + Resolve more accurately, if possible. Pass ``include_extras`` to + ``typing.get_hints``, if supported by the typing module. On + supported Python versions (3.9+), this resolves the types more + accurately. + + Raises: + TypeError: If *cls* is not a class. + + attrs.exceptions.NotAnAttrsClassError: + If *cls* is not an *attrs* class and you didn't pass any attribs. + + NameError: If types cannot be resolved because of missing variables. + + Returns: + *cls* so you can use this function also as a class decorator. Please + note that you have to apply it **after** `attrs.define`. That means the + decorator has to come in the line **before** `attrs.define`. + + .. versionadded:: 20.1.0 + .. versionadded:: 21.1.0 *attribs* + .. versionadded:: 23.1.0 *include_extras* + """ + # Since calling get_type_hints is expensive we cache whether we've + # done it already. + if getattr(cls, "__attrs_types_resolved__", None) != cls: + import typing + + kwargs = {"globalns": globalns, "localns": localns} + + if PY_3_9_PLUS: + kwargs["include_extras"] = include_extras + + hints = typing.get_type_hints(cls, **kwargs) + for field in fields(cls) if attribs is None else attribs: + if field.name in hints: + # Since fields have been frozen we must work around it. + _OBJ_SETATTR(field, "type", hints[field.name]) + # We store the class we resolved so that subclasses know they haven't + # been resolved. + cls.__attrs_types_resolved__ = cls + + # Return the class so you can use it as a decorator too. + return cls diff --git a/lambdas/aws-dd-forwarder-3.127.0/attr/_make.py b/lambdas/aws-dd-forwarder-3.127.0/attr/_make.py new file mode 100644 index 0000000..bf00c5f --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attr/_make.py @@ -0,0 +1,2960 @@ +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import abc +import contextlib +import copy +import enum +import functools +import inspect +import itertools +import linecache +import sys +import types +import typing + +from operator import itemgetter + +# We need to import _compat itself in addition to the _compat members to avoid +# having the thread-local in the globals here. +from . import _compat, _config, setters +from ._compat import ( + PY_3_8_PLUS, + PY_3_10_PLUS, + PY_3_11_PLUS, + _AnnotationExtractor, + _get_annotations, + get_generic_base, +) +from .exceptions import ( + DefaultAlreadySetError, + FrozenInstanceError, + NotAnAttrsClassError, + UnannotatedAttributeError, +) + + +# This is used at least twice, so cache it here. +_OBJ_SETATTR = object.__setattr__ +_INIT_FACTORY_PAT = "__attr_factory_%s" +_CLASSVAR_PREFIXES = ( + "typing.ClassVar", + "t.ClassVar", + "ClassVar", + "typing_extensions.ClassVar", +) +# we don't use a double-underscore prefix because that triggers +# name mangling when trying to create a slot for the field +# (when slots=True) +_HASH_CACHE_FIELD = "_attrs_cached_hash" + +_EMPTY_METADATA_SINGLETON = types.MappingProxyType({}) + +# Unique object for unequivocal getattr() defaults. +_SENTINEL = object() + +_DEFAULT_ON_SETATTR = setters.pipe(setters.convert, setters.validate) + + +class _Nothing(enum.Enum): + """ + Sentinel to indicate the lack of a value when `None` is ambiguous. + + If extending attrs, you can use ``typing.Literal[NOTHING]`` to show + that a value may be ``NOTHING``. + + .. versionchanged:: 21.1.0 ``bool(NOTHING)`` is now False. + .. versionchanged:: 22.2.0 ``NOTHING`` is now an ``enum.Enum`` variant. + """ + + NOTHING = enum.auto() + + def __repr__(self): + return "NOTHING" + + def __bool__(self): + return False + + +NOTHING = _Nothing.NOTHING +""" +Sentinel to indicate the lack of a value when `None` is ambiguous. +""" + + +class _CacheHashWrapper(int): + """ + An integer subclass that pickles / copies as None + + This is used for non-slots classes with ``cache_hash=True``, to avoid + serializing a potentially (even likely) invalid hash value. Since `None` + is the default value for uncalculated hashes, whenever this is copied, + the copy's value for the hash should automatically reset. + + See GH #613 for more details. + """ + + def __reduce__(self, _none_constructor=type(None), _args=()): # noqa: B008 + return _none_constructor, _args + + +def attrib( + default=NOTHING, + validator=None, + repr=True, + cmp=None, + hash=None, + init=True, + metadata=None, + type=None, + converter=None, + factory=None, + kw_only=False, + eq=None, + order=None, + on_setattr=None, + alias=None, +): + """ + Create a new field / attribute on a class. + + Identical to `attrs.field`, except it's not keyword-only. + + Consider using `attrs.field` in new code (``attr.ib`` will *never* go away, + though). + + .. warning:: + + Does **nothing** unless the class is also decorated with + `attr.s` (or similar)! + + + .. versionadded:: 15.2.0 *convert* + .. versionadded:: 16.3.0 *metadata* + .. versionchanged:: 17.1.0 *validator* can be a ``list`` now. + .. versionchanged:: 17.1.0 + *hash* is `None` and therefore mirrors *eq* by default. + .. versionadded:: 17.3.0 *type* + .. deprecated:: 17.4.0 *convert* + .. versionadded:: 17.4.0 + *converter* as a replacement for the deprecated *convert* to achieve + consistency with other noun-based arguments. + .. versionadded:: 18.1.0 + ``factory=f`` is syntactic sugar for ``default=attr.Factory(f)``. + .. versionadded:: 18.2.0 *kw_only* + .. versionchanged:: 19.2.0 *convert* keyword argument removed. + .. versionchanged:: 19.2.0 *repr* also accepts a custom callable. + .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01. + .. versionadded:: 19.2.0 *eq* and *order* + .. versionadded:: 20.1.0 *on_setattr* + .. versionchanged:: 20.3.0 *kw_only* backported to Python 2 + .. versionchanged:: 21.1.0 + *eq*, *order*, and *cmp* also accept a custom callable + .. versionchanged:: 21.1.0 *cmp* undeprecated + .. versionadded:: 22.2.0 *alias* + """ + eq, eq_key, order, order_key = _determine_attrib_eq_order( + cmp, eq, order, True + ) + + if hash is not None and hash is not True and hash is not False: + msg = "Invalid value for hash. Must be True, False, or None." + raise TypeError(msg) + + if factory is not None: + if default is not NOTHING: + msg = ( + "The `default` and `factory` arguments are mutually exclusive." + ) + raise ValueError(msg) + if not callable(factory): + msg = "The `factory` argument must be a callable." + raise ValueError(msg) + default = Factory(factory) + + if metadata is None: + metadata = {} + + # Apply syntactic sugar by auto-wrapping. + if isinstance(on_setattr, (list, tuple)): + on_setattr = setters.pipe(*on_setattr) + + if validator and isinstance(validator, (list, tuple)): + validator = and_(*validator) + + if converter and isinstance(converter, (list, tuple)): + converter = pipe(*converter) + + return _CountingAttr( + default=default, + validator=validator, + repr=repr, + cmp=None, + hash=hash, + init=init, + converter=converter, + metadata=metadata, + type=type, + kw_only=kw_only, + eq=eq, + eq_key=eq_key, + order=order, + order_key=order_key, + on_setattr=on_setattr, + alias=alias, + ) + + +def _compile_and_eval(script, globs, locs=None, filename=""): + """ + Evaluate the script with the given global (globs) and local (locs) + variables. + """ + bytecode = compile(script, filename, "exec") + eval(bytecode, globs, locs) + + +def _make_method(name, script, filename, globs, locals=None): + """ + Create the method with the script given and return the method object. + """ + locs = {} if locals is None else locals + + # In order of debuggers like PDB being able to step through the code, + # we add a fake linecache entry. + count = 1 + base_filename = filename + while True: + linecache_tuple = ( + len(script), + None, + script.splitlines(True), + filename, + ) + old_val = linecache.cache.setdefault(filename, linecache_tuple) + if old_val == linecache_tuple: + break + + filename = f"{base_filename[:-1]}-{count}>" + count += 1 + + _compile_and_eval(script, globs, locs, filename) + + return locs[name] + + +def _make_attr_tuple_class(cls_name, attr_names): + """ + Create a tuple subclass to hold `Attribute`s for an `attrs` class. + + The subclass is a bare tuple with properties for names. + + class MyClassAttributes(tuple): + __slots__ = () + x = property(itemgetter(0)) + """ + attr_class_name = f"{cls_name}Attributes" + attr_class_template = [ + f"class {attr_class_name}(tuple):", + " __slots__ = ()", + ] + if attr_names: + for i, attr_name in enumerate(attr_names): + attr_class_template.append( + f" {attr_name} = _attrs_property(_attrs_itemgetter({i}))" + ) + else: + attr_class_template.append(" pass") + globs = {"_attrs_itemgetter": itemgetter, "_attrs_property": property} + _compile_and_eval("\n".join(attr_class_template), globs) + return globs[attr_class_name] + + +# Tuple class for extracted attributes from a class definition. +# `base_attrs` is a subset of `attrs`. +_Attributes = _make_attr_tuple_class( + "_Attributes", + [ + # all attributes to build dunder methods for + "attrs", + # attributes that have been inherited + "base_attrs", + # map inherited attributes to their originating classes + "base_attrs_map", + ], +) + + +def _is_class_var(annot): + """ + Check whether *annot* is a typing.ClassVar. + + The string comparison hack is used to avoid evaluating all string + annotations which would put attrs-based classes at a performance + disadvantage compared to plain old classes. + """ + annot = str(annot) + + # Annotation can be quoted. + if annot.startswith(("'", '"')) and annot.endswith(("'", '"')): + annot = annot[1:-1] + + return annot.startswith(_CLASSVAR_PREFIXES) + + +def _has_own_attribute(cls, attrib_name): + """ + Check whether *cls* defines *attrib_name* (and doesn't just inherit it). + """ + return attrib_name in cls.__dict__ + + +def _collect_base_attrs(cls, taken_attr_names): + """ + Collect attr.ibs from base classes of *cls*, except *taken_attr_names*. + """ + base_attrs = [] + base_attr_map = {} # A dictionary of base attrs to their classes. + + # Traverse the MRO and collect attributes. + for base_cls in reversed(cls.__mro__[1:-1]): + for a in getattr(base_cls, "__attrs_attrs__", []): + if a.inherited or a.name in taken_attr_names: + continue + + a = a.evolve(inherited=True) # noqa: PLW2901 + base_attrs.append(a) + base_attr_map[a.name] = base_cls + + # For each name, only keep the freshest definition i.e. the furthest at the + # back. base_attr_map is fine because it gets overwritten with every new + # instance. + filtered = [] + seen = set() + for a in reversed(base_attrs): + if a.name in seen: + continue + filtered.insert(0, a) + seen.add(a.name) + + return filtered, base_attr_map + + +def _collect_base_attrs_broken(cls, taken_attr_names): + """ + Collect attr.ibs from base classes of *cls*, except *taken_attr_names*. + + N.B. *taken_attr_names* will be mutated. + + Adhere to the old incorrect behavior. + + Notably it collects from the front and considers inherited attributes which + leads to the buggy behavior reported in #428. + """ + base_attrs = [] + base_attr_map = {} # A dictionary of base attrs to their classes. + + # Traverse the MRO and collect attributes. + for base_cls in cls.__mro__[1:-1]: + for a in getattr(base_cls, "__attrs_attrs__", []): + if a.name in taken_attr_names: + continue + + a = a.evolve(inherited=True) # noqa: PLW2901 + taken_attr_names.add(a.name) + base_attrs.append(a) + base_attr_map[a.name] = base_cls + + return base_attrs, base_attr_map + + +def _transform_attrs( + cls, these, auto_attribs, kw_only, collect_by_mro, field_transformer +): + """ + Transform all `_CountingAttr`s on a class into `Attribute`s. + + If *these* is passed, use that and don't look for them on the class. + + If *collect_by_mro* is True, collect them in the correct MRO order, + otherwise use the old -- incorrect -- order. See #428. + + Return an `_Attributes`. + """ + cd = cls.__dict__ + anns = _get_annotations(cls) + + if these is not None: + ca_list = list(these.items()) + elif auto_attribs is True: + ca_names = { + name + for name, attr in cd.items() + if isinstance(attr, _CountingAttr) + } + ca_list = [] + annot_names = set() + for attr_name, type in anns.items(): + if _is_class_var(type): + continue + annot_names.add(attr_name) + a = cd.get(attr_name, NOTHING) + + if not isinstance(a, _CountingAttr): + a = attrib() if a is NOTHING else attrib(default=a) + ca_list.append((attr_name, a)) + + unannotated = ca_names - annot_names + if len(unannotated) > 0: + raise UnannotatedAttributeError( + "The following `attr.ib`s lack a type annotation: " + + ", ".join( + sorted(unannotated, key=lambda n: cd.get(n).counter) + ) + + "." + ) + else: + ca_list = sorted( + ( + (name, attr) + for name, attr in cd.items() + if isinstance(attr, _CountingAttr) + ), + key=lambda e: e[1].counter, + ) + + own_attrs = [ + Attribute.from_counting_attr( + name=attr_name, ca=ca, type=anns.get(attr_name) + ) + for attr_name, ca in ca_list + ] + + if collect_by_mro: + base_attrs, base_attr_map = _collect_base_attrs( + cls, {a.name for a in own_attrs} + ) + else: + base_attrs, base_attr_map = _collect_base_attrs_broken( + cls, {a.name for a in own_attrs} + ) + + if kw_only: + own_attrs = [a.evolve(kw_only=True) for a in own_attrs] + base_attrs = [a.evolve(kw_only=True) for a in base_attrs] + + attrs = base_attrs + own_attrs + + # Mandatory vs non-mandatory attr order only matters when they are part of + # the __init__ signature and when they aren't kw_only (which are moved to + # the end and can be mandatory or non-mandatory in any order, as they will + # be specified as keyword args anyway). Check the order of those attrs: + had_default = False + for a in (a for a in attrs if a.init is not False and a.kw_only is False): + if had_default is True and a.default is NOTHING: + msg = f"No mandatory attributes allowed after an attribute with a default value or factory. Attribute in question: {a!r}" + raise ValueError(msg) + + if had_default is False and a.default is not NOTHING: + had_default = True + + if field_transformer is not None: + attrs = field_transformer(cls, attrs) + + # Resolve default field alias after executing field_transformer. + # This allows field_transformer to differentiate between explicit vs + # default aliases and supply their own defaults. + attrs = [ + a.evolve(alias=_default_init_alias_for(a.name)) if not a.alias else a + for a in attrs + ] + + # Create AttrsClass *after* applying the field_transformer since it may + # add or remove attributes! + attr_names = [a.name for a in attrs] + AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names) + + return _Attributes((AttrsClass(attrs), base_attrs, base_attr_map)) + + +def _make_cached_property_getattr(cached_properties, original_getattr, cls): + lines = [ + # Wrapped to get `__class__` into closure cell for super() + # (It will be replaced with the newly constructed class after construction). + "def wrapper(_cls):", + " __class__ = _cls", + " def __getattr__(self, item, cached_properties=cached_properties, original_getattr=original_getattr, _cached_setattr_get=_cached_setattr_get):", + " func = cached_properties.get(item)", + " if func is not None:", + " result = func(self)", + " _setter = _cached_setattr_get(self)", + " _setter(item, result)", + " return result", + ] + if original_getattr is not None: + lines.append( + " return original_getattr(self, item)", + ) + else: + lines.extend( + [ + " try:", + " return super().__getattribute__(item)", + " except AttributeError:", + " if not hasattr(super(), '__getattr__'):", + " raise", + " return super().__getattr__(item)", + " original_error = f\"'{self.__class__.__name__}' object has no attribute '{item}'\"", + " raise AttributeError(original_error)", + ] + ) + + lines.extend( + [ + " return __getattr__", + "__getattr__ = wrapper(_cls)", + ] + ) + + unique_filename = _generate_unique_filename(cls, "getattr") + + glob = { + "cached_properties": cached_properties, + "_cached_setattr_get": _OBJ_SETATTR.__get__, + "original_getattr": original_getattr, + } + + return _make_method( + "__getattr__", + "\n".join(lines), + unique_filename, + glob, + locals={ + "_cls": cls, + }, + ) + + +def _frozen_setattrs(self, name, value): + """ + Attached to frozen classes as __setattr__. + """ + if isinstance(self, BaseException) and name in ( + "__cause__", + "__context__", + "__traceback__", + ): + BaseException.__setattr__(self, name, value) + return + + raise FrozenInstanceError() + + +def _frozen_delattrs(self, name): + """ + Attached to frozen classes as __delattr__. + """ + raise FrozenInstanceError() + + +class _ClassBuilder: + """ + Iteratively build *one* class. + """ + + __slots__ = ( + "_attr_names", + "_attrs", + "_base_attr_map", + "_base_names", + "_cache_hash", + "_cls", + "_cls_dict", + "_delete_attribs", + "_frozen", + "_has_pre_init", + "_pre_init_has_args", + "_has_post_init", + "_is_exc", + "_on_setattr", + "_slots", + "_weakref_slot", + "_wrote_own_setattr", + "_has_custom_setattr", + ) + + def __init__( + self, + cls, + these, + slots, + frozen, + weakref_slot, + getstate_setstate, + auto_attribs, + kw_only, + cache_hash, + is_exc, + collect_by_mro, + on_setattr, + has_custom_setattr, + field_transformer, + ): + attrs, base_attrs, base_map = _transform_attrs( + cls, + these, + auto_attribs, + kw_only, + collect_by_mro, + field_transformer, + ) + + self._cls = cls + self._cls_dict = dict(cls.__dict__) if slots else {} + self._attrs = attrs + self._base_names = {a.name for a in base_attrs} + self._base_attr_map = base_map + self._attr_names = tuple(a.name for a in attrs) + self._slots = slots + self._frozen = frozen + self._weakref_slot = weakref_slot + self._cache_hash = cache_hash + self._has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False)) + self._pre_init_has_args = False + if self._has_pre_init: + # Check if the pre init method has more arguments than just `self` + # We want to pass arguments if pre init expects arguments + pre_init_func = cls.__attrs_pre_init__ + pre_init_signature = inspect.signature(pre_init_func) + self._pre_init_has_args = len(pre_init_signature.parameters) > 1 + self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False)) + self._delete_attribs = not bool(these) + self._is_exc = is_exc + self._on_setattr = on_setattr + + self._has_custom_setattr = has_custom_setattr + self._wrote_own_setattr = False + + self._cls_dict["__attrs_attrs__"] = self._attrs + + if frozen: + self._cls_dict["__setattr__"] = _frozen_setattrs + self._cls_dict["__delattr__"] = _frozen_delattrs + + self._wrote_own_setattr = True + elif on_setattr in ( + _DEFAULT_ON_SETATTR, + setters.validate, + setters.convert, + ): + has_validator = has_converter = False + for a in attrs: + if a.validator is not None: + has_validator = True + if a.converter is not None: + has_converter = True + + if has_validator and has_converter: + break + if ( + ( + on_setattr == _DEFAULT_ON_SETATTR + and not (has_validator or has_converter) + ) + or (on_setattr == setters.validate and not has_validator) + or (on_setattr == setters.convert and not has_converter) + ): + # If class-level on_setattr is set to convert + validate, but + # there's no field to convert or validate, pretend like there's + # no on_setattr. + self._on_setattr = None + + if getstate_setstate: + ( + self._cls_dict["__getstate__"], + self._cls_dict["__setstate__"], + ) = self._make_getstate_setstate() + + def __repr__(self): + return f"<_ClassBuilder(cls={self._cls.__name__})>" + + def build_class(self): + """ + Finalize class based on the accumulated configuration. + + Builder cannot be used after calling this method. + """ + if self._slots is True: + cls = self._create_slots_class() + else: + cls = self._patch_original_class() + if PY_3_10_PLUS: + cls = abc.update_abstractmethods(cls) + + # The method gets only called if it's not inherited from a base class. + # _has_own_attribute does NOT work properly for classmethods. + if ( + getattr(cls, "__attrs_init_subclass__", None) + and "__attrs_init_subclass__" not in cls.__dict__ + ): + cls.__attrs_init_subclass__() + + return cls + + def _patch_original_class(self): + """ + Apply accumulated methods and return the class. + """ + cls = self._cls + base_names = self._base_names + + # Clean class of attribute definitions (`attr.ib()`s). + if self._delete_attribs: + for name in self._attr_names: + if ( + name not in base_names + and getattr(cls, name, _SENTINEL) is not _SENTINEL + ): + # An AttributeError can happen if a base class defines a + # class variable and we want to set an attribute with the + # same name by using only a type annotation. + with contextlib.suppress(AttributeError): + delattr(cls, name) + + # Attach our dunder methods. + for name, value in self._cls_dict.items(): + setattr(cls, name, value) + + # If we've inherited an attrs __setattr__ and don't write our own, + # reset it to object's. + if not self._wrote_own_setattr and getattr( + cls, "__attrs_own_setattr__", False + ): + cls.__attrs_own_setattr__ = False + + if not self._has_custom_setattr: + cls.__setattr__ = _OBJ_SETATTR + + return cls + + def _create_slots_class(self): + """ + Build and return a new class with a `__slots__` attribute. + """ + cd = { + k: v + for k, v in self._cls_dict.items() + if k not in (*tuple(self._attr_names), "__dict__", "__weakref__") + } + + # If our class doesn't have its own implementation of __setattr__ + # (either from the user or by us), check the bases, if one of them has + # an attrs-made __setattr__, that needs to be reset. We don't walk the + # MRO because we only care about our immediate base classes. + # XXX: This can be confused by subclassing a slotted attrs class with + # XXX: a non-attrs class and subclass the resulting class with an attrs + # XXX: class. See `test_slotted_confused` for details. For now that's + # XXX: OK with us. + if not self._wrote_own_setattr: + cd["__attrs_own_setattr__"] = False + + if not self._has_custom_setattr: + for base_cls in self._cls.__bases__: + if base_cls.__dict__.get("__attrs_own_setattr__", False): + cd["__setattr__"] = _OBJ_SETATTR + break + + # Traverse the MRO to collect existing slots + # and check for an existing __weakref__. + existing_slots = {} + weakref_inherited = False + for base_cls in self._cls.__mro__[1:-1]: + if base_cls.__dict__.get("__weakref__", None) is not None: + weakref_inherited = True + existing_slots.update( + { + name: getattr(base_cls, name) + for name in getattr(base_cls, "__slots__", []) + } + ) + + base_names = set(self._base_names) + + names = self._attr_names + if ( + self._weakref_slot + and "__weakref__" not in getattr(self._cls, "__slots__", ()) + and "__weakref__" not in names + and not weakref_inherited + ): + names += ("__weakref__",) + + if PY_3_8_PLUS: + cached_properties = { + name: cached_property.func + for name, cached_property in cd.items() + if isinstance(cached_property, functools.cached_property) + } + else: + # `functools.cached_property` was introduced in 3.8. + # So can't be used before this. + cached_properties = {} + + # Collect methods with a `__class__` reference that are shadowed in the new class. + # To know to update them. + additional_closure_functions_to_update = [] + if cached_properties: + class_annotations = _get_annotations(self._cls) + for name, func in cached_properties.items(): + # Add cached properties to names for slotting. + names += (name,) + # Clear out function from class to avoid clashing. + del cd[name] + additional_closure_functions_to_update.append(func) + annotation = inspect.signature(func).return_annotation + if annotation is not inspect.Parameter.empty: + class_annotations[name] = annotation + + original_getattr = cd.get("__getattr__") + if original_getattr is not None: + additional_closure_functions_to_update.append(original_getattr) + + cd["__getattr__"] = _make_cached_property_getattr( + cached_properties, original_getattr, self._cls + ) + + # We only add the names of attributes that aren't inherited. + # Setting __slots__ to inherited attributes wastes memory. + slot_names = [name for name in names if name not in base_names] + + # There are slots for attributes from current class + # that are defined in parent classes. + # As their descriptors may be overridden by a child class, + # we collect them here and update the class dict + reused_slots = { + slot: slot_descriptor + for slot, slot_descriptor in existing_slots.items() + if slot in slot_names + } + slot_names = [name for name in slot_names if name not in reused_slots] + cd.update(reused_slots) + if self._cache_hash: + slot_names.append(_HASH_CACHE_FIELD) + + cd["__slots__"] = tuple(slot_names) + + cd["__qualname__"] = self._cls.__qualname__ + + # Create new class based on old class and our methods. + cls = type(self._cls)(self._cls.__name__, self._cls.__bases__, cd) + + # The following is a fix for + # . + # If a method mentions `__class__` or uses the no-arg super(), the + # compiler will bake a reference to the class in the method itself + # as `method.__closure__`. Since we replace the class with a + # clone, we rewrite these references so it keeps working. + for item in itertools.chain( + cls.__dict__.values(), additional_closure_functions_to_update + ): + if isinstance(item, (classmethod, staticmethod)): + # Class- and staticmethods hide their functions inside. + # These might need to be rewritten as well. + closure_cells = getattr(item.__func__, "__closure__", None) + elif isinstance(item, property): + # Workaround for property `super()` shortcut (PY3-only). + # There is no universal way for other descriptors. + closure_cells = getattr(item.fget, "__closure__", None) + else: + closure_cells = getattr(item, "__closure__", None) + + if not closure_cells: # Catch None or the empty list. + continue + for cell in closure_cells: + try: + match = cell.cell_contents is self._cls + except ValueError: # noqa: PERF203 + # ValueError: Cell is empty + pass + else: + if match: + cell.cell_contents = cls + return cls + + def add_repr(self, ns): + self._cls_dict["__repr__"] = self._add_method_dunders( + _make_repr(self._attrs, ns, self._cls) + ) + return self + + def add_str(self): + repr = self._cls_dict.get("__repr__") + if repr is None: + msg = "__str__ can only be generated if a __repr__ exists." + raise ValueError(msg) + + def __str__(self): + return self.__repr__() + + self._cls_dict["__str__"] = self._add_method_dunders(__str__) + return self + + def _make_getstate_setstate(self): + """ + Create custom __setstate__ and __getstate__ methods. + """ + # __weakref__ is not writable. + state_attr_names = tuple( + an for an in self._attr_names if an != "__weakref__" + ) + + def slots_getstate(self): + """ + Automatically created by attrs. + """ + return {name: getattr(self, name) for name in state_attr_names} + + hash_caching_enabled = self._cache_hash + + def slots_setstate(self, state): + """ + Automatically created by attrs. + """ + __bound_setattr = _OBJ_SETATTR.__get__(self) + if isinstance(state, tuple): + # Backward compatibility with attrs instances pickled with + # attrs versions before v22.2.0 which stored tuples. + for name, value in zip(state_attr_names, state): + __bound_setattr(name, value) + else: + for name in state_attr_names: + if name in state: + __bound_setattr(name, state[name]) + + # The hash code cache is not included when the object is + # serialized, but it still needs to be initialized to None to + # indicate that the first call to __hash__ should be a cache + # miss. + if hash_caching_enabled: + __bound_setattr(_HASH_CACHE_FIELD, None) + + return slots_getstate, slots_setstate + + def make_unhashable(self): + self._cls_dict["__hash__"] = None + return self + + def add_hash(self): + self._cls_dict["__hash__"] = self._add_method_dunders( + _make_hash( + self._cls, + self._attrs, + frozen=self._frozen, + cache_hash=self._cache_hash, + ) + ) + + return self + + def add_init(self): + self._cls_dict["__init__"] = self._add_method_dunders( + _make_init( + self._cls, + self._attrs, + self._has_pre_init, + self._pre_init_has_args, + self._has_post_init, + self._frozen, + self._slots, + self._cache_hash, + self._base_attr_map, + self._is_exc, + self._on_setattr, + attrs_init=False, + ) + ) + + return self + + def add_match_args(self): + self._cls_dict["__match_args__"] = tuple( + field.name + for field in self._attrs + if field.init and not field.kw_only + ) + + def add_attrs_init(self): + self._cls_dict["__attrs_init__"] = self._add_method_dunders( + _make_init( + self._cls, + self._attrs, + self._has_pre_init, + self._pre_init_has_args, + self._has_post_init, + self._frozen, + self._slots, + self._cache_hash, + self._base_attr_map, + self._is_exc, + self._on_setattr, + attrs_init=True, + ) + ) + + return self + + def add_eq(self): + cd = self._cls_dict + + cd["__eq__"] = self._add_method_dunders( + _make_eq(self._cls, self._attrs) + ) + cd["__ne__"] = self._add_method_dunders(_make_ne()) + + return self + + def add_order(self): + cd = self._cls_dict + + cd["__lt__"], cd["__le__"], cd["__gt__"], cd["__ge__"] = ( + self._add_method_dunders(meth) + for meth in _make_order(self._cls, self._attrs) + ) + + return self + + def add_setattr(self): + if self._frozen: + return self + + sa_attrs = {} + for a in self._attrs: + on_setattr = a.on_setattr or self._on_setattr + if on_setattr and on_setattr is not setters.NO_OP: + sa_attrs[a.name] = a, on_setattr + + if not sa_attrs: + return self + + if self._has_custom_setattr: + # We need to write a __setattr__ but there already is one! + msg = "Can't combine custom __setattr__ with on_setattr hooks." + raise ValueError(msg) + + # docstring comes from _add_method_dunders + def __setattr__(self, name, val): + try: + a, hook = sa_attrs[name] + except KeyError: + nval = val + else: + nval = hook(self, a, val) + + _OBJ_SETATTR(self, name, nval) + + self._cls_dict["__attrs_own_setattr__"] = True + self._cls_dict["__setattr__"] = self._add_method_dunders(__setattr__) + self._wrote_own_setattr = True + + return self + + def _add_method_dunders(self, method): + """ + Add __module__ and __qualname__ to a *method* if possible. + """ + with contextlib.suppress(AttributeError): + method.__module__ = self._cls.__module__ + + with contextlib.suppress(AttributeError): + method.__qualname__ = f"{self._cls.__qualname__}.{method.__name__}" + + with contextlib.suppress(AttributeError): + method.__doc__ = ( + "Method generated by attrs for class " + f"{self._cls.__qualname__}." + ) + + return method + + +def _determine_attrs_eq_order(cmp, eq, order, default_eq): + """ + Validate the combination of *cmp*, *eq*, and *order*. Derive the effective + values of eq and order. If *eq* is None, set it to *default_eq*. + """ + if cmp is not None and any((eq is not None, order is not None)): + msg = "Don't mix `cmp` with `eq' and `order`." + raise ValueError(msg) + + # cmp takes precedence due to bw-compatibility. + if cmp is not None: + return cmp, cmp + + # If left None, equality is set to the specified default and ordering + # mirrors equality. + if eq is None: + eq = default_eq + + if order is None: + order = eq + + if eq is False and order is True: + msg = "`order` can only be True if `eq` is True too." + raise ValueError(msg) + + return eq, order + + +def _determine_attrib_eq_order(cmp, eq, order, default_eq): + """ + Validate the combination of *cmp*, *eq*, and *order*. Derive the effective + values of eq and order. If *eq* is None, set it to *default_eq*. + """ + if cmp is not None and any((eq is not None, order is not None)): + msg = "Don't mix `cmp` with `eq' and `order`." + raise ValueError(msg) + + def decide_callable_or_boolean(value): + """ + Decide whether a key function is used. + """ + if callable(value): + value, key = True, value + else: + key = None + return value, key + + # cmp takes precedence due to bw-compatibility. + if cmp is not None: + cmp, cmp_key = decide_callable_or_boolean(cmp) + return cmp, cmp_key, cmp, cmp_key + + # If left None, equality is set to the specified default and ordering + # mirrors equality. + if eq is None: + eq, eq_key = default_eq, None + else: + eq, eq_key = decide_callable_or_boolean(eq) + + if order is None: + order, order_key = eq, eq_key + else: + order, order_key = decide_callable_or_boolean(order) + + if eq is False and order is True: + msg = "`order` can only be True if `eq` is True too." + raise ValueError(msg) + + return eq, eq_key, order, order_key + + +def _determine_whether_to_implement( + cls, flag, auto_detect, dunders, default=True +): + """ + Check whether we should implement a set of methods for *cls*. + + *flag* is the argument passed into @attr.s like 'init', *auto_detect* the + same as passed into @attr.s and *dunders* is a tuple of attribute names + whose presence signal that the user has implemented it themselves. + + Return *default* if no reason for either for or against is found. + """ + if flag is True or flag is False: + return flag + + if flag is None and auto_detect is False: + return default + + # Logically, flag is None and auto_detect is True here. + for dunder in dunders: + if _has_own_attribute(cls, dunder): + return False + + return default + + +def attrs( + maybe_cls=None, + these=None, + repr_ns=None, + repr=None, + cmp=None, + hash=None, + init=None, + slots=False, + frozen=False, + weakref_slot=True, + str=False, + auto_attribs=False, + kw_only=False, + cache_hash=False, + auto_exc=False, + eq=None, + order=None, + auto_detect=False, + collect_by_mro=False, + getstate_setstate=None, + on_setattr=None, + field_transformer=None, + match_args=True, + unsafe_hash=None, +): + r""" + A class decorator that adds :term:`dunder methods` according to the + specified attributes using `attr.ib` or the *these* argument. + + Consider using `attrs.define` / `attrs.frozen` in new code (``attr.s`` will + *never* go away, though). + + Args: + repr_ns (str): + When using nested classes, there was no way in Python 2 to + automatically detect that. This argument allows to set a custom + name for a more meaningful ``repr`` output. This argument is + pointless in Python 3 and is therefore deprecated. + + .. caution:: + Refer to `attrs.define` for the rest of the parameters, but note that they + can have different defaults. + + Notably, leaving *on_setattr* as `None` will **not** add any hooks. + + .. versionadded:: 16.0.0 *slots* + .. versionadded:: 16.1.0 *frozen* + .. versionadded:: 16.3.0 *str* + .. versionadded:: 16.3.0 Support for ``__attrs_post_init__``. + .. versionchanged:: 17.1.0 + *hash* supports `None` as value which is also the default now. + .. versionadded:: 17.3.0 *auto_attribs* + .. versionchanged:: 18.1.0 + If *these* is passed, no attributes are deleted from the class body. + .. versionchanged:: 18.1.0 If *these* is ordered, the order is retained. + .. versionadded:: 18.2.0 *weakref_slot* + .. deprecated:: 18.2.0 + ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now raise a + `DeprecationWarning` if the classes compared are subclasses of + each other. ``__eq`` and ``__ne__`` never tried to compared subclasses + to each other. + .. versionchanged:: 19.2.0 + ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now do not consider + subclasses comparable anymore. + .. versionadded:: 18.2.0 *kw_only* + .. versionadded:: 18.2.0 *cache_hash* + .. versionadded:: 19.1.0 *auto_exc* + .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01. + .. versionadded:: 19.2.0 *eq* and *order* + .. versionadded:: 20.1.0 *auto_detect* + .. versionadded:: 20.1.0 *collect_by_mro* + .. versionadded:: 20.1.0 *getstate_setstate* + .. versionadded:: 20.1.0 *on_setattr* + .. versionadded:: 20.3.0 *field_transformer* + .. versionchanged:: 21.1.0 + ``init=False`` injects ``__attrs_init__`` + .. versionchanged:: 21.1.0 Support for ``__attrs_pre_init__`` + .. versionchanged:: 21.1.0 *cmp* undeprecated + .. versionadded:: 21.3.0 *match_args* + .. versionadded:: 22.2.0 + *unsafe_hash* as an alias for *hash* (for :pep:`681` compliance). + .. deprecated:: 24.1.0 *repr_ns* + .. versionchanged:: 24.1.0 + Instances are not compared as tuples of attributes anymore, but using a + big ``and`` condition. This is faster and has more correct behavior for + uncomparable values like `math.nan`. + .. versionadded:: 24.1.0 + If a class has an *inherited* classmethod called + ``__attrs_init_subclass__``, it is executed after the class is created. + .. deprecated:: 24.1.0 *hash* is deprecated in favor of *unsafe_hash*. + """ + if repr_ns is not None: + import warnings + + warnings.warn( + DeprecationWarning( + "The `repr_ns` argument is deprecated and will be removed in or after August 2025." + ), + stacklevel=2, + ) + + eq_, order_ = _determine_attrs_eq_order(cmp, eq, order, None) + + # unsafe_hash takes precedence due to PEP 681. + if unsafe_hash is not None: + hash = unsafe_hash + + if isinstance(on_setattr, (list, tuple)): + on_setattr = setters.pipe(*on_setattr) + + def wrap(cls): + is_frozen = frozen or _has_frozen_base_class(cls) + is_exc = auto_exc is True and issubclass(cls, BaseException) + has_own_setattr = auto_detect and _has_own_attribute( + cls, "__setattr__" + ) + + if has_own_setattr and is_frozen: + msg = "Can't freeze a class with a custom __setattr__." + raise ValueError(msg) + + builder = _ClassBuilder( + cls, + these, + slots, + is_frozen, + weakref_slot, + _determine_whether_to_implement( + cls, + getstate_setstate, + auto_detect, + ("__getstate__", "__setstate__"), + default=slots, + ), + auto_attribs, + kw_only, + cache_hash, + is_exc, + collect_by_mro, + on_setattr, + has_own_setattr, + field_transformer, + ) + if _determine_whether_to_implement( + cls, repr, auto_detect, ("__repr__",) + ): + builder.add_repr(repr_ns) + if str is True: + builder.add_str() + + eq = _determine_whether_to_implement( + cls, eq_, auto_detect, ("__eq__", "__ne__") + ) + if not is_exc and eq is True: + builder.add_eq() + if not is_exc and _determine_whether_to_implement( + cls, order_, auto_detect, ("__lt__", "__le__", "__gt__", "__ge__") + ): + builder.add_order() + + builder.add_setattr() + + nonlocal hash + if ( + hash is None + and auto_detect is True + and _has_own_attribute(cls, "__hash__") + ): + hash = False + + if hash is not True and hash is not False and hash is not None: + # Can't use `hash in` because 1 == True for example. + msg = "Invalid value for hash. Must be True, False, or None." + raise TypeError(msg) + + if hash is False or (hash is None and eq is False) or is_exc: + # Don't do anything. Should fall back to __object__'s __hash__ + # which is by id. + if cache_hash: + msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled." + raise TypeError(msg) + elif hash is True or ( + hash is None and eq is True and is_frozen is True + ): + # Build a __hash__ if told so, or if it's safe. + builder.add_hash() + else: + # Raise TypeError on attempts to hash. + if cache_hash: + msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled." + raise TypeError(msg) + builder.make_unhashable() + + if _determine_whether_to_implement( + cls, init, auto_detect, ("__init__",) + ): + builder.add_init() + else: + builder.add_attrs_init() + if cache_hash: + msg = "Invalid value for cache_hash. To use hash caching, init must be True." + raise TypeError(msg) + + if ( + PY_3_10_PLUS + and match_args + and not _has_own_attribute(cls, "__match_args__") + ): + builder.add_match_args() + + return builder.build_class() + + # maybe_cls's type depends on the usage of the decorator. It's a class + # if it's used as `@attrs` but `None` if used as `@attrs()`. + if maybe_cls is None: + return wrap + + return wrap(maybe_cls) + + +_attrs = attrs +""" +Internal alias so we can use it in functions that take an argument called +*attrs*. +""" + + +def _has_frozen_base_class(cls): + """ + Check whether *cls* has a frozen ancestor by looking at its + __setattr__. + """ + return cls.__setattr__ is _frozen_setattrs + + +def _generate_unique_filename(cls, func_name): + """ + Create a "filename" suitable for a function being generated. + """ + return ( + f"" + ) + + +def _make_hash(cls, attrs, frozen, cache_hash): + attrs = tuple( + a for a in attrs if a.hash is True or (a.hash is None and a.eq is True) + ) + + tab = " " + + unique_filename = _generate_unique_filename(cls, "hash") + type_hash = hash(unique_filename) + # If eq is custom generated, we need to include the functions in globs + globs = {} + + hash_def = "def __hash__(self" + hash_func = "hash((" + closing_braces = "))" + if not cache_hash: + hash_def += "):" + else: + hash_def += ", *" + + hash_def += ", _cache_wrapper=__import__('attr._make')._make._CacheHashWrapper):" + hash_func = "_cache_wrapper(" + hash_func + closing_braces += ")" + + method_lines = [hash_def] + + def append_hash_computation_lines(prefix, indent): + """ + Generate the code for actually computing the hash code. + Below this will either be returned directly or used to compute + a value which is then cached, depending on the value of cache_hash + """ + + method_lines.extend( + [ + indent + prefix + hash_func, + indent + f" {type_hash},", + ] + ) + + for a in attrs: + if a.eq_key: + cmp_name = f"_{a.name}_key" + globs[cmp_name] = a.eq_key + method_lines.append( + indent + f" {cmp_name}(self.{a.name})," + ) + else: + method_lines.append(indent + f" self.{a.name},") + + method_lines.append(indent + " " + closing_braces) + + if cache_hash: + method_lines.append(tab + f"if self.{_HASH_CACHE_FIELD} is None:") + if frozen: + append_hash_computation_lines( + f"object.__setattr__(self, '{_HASH_CACHE_FIELD}', ", tab * 2 + ) + method_lines.append(tab * 2 + ")") # close __setattr__ + else: + append_hash_computation_lines( + f"self.{_HASH_CACHE_FIELD} = ", tab * 2 + ) + method_lines.append(tab + f"return self.{_HASH_CACHE_FIELD}") + else: + append_hash_computation_lines("return ", tab) + + script = "\n".join(method_lines) + return _make_method("__hash__", script, unique_filename, globs) + + +def _add_hash(cls, attrs): + """ + Add a hash method to *cls*. + """ + cls.__hash__ = _make_hash(cls, attrs, frozen=False, cache_hash=False) + return cls + + +def _make_ne(): + """ + Create __ne__ method. + """ + + def __ne__(self, other): + """ + Check equality and either forward a NotImplemented or + return the result negated. + """ + result = self.__eq__(other) + if result is NotImplemented: + return NotImplemented + + return not result + + return __ne__ + + +def _make_eq(cls, attrs): + """ + Create __eq__ method for *cls* with *attrs*. + """ + attrs = [a for a in attrs if a.eq] + + unique_filename = _generate_unique_filename(cls, "eq") + lines = [ + "def __eq__(self, other):", + " if other.__class__ is not self.__class__:", + " return NotImplemented", + ] + + # We can't just do a big self.x = other.x and... clause due to + # irregularities like nan == nan is false but (nan,) == (nan,) is true. + globs = {} + if attrs: + lines.append(" return (") + for a in attrs: + if a.eq_key: + cmp_name = f"_{a.name}_key" + # Add the key function to the global namespace + # of the evaluated function. + globs[cmp_name] = a.eq_key + lines.append( + f" {cmp_name}(self.{a.name}) == {cmp_name}(other.{a.name})" + ) + else: + lines.append(f" self.{a.name} == other.{a.name}") + if a is not attrs[-1]: + lines[-1] = f"{lines[-1]} and" + lines.append(" )") + else: + lines.append(" return True") + + script = "\n".join(lines) + + return _make_method("__eq__", script, unique_filename, globs) + + +def _make_order(cls, attrs): + """ + Create ordering methods for *cls* with *attrs*. + """ + attrs = [a for a in attrs if a.order] + + def attrs_to_tuple(obj): + """ + Save us some typing. + """ + return tuple( + key(value) if key else value + for value, key in ( + (getattr(obj, a.name), a.order_key) for a in attrs + ) + ) + + def __lt__(self, other): + """ + Automatically created by attrs. + """ + if other.__class__ is self.__class__: + return attrs_to_tuple(self) < attrs_to_tuple(other) + + return NotImplemented + + def __le__(self, other): + """ + Automatically created by attrs. + """ + if other.__class__ is self.__class__: + return attrs_to_tuple(self) <= attrs_to_tuple(other) + + return NotImplemented + + def __gt__(self, other): + """ + Automatically created by attrs. + """ + if other.__class__ is self.__class__: + return attrs_to_tuple(self) > attrs_to_tuple(other) + + return NotImplemented + + def __ge__(self, other): + """ + Automatically created by attrs. + """ + if other.__class__ is self.__class__: + return attrs_to_tuple(self) >= attrs_to_tuple(other) + + return NotImplemented + + return __lt__, __le__, __gt__, __ge__ + + +def _add_eq(cls, attrs=None): + """ + Add equality methods to *cls* with *attrs*. + """ + if attrs is None: + attrs = cls.__attrs_attrs__ + + cls.__eq__ = _make_eq(cls, attrs) + cls.__ne__ = _make_ne() + + return cls + + +def _make_repr(attrs, ns, cls): + unique_filename = _generate_unique_filename(cls, "repr") + # Figure out which attributes to include, and which function to use to + # format them. The a.repr value can be either bool or a custom + # callable. + attr_names_with_reprs = tuple( + (a.name, (repr if a.repr is True else a.repr), a.init) + for a in attrs + if a.repr is not False + ) + globs = { + name + "_repr": r for name, r, _ in attr_names_with_reprs if r != repr + } + globs["_compat"] = _compat + globs["AttributeError"] = AttributeError + globs["NOTHING"] = NOTHING + attribute_fragments = [] + for name, r, i in attr_names_with_reprs: + accessor = ( + "self." + name if i else 'getattr(self, "' + name + '", NOTHING)' + ) + fragment = ( + "%s={%s!r}" % (name, accessor) + if r == repr + else "%s={%s_repr(%s)}" % (name, name, accessor) + ) + attribute_fragments.append(fragment) + repr_fragment = ", ".join(attribute_fragments) + + if ns is None: + cls_name_fragment = '{self.__class__.__qualname__.rsplit(">.", 1)[-1]}' + else: + cls_name_fragment = ns + ".{self.__class__.__name__}" + + lines = [ + "def __repr__(self):", + " try:", + " already_repring = _compat.repr_context.already_repring", + " except AttributeError:", + " already_repring = {id(self),}", + " _compat.repr_context.already_repring = already_repring", + " else:", + " if id(self) in already_repring:", + " return '...'", + " else:", + " already_repring.add(id(self))", + " try:", + f" return f'{cls_name_fragment}({repr_fragment})'", + " finally:", + " already_repring.remove(id(self))", + ] + + return _make_method( + "__repr__", "\n".join(lines), unique_filename, globs=globs + ) + + +def _add_repr(cls, ns=None, attrs=None): + """ + Add a repr method to *cls*. + """ + if attrs is None: + attrs = cls.__attrs_attrs__ + + cls.__repr__ = _make_repr(attrs, ns, cls) + return cls + + +def fields(cls): + """ + Return the tuple of *attrs* attributes for a class. + + The tuple also allows accessing the fields by their names (see below for + examples). + + Args: + cls (type): Class to introspect. + + Raises: + TypeError: If *cls* is not a class. + + attrs.exceptions.NotAnAttrsClassError: + If *cls* is not an *attrs* class. + + Returns: + tuple (with name accessors) of `attrs.Attribute` + + .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields + by name. + .. versionchanged:: 23.1.0 Add support for generic classes. + """ + generic_base = get_generic_base(cls) + + if generic_base is None and not isinstance(cls, type): + msg = "Passed object must be a class." + raise TypeError(msg) + + attrs = getattr(cls, "__attrs_attrs__", None) + + if attrs is None: + if generic_base is not None: + attrs = getattr(generic_base, "__attrs_attrs__", None) + if attrs is not None: + # Even though this is global state, stick it on here to speed + # it up. We rely on `cls` being cached for this to be + # efficient. + cls.__attrs_attrs__ = attrs + return attrs + msg = f"{cls!r} is not an attrs-decorated class." + raise NotAnAttrsClassError(msg) + + return attrs + + +def fields_dict(cls): + """ + Return an ordered dictionary of *attrs* attributes for a class, whose keys + are the attribute names. + + Args: + cls (type): Class to introspect. + + Raises: + TypeError: If *cls* is not a class. + + attrs.exceptions.NotAnAttrsClassError: + If *cls* is not an *attrs* class. + + Returns: + dict[str, attrs.Attribute]: Dict of attribute name to definition + + .. versionadded:: 18.1.0 + """ + if not isinstance(cls, type): + msg = "Passed object must be a class." + raise TypeError(msg) + attrs = getattr(cls, "__attrs_attrs__", None) + if attrs is None: + msg = f"{cls!r} is not an attrs-decorated class." + raise NotAnAttrsClassError(msg) + return {a.name: a for a in attrs} + + +def validate(inst): + """ + Validate all attributes on *inst* that have a validator. + + Leaves all exceptions through. + + Args: + inst: Instance of a class with *attrs* attributes. + """ + if _config._run_validators is False: + return + + for a in fields(inst.__class__): + v = a.validator + if v is not None: + v(inst, a, getattr(inst, a.name)) + + +def _is_slot_attr(a_name, base_attr_map): + """ + Check if the attribute name comes from a slot class. + """ + cls = base_attr_map.get(a_name) + return cls and "__slots__" in cls.__dict__ + + +def _make_init( + cls, + attrs, + pre_init, + pre_init_has_args, + post_init, + frozen, + slots, + cache_hash, + base_attr_map, + is_exc, + cls_on_setattr, + attrs_init, +): + has_cls_on_setattr = ( + cls_on_setattr is not None and cls_on_setattr is not setters.NO_OP + ) + + if frozen and has_cls_on_setattr: + msg = "Frozen classes can't use on_setattr." + raise ValueError(msg) + + needs_cached_setattr = cache_hash or frozen + filtered_attrs = [] + attr_dict = {} + for a in attrs: + if not a.init and a.default is NOTHING: + continue + + filtered_attrs.append(a) + attr_dict[a.name] = a + + if a.on_setattr is not None: + if frozen is True: + msg = "Frozen classes can't use on_setattr." + raise ValueError(msg) + + needs_cached_setattr = True + elif has_cls_on_setattr and a.on_setattr is not setters.NO_OP: + needs_cached_setattr = True + + unique_filename = _generate_unique_filename(cls, "init") + + script, globs, annotations = _attrs_to_init_script( + filtered_attrs, + frozen, + slots, + pre_init, + pre_init_has_args, + post_init, + cache_hash, + base_attr_map, + is_exc, + needs_cached_setattr, + has_cls_on_setattr, + "__attrs_init__" if attrs_init else "__init__", + ) + if cls.__module__ in sys.modules: + # This makes typing.get_type_hints(CLS.__init__) resolve string types. + globs.update(sys.modules[cls.__module__].__dict__) + + globs.update({"NOTHING": NOTHING, "attr_dict": attr_dict}) + + if needs_cached_setattr: + # Save the lookup overhead in __init__ if we need to circumvent + # setattr hooks. + globs["_cached_setattr_get"] = _OBJ_SETATTR.__get__ + + init = _make_method( + "__attrs_init__" if attrs_init else "__init__", + script, + unique_filename, + globs, + ) + init.__annotations__ = annotations + + return init + + +def _setattr(attr_name: str, value_var: str, has_on_setattr: bool) -> str: + """ + Use the cached object.setattr to set *attr_name* to *value_var*. + """ + return f"_setattr('{attr_name}', {value_var})" + + +def _setattr_with_converter( + attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter +) -> str: + """ + Use the cached object.setattr to set *attr_name* to *value_var*, but run + its converter first. + """ + return f"_setattr('{attr_name}', {converter._fmt_converter_call(attr_name, value_var)})" + + +def _assign(attr_name: str, value: str, has_on_setattr: bool) -> str: + """ + Unless *attr_name* has an on_setattr hook, use normal assignment. Otherwise + relegate to _setattr. + """ + if has_on_setattr: + return _setattr(attr_name, value, True) + + return f"self.{attr_name} = {value}" + + +def _assign_with_converter( + attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter +) -> str: + """ + Unless *attr_name* has an on_setattr hook, use normal assignment after + conversion. Otherwise relegate to _setattr_with_converter. + """ + if has_on_setattr: + return _setattr_with_converter(attr_name, value_var, True, converter) + + return f"self.{attr_name} = {converter._fmt_converter_call(attr_name, value_var)}" + + +def _determine_setters( + frozen: bool, slots: bool, base_attr_map: dict[str, type] +): + """ + Determine the correct setter functions based on whether a class is frozen + and/or slotted. + """ + if frozen is True: + if slots is True: + return (), _setattr, _setattr_with_converter + + # Dict frozen classes assign directly to __dict__. + # But only if the attribute doesn't come from an ancestor slot + # class. + # Note _inst_dict will be used again below if cache_hash is True + + def fmt_setter( + attr_name: str, value_var: str, has_on_setattr: bool + ) -> str: + if _is_slot_attr(attr_name, base_attr_map): + return _setattr(attr_name, value_var, has_on_setattr) + + return f"_inst_dict['{attr_name}'] = {value_var}" + + def fmt_setter_with_converter( + attr_name: str, + value_var: str, + has_on_setattr: bool, + converter: Converter, + ) -> str: + if has_on_setattr or _is_slot_attr(attr_name, base_attr_map): + return _setattr_with_converter( + attr_name, value_var, has_on_setattr, converter + ) + + return f"_inst_dict['{attr_name}'] = {converter._fmt_converter_call(attr_name, value_var)}" + + return ( + ("_inst_dict = self.__dict__",), + fmt_setter, + fmt_setter_with_converter, + ) + + # Not frozen -- we can just assign directly. + return (), _assign, _assign_with_converter + + +def _attrs_to_init_script( + attrs: list[Attribute], + is_frozen: bool, + is_slotted: bool, + call_pre_init: bool, + pre_init_has_args: bool, + call_post_init: bool, + does_cache_hash: bool, + base_attr_map: dict[str, type], + is_exc: bool, + needs_cached_setattr: bool, + has_cls_on_setattr: bool, + method_name: str, +) -> tuple[str, dict, dict]: + """ + Return a script of an initializer for *attrs*, a dict of globals, and + annotations for the initializer. + + The globals are required by the generated script. + """ + lines = ["self.__attrs_pre_init__()"] if call_pre_init else [] + + if needs_cached_setattr: + lines.append( + # Circumvent the __setattr__ descriptor to save one lookup per + # assignment. Note _setattr will be used again below if + # does_cache_hash is True. + "_setattr = _cached_setattr_get(self)" + ) + + extra_lines, fmt_setter, fmt_setter_with_converter = _determine_setters( + is_frozen, is_slotted, base_attr_map + ) + lines.extend(extra_lines) + + args = [] + kw_only_args = [] + attrs_to_validate = [] + + # This is a dictionary of names to validator and converter callables. + # Injecting this into __init__ globals lets us avoid lookups. + names_for_globals = {} + annotations = {"return": None} + + for a in attrs: + if a.validator: + attrs_to_validate.append(a) + + attr_name = a.name + has_on_setattr = a.on_setattr is not None or ( + a.on_setattr is not setters.NO_OP and has_cls_on_setattr + ) + # a.alias is set to maybe-mangled attr_name in _ClassBuilder if not + # explicitly provided + arg_name = a.alias + + has_factory = isinstance(a.default, Factory) + maybe_self = "self" if has_factory and a.default.takes_self else "" + + if a.converter and not isinstance(a.converter, Converter): + converter = Converter(a.converter) + else: + converter = a.converter + + if a.init is False: + if has_factory: + init_factory_name = _INIT_FACTORY_PAT % (a.name,) + if converter is not None: + lines.append( + fmt_setter_with_converter( + attr_name, + init_factory_name + f"({maybe_self})", + has_on_setattr, + converter, + ) + ) + names_for_globals[converter._get_global_name(a.name)] = ( + converter.converter + ) + else: + lines.append( + fmt_setter( + attr_name, + init_factory_name + f"({maybe_self})", + has_on_setattr, + ) + ) + names_for_globals[init_factory_name] = a.default.factory + elif converter is not None: + lines.append( + fmt_setter_with_converter( + attr_name, + f"attr_dict['{attr_name}'].default", + has_on_setattr, + converter, + ) + ) + names_for_globals[converter._get_global_name(a.name)] = ( + converter.converter + ) + else: + lines.append( + fmt_setter( + attr_name, + f"attr_dict['{attr_name}'].default", + has_on_setattr, + ) + ) + elif a.default is not NOTHING and not has_factory: + arg = f"{arg_name}=attr_dict['{attr_name}'].default" + if a.kw_only: + kw_only_args.append(arg) + else: + args.append(arg) + + if converter is not None: + lines.append( + fmt_setter_with_converter( + attr_name, arg_name, has_on_setattr, converter + ) + ) + names_for_globals[converter._get_global_name(a.name)] = ( + converter.converter + ) + else: + lines.append(fmt_setter(attr_name, arg_name, has_on_setattr)) + + elif has_factory: + arg = f"{arg_name}=NOTHING" + if a.kw_only: + kw_only_args.append(arg) + else: + args.append(arg) + lines.append(f"if {arg_name} is not NOTHING:") + + init_factory_name = _INIT_FACTORY_PAT % (a.name,) + if converter is not None: + lines.append( + " " + + fmt_setter_with_converter( + attr_name, arg_name, has_on_setattr, converter + ) + ) + lines.append("else:") + lines.append( + " " + + fmt_setter_with_converter( + attr_name, + init_factory_name + "(" + maybe_self + ")", + has_on_setattr, + converter, + ) + ) + names_for_globals[converter._get_global_name(a.name)] = ( + converter.converter + ) + else: + lines.append( + " " + fmt_setter(attr_name, arg_name, has_on_setattr) + ) + lines.append("else:") + lines.append( + " " + + fmt_setter( + attr_name, + init_factory_name + "(" + maybe_self + ")", + has_on_setattr, + ) + ) + names_for_globals[init_factory_name] = a.default.factory + else: + if a.kw_only: + kw_only_args.append(arg_name) + else: + args.append(arg_name) + + if converter is not None: + lines.append( + fmt_setter_with_converter( + attr_name, arg_name, has_on_setattr, converter + ) + ) + names_for_globals[converter._get_global_name(a.name)] = ( + converter.converter + ) + else: + lines.append(fmt_setter(attr_name, arg_name, has_on_setattr)) + + if a.init is True: + if a.type is not None and converter is None: + annotations[arg_name] = a.type + elif converter is not None and converter._first_param_type: + # Use the type from the converter if present. + annotations[arg_name] = converter._first_param_type + + if attrs_to_validate: # we can skip this if there are no validators. + names_for_globals["_config"] = _config + lines.append("if _config._run_validators is True:") + for a in attrs_to_validate: + val_name = "__attr_validator_" + a.name + attr_name = "__attr_" + a.name + lines.append(f" {val_name}(self, {attr_name}, self.{a.name})") + names_for_globals[val_name] = a.validator + names_for_globals[attr_name] = a + + if call_post_init: + lines.append("self.__attrs_post_init__()") + + # Because this is set only after __attrs_post_init__ is called, a crash + # will result if post-init tries to access the hash code. This seemed + # preferable to setting this beforehand, in which case alteration to field + # values during post-init combined with post-init accessing the hash code + # would result in silent bugs. + if does_cache_hash: + if is_frozen: + if is_slotted: + init_hash_cache = f"_setattr('{_HASH_CACHE_FIELD}', None)" + else: + init_hash_cache = f"_inst_dict['{_HASH_CACHE_FIELD}'] = None" + else: + init_hash_cache = f"self.{_HASH_CACHE_FIELD} = None" + lines.append(init_hash_cache) + + # For exceptions we rely on BaseException.__init__ for proper + # initialization. + if is_exc: + vals = ",".join(f"self.{a.name}" for a in attrs if a.init) + + lines.append(f"BaseException.__init__(self, {vals})") + + args = ", ".join(args) + pre_init_args = args + if kw_only_args: + # leading comma & kw_only args + args += f"{', ' if args else ''}*, {', '.join(kw_only_args)}" + pre_init_kw_only_args = ", ".join( + [ + f"{kw_arg_name}={kw_arg_name}" + # We need to remove the defaults from the kw_only_args. + for kw_arg_name in (kwa.split("=")[0] for kwa in kw_only_args) + ] + ) + pre_init_args += ", " if pre_init_args else "" + pre_init_args += pre_init_kw_only_args + + if call_pre_init and pre_init_has_args: + # If pre init method has arguments, pass same arguments as `__init__`. + lines[0] = f"self.__attrs_pre_init__({pre_init_args})" + + # Python 3.7 doesn't allow backslashes in f strings. + NL = "\n " + return ( + f"""def {method_name}(self, {args}): + {NL.join(lines) if lines else 'pass'} +""", + names_for_globals, + annotations, + ) + + +def _default_init_alias_for(name: str) -> str: + """ + The default __init__ parameter name for a field. + + This performs private-name adjustment via leading-unscore stripping, + and is the default value of Attribute.alias if not provided. + """ + + return name.lstrip("_") + + +class Attribute: + """ + *Read-only* representation of an attribute. + + .. warning:: + + You should never instantiate this class yourself. + + The class has *all* arguments of `attr.ib` (except for ``factory`` which is + only syntactic sugar for ``default=Factory(...)`` plus the following: + + - ``name`` (`str`): The name of the attribute. + - ``alias`` (`str`): The __init__ parameter name of the attribute, after + any explicit overrides and default private-attribute-name handling. + - ``inherited`` (`bool`): Whether or not that attribute has been inherited + from a base class. + - ``eq_key`` and ``order_key`` (`typing.Callable` or `None`): The + callables that are used for comparing and ordering objects by this + attribute, respectively. These are set by passing a callable to + `attr.ib`'s ``eq``, ``order``, or ``cmp`` arguments. See also + :ref:`comparison customization `. + + Instances of this class are frequently used for introspection purposes + like: + + - `fields` returns a tuple of them. + - Validators get them passed as the first argument. + - The :ref:`field transformer ` hook receives a list of + them. + - The ``alias`` property exposes the __init__ parameter name of the field, + with any overrides and default private-attribute handling applied. + + + .. versionadded:: 20.1.0 *inherited* + .. versionadded:: 20.1.0 *on_setattr* + .. versionchanged:: 20.2.0 *inherited* is not taken into account for + equality checks and hashing anymore. + .. versionadded:: 21.1.0 *eq_key* and *order_key* + .. versionadded:: 22.2.0 *alias* + + For the full version history of the fields, see `attr.ib`. + """ + + __slots__ = ( + "name", + "default", + "validator", + "repr", + "eq", + "eq_key", + "order", + "order_key", + "hash", + "init", + "metadata", + "type", + "converter", + "kw_only", + "inherited", + "on_setattr", + "alias", + ) + + def __init__( + self, + name, + default, + validator, + repr, + cmp, # XXX: unused, remove along with other cmp code. + hash, + init, + inherited, + metadata=None, + type=None, + converter=None, + kw_only=False, + eq=None, + eq_key=None, + order=None, + order_key=None, + on_setattr=None, + alias=None, + ): + eq, eq_key, order, order_key = _determine_attrib_eq_order( + cmp, eq_key or eq, order_key or order, True + ) + + # Cache this descriptor here to speed things up later. + bound_setattr = _OBJ_SETATTR.__get__(self) + + # Despite the big red warning, people *do* instantiate `Attribute` + # themselves. + bound_setattr("name", name) + bound_setattr("default", default) + bound_setattr("validator", validator) + bound_setattr("repr", repr) + bound_setattr("eq", eq) + bound_setattr("eq_key", eq_key) + bound_setattr("order", order) + bound_setattr("order_key", order_key) + bound_setattr("hash", hash) + bound_setattr("init", init) + bound_setattr("converter", converter) + bound_setattr( + "metadata", + ( + types.MappingProxyType(dict(metadata)) # Shallow copy + if metadata + else _EMPTY_METADATA_SINGLETON + ), + ) + bound_setattr("type", type) + bound_setattr("kw_only", kw_only) + bound_setattr("inherited", inherited) + bound_setattr("on_setattr", on_setattr) + bound_setattr("alias", alias) + + def __setattr__(self, name, value): + raise FrozenInstanceError() + + @classmethod + def from_counting_attr(cls, name, ca, type=None): + # type holds the annotated value. deal with conflicts: + if type is None: + type = ca.type + elif ca.type is not None: + msg = "Type annotation and type argument cannot both be present" + raise ValueError(msg) + inst_dict = { + k: getattr(ca, k) + for k in Attribute.__slots__ + if k + not in ( + "name", + "validator", + "default", + "type", + "inherited", + ) # exclude methods and deprecated alias + } + return cls( + name=name, + validator=ca._validator, + default=ca._default, + type=type, + cmp=None, + inherited=False, + **inst_dict, + ) + + # Don't use attrs.evolve since fields(Attribute) doesn't work + def evolve(self, **changes): + """ + Copy *self* and apply *changes*. + + This works similarly to `attrs.evolve` but that function does not work + with {class}`Attribute`. + + It is mainly meant to be used for `transform-fields`. + + .. versionadded:: 20.3.0 + """ + new = copy.copy(self) + + new._setattrs(changes.items()) + + return new + + # Don't use _add_pickle since fields(Attribute) doesn't work + def __getstate__(self): + """ + Play nice with pickle. + """ + return tuple( + getattr(self, name) if name != "metadata" else dict(self.metadata) + for name in self.__slots__ + ) + + def __setstate__(self, state): + """ + Play nice with pickle. + """ + self._setattrs(zip(self.__slots__, state)) + + def _setattrs(self, name_values_pairs): + bound_setattr = _OBJ_SETATTR.__get__(self) + for name, value in name_values_pairs: + if name != "metadata": + bound_setattr(name, value) + else: + bound_setattr( + name, + ( + types.MappingProxyType(dict(value)) + if value + else _EMPTY_METADATA_SINGLETON + ), + ) + + +_a = [ + Attribute( + name=name, + default=NOTHING, + validator=None, + repr=True, + cmp=None, + eq=True, + order=False, + hash=(name != "metadata"), + init=True, + inherited=False, + alias=_default_init_alias_for(name), + ) + for name in Attribute.__slots__ +] + +Attribute = _add_hash( + _add_eq( + _add_repr(Attribute, attrs=_a), + attrs=[a for a in _a if a.name != "inherited"], + ), + attrs=[a for a in _a if a.hash and a.name != "inherited"], +) + + +class _CountingAttr: + """ + Intermediate representation of attributes that uses a counter to preserve + the order in which the attributes have been defined. + + *Internal* data structure of the attrs library. Running into is most + likely the result of a bug like a forgotten `@attr.s` decorator. + """ + + __slots__ = ( + "counter", + "_default", + "repr", + "eq", + "eq_key", + "order", + "order_key", + "hash", + "init", + "metadata", + "_validator", + "converter", + "type", + "kw_only", + "on_setattr", + "alias", + ) + __attrs_attrs__ = ( + *tuple( + Attribute( + name=name, + alias=_default_init_alias_for(name), + default=NOTHING, + validator=None, + repr=True, + cmp=None, + hash=True, + init=True, + kw_only=False, + eq=True, + eq_key=None, + order=False, + order_key=None, + inherited=False, + on_setattr=None, + ) + for name in ( + "counter", + "_default", + "repr", + "eq", + "order", + "hash", + "init", + "on_setattr", + "alias", + ) + ), + Attribute( + name="metadata", + alias="metadata", + default=None, + validator=None, + repr=True, + cmp=None, + hash=False, + init=True, + kw_only=False, + eq=True, + eq_key=None, + order=False, + order_key=None, + inherited=False, + on_setattr=None, + ), + ) + cls_counter = 0 + + def __init__( + self, + default, + validator, + repr, + cmp, + hash, + init, + converter, + metadata, + type, + kw_only, + eq, + eq_key, + order, + order_key, + on_setattr, + alias, + ): + _CountingAttr.cls_counter += 1 + self.counter = _CountingAttr.cls_counter + self._default = default + self._validator = validator + self.converter = converter + self.repr = repr + self.eq = eq + self.eq_key = eq_key + self.order = order + self.order_key = order_key + self.hash = hash + self.init = init + self.metadata = metadata + self.type = type + self.kw_only = kw_only + self.on_setattr = on_setattr + self.alias = alias + + def validator(self, meth): + """ + Decorator that adds *meth* to the list of validators. + + Returns *meth* unchanged. + + .. versionadded:: 17.1.0 + """ + if self._validator is None: + self._validator = meth + else: + self._validator = and_(self._validator, meth) + return meth + + def default(self, meth): + """ + Decorator that allows to set the default for an attribute. + + Returns *meth* unchanged. + + Raises: + DefaultAlreadySetError: If default has been set before. + + .. versionadded:: 17.1.0 + """ + if self._default is not NOTHING: + raise DefaultAlreadySetError() + + self._default = Factory(meth, takes_self=True) + + return meth + + +_CountingAttr = _add_eq(_add_repr(_CountingAttr)) + + +class Factory: + """ + Stores a factory callable. + + If passed as the default value to `attrs.field`, the factory is used to + generate a new value. + + Args: + factory (typing.Callable): + A callable that takes either none or exactly one mandatory + positional argument depending on *takes_self*. + + takes_self (bool): + Pass the partially initialized instance that is being initialized + as a positional argument. + + .. versionadded:: 17.1.0 *takes_self* + """ + + __slots__ = ("factory", "takes_self") + + def __init__(self, factory, takes_self=False): + self.factory = factory + self.takes_self = takes_self + + def __getstate__(self): + """ + Play nice with pickle. + """ + return tuple(getattr(self, name) for name in self.__slots__) + + def __setstate__(self, state): + """ + Play nice with pickle. + """ + for name, value in zip(self.__slots__, state): + setattr(self, name, value) + + +_f = [ + Attribute( + name=name, + default=NOTHING, + validator=None, + repr=True, + cmp=None, + eq=True, + order=False, + hash=True, + init=True, + inherited=False, + ) + for name in Factory.__slots__ +] + +Factory = _add_hash(_add_eq(_add_repr(Factory, attrs=_f), attrs=_f), attrs=_f) + + +class Converter: + """ + Stores a converter callable. + + Allows for the wrapped converter to take additional arguments. The + arguments are passed in the order they are documented. + + Args: + converter (Callable): A callable that converts the passed value. + + takes_self (bool): + Pass the partially initialized instance that is being initialized + as a positional argument. (default: `False`) + + takes_field (bool): + Pass the field definition (an :class:`Attribute`) into the + converter as a positional argument. (default: `False`) + + .. versionadded:: 24.1.0 + """ + + __slots__ = ( + "converter", + "takes_self", + "takes_field", + "_first_param_type", + "_global_name", + "__call__", + ) + + def __init__(self, converter, *, takes_self=False, takes_field=False): + self.converter = converter + self.takes_self = takes_self + self.takes_field = takes_field + + ex = _AnnotationExtractor(converter) + self._first_param_type = ex.get_first_param_type() + + if not (self.takes_self or self.takes_field): + self.__call__ = lambda value, _, __: self.converter(value) + elif self.takes_self and not self.takes_field: + self.__call__ = lambda value, instance, __: self.converter( + value, instance + ) + elif not self.takes_self and self.takes_field: + self.__call__ = lambda value, __, field: self.converter( + value, field + ) + else: + self.__call__ = lambda value, instance, field: self.converter( + value, instance, field + ) + + rt = ex.get_return_type() + if rt is not None: + self.__call__.__annotations__["return"] = rt + + @staticmethod + def _get_global_name(attr_name: str) -> str: + """ + Return the name that a converter for an attribute name *attr_name* + would have. + """ + return f"__attr_converter_{attr_name}" + + def _fmt_converter_call(self, attr_name: str, value_var: str) -> str: + """ + Return a string that calls the converter for an attribute name + *attr_name* and the value in variable named *value_var* according to + `self.takes_self` and `self.takes_field`. + """ + if not (self.takes_self or self.takes_field): + return f"{self._get_global_name(attr_name)}({value_var})" + + if self.takes_self and self.takes_field: + return f"{self._get_global_name(attr_name)}({value_var}, self, attr_dict['{attr_name}'])" + + if self.takes_self: + return f"{self._get_global_name(attr_name)}({value_var}, self)" + + return f"{self._get_global_name(attr_name)}({value_var}, attr_dict['{attr_name}'])" + + def __getstate__(self): + """ + Return a dict containing only converter and takes_self -- the rest gets + computed when loading. + """ + return { + "converter": self.converter, + "takes_self": self.takes_self, + "takes_field": self.takes_field, + } + + def __setstate__(self, state): + """ + Load instance from state. + """ + self.__init__(**state) + + +_f = [ + Attribute( + name=name, + default=NOTHING, + validator=None, + repr=True, + cmp=None, + eq=True, + order=False, + hash=True, + init=True, + inherited=False, + ) + for name in ("converter", "takes_self", "takes_field") +] + +Converter = _add_hash( + _add_eq(_add_repr(Converter, attrs=_f), attrs=_f), attrs=_f +) + + +def make_class( + name, attrs, bases=(object,), class_body=None, **attributes_arguments +): + r""" + A quick way to create a new class called *name* with *attrs*. + + Args: + name (str): The name for the new class. + + attrs( list | dict): + A list of names or a dictionary of mappings of names to `attr.ib`\ + s / `attrs.field`\ s. + + The order is deduced from the order of the names or attributes + inside *attrs*. Otherwise the order of the definition of the + attributes is used. + + bases (tuple[type, ...]): Classes that the new class will subclass. + + class_body (dict): + An optional dictionary of class attributes for the new class. + + attributes_arguments: Passed unmodified to `attr.s`. + + Returns: + type: A new class with *attrs*. + + .. versionadded:: 17.1.0 *bases* + .. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained. + .. versionchanged:: 23.2.0 *class_body* + """ + if isinstance(attrs, dict): + cls_dict = attrs + elif isinstance(attrs, (list, tuple)): + cls_dict = {a: attrib() for a in attrs} + else: + msg = "attrs argument must be a dict or a list." + raise TypeError(msg) + + pre_init = cls_dict.pop("__attrs_pre_init__", None) + post_init = cls_dict.pop("__attrs_post_init__", None) + user_init = cls_dict.pop("__init__", None) + + body = {} + if class_body is not None: + body.update(class_body) + if pre_init is not None: + body["__attrs_pre_init__"] = pre_init + if post_init is not None: + body["__attrs_post_init__"] = post_init + if user_init is not None: + body["__init__"] = user_init + + type_ = types.new_class(name, bases, {}, lambda ns: ns.update(body)) + + # For pickling to work, the __module__ variable needs to be set to the + # frame where the class is created. Bypass this step in environments where + # sys._getframe is not defined (Jython for example) or sys._getframe is not + # defined for arguments greater than 0 (IronPython). + with contextlib.suppress(AttributeError, ValueError): + type_.__module__ = sys._getframe(1).f_globals.get( + "__name__", "__main__" + ) + + # We do it here for proper warnings with meaningful stacklevel. + cmp = attributes_arguments.pop("cmp", None) + ( + attributes_arguments["eq"], + attributes_arguments["order"], + ) = _determine_attrs_eq_order( + cmp, + attributes_arguments.get("eq"), + attributes_arguments.get("order"), + True, + ) + + cls = _attrs(these=cls_dict, **attributes_arguments)(type_) + # Only add type annotations now or "_attrs()" will complain: + cls.__annotations__ = { + k: v.type for k, v in cls_dict.items() if v.type is not None + } + return cls + + +# These are required by within this module so we define them here and merely +# import into .validators / .converters. + + +@attrs(slots=True, unsafe_hash=True) +class _AndValidator: + """ + Compose many validators to a single one. + """ + + _validators = attrib() + + def __call__(self, inst, attr, value): + for v in self._validators: + v(inst, attr, value) + + +def and_(*validators): + """ + A validator that composes multiple validators into one. + + When called on a value, it runs all wrapped validators. + + Args: + validators (~collections.abc.Iterable[typing.Callable]): + Arbitrary number of validators. + + .. versionadded:: 17.1.0 + """ + vals = [] + for validator in validators: + vals.extend( + validator._validators + if isinstance(validator, _AndValidator) + else [validator] + ) + + return _AndValidator(tuple(vals)) + + +def pipe(*converters): + """ + A converter that composes multiple converters into one. + + When called on a value, it runs all wrapped converters, returning the + *last* value. + + Type annotations will be inferred from the wrapped converters', if they + have any. + + converters (~collections.abc.Iterable[typing.Callable]): + Arbitrary number of converters. + + .. versionadded:: 20.1.0 + """ + + def pipe_converter(val, inst, field): + for c in converters: + val = c(val, inst, field) if isinstance(c, Converter) else c(val) + + return val + + if not converters: + # If the converter list is empty, pipe_converter is the identity. + A = typing.TypeVar("A") + pipe_converter.__annotations__.update({"val": A, "return": A}) + else: + # Get parameter type from first converter. + t = _AnnotationExtractor(converters[0]).get_first_param_type() + if t: + pipe_converter.__annotations__["val"] = t + + last = converters[-1] + if not PY_3_11_PLUS and isinstance(last, Converter): + last = last.__call__ + + # Get return type from last converter. + rt = _AnnotationExtractor(last).get_return_type() + if rt: + pipe_converter.__annotations__["return"] = rt + + return Converter(pipe_converter, takes_self=True, takes_field=True) diff --git a/lambdas/aws-dd-forwarder-3.127.0/attr/_next_gen.py b/lambdas/aws-dd-forwarder-3.127.0/attr/_next_gen.py new file mode 100644 index 0000000..dbb65cc --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attr/_next_gen.py @@ -0,0 +1,631 @@ +# SPDX-License-Identifier: MIT + +""" +These are keyword-only APIs that call `attr.s` and `attr.ib` with different +default values. +""" + + +from functools import partial + +from . import setters +from ._funcs import asdict as _asdict +from ._funcs import astuple as _astuple +from ._make import ( + _DEFAULT_ON_SETATTR, + NOTHING, + _frozen_setattrs, + attrib, + attrs, +) +from .exceptions import UnannotatedAttributeError + + +def define( + maybe_cls=None, + *, + these=None, + repr=None, + unsafe_hash=None, + hash=None, + init=None, + slots=True, + frozen=False, + weakref_slot=True, + str=False, + auto_attribs=None, + kw_only=False, + cache_hash=False, + auto_exc=True, + eq=None, + order=False, + auto_detect=True, + getstate_setstate=None, + on_setattr=None, + field_transformer=None, + match_args=True, +): + r""" + A class decorator that adds :term:`dunder methods` according to + :term:`fields ` specified using :doc:`type annotations `, + `field()` calls, or the *these* argument. + + Since *attrs* patches or replaces an existing class, you cannot use + `object.__init_subclass__` with *attrs* classes, because it runs too early. + As a replacement, you can define ``__attrs_init_subclass__`` on your class. + It will be called by *attrs* classes that subclass it after they're + created. See also :ref:`init-subclass`. + + Args: + slots (bool): + Create a :term:`slotted class ` that's more + memory-efficient. Slotted classes are generally superior to the + default dict classes, but have some gotchas you should know about, + so we encourage you to read the :term:`glossary entry `. + + auto_detect (bool): + Instead of setting the *init*, *repr*, *eq*, and *hash* arguments + explicitly, assume they are set to True **unless any** of the + involved methods for one of the arguments is implemented in the + *current* class (meaning, it is *not* inherited from some base + class). + + So, for example by implementing ``__eq__`` on a class yourself, + *attrs* will deduce ``eq=False`` and will create *neither* + ``__eq__`` *nor* ``__ne__`` (but Python classes come with a + sensible ``__ne__`` by default, so it *should* be enough to only + implement ``__eq__`` in most cases). + + Passing True or False` to *init*, *repr*, *eq*, *cmp*, or *hash* + overrides whatever *auto_detect* would determine. + + auto_exc (bool): + If the class subclasses `BaseException` (which implicitly includes + any subclass of any exception), the following happens to behave + like a well-behaved Python exception class: + + - the values for *eq*, *order*, and *hash* are ignored and the + instances compare and hash by the instance's ids [#]_ , + - all attributes that are either passed into ``__init__`` or have a + default value are additionally available as a tuple in the + ``args`` attribute, + - the value of *str* is ignored leaving ``__str__`` to base + classes. + + .. [#] + Note that *attrs* will *not* remove existing implementations of + ``__hash__`` or the equality methods. It just won't add own + ones. + + on_setattr (~typing.Callable | list[~typing.Callable] | None | ~typing.Literal[attrs.setters.NO_OP]): + A callable that is run whenever the user attempts to set an + attribute (either by assignment like ``i.x = 42`` or by using + `setattr` like ``setattr(i, "x", 42)``). It receives the same + arguments as validators: the instance, the attribute that is being + modified, and the new value. + + If no exception is raised, the attribute is set to the return value + of the callable. + + If a list of callables is passed, they're automatically wrapped in + an `attrs.setters.pipe`. + + If left None, the default behavior is to run converters and + validators whenever an attribute is set. + + init (bool): + Create a ``__init__`` method that initializes the *attrs* + attributes. Leading underscores are stripped for the argument name, + unless an alias is set on the attribute. + + .. seealso:: + `init` shows advanced ways to customize the generated + ``__init__`` method, including executing code before and after. + + repr(bool): + Create a ``__repr__`` method with a human readable representation + of *attrs* attributes. + + str (bool): + Create a ``__str__`` method that is identical to ``__repr__``. This + is usually not necessary except for `Exception`\ s. + + eq (bool | None): + If True or None (default), add ``__eq__`` and ``__ne__`` methods + that check two instances for equality. + + .. seealso:: + `comparison` describes how to customize the comparison behavior + going as far comparing NumPy arrays. + + order (bool | None): + If True, add ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` + methods that behave like *eq* above and allow instances to be + ordered. + + They compare the instances as if they were tuples of their *attrs* + attributes if and only if the types of both classes are + *identical*. + + If `None` mirror value of *eq*. + + .. seealso:: `comparison` + + cmp (bool | None): + Setting *cmp* is equivalent to setting *eq* and *order* to the same + value. Must not be mixed with *eq* or *order*. + + unsafe_hash (bool | None): + If None (default), the ``__hash__`` method is generated according + how *eq* and *frozen* are set. + + 1. If *both* are True, *attrs* will generate a ``__hash__`` for + you. + 2. If *eq* is True and *frozen* is False, ``__hash__`` will be set + to None, marking it unhashable (which it is). + 3. If *eq* is False, ``__hash__`` will be left untouched meaning + the ``__hash__`` method of the base class will be used. If the + base class is `object`, this means it will fall back to id-based + hashing. + + Although not recommended, you can decide for yourself and force + *attrs* to create one (for example, if the class is immutable even + though you didn't freeze it programmatically) by passing True or + not. Both of these cases are rather special and should be used + carefully. + + .. seealso:: + + - Our documentation on `hashing`, + - Python's documentation on `object.__hash__`, + - and the `GitHub issue that led to the default \ behavior + `_ for more + details. + + hash (bool | None): + Deprecated alias for *unsafe_hash*. *unsafe_hash* takes precedence. + + cache_hash (bool): + Ensure that the object's hash code is computed only once and stored + on the object. If this is set to True, hashing must be either + explicitly or implicitly enabled for this class. If the hash code + is cached, avoid any reassignments of fields involved in hash code + computation or mutations of the objects those fields point to after + object creation. If such changes occur, the behavior of the + object's hash code is undefined. + + frozen (bool): + Make instances immutable after initialization. If someone attempts + to modify a frozen instance, `attrs.exceptions.FrozenInstanceError` + is raised. + + .. note:: + + 1. This is achieved by installing a custom ``__setattr__`` + method on your class, so you can't implement your own. + + 2. True immutability is impossible in Python. + + 3. This *does* have a minor a runtime performance `impact + ` when initializing new instances. In other + words: ``__init__`` is slightly slower with ``frozen=True``. + + 4. If a class is frozen, you cannot modify ``self`` in + ``__attrs_post_init__`` or a self-written ``__init__``. You + can circumvent that limitation by using + ``object.__setattr__(self, "attribute_name", value)``. + + 5. Subclasses of a frozen class are frozen too. + + kw_only (bool): + Make all attributes keyword-only in the generated ``__init__`` (if + *init* is False, this parameter is ignored). + + weakref_slot (bool): + Make instances weak-referenceable. This has no effect unless + *slots* is True. + + field_transformer (~typing.Callable | None): + A function that is called with the original class object and all + fields right before *attrs* finalizes the class. You can use this, + for example, to automatically add converters or validators to + fields based on their types. + + .. seealso:: `transform-fields` + + match_args (bool): + If True (default), set ``__match_args__`` on the class to support + :pep:`634` (*Structural Pattern Matching*). It is a tuple of all + non-keyword-only ``__init__`` parameter names on Python 3.10 and + later. Ignored on older Python versions. + + collect_by_mro (bool): + If True, *attrs* collects attributes from base classes correctly + according to the `method resolution order + `_. If False, *attrs* + will mimic the (wrong) behavior of `dataclasses` and :pep:`681`. + + See also `issue #428 + `_. + + getstate_setstate (bool | None): + .. note:: + + This is usually only interesting for slotted classes and you + should probably just set *auto_detect* to True. + + If True, ``__getstate__`` and ``__setstate__`` are generated and + attached to the class. This is necessary for slotted classes to be + pickleable. If left None, it's True by default for slotted classes + and False for dict classes. + + If *auto_detect* is True, and *getstate_setstate* is left None, and + **either** ``__getstate__`` or ``__setstate__`` is detected + directly on the class (meaning: not inherited), it is set to False + (this is usually what you want). + + auto_attribs (bool | None): + If True, look at type annotations to determine which attributes to + use, like `dataclasses`. If False, it will only look for explicit + :func:`field` class attributes, like classic *attrs*. + + If left None, it will guess: + + 1. If any attributes are annotated and no unannotated + `attrs.field`\ s are found, it assumes *auto_attribs=True*. + 2. Otherwise it assumes *auto_attribs=False* and tries to collect + `attrs.field`\ s. + + If *attrs* decides to look at type annotations, **all** fields + **must** be annotated. If *attrs* encounters a field that is set to + a :func:`field` / `attr.ib` but lacks a type annotation, an + `attrs.exceptions.UnannotatedAttributeError` is raised. Use + ``field_name: typing.Any = field(...)`` if you don't want to set a + type. + + .. warning:: + + For features that use the attribute name to create decorators + (for example, :ref:`validators `), you still *must* + assign :func:`field` / `attr.ib` to them. Otherwise Python will + either not find the name or try to use the default value to + call, for example, ``validator`` on it. + + Attributes annotated as `typing.ClassVar`, and attributes that are + neither annotated nor set to an `field()` are **ignored**. + + these (dict[str, object]): + A dictionary of name to the (private) return value of `field()` + mappings. This is useful to avoid the definition of your attributes + within the class body because you can't (for example, if you want + to add ``__repr__`` methods to Django models) or don't want to. + + If *these* is not `None`, *attrs* will *not* search the class body + for attributes and will *not* remove any attributes from it. + + The order is deduced from the order of the attributes inside + *these*. + + Arguably, this is a rather obscure feature. + + .. versionadded:: 20.1.0 + .. versionchanged:: 21.3.0 Converters are also run ``on_setattr``. + .. versionadded:: 22.2.0 + *unsafe_hash* as an alias for *hash* (for :pep:`681` compliance). + .. versionchanged:: 24.1.0 + Instances are not compared as tuples of attributes anymore, but using a + big ``and`` condition. This is faster and has more correct behavior for + uncomparable values like `math.nan`. + .. versionadded:: 24.1.0 + If a class has an *inherited* classmethod called + ``__attrs_init_subclass__``, it is executed after the class is created. + .. deprecated:: 24.1.0 *hash* is deprecated in favor of *unsafe_hash*. + + .. note:: + + The main differences to the classic `attr.s` are: + + - Automatically detect whether or not *auto_attribs* should be `True` + (c.f. *auto_attribs* parameter). + - Converters and validators run when attributes are set by default -- + if *frozen* is `False`. + - *slots=True* + + Usually, this has only upsides and few visible effects in everyday + programming. But it *can* lead to some surprising behaviors, so + please make sure to read :term:`slotted classes`. + + - *auto_exc=True* + - *auto_detect=True* + - *order=False* + - Some options that were only relevant on Python 2 or were kept around + for backwards-compatibility have been removed. + + """ + + def do_it(cls, auto_attribs): + return attrs( + maybe_cls=cls, + these=these, + repr=repr, + hash=hash, + unsafe_hash=unsafe_hash, + init=init, + slots=slots, + frozen=frozen, + weakref_slot=weakref_slot, + str=str, + auto_attribs=auto_attribs, + kw_only=kw_only, + cache_hash=cache_hash, + auto_exc=auto_exc, + eq=eq, + order=order, + auto_detect=auto_detect, + collect_by_mro=True, + getstate_setstate=getstate_setstate, + on_setattr=on_setattr, + field_transformer=field_transformer, + match_args=match_args, + ) + + def wrap(cls): + """ + Making this a wrapper ensures this code runs during class creation. + + We also ensure that frozen-ness of classes is inherited. + """ + nonlocal frozen, on_setattr + + had_on_setattr = on_setattr not in (None, setters.NO_OP) + + # By default, mutable classes convert & validate on setattr. + if frozen is False and on_setattr is None: + on_setattr = _DEFAULT_ON_SETATTR + + # However, if we subclass a frozen class, we inherit the immutability + # and disable on_setattr. + for base_cls in cls.__bases__: + if base_cls.__setattr__ is _frozen_setattrs: + if had_on_setattr: + msg = "Frozen classes can't use on_setattr (frozen-ness was inherited)." + raise ValueError(msg) + + on_setattr = setters.NO_OP + break + + if auto_attribs is not None: + return do_it(cls, auto_attribs) + + try: + return do_it(cls, True) + except UnannotatedAttributeError: + return do_it(cls, False) + + # maybe_cls's type depends on the usage of the decorator. It's a class + # if it's used as `@attrs` but `None` if used as `@attrs()`. + if maybe_cls is None: + return wrap + + return wrap(maybe_cls) + + +mutable = define +frozen = partial(define, frozen=True, on_setattr=None) + + +def field( + *, + default=NOTHING, + validator=None, + repr=True, + hash=None, + init=True, + metadata=None, + type=None, + converter=None, + factory=None, + kw_only=False, + eq=None, + order=None, + on_setattr=None, + alias=None, +): + """ + Create a new :term:`field` / :term:`attribute` on a class. + + .. warning:: + + Does **nothing** unless the class is also decorated with + `attrs.define` (or similar)! + + Args: + default: + A value that is used if an *attrs*-generated ``__init__`` is used + and no value is passed while instantiating or the attribute is + excluded using ``init=False``. + + If the value is an instance of `attrs.Factory`, its callable will + be used to construct a new value (useful for mutable data types + like lists or dicts). + + If a default is not set (or set manually to `attrs.NOTHING`), a + value *must* be supplied when instantiating; otherwise a + `TypeError` will be raised. + + .. seealso:: `defaults` + + factory (~typing.Callable): + Syntactic sugar for ``default=attr.Factory(factory)``. + + validator (~typing.Callable | list[~typing.Callable]): + Callable that is called by *attrs*-generated ``__init__`` methods + after the instance has been initialized. They receive the + initialized instance, the :func:`~attrs.Attribute`, and the passed + value. + + The return value is *not* inspected so the validator has to throw + an exception itself. + + If a `list` is passed, its items are treated as validators and must + all pass. + + Validators can be globally disabled and re-enabled using + `attrs.validators.get_disabled` / `attrs.validators.set_disabled`. + + The validator can also be set using decorator notation as shown + below. + + .. seealso:: :ref:`validators` + + repr (bool | ~typing.Callable): + Include this attribute in the generated ``__repr__`` method. If + True, include the attribute; if False, omit it. By default, the + built-in ``repr()`` function is used. To override how the attribute + value is formatted, pass a ``callable`` that takes a single value + and returns a string. Note that the resulting string is used as-is, + which means it will be used directly *instead* of calling + ``repr()`` (the default). + + eq (bool | ~typing.Callable): + If True (default), include this attribute in the generated + ``__eq__`` and ``__ne__`` methods that check two instances for + equality. To override how the attribute value is compared, pass a + callable that takes a single value and returns the value to be + compared. + + .. seealso:: `comparison` + + order (bool | ~typing.Callable): + If True (default), include this attributes in the generated + ``__lt__``, ``__le__``, ``__gt__`` and ``__ge__`` methods. To + override how the attribute value is ordered, pass a callable that + takes a single value and returns the value to be ordered. + + .. seealso:: `comparison` + + cmp(bool | ~typing.Callable): + Setting *cmp* is equivalent to setting *eq* and *order* to the same + value. Must not be mixed with *eq* or *order*. + + .. seealso:: `comparison` + + hash (bool | None): + Include this attribute in the generated ``__hash__`` method. If + None (default), mirror *eq*'s value. This is the correct behavior + according the Python spec. Setting this value to anything else + than None is *discouraged*. + + .. seealso:: `hashing` + + init (bool): + Include this attribute in the generated ``__init__`` method. + + It is possible to set this to False and set a default value. In + that case this attributed is unconditionally initialized with the + specified default value or factory. + + .. seealso:: `init` + + converter (typing.Callable | Converter): + A callable that is called by *attrs*-generated ``__init__`` methods + to convert attribute's value to the desired format. + + If a vanilla callable is passed, it is given the passed-in value as + the only positional argument. It is possible to receive additional + arguments by wrapping the callable in a `Converter`. + + Either way, the returned value will be used as the new value of the + attribute. The value is converted before being passed to the + validator, if any. + + .. seealso:: :ref:`converters` + + metadata (dict | None): + An arbitrary mapping, to be used by third-party code. + + .. seealso:: `extending-metadata`. + + type (type): + The type of the attribute. Nowadays, the preferred method to + specify the type is using a variable annotation (see :pep:`526`). + This argument is provided for backwards-compatibility and for usage + with `make_class`. Regardless of the approach used, the type will + be stored on ``Attribute.type``. + + Please note that *attrs* doesn't do anything with this metadata by + itself. You can use it as part of your own code or for `static type + checking `. + + kw_only (bool): + Make this attribute keyword-only in the generated ``__init__`` (if + ``init`` is False, this parameter is ignored). + + on_setattr (~typing.Callable | list[~typing.Callable] | None | ~typing.Literal[attrs.setters.NO_OP]): + Allows to overwrite the *on_setattr* setting from `attr.s`. If left + None, the *on_setattr* value from `attr.s` is used. Set to + `attrs.setters.NO_OP` to run **no** `setattr` hooks for this + attribute -- regardless of the setting in `define()`. + + alias (str | None): + Override this attribute's parameter name in the generated + ``__init__`` method. If left None, default to ``name`` stripped + of leading underscores. See `private-attributes`. + + .. versionadded:: 20.1.0 + .. versionchanged:: 21.1.0 + *eq*, *order*, and *cmp* also accept a custom callable + .. versionadded:: 22.2.0 *alias* + .. versionadded:: 23.1.0 + The *type* parameter has been re-added; mostly for `attrs.make_class`. + Please note that type checkers ignore this metadata. + + .. seealso:: + + `attr.ib` + """ + return attrib( + default=default, + validator=validator, + repr=repr, + hash=hash, + init=init, + metadata=metadata, + type=type, + converter=converter, + factory=factory, + kw_only=kw_only, + eq=eq, + order=order, + on_setattr=on_setattr, + alias=alias, + ) + + +def asdict(inst, *, recurse=True, filter=None, value_serializer=None): + """ + Same as `attr.asdict`, except that collections types are always retained + and dict is always used as *dict_factory*. + + .. versionadded:: 21.3.0 + """ + return _asdict( + inst=inst, + recurse=recurse, + filter=filter, + value_serializer=value_serializer, + retain_collection_types=True, + ) + + +def astuple(inst, *, recurse=True, filter=None): + """ + Same as `attr.astuple`, except that collections types are always retained + and `tuple` is always used as the *tuple_factory*. + + .. versionadded:: 21.3.0 + """ + return _astuple( + inst=inst, recurse=recurse, filter=filter, retain_collection_types=True + ) diff --git a/lambdas/aws-dd-forwarder-3.127.0/attr/_typing_compat.pyi b/lambdas/aws-dd-forwarder-3.127.0/attr/_typing_compat.pyi new file mode 100644 index 0000000..ca7b71e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attr/_typing_compat.pyi @@ -0,0 +1,15 @@ +from typing import Any, ClassVar, Protocol + +# MYPY is a special constant in mypy which works the same way as `TYPE_CHECKING`. +MYPY = False + +if MYPY: + # A protocol to be able to statically accept an attrs class. + class AttrsInstance_(Protocol): + __attrs_attrs__: ClassVar[Any] + +else: + # For type checkers without plug-in support use an empty protocol that + # will (hopefully) be combined into a union. + class AttrsInstance_(Protocol): + pass diff --git a/lambdas/aws-dd-forwarder-3.127.0/attr/_version_info.py b/lambdas/aws-dd-forwarder-3.127.0/attr/_version_info.py new file mode 100644 index 0000000..51a1312 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attr/_version_info.py @@ -0,0 +1,86 @@ +# SPDX-License-Identifier: MIT + + +from functools import total_ordering + +from ._funcs import astuple +from ._make import attrib, attrs + + +@total_ordering +@attrs(eq=False, order=False, slots=True, frozen=True) +class VersionInfo: + """ + A version object that can be compared to tuple of length 1--4: + + >>> attr.VersionInfo(19, 1, 0, "final") <= (19, 2) + True + >>> attr.VersionInfo(19, 1, 0, "final") < (19, 1, 1) + True + >>> vi = attr.VersionInfo(19, 2, 0, "final") + >>> vi < (19, 1, 1) + False + >>> vi < (19,) + False + >>> vi == (19, 2,) + True + >>> vi == (19, 2, 1) + False + + .. versionadded:: 19.2 + """ + + year = attrib(type=int) + minor = attrib(type=int) + micro = attrib(type=int) + releaselevel = attrib(type=str) + + @classmethod + def _from_version_string(cls, s): + """ + Parse *s* and return a _VersionInfo. + """ + v = s.split(".") + if len(v) == 3: + v.append("final") + + return cls( + year=int(v[0]), minor=int(v[1]), micro=int(v[2]), releaselevel=v[3] + ) + + def _ensure_tuple(self, other): + """ + Ensure *other* is a tuple of a valid length. + + Returns a possibly transformed *other* and ourselves as a tuple of + the same length as *other*. + """ + + if self.__class__ is other.__class__: + other = astuple(other) + + if not isinstance(other, tuple): + raise NotImplementedError + + if not (1 <= len(other) <= 4): + raise NotImplementedError + + return astuple(self)[: len(other)], other + + def __eq__(self, other): + try: + us, them = self._ensure_tuple(other) + except NotImplementedError: + return NotImplemented + + return us == them + + def __lt__(self, other): + try: + us, them = self._ensure_tuple(other) + except NotImplementedError: + return NotImplemented + + # Since alphabetically "dev0" < "final" < "post1" < "post2", we don't + # have to do anything special with releaselevel for now. + return us < them diff --git a/lambdas/aws-dd-forwarder-3.127.0/attr/_version_info.pyi b/lambdas/aws-dd-forwarder-3.127.0/attr/_version_info.pyi new file mode 100644 index 0000000..45ced08 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attr/_version_info.pyi @@ -0,0 +1,9 @@ +class VersionInfo: + @property + def year(self) -> int: ... + @property + def minor(self) -> int: ... + @property + def micro(self) -> int: ... + @property + def releaselevel(self) -> str: ... diff --git a/lambdas/aws-dd-forwarder-3.127.0/attr/converters.py b/lambdas/aws-dd-forwarder-3.127.0/attr/converters.py new file mode 100644 index 0000000..9238311 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attr/converters.py @@ -0,0 +1,151 @@ +# SPDX-License-Identifier: MIT + +""" +Commonly useful converters. +""" + + +import typing + +from ._compat import _AnnotationExtractor +from ._make import NOTHING, Factory, pipe + + +__all__ = [ + "default_if_none", + "optional", + "pipe", + "to_bool", +] + + +def optional(converter): + """ + A converter that allows an attribute to be optional. An optional attribute + is one which can be set to `None`. + + Type annotations will be inferred from the wrapped converter's, if it has + any. + + Args: + converter (typing.Callable): + the converter that is used for non-`None` values. + + .. versionadded:: 17.1.0 + """ + + def optional_converter(val): + if val is None: + return None + return converter(val) + + xtr = _AnnotationExtractor(converter) + + t = xtr.get_first_param_type() + if t: + optional_converter.__annotations__["val"] = typing.Optional[t] + + rt = xtr.get_return_type() + if rt: + optional_converter.__annotations__["return"] = typing.Optional[rt] + + return optional_converter + + +def default_if_none(default=NOTHING, factory=None): + """ + A converter that allows to replace `None` values by *default* or the result + of *factory*. + + Args: + default: + Value to be used if `None` is passed. Passing an instance of + `attrs.Factory` is supported, however the ``takes_self`` option is + *not*. + + factory (typing.Callable): + A callable that takes no parameters whose result is used if `None` + is passed. + + Raises: + TypeError: If **neither** *default* or *factory* is passed. + + TypeError: If **both** *default* and *factory* are passed. + + ValueError: + If an instance of `attrs.Factory` is passed with + ``takes_self=True``. + + .. versionadded:: 18.2.0 + """ + if default is NOTHING and factory is None: + msg = "Must pass either `default` or `factory`." + raise TypeError(msg) + + if default is not NOTHING and factory is not None: + msg = "Must pass either `default` or `factory` but not both." + raise TypeError(msg) + + if factory is not None: + default = Factory(factory) + + if isinstance(default, Factory): + if default.takes_self: + msg = "`takes_self` is not supported by default_if_none." + raise ValueError(msg) + + def default_if_none_converter(val): + if val is not None: + return val + + return default.factory() + + else: + + def default_if_none_converter(val): + if val is not None: + return val + + return default + + return default_if_none_converter + + +def to_bool(val): + """ + Convert "boolean" strings (for example, from environment variables) to real + booleans. + + Values mapping to `True`: + + - ``True`` + - ``"true"`` / ``"t"`` + - ``"yes"`` / ``"y"`` + - ``"on"`` + - ``"1"`` + - ``1`` + + Values mapping to `False`: + + - ``False`` + - ``"false"`` / ``"f"`` + - ``"no"`` / ``"n"`` + - ``"off"`` + - ``"0"`` + - ``0`` + + Raises: + ValueError: For any other value. + + .. versionadded:: 21.3.0 + """ + if isinstance(val, str): + val = val.lower() + + if val in (True, "true", "t", "yes", "y", "on", "1", 1): + return True + if val in (False, "false", "f", "no", "n", "off", "0", 0): + return False + + msg = f"Cannot convert value to bool: {val!r}" + raise ValueError(msg) diff --git a/lambdas/aws-dd-forwarder-3.127.0/attr/converters.pyi b/lambdas/aws-dd-forwarder-3.127.0/attr/converters.pyi new file mode 100644 index 0000000..9ef478f --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attr/converters.pyi @@ -0,0 +1,13 @@ +from typing import Callable, TypeVar, overload + +from attrs import _ConverterType + +_T = TypeVar("_T") + +def pipe(*validators: _ConverterType) -> _ConverterType: ... +def optional(converter: _ConverterType) -> _ConverterType: ... +@overload +def default_if_none(default: _T) -> _ConverterType: ... +@overload +def default_if_none(*, factory: Callable[[], _T]) -> _ConverterType: ... +def to_bool(val: str) -> bool: ... diff --git a/lambdas/aws-dd-forwarder-3.127.0/attr/exceptions.py b/lambdas/aws-dd-forwarder-3.127.0/attr/exceptions.py new file mode 100644 index 0000000..3b7abb8 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attr/exceptions.py @@ -0,0 +1,95 @@ +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +from typing import ClassVar + + +class FrozenError(AttributeError): + """ + A frozen/immutable instance or attribute have been attempted to be + modified. + + It mirrors the behavior of ``namedtuples`` by using the same error message + and subclassing `AttributeError`. + + .. versionadded:: 20.1.0 + """ + + msg = "can't set attribute" + args: ClassVar[tuple[str]] = [msg] + + +class FrozenInstanceError(FrozenError): + """ + A frozen instance has been attempted to be modified. + + .. versionadded:: 16.1.0 + """ + + +class FrozenAttributeError(FrozenError): + """ + A frozen attribute has been attempted to be modified. + + .. versionadded:: 20.1.0 + """ + + +class AttrsAttributeNotFoundError(ValueError): + """ + An *attrs* function couldn't find an attribute that the user asked for. + + .. versionadded:: 16.2.0 + """ + + +class NotAnAttrsClassError(ValueError): + """ + A non-*attrs* class has been passed into an *attrs* function. + + .. versionadded:: 16.2.0 + """ + + +class DefaultAlreadySetError(RuntimeError): + """ + A default has been set when defining the field and is attempted to be reset + using the decorator. + + .. versionadded:: 17.1.0 + """ + + +class UnannotatedAttributeError(RuntimeError): + """ + A class with ``auto_attribs=True`` has a field without a type annotation. + + .. versionadded:: 17.3.0 + """ + + +class PythonTooOldError(RuntimeError): + """ + It was attempted to use an *attrs* feature that requires a newer Python + version. + + .. versionadded:: 18.2.0 + """ + + +class NotCallableError(TypeError): + """ + A field requiring a callable has been set with a value that is not + callable. + + .. versionadded:: 19.2.0 + """ + + def __init__(self, msg, value): + super(TypeError, self).__init__(msg, value) + self.msg = msg + self.value = value + + def __str__(self): + return str(self.msg) diff --git a/lambdas/aws-dd-forwarder-3.127.0/attr/exceptions.pyi b/lambdas/aws-dd-forwarder-3.127.0/attr/exceptions.pyi new file mode 100644 index 0000000..f268011 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attr/exceptions.pyi @@ -0,0 +1,17 @@ +from typing import Any + +class FrozenError(AttributeError): + msg: str = ... + +class FrozenInstanceError(FrozenError): ... +class FrozenAttributeError(FrozenError): ... +class AttrsAttributeNotFoundError(ValueError): ... +class NotAnAttrsClassError(ValueError): ... +class DefaultAlreadySetError(RuntimeError): ... +class UnannotatedAttributeError(RuntimeError): ... +class PythonTooOldError(RuntimeError): ... + +class NotCallableError(TypeError): + msg: str = ... + value: Any = ... + def __init__(self, msg: str, value: Any) -> None: ... diff --git a/lambdas/aws-dd-forwarder-3.127.0/attr/filters.py b/lambdas/aws-dd-forwarder-3.127.0/attr/filters.py new file mode 100644 index 0000000..689b170 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attr/filters.py @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: MIT + +""" +Commonly useful filters for `attrs.asdict` and `attrs.astuple`. +""" + +from ._make import Attribute + + +def _split_what(what): + """ + Returns a tuple of `frozenset`s of classes and attributes. + """ + return ( + frozenset(cls for cls in what if isinstance(cls, type)), + frozenset(cls for cls in what if isinstance(cls, str)), + frozenset(cls for cls in what if isinstance(cls, Attribute)), + ) + + +def include(*what): + """ + Create a filter that only allows *what*. + + Args: + what (list[type, str, attrs.Attribute]): + What to include. Can be a type, a name, or an attribute. + + Returns: + Callable: + A callable that can be passed to `attrs.asdict`'s and + `attrs.astuple`'s *filter* argument. + + .. versionchanged:: 23.1.0 Accept strings with field names. + """ + cls, names, attrs = _split_what(what) + + def include_(attribute, value): + return ( + value.__class__ in cls + or attribute.name in names + or attribute in attrs + ) + + return include_ + + +def exclude(*what): + """ + Create a filter that does **not** allow *what*. + + Args: + what (list[type, str, attrs.Attribute]): + What to exclude. Can be a type, a name, or an attribute. + + Returns: + Callable: + A callable that can be passed to `attrs.asdict`'s and + `attrs.astuple`'s *filter* argument. + + .. versionchanged:: 23.3.0 Accept field name string as input argument + """ + cls, names, attrs = _split_what(what) + + def exclude_(attribute, value): + return not ( + value.__class__ in cls + or attribute.name in names + or attribute in attrs + ) + + return exclude_ diff --git a/lambdas/aws-dd-forwarder-3.127.0/attr/filters.pyi b/lambdas/aws-dd-forwarder-3.127.0/attr/filters.pyi new file mode 100644 index 0000000..974abdc --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attr/filters.pyi @@ -0,0 +1,6 @@ +from typing import Any + +from . import Attribute, _FilterType + +def include(*what: type | str | Attribute[Any]) -> _FilterType[Any]: ... +def exclude(*what: type | str | Attribute[Any]) -> _FilterType[Any]: ... diff --git a/lambdas/aws-dd-forwarder-3.127.0/attr/py.typed b/lambdas/aws-dd-forwarder-3.127.0/attr/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/attr/setters.py b/lambdas/aws-dd-forwarder-3.127.0/attr/setters.py new file mode 100644 index 0000000..a9ce016 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attr/setters.py @@ -0,0 +1,79 @@ +# SPDX-License-Identifier: MIT + +""" +Commonly used hooks for on_setattr. +""" + +from . import _config +from .exceptions import FrozenAttributeError + + +def pipe(*setters): + """ + Run all *setters* and return the return value of the last one. + + .. versionadded:: 20.1.0 + """ + + def wrapped_pipe(instance, attrib, new_value): + rv = new_value + + for setter in setters: + rv = setter(instance, attrib, rv) + + return rv + + return wrapped_pipe + + +def frozen(_, __, ___): + """ + Prevent an attribute to be modified. + + .. versionadded:: 20.1.0 + """ + raise FrozenAttributeError() + + +def validate(instance, attrib, new_value): + """ + Run *attrib*'s validator on *new_value* if it has one. + + .. versionadded:: 20.1.0 + """ + if _config._run_validators is False: + return new_value + + v = attrib.validator + if not v: + return new_value + + v(instance, attrib, new_value) + + return new_value + + +def convert(instance, attrib, new_value): + """ + Run *attrib*'s converter -- if it has one -- on *new_value* and return the + result. + + .. versionadded:: 20.1.0 + """ + c = attrib.converter + if c: + # This can be removed once we drop 3.8 and use attrs.Converter instead. + from ._make import Converter + + if not isinstance(c, Converter): + return c(new_value) + + return c(new_value, instance, attrib) + + return new_value + + +# Sentinel for disabling class-wide *on_setattr* hooks for certain attributes. +# Sphinx's autodata stopped working, so the docstring is inlined in the API +# docs. +NO_OP = object() diff --git a/lambdas/aws-dd-forwarder-3.127.0/attr/setters.pyi b/lambdas/aws-dd-forwarder-3.127.0/attr/setters.pyi new file mode 100644 index 0000000..73abf36 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attr/setters.pyi @@ -0,0 +1,20 @@ +from typing import Any, NewType, NoReturn, TypeVar + +from . import Attribute +from attrs import _OnSetAttrType + +_T = TypeVar("_T") + +def frozen( + instance: Any, attribute: Attribute[Any], new_value: Any +) -> NoReturn: ... +def pipe(*setters: _OnSetAttrType) -> _OnSetAttrType: ... +def validate(instance: Any, attribute: Attribute[_T], new_value: _T) -> _T: ... + +# convert is allowed to return Any, because they can be chained using pipe. +def convert( + instance: Any, attribute: Attribute[Any], new_value: Any +) -> Any: ... + +_NoOpType = NewType("_NoOpType", object) +NO_OP: _NoOpType diff --git a/lambdas/aws-dd-forwarder-3.127.0/attr/validators.py b/lambdas/aws-dd-forwarder-3.127.0/attr/validators.py new file mode 100644 index 0000000..8a56717 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attr/validators.py @@ -0,0 +1,711 @@ +# SPDX-License-Identifier: MIT + +""" +Commonly useful validators. +""" + + +import operator +import re + +from contextlib import contextmanager +from re import Pattern + +from ._config import get_run_validators, set_run_validators +from ._make import _AndValidator, and_, attrib, attrs +from .converters import default_if_none +from .exceptions import NotCallableError + + +__all__ = [ + "and_", + "deep_iterable", + "deep_mapping", + "disabled", + "ge", + "get_disabled", + "gt", + "in_", + "instance_of", + "is_callable", + "le", + "lt", + "matches_re", + "max_len", + "min_len", + "not_", + "optional", + "or_", + "set_disabled", +] + + +def set_disabled(disabled): + """ + Globally disable or enable running validators. + + By default, they are run. + + Args: + disabled (bool): If `True`, disable running all validators. + + .. warning:: + + This function is not thread-safe! + + .. versionadded:: 21.3.0 + """ + set_run_validators(not disabled) + + +def get_disabled(): + """ + Return a bool indicating whether validators are currently disabled or not. + + Returns: + bool:`True` if validators are currently disabled. + + .. versionadded:: 21.3.0 + """ + return not get_run_validators() + + +@contextmanager +def disabled(): + """ + Context manager that disables running validators within its context. + + .. warning:: + + This context manager is not thread-safe! + + .. versionadded:: 21.3.0 + """ + set_run_validators(False) + try: + yield + finally: + set_run_validators(True) + + +@attrs(repr=False, slots=True, unsafe_hash=True) +class _InstanceOfValidator: + type = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not isinstance(value, self.type): + msg = f"'{attr.name}' must be {self.type!r} (got {value!r} that is a {value.__class__!r})." + raise TypeError( + msg, + attr, + self.type, + value, + ) + + def __repr__(self): + return f"" + + +def instance_of(type): + """ + A validator that raises a `TypeError` if the initializer is called with a + wrong type for this particular attribute (checks are performed using + `isinstance` therefore it's also valid to pass a tuple of types). + + Args: + type (type | tuple[type]): The type to check for. + + Raises: + TypeError: + With a human readable error message, the attribute (of type + `attrs.Attribute`), the expected type, and the value it got. + """ + return _InstanceOfValidator(type) + + +@attrs(repr=False, frozen=True, slots=True) +class _MatchesReValidator: + pattern = attrib() + match_func = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not self.match_func(value): + msg = f"'{attr.name}' must match regex {self.pattern.pattern!r} ({value!r} doesn't)" + raise ValueError( + msg, + attr, + self.pattern, + value, + ) + + def __repr__(self): + return f"" + + +def matches_re(regex, flags=0, func=None): + r""" + A validator that raises `ValueError` if the initializer is called with a + string that doesn't match *regex*. + + Args: + regex (str, re.Pattern): + A regex string or precompiled pattern to match against + + flags (int): + Flags that will be passed to the underlying re function (default 0) + + func (typing.Callable): + Which underlying `re` function to call. Valid options are + `re.fullmatch`, `re.search`, and `re.match`; the default `None` + means `re.fullmatch`. For performance reasons, the pattern is + always precompiled using `re.compile`. + + .. versionadded:: 19.2.0 + .. versionchanged:: 21.3.0 *regex* can be a pre-compiled pattern. + """ + valid_funcs = (re.fullmatch, None, re.search, re.match) + if func not in valid_funcs: + msg = "'func' must be one of {}.".format( + ", ".join( + sorted(e and e.__name__ or "None" for e in set(valid_funcs)) + ) + ) + raise ValueError(msg) + + if isinstance(regex, Pattern): + if flags: + msg = "'flags' can only be used with a string pattern; pass flags to re.compile() instead" + raise TypeError(msg) + pattern = regex + else: + pattern = re.compile(regex, flags) + + if func is re.match: + match_func = pattern.match + elif func is re.search: + match_func = pattern.search + else: + match_func = pattern.fullmatch + + return _MatchesReValidator(pattern, match_func) + + +@attrs(repr=False, slots=True, unsafe_hash=True) +class _OptionalValidator: + validator = attrib() + + def __call__(self, inst, attr, value): + if value is None: + return + + self.validator(inst, attr, value) + + def __repr__(self): + return f"" + + +def optional(validator): + """ + A validator that makes an attribute optional. An optional attribute is one + which can be set to `None` in addition to satisfying the requirements of + the sub-validator. + + Args: + validator + (typing.Callable | tuple[typing.Callable] | list[typing.Callable]): + A validator (or validators) that is used for non-`None` values. + + .. versionadded:: 15.1.0 + .. versionchanged:: 17.1.0 *validator* can be a list of validators. + .. versionchanged:: 23.1.0 *validator* can also be a tuple of validators. + """ + if isinstance(validator, (list, tuple)): + return _OptionalValidator(_AndValidator(validator)) + + return _OptionalValidator(validator) + + +@attrs(repr=False, slots=True, unsafe_hash=True) +class _InValidator: + options = attrib() + _original_options = attrib(hash=False) + + def __call__(self, inst, attr, value): + try: + in_options = value in self.options + except TypeError: # e.g. `1 in "abc"` + in_options = False + + if not in_options: + msg = f"'{attr.name}' must be in {self._original_options!r} (got {value!r})" + raise ValueError( + msg, + attr, + self._original_options, + value, + ) + + def __repr__(self): + return f"" + + +def in_(options): + """ + A validator that raises a `ValueError` if the initializer is called with a + value that does not belong in the *options* provided. + + The check is performed using ``value in options``, so *options* has to + support that operation. + + To keep the validator hashable, dicts, lists, and sets are transparently + transformed into a `tuple`. + + Args: + options: Allowed options. + + Raises: + ValueError: + With a human readable error message, the attribute (of type + `attrs.Attribute`), the expected options, and the value it got. + + .. versionadded:: 17.1.0 + .. versionchanged:: 22.1.0 + The ValueError was incomplete until now and only contained the human + readable error message. Now it contains all the information that has + been promised since 17.1.0. + .. versionchanged:: 24.1.0 + *options* that are a list, dict, or a set are now transformed into a + tuple to keep the validator hashable. + """ + repr_options = options + if isinstance(options, (list, dict, set)): + options = tuple(options) + + return _InValidator(options, repr_options) + + +@attrs(repr=False, slots=False, unsafe_hash=True) +class _IsCallableValidator: + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not callable(value): + message = ( + "'{name}' must be callable " + "(got {value!r} that is a {actual!r})." + ) + raise NotCallableError( + msg=message.format( + name=attr.name, value=value, actual=value.__class__ + ), + value=value, + ) + + def __repr__(self): + return "" + + +def is_callable(): + """ + A validator that raises a `attrs.exceptions.NotCallableError` if the + initializer is called with a value for this particular attribute that is + not callable. + + .. versionadded:: 19.1.0 + + Raises: + attrs.exceptions.NotCallableError: + With a human readable error message containing the attribute + (`attrs.Attribute`) name, and the value it got. + """ + return _IsCallableValidator() + + +@attrs(repr=False, slots=True, unsafe_hash=True) +class _DeepIterable: + member_validator = attrib(validator=is_callable()) + iterable_validator = attrib( + default=None, validator=optional(is_callable()) + ) + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if self.iterable_validator is not None: + self.iterable_validator(inst, attr, value) + + for member in value: + self.member_validator(inst, attr, member) + + def __repr__(self): + iterable_identifier = ( + "" + if self.iterable_validator is None + else f" {self.iterable_validator!r}" + ) + return ( + f"" + ) + + +def deep_iterable(member_validator, iterable_validator=None): + """ + A validator that performs deep validation of an iterable. + + Args: + member_validator: Validator to apply to iterable members. + + iterable_validator: + Validator to apply to iterable itself (optional). + + Raises + TypeError: if any sub-validators fail + + .. versionadded:: 19.1.0 + """ + if isinstance(member_validator, (list, tuple)): + member_validator = and_(*member_validator) + return _DeepIterable(member_validator, iterable_validator) + + +@attrs(repr=False, slots=True, unsafe_hash=True) +class _DeepMapping: + key_validator = attrib(validator=is_callable()) + value_validator = attrib(validator=is_callable()) + mapping_validator = attrib(default=None, validator=optional(is_callable())) + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if self.mapping_validator is not None: + self.mapping_validator(inst, attr, value) + + for key in value: + self.key_validator(inst, attr, key) + self.value_validator(inst, attr, value[key]) + + def __repr__(self): + return f"" + + +def deep_mapping(key_validator, value_validator, mapping_validator=None): + """ + A validator that performs deep validation of a dictionary. + + Args: + key_validator: Validator to apply to dictionary keys. + + value_validator: Validator to apply to dictionary values. + + mapping_validator: + Validator to apply to top-level mapping attribute (optional). + + .. versionadded:: 19.1.0 + + Raises: + TypeError: if any sub-validators fail + """ + return _DeepMapping(key_validator, value_validator, mapping_validator) + + +@attrs(repr=False, frozen=True, slots=True) +class _NumberValidator: + bound = attrib() + compare_op = attrib() + compare_func = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not self.compare_func(value, self.bound): + msg = f"'{attr.name}' must be {self.compare_op} {self.bound}: {value}" + raise ValueError(msg) + + def __repr__(self): + return f"" + + +def lt(val): + """ + A validator that raises `ValueError` if the initializer is called with a + number larger or equal to *val*. + + The validator uses `operator.lt` to compare the values. + + Args: + val: Exclusive upper bound for values. + + .. versionadded:: 21.3.0 + """ + return _NumberValidator(val, "<", operator.lt) + + +def le(val): + """ + A validator that raises `ValueError` if the initializer is called with a + number greater than *val*. + + The validator uses `operator.le` to compare the values. + + Args: + val: Inclusive upper bound for values. + + .. versionadded:: 21.3.0 + """ + return _NumberValidator(val, "<=", operator.le) + + +def ge(val): + """ + A validator that raises `ValueError` if the initializer is called with a + number smaller than *val*. + + The validator uses `operator.ge` to compare the values. + + Args: + val: Inclusive lower bound for values + + .. versionadded:: 21.3.0 + """ + return _NumberValidator(val, ">=", operator.ge) + + +def gt(val): + """ + A validator that raises `ValueError` if the initializer is called with a + number smaller or equal to *val*. + + The validator uses `operator.ge` to compare the values. + + Args: + val: Exclusive lower bound for values + + .. versionadded:: 21.3.0 + """ + return _NumberValidator(val, ">", operator.gt) + + +@attrs(repr=False, frozen=True, slots=True) +class _MaxLengthValidator: + max_length = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if len(value) > self.max_length: + msg = f"Length of '{attr.name}' must be <= {self.max_length}: {len(value)}" + raise ValueError(msg) + + def __repr__(self): + return f"" + + +def max_len(length): + """ + A validator that raises `ValueError` if the initializer is called + with a string or iterable that is longer than *length*. + + Args: + length (int): Maximum length of the string or iterable + + .. versionadded:: 21.3.0 + """ + return _MaxLengthValidator(length) + + +@attrs(repr=False, frozen=True, slots=True) +class _MinLengthValidator: + min_length = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if len(value) < self.min_length: + msg = f"Length of '{attr.name}' must be >= {self.min_length}: {len(value)}" + raise ValueError(msg) + + def __repr__(self): + return f"" + + +def min_len(length): + """ + A validator that raises `ValueError` if the initializer is called + with a string or iterable that is shorter than *length*. + + Args: + length (int): Minimum length of the string or iterable + + .. versionadded:: 22.1.0 + """ + return _MinLengthValidator(length) + + +@attrs(repr=False, slots=True, unsafe_hash=True) +class _SubclassOfValidator: + type = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not issubclass(value, self.type): + msg = f"'{attr.name}' must be a subclass of {self.type!r} (got {value!r})." + raise TypeError( + msg, + attr, + self.type, + value, + ) + + def __repr__(self): + return f"" + + +def _subclass_of(type): + """ + A validator that raises a `TypeError` if the initializer is called with a + wrong type for this particular attribute (checks are performed using + `issubclass` therefore it's also valid to pass a tuple of types). + + Args: + type (type | tuple[type, ...]): The type(s) to check for. + + Raises: + TypeError: + With a human readable error message, the attribute (of type + `attrs.Attribute`), the expected type, and the value it got. + """ + return _SubclassOfValidator(type) + + +@attrs(repr=False, slots=True, unsafe_hash=True) +class _NotValidator: + validator = attrib() + msg = attrib( + converter=default_if_none( + "not_ validator child '{validator!r}' " + "did not raise a captured error" + ) + ) + exc_types = attrib( + validator=deep_iterable( + member_validator=_subclass_of(Exception), + iterable_validator=instance_of(tuple), + ), + ) + + def __call__(self, inst, attr, value): + try: + self.validator(inst, attr, value) + except self.exc_types: + pass # suppress error to invert validity + else: + raise ValueError( + self.msg.format( + validator=self.validator, + exc_types=self.exc_types, + ), + attr, + self.validator, + value, + self.exc_types, + ) + + def __repr__(self): + return f"" + + +def not_(validator, *, msg=None, exc_types=(ValueError, TypeError)): + """ + A validator that wraps and logically 'inverts' the validator passed to it. + It will raise a `ValueError` if the provided validator *doesn't* raise a + `ValueError` or `TypeError` (by default), and will suppress the exception + if the provided validator *does*. + + Intended to be used with existing validators to compose logic without + needing to create inverted variants, for example, ``not_(in_(...))``. + + Args: + validator: A validator to be logically inverted. + + msg (str): + Message to raise if validator fails. Formatted with keys + ``exc_types`` and ``validator``. + + exc_types (tuple[type, ...]): + Exception type(s) to capture. Other types raised by child + validators will not be intercepted and pass through. + + Raises: + ValueError: + With a human readable error message, the attribute (of type + `attrs.Attribute`), the validator that failed to raise an + exception, the value it got, and the expected exception types. + + .. versionadded:: 22.2.0 + """ + try: + exc_types = tuple(exc_types) + except TypeError: + exc_types = (exc_types,) + return _NotValidator(validator, msg, exc_types) + + +@attrs(repr=False, slots=True, unsafe_hash=True) +class _OrValidator: + validators = attrib() + + def __call__(self, inst, attr, value): + for v in self.validators: + try: + v(inst, attr, value) + except Exception: # noqa: BLE001, PERF203, S112 + continue + else: + return + + msg = f"None of {self.validators!r} satisfied for value {value!r}" + raise ValueError(msg) + + def __repr__(self): + return f"" + + +def or_(*validators): + """ + A validator that composes multiple validators into one. + + When called on a value, it runs all wrapped validators until one of them is + satisfied. + + Args: + validators (~collections.abc.Iterable[typing.Callable]): + Arbitrary number of validators. + + Raises: + ValueError: + If no validator is satisfied. Raised with a human-readable error + message listing all the wrapped validators and the value that + failed all of them. + + .. versionadded:: 24.1.0 + """ + vals = [] + for v in validators: + vals.extend(v.validators if isinstance(v, _OrValidator) else [v]) + + return _OrValidator(tuple(vals)) diff --git a/lambdas/aws-dd-forwarder-3.127.0/attr/validators.pyi b/lambdas/aws-dd-forwarder-3.127.0/attr/validators.pyi new file mode 100644 index 0000000..a314110 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attr/validators.pyi @@ -0,0 +1,83 @@ +from typing import ( + Any, + AnyStr, + Callable, + Container, + ContextManager, + Iterable, + Mapping, + Match, + Pattern, + TypeVar, + overload, +) + +from attrs import _ValidatorType +from attrs import _ValidatorArgType + +_T = TypeVar("_T") +_T1 = TypeVar("_T1") +_T2 = TypeVar("_T2") +_T3 = TypeVar("_T3") +_I = TypeVar("_I", bound=Iterable) +_K = TypeVar("_K") +_V = TypeVar("_V") +_M = TypeVar("_M", bound=Mapping) + +def set_disabled(run: bool) -> None: ... +def get_disabled() -> bool: ... +def disabled() -> ContextManager[None]: ... + +# To be more precise on instance_of use some overloads. +# If there are more than 3 items in the tuple then we fall back to Any +@overload +def instance_of(type: type[_T]) -> _ValidatorType[_T]: ... +@overload +def instance_of(type: tuple[type[_T]]) -> _ValidatorType[_T]: ... +@overload +def instance_of( + type: tuple[type[_T1], type[_T2]] +) -> _ValidatorType[_T1 | _T2]: ... +@overload +def instance_of( + type: tuple[type[_T1], type[_T2], type[_T3]] +) -> _ValidatorType[_T1 | _T2 | _T3]: ... +@overload +def instance_of(type: tuple[type, ...]) -> _ValidatorType[Any]: ... +def optional( + validator: ( + _ValidatorType[_T] + | list[_ValidatorType[_T]] + | tuple[_ValidatorType[_T]] + ), +) -> _ValidatorType[_T | None]: ... +def in_(options: Container[_T]) -> _ValidatorType[_T]: ... +def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ... +def matches_re( + regex: Pattern[AnyStr] | AnyStr, + flags: int = ..., + func: Callable[[AnyStr, AnyStr, int], Match[AnyStr] | None] | None = ..., +) -> _ValidatorType[AnyStr]: ... +def deep_iterable( + member_validator: _ValidatorArgType[_T], + iterable_validator: _ValidatorType[_I] | None = ..., +) -> _ValidatorType[_I]: ... +def deep_mapping( + key_validator: _ValidatorType[_K], + value_validator: _ValidatorType[_V], + mapping_validator: _ValidatorType[_M] | None = ..., +) -> _ValidatorType[_M]: ... +def is_callable() -> _ValidatorType[_T]: ... +def lt(val: _T) -> _ValidatorType[_T]: ... +def le(val: _T) -> _ValidatorType[_T]: ... +def ge(val: _T) -> _ValidatorType[_T]: ... +def gt(val: _T) -> _ValidatorType[_T]: ... +def max_len(length: int) -> _ValidatorType[_T]: ... +def min_len(length: int) -> _ValidatorType[_T]: ... +def not_( + validator: _ValidatorType[_T], + *, + msg: str | None = None, + exc_types: type[Exception] | Iterable[type[Exception]] = ..., +) -> _ValidatorType[_T]: ... +def or_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ... diff --git a/lambdas/aws-dd-forwarder-3.127.0/attrs-24.2.0.dist-info/INSTALLER b/lambdas/aws-dd-forwarder-3.127.0/attrs-24.2.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attrs-24.2.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lambdas/aws-dd-forwarder-3.127.0/attrs-24.2.0.dist-info/METADATA b/lambdas/aws-dd-forwarder-3.127.0/attrs-24.2.0.dist-info/METADATA new file mode 100644 index 0000000..a85b378 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attrs-24.2.0.dist-info/METADATA @@ -0,0 +1,242 @@ +Metadata-Version: 2.3 +Name: attrs +Version: 24.2.0 +Summary: Classes Without Boilerplate +Project-URL: Documentation, https://www.attrs.org/ +Project-URL: Changelog, https://www.attrs.org/en/stable/changelog.html +Project-URL: GitHub, https://github.com/python-attrs/attrs +Project-URL: Funding, https://github.com/sponsors/hynek +Project-URL: Tidelift, https://tidelift.com/subscription/pkg/pypi-attrs?utm_source=pypi-attrs&utm_medium=pypi +Author-email: Hynek Schlawack +License-Expression: MIT +License-File: LICENSE +Keywords: attribute,boilerplate,class +Classifier: Development Status :: 5 - Production/Stable +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Typing :: Typed +Requires-Python: >=3.7 +Requires-Dist: importlib-metadata; python_version < '3.8' +Provides-Extra: benchmark +Requires-Dist: cloudpickle; (platform_python_implementation == 'CPython') and extra == 'benchmark' +Requires-Dist: hypothesis; extra == 'benchmark' +Requires-Dist: mypy>=1.11.1; (platform_python_implementation == 'CPython' and python_version >= '3.9') and extra == 'benchmark' +Requires-Dist: pympler; extra == 'benchmark' +Requires-Dist: pytest-codspeed; extra == 'benchmark' +Requires-Dist: pytest-mypy-plugins; (platform_python_implementation == 'CPython' and python_version >= '3.9' and python_version < '3.13') and extra == 'benchmark' +Requires-Dist: pytest-xdist[psutil]; extra == 'benchmark' +Requires-Dist: pytest>=4.3.0; extra == 'benchmark' +Provides-Extra: cov +Requires-Dist: cloudpickle; (platform_python_implementation == 'CPython') and extra == 'cov' +Requires-Dist: coverage[toml]>=5.3; extra == 'cov' +Requires-Dist: hypothesis; extra == 'cov' +Requires-Dist: mypy>=1.11.1; (platform_python_implementation == 'CPython' and python_version >= '3.9') and extra == 'cov' +Requires-Dist: pympler; extra == 'cov' +Requires-Dist: pytest-mypy-plugins; (platform_python_implementation == 'CPython' and python_version >= '3.9' and python_version < '3.13') and extra == 'cov' +Requires-Dist: pytest-xdist[psutil]; extra == 'cov' +Requires-Dist: pytest>=4.3.0; extra == 'cov' +Provides-Extra: dev +Requires-Dist: cloudpickle; (platform_python_implementation == 'CPython') and extra == 'dev' +Requires-Dist: hypothesis; extra == 'dev' +Requires-Dist: mypy>=1.11.1; (platform_python_implementation == 'CPython' and python_version >= '3.9') and extra == 'dev' +Requires-Dist: pre-commit; extra == 'dev' +Requires-Dist: pympler; extra == 'dev' +Requires-Dist: pytest-mypy-plugins; (platform_python_implementation == 'CPython' and python_version >= '3.9' and python_version < '3.13') and extra == 'dev' +Requires-Dist: pytest-xdist[psutil]; extra == 'dev' +Requires-Dist: pytest>=4.3.0; extra == 'dev' +Provides-Extra: docs +Requires-Dist: cogapp; extra == 'docs' +Requires-Dist: furo; extra == 'docs' +Requires-Dist: myst-parser; extra == 'docs' +Requires-Dist: sphinx; extra == 'docs' +Requires-Dist: sphinx-notfound-page; extra == 'docs' +Requires-Dist: sphinxcontrib-towncrier; extra == 'docs' +Requires-Dist: towncrier<24.7; extra == 'docs' +Provides-Extra: tests +Requires-Dist: cloudpickle; (platform_python_implementation == 'CPython') and extra == 'tests' +Requires-Dist: hypothesis; extra == 'tests' +Requires-Dist: mypy>=1.11.1; (platform_python_implementation == 'CPython' and python_version >= '3.9') and extra == 'tests' +Requires-Dist: pympler; extra == 'tests' +Requires-Dist: pytest-mypy-plugins; (platform_python_implementation == 'CPython' and python_version >= '3.9' and python_version < '3.13') and extra == 'tests' +Requires-Dist: pytest-xdist[psutil]; extra == 'tests' +Requires-Dist: pytest>=4.3.0; extra == 'tests' +Provides-Extra: tests-mypy +Requires-Dist: mypy>=1.11.1; (platform_python_implementation == 'CPython' and python_version >= '3.9') and extra == 'tests-mypy' +Requires-Dist: pytest-mypy-plugins; (platform_python_implementation == 'CPython' and python_version >= '3.9' and python_version < '3.13') and extra == 'tests-mypy' +Description-Content-Type: text/markdown + +

+ + attrs + +

+ + +*attrs* is the Python package that will bring back the **joy** of **writing classes** by relieving you from the drudgery of implementing object protocols (aka [dunder methods](https://www.attrs.org/en/latest/glossary.html#term-dunder-methods)). +[Trusted by NASA](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/customizing-your-profile/personalizing-your-profile#list-of-qualifying-repositories-for-mars-2020-helicopter-contributor-achievement) for Mars missions since 2020! + +Its main goal is to help you to write **concise** and **correct** software without slowing down your code. + + +## Sponsors + +*attrs* would not be possible without our [amazing sponsors](https://github.com/sponsors/hynek). +Especially those generously supporting us at the *The Organization* tier and higher: + + + +

+ + + + + + + + +

+ + + +

+ Please consider joining them to help make attrs’s maintenance more sustainable! +

+ + + +## Example + +*attrs* gives you a class decorator and a way to declaratively define the attributes on that class: + + + +```pycon +>>> from attrs import asdict, define, make_class, Factory + +>>> @define +... class SomeClass: +... a_number: int = 42 +... list_of_numbers: list[int] = Factory(list) +... +... def hard_math(self, another_number): +... return self.a_number + sum(self.list_of_numbers) * another_number + + +>>> sc = SomeClass(1, [1, 2, 3]) +>>> sc +SomeClass(a_number=1, list_of_numbers=[1, 2, 3]) + +>>> sc.hard_math(3) +19 +>>> sc == SomeClass(1, [1, 2, 3]) +True +>>> sc != SomeClass(2, [3, 2, 1]) +True + +>>> asdict(sc) +{'a_number': 1, 'list_of_numbers': [1, 2, 3]} + +>>> SomeClass() +SomeClass(a_number=42, list_of_numbers=[]) + +>>> C = make_class("C", ["a", "b"]) +>>> C("foo", "bar") +C(a='foo', b='bar') +``` + +After *declaring* your attributes, *attrs* gives you: + +- a concise and explicit overview of the class's attributes, +- a nice human-readable `__repr__`, +- equality-checking methods, +- an initializer, +- and much more, + +*without* writing dull boilerplate code again and again and *without* runtime performance penalties. + +--- + +This example uses *attrs*'s modern APIs that have been introduced in version 20.1.0, and the *attrs* package import name that has been added in version 21.3.0. +The classic APIs (`@attr.s`, `attr.ib`, plus their serious-business aliases) and the `attr` package import name will remain **indefinitely**. + +Check out [*On The Core API Names*](https://www.attrs.org/en/latest/names.html) for an in-depth explanation! + + +### Hate Type Annotations!? + +No problem! +Types are entirely **optional** with *attrs*. +Simply assign `attrs.field()` to the attributes instead of annotating them with types: + +```python +from attrs import define, field + +@define +class SomeClass: + a_number = field(default=42) + list_of_numbers = field(factory=list) +``` + + +## Data Classes + +On the tin, *attrs* might remind you of `dataclasses` (and indeed, `dataclasses` [are a descendant](https://hynek.me/articles/import-attrs/) of *attrs*). +In practice it does a lot more and is more flexible. +For instance, it allows you to define [special handling of NumPy arrays for equality checks](https://www.attrs.org/en/stable/comparison.html#customization), allows more ways to [plug into the initialization process](https://www.attrs.org/en/stable/init.html#hooking-yourself-into-initialization), has a replacement for `__init_subclass__`, and allows for stepping through the generated methods using a debugger. + +For more details, please refer to our [comparison page](https://www.attrs.org/en/stable/why.html#data-classes), but generally speaking, we are more likely to commit crimes against nature to make things work that one would expect to work, but that are quite complicated in practice. + + +## Project Information + +- [**Changelog**](https://www.attrs.org/en/stable/changelog.html) +- [**Documentation**](https://www.attrs.org/) +- [**PyPI**](https://pypi.org/project/attrs/) +- [**Source Code**](https://github.com/python-attrs/attrs) +- [**Contributing**](https://github.com/python-attrs/attrs/blob/main/.github/CONTRIBUTING.md) +- [**Third-party Extensions**](https://github.com/python-attrs/attrs/wiki/Extensions-to-attrs) +- **Get Help**: use the `python-attrs` tag on [Stack Overflow](https://stackoverflow.com/questions/tagged/python-attrs) + + +### *attrs* for Enterprise + +Available as part of the Tidelift Subscription. + +The maintainers of *attrs* and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. +Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use. +[Learn more](https://tidelift.com/?utm_source=lifter&utm_medium=referral&utm_campaign=hynek). + +## Release Information + +### Deprecations + +- Given the amount of warnings raised in the broader ecosystem, we've decided to only soft-deprecate the *hash* argument to `@define` / `@attr.s`. + Please don't use it in new code, but we don't intend to remove it anymore. + [#1330](https://github.com/python-attrs/attrs/issues/1330) + + +### Changes + +- `attrs.converters.pipe()` (and its syntactic sugar of passing a list for `attrs.field()`'s / `attr.ib()`'s *converter* argument) works again when passing `attrs.setters.convert` to *on_setattr* (which is default for `attrs.define`). + [#1328](https://github.com/python-attrs/attrs/issues/1328) +- Restored support for PEP [649](https://peps.python.org/pep-0649/) / [749](https://peps.python.org/pep-0749/)-implementing Pythons -- currently 3.14-dev. + [#1329](https://github.com/python-attrs/attrs/issues/1329) + + + +--- + +[Full changelog →](https://www.attrs.org/en/stable/changelog.html) diff --git a/lambdas/aws-dd-forwarder-3.127.0/attrs-24.2.0.dist-info/RECORD b/lambdas/aws-dd-forwarder-3.127.0/attrs-24.2.0.dist-info/RECORD new file mode 100644 index 0000000..2a91231 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attrs-24.2.0.dist-info/RECORD @@ -0,0 +1,56 @@ +attr/__init__.py,sha256=l8Ewh5KZE7CCY0i1iDfSCnFiUTIkBVoqsXjX9EZnIVA,2087 +attr/__init__.pyi,sha256=aTVHBPX6krCGvbQvOl_UKqEzmi2HFsaIVm2WKmAiqVs,11434 +attr/__pycache__/__init__.cpython-311.pyc,, +attr/__pycache__/_cmp.cpython-311.pyc,, +attr/__pycache__/_compat.cpython-311.pyc,, +attr/__pycache__/_config.cpython-311.pyc,, +attr/__pycache__/_funcs.cpython-311.pyc,, +attr/__pycache__/_make.cpython-311.pyc,, +attr/__pycache__/_next_gen.cpython-311.pyc,, +attr/__pycache__/_version_info.cpython-311.pyc,, +attr/__pycache__/converters.cpython-311.pyc,, +attr/__pycache__/exceptions.cpython-311.pyc,, +attr/__pycache__/filters.cpython-311.pyc,, +attr/__pycache__/setters.cpython-311.pyc,, +attr/__pycache__/validators.cpython-311.pyc,, +attr/_cmp.py,sha256=3umHiBtgsEYtvNP_8XrQwTCdFoZIX4DEur76N-2a3X8,4123 +attr/_cmp.pyi,sha256=U-_RU_UZOyPUEQzXE6RMYQQcjkZRY25wTH99sN0s7MM,368 +attr/_compat.py,sha256=n2Uk3c-ywv0PkFfGlvqR7SzDXp4NOhWmNV_ZK6YfWoM,2958 +attr/_config.py,sha256=z81Vt-GeT_2taxs1XZfmHx9TWlSxjPb6eZH1LTGsS54,843 +attr/_funcs.py,sha256=SGDmNlED1TM3tgO9Ap2mfRfVI24XEAcrNQs7o2eBXHQ,17386 +attr/_make.py,sha256=BjENJz5eJoojJVbCoupWjXLLEZJ7VID89lisLbQUlmQ,91479 +attr/_next_gen.py,sha256=dhGb96VFg4kXBkS9Zdz1A2uxVJ99q_RT1hw3kLA9-uI,24630 +attr/_typing_compat.pyi,sha256=XDP54TUn-ZKhD62TOQebmzrwFyomhUCoGRpclb6alRA,469 +attr/_version_info.py,sha256=exSqb3b5E-fMSsgZAlEw9XcLpEgobPORCZpcaEglAM4,2121 +attr/_version_info.pyi,sha256=x_M3L3WuB7r_ULXAWjx959udKQ4HLB8l-hsc1FDGNvk,209 +attr/converters.py,sha256=vNa58pZi9V6uxBzl4t1QrHbQfkT4iRFAodyXe7lcgg0,3506 +attr/converters.pyi,sha256=mpDoVFO3Cpx8xYSSV0iZFl7IAHuoNBglxKfxHvLj_sY,410 +attr/exceptions.py,sha256=HRFq4iybmv7-DcZwyjl6M1euM2YeJVK_hFxuaBGAngI,1977 +attr/exceptions.pyi,sha256=zZq8bCUnKAy9mDtBEw42ZhPhAUIHoTKedDQInJD883M,539 +attr/filters.py,sha256=ZBiKWLp3R0LfCZsq7X11pn9WX8NslS2wXM4jsnLOGc8,1795 +attr/filters.pyi,sha256=3J5BG-dTxltBk1_-RuNRUHrv2qu1v8v4aDNAQ7_mifA,208 +attr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +attr/setters.py,sha256=faMQeiBo_nbXYnPaQ1pq8PXeA7Zr-uNsVsPMiKCmxhc,1619 +attr/setters.pyi,sha256=NnVkaFU1BB4JB8E4JuXyrzTUgvtMpj8p3wBdJY7uix4,584 +attr/validators.py,sha256=985eTP6RHyon61YEauMJgyNy1rEOhJWiSXMJgRxPtrQ,20045 +attr/validators.pyi,sha256=LjKf7AoXZfvGSfT3LRs61Qfln94konYyMUPoJJjOxK4,2502 +attrs-24.2.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +attrs-24.2.0.dist-info/METADATA,sha256=3Jgk4lr9Y1SAqAcwOLPN_mpW0wc6VOGm-yHt1LsPIHw,11524 +attrs-24.2.0.dist-info/RECORD,, +attrs-24.2.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +attrs-24.2.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87 +attrs-24.2.0.dist-info/licenses/LICENSE,sha256=iCEVyV38KvHutnFPjsbVy8q_Znyv-HKfQkINpj9xTp8,1109 +attrs/__init__.py,sha256=5FHo-EMFOX-g4ialSK4fwOjuoHzLISJDZCwoOl02Ty8,1071 +attrs/__init__.pyi,sha256=o3l92VsD9kHz8sldEtb_tllBTs3TeL-vIBMTxo2Zc_4,7703 +attrs/__pycache__/__init__.cpython-311.pyc,, +attrs/__pycache__/converters.cpython-311.pyc,, +attrs/__pycache__/exceptions.cpython-311.pyc,, +attrs/__pycache__/filters.cpython-311.pyc,, +attrs/__pycache__/setters.cpython-311.pyc,, +attrs/__pycache__/validators.cpython-311.pyc,, +attrs/converters.py,sha256=8kQljrVwfSTRu8INwEk8SI0eGrzmWftsT7rM0EqyohM,76 +attrs/exceptions.py,sha256=ACCCmg19-vDFaDPY9vFl199SPXCQMN_bENs4DALjzms,76 +attrs/filters.py,sha256=VOUMZug9uEU6dUuA0dF1jInUK0PL3fLgP0VBS5d-CDE,73 +attrs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +attrs/setters.py,sha256=eL1YidYQV3T2h9_SYIZSZR1FAcHGb1TuCTy0E0Lv2SU,73 +attrs/validators.py,sha256=xcy6wD5TtTkdCG1f4XWbocPSO0faBjk5IfVJfP6SUj0,76 diff --git a/lambdas/aws-dd-forwarder-3.127.0/attrs-24.2.0.dist-info/REQUESTED b/lambdas/aws-dd-forwarder-3.127.0/attrs-24.2.0.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/attrs-24.2.0.dist-info/WHEEL b/lambdas/aws-dd-forwarder-3.127.0/attrs-24.2.0.dist-info/WHEEL new file mode 100644 index 0000000..cdd68a4 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attrs-24.2.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.25.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/lambdas/aws-dd-forwarder-3.127.0/attrs-24.2.0.dist-info/licenses/LICENSE b/lambdas/aws-dd-forwarder-3.127.0/attrs-24.2.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000..2bd6453 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attrs-24.2.0.dist-info/licenses/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Hynek Schlawack and the attrs contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lambdas/aws-dd-forwarder-3.127.0/attrs/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/attrs/__init__.py new file mode 100644 index 0000000..963b197 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attrs/__init__.py @@ -0,0 +1,67 @@ +# SPDX-License-Identifier: MIT + +from attr import ( + NOTHING, + Attribute, + AttrsInstance, + Converter, + Factory, + _make_getattr, + assoc, + cmp_using, + define, + evolve, + field, + fields, + fields_dict, + frozen, + has, + make_class, + mutable, + resolve_types, + validate, +) +from attr._next_gen import asdict, astuple + +from . import converters, exceptions, filters, setters, validators + + +__all__ = [ + "__author__", + "__copyright__", + "__description__", + "__doc__", + "__email__", + "__license__", + "__title__", + "__url__", + "__version__", + "__version_info__", + "asdict", + "assoc", + "astuple", + "Attribute", + "AttrsInstance", + "cmp_using", + "Converter", + "converters", + "define", + "evolve", + "exceptions", + "Factory", + "field", + "fields_dict", + "fields", + "filters", + "frozen", + "has", + "make_class", + "mutable", + "NOTHING", + "resolve_types", + "setters", + "validate", + "validators", +] + +__getattr__ = _make_getattr(__name__) diff --git a/lambdas/aws-dd-forwarder-3.127.0/attrs/__init__.pyi b/lambdas/aws-dd-forwarder-3.127.0/attrs/__init__.pyi new file mode 100644 index 0000000..b2670de --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attrs/__init__.pyi @@ -0,0 +1,252 @@ +import sys + +from typing import ( + Any, + Callable, + Mapping, + Sequence, + overload, + TypeVar, +) + +# Because we need to type our own stuff, we have to make everything from +# attr explicitly public too. +from attr import __author__ as __author__ +from attr import __copyright__ as __copyright__ +from attr import __description__ as __description__ +from attr import __email__ as __email__ +from attr import __license__ as __license__ +from attr import __title__ as __title__ +from attr import __url__ as __url__ +from attr import __version__ as __version__ +from attr import __version_info__ as __version_info__ +from attr import assoc as assoc +from attr import Attribute as Attribute +from attr import AttrsInstance as AttrsInstance +from attr import cmp_using as cmp_using +from attr import converters as converters +from attr import Converter as Converter +from attr import evolve as evolve +from attr import exceptions as exceptions +from attr import Factory as Factory +from attr import fields as fields +from attr import fields_dict as fields_dict +from attr import filters as filters +from attr import has as has +from attr import make_class as make_class +from attr import NOTHING as NOTHING +from attr import resolve_types as resolve_types +from attr import setters as setters +from attr import validate as validate +from attr import validators as validators +from attr import attrib, asdict as asdict, astuple as astuple + +if sys.version_info >= (3, 11): + from typing import dataclass_transform +else: + from typing_extensions import dataclass_transform + +_T = TypeVar("_T") +_C = TypeVar("_C", bound=type) + +_EqOrderType = bool | Callable[[Any], Any] +_ValidatorType = Callable[[Any, "Attribute[_T]", _T], Any] +_ConverterType = Callable[[Any], Any] +_ReprType = Callable[[Any], str] +_ReprArgType = bool | _ReprType +_OnSetAttrType = Callable[[Any, "Attribute[Any]", Any], Any] +_OnSetAttrArgType = _OnSetAttrType | list[_OnSetAttrType] | setters._NoOpType +_FieldTransformer = Callable[ + [type, list["Attribute[Any]"]], list["Attribute[Any]"] +] +# FIXME: in reality, if multiple validators are passed they must be in a list +# or tuple, but those are invariant and so would prevent subtypes of +# _ValidatorType from working when passed in a list or tuple. +_ValidatorArgType = _ValidatorType[_T] | Sequence[_ValidatorType[_T]] + +@overload +def field( + *, + default: None = ..., + validator: None = ..., + repr: _ReprArgType = ..., + hash: bool | None = ..., + init: bool = ..., + metadata: Mapping[Any, Any] | None = ..., + converter: None = ..., + factory: None = ..., + kw_only: bool = ..., + eq: bool | None = ..., + order: bool | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., + type: type | None = ..., +) -> Any: ... + +# This form catches an explicit None or no default and infers the type from the +# other arguments. +@overload +def field( + *, + default: None = ..., + validator: _ValidatorArgType[_T] | None = ..., + repr: _ReprArgType = ..., + hash: bool | None = ..., + init: bool = ..., + metadata: Mapping[Any, Any] | None = ..., + converter: _ConverterType | Converter[Any, _T] | None = ..., + factory: Callable[[], _T] | None = ..., + kw_only: bool = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., + type: type | None = ..., +) -> _T: ... + +# This form catches an explicit default argument. +@overload +def field( + *, + default: _T, + validator: _ValidatorArgType[_T] | None = ..., + repr: _ReprArgType = ..., + hash: bool | None = ..., + init: bool = ..., + metadata: Mapping[Any, Any] | None = ..., + converter: _ConverterType | Converter[Any, _T] | None = ..., + factory: Callable[[], _T] | None = ..., + kw_only: bool = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., + type: type | None = ..., +) -> _T: ... + +# This form covers type=non-Type: e.g. forward references (str), Any +@overload +def field( + *, + default: _T | None = ..., + validator: _ValidatorArgType[_T] | None = ..., + repr: _ReprArgType = ..., + hash: bool | None = ..., + init: bool = ..., + metadata: Mapping[Any, Any] | None = ..., + converter: _ConverterType | Converter[Any, _T] | None = ..., + factory: Callable[[], _T] | None = ..., + kw_only: bool = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., + type: type | None = ..., +) -> Any: ... +@overload +@dataclass_transform(field_specifiers=(attrib, field)) +def define( + maybe_cls: _C, + *, + these: dict[str, Any] | None = ..., + repr: bool = ..., + unsafe_hash: bool | None = ..., + hash: bool | None = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: bool | None = ..., + order: bool | None = ..., + auto_detect: bool = ..., + getstate_setstate: bool | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + field_transformer: _FieldTransformer | None = ..., + match_args: bool = ..., +) -> _C: ... +@overload +@dataclass_transform(field_specifiers=(attrib, field)) +def define( + maybe_cls: None = ..., + *, + these: dict[str, Any] | None = ..., + repr: bool = ..., + unsafe_hash: bool | None = ..., + hash: bool | None = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: bool | None = ..., + order: bool | None = ..., + auto_detect: bool = ..., + getstate_setstate: bool | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + field_transformer: _FieldTransformer | None = ..., + match_args: bool = ..., +) -> Callable[[_C], _C]: ... + +mutable = define + +@overload +@dataclass_transform(frozen_default=True, field_specifiers=(attrib, field)) +def frozen( + maybe_cls: _C, + *, + these: dict[str, Any] | None = ..., + repr: bool = ..., + unsafe_hash: bool | None = ..., + hash: bool | None = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: bool | None = ..., + order: bool | None = ..., + auto_detect: bool = ..., + getstate_setstate: bool | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + field_transformer: _FieldTransformer | None = ..., + match_args: bool = ..., +) -> _C: ... +@overload +@dataclass_transform(frozen_default=True, field_specifiers=(attrib, field)) +def frozen( + maybe_cls: None = ..., + *, + these: dict[str, Any] | None = ..., + repr: bool = ..., + unsafe_hash: bool | None = ..., + hash: bool | None = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: bool | None = ..., + order: bool | None = ..., + auto_detect: bool = ..., + getstate_setstate: bool | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + field_transformer: _FieldTransformer | None = ..., + match_args: bool = ..., +) -> Callable[[_C], _C]: ... diff --git a/lambdas/aws-dd-forwarder-3.127.0/attrs/converters.py b/lambdas/aws-dd-forwarder-3.127.0/attrs/converters.py new file mode 100644 index 0000000..7821f6c --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attrs/converters.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +from attr.converters import * # noqa: F403 diff --git a/lambdas/aws-dd-forwarder-3.127.0/attrs/exceptions.py b/lambdas/aws-dd-forwarder-3.127.0/attrs/exceptions.py new file mode 100644 index 0000000..3323f9d --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attrs/exceptions.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +from attr.exceptions import * # noqa: F403 diff --git a/lambdas/aws-dd-forwarder-3.127.0/attrs/filters.py b/lambdas/aws-dd-forwarder-3.127.0/attrs/filters.py new file mode 100644 index 0000000..3080f48 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attrs/filters.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +from attr.filters import * # noqa: F403 diff --git a/lambdas/aws-dd-forwarder-3.127.0/attrs/py.typed b/lambdas/aws-dd-forwarder-3.127.0/attrs/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/attrs/setters.py b/lambdas/aws-dd-forwarder-3.127.0/attrs/setters.py new file mode 100644 index 0000000..f3d73bb --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attrs/setters.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +from attr.setters import * # noqa: F403 diff --git a/lambdas/aws-dd-forwarder-3.127.0/attrs/validators.py b/lambdas/aws-dd-forwarder-3.127.0/attrs/validators.py new file mode 100644 index 0000000..037e124 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/attrs/validators.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +from attr.validators import * # noqa: F403 diff --git a/lambdas/aws-dd-forwarder-3.127.0/bin/ddtrace-run b/lambdas/aws-dd-forwarder-3.127.0/bin/ddtrace-run new file mode 100755 index 0000000..0cc0787 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/bin/ddtrace-run @@ -0,0 +1,8 @@ +#!/usr/local/bin/python3.11 +# -*- coding: utf-8 -*- +import re +import sys +from ddtrace.commands.ddtrace_run import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/lambdas/aws-dd-forwarder-3.127.0/bin/dog b/lambdas/aws-dd-forwarder-3.127.0/bin/dog new file mode 100755 index 0000000..7111893 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/bin/dog @@ -0,0 +1,8 @@ +#!/usr/local/bin/python3.11 +# -*- coding: utf-8 -*- +import re +import sys +from datadog.dogshell import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/lambdas/aws-dd-forwarder-3.127.0/bin/dogshell b/lambdas/aws-dd-forwarder-3.127.0/bin/dogshell new file mode 100755 index 0000000..7111893 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/bin/dogshell @@ -0,0 +1,8 @@ +#!/usr/local/bin/python3.11 +# -*- coding: utf-8 -*- +import re +import sys +from datadog.dogshell import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/lambdas/aws-dd-forwarder-3.127.0/bin/dogshellwrap b/lambdas/aws-dd-forwarder-3.127.0/bin/dogshellwrap new file mode 100755 index 0000000..5be900e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/bin/dogshellwrap @@ -0,0 +1,8 @@ +#!/usr/local/bin/python3.11 +# -*- coding: utf-8 -*- +import re +import sys +from datadog.dogshell.wrap import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/lambdas/aws-dd-forwarder-3.127.0/bin/dogwrap b/lambdas/aws-dd-forwarder-3.127.0/bin/dogwrap new file mode 100755 index 0000000..5be900e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/bin/dogwrap @@ -0,0 +1,8 @@ +#!/usr/local/bin/python3.11 +# -*- coding: utf-8 -*- +import re +import sys +from datadog.dogshell.wrap import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/lambdas/aws-dd-forwarder-3.127.0/bin/normalizer b/lambdas/aws-dd-forwarder-3.127.0/bin/normalizer new file mode 100755 index 0000000..aae4757 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/bin/normalizer @@ -0,0 +1,8 @@ +#!/usr/local/bin/python3.11 +# -*- coding: utf-8 -*- +import re +import sys +from charset_normalizer.cli import cli_detect +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(cli_detect()) diff --git a/lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/COPYING b/lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/COPYING new file mode 100644 index 0000000..ba5a523 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/COPYING @@ -0,0 +1,21 @@ +The MIT License (MIT) +Copyright Contributors to the bytecode project. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/INSTALLER b/lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/METADATA b/lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/METADATA new file mode 100644 index 0000000..19faf45 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/METADATA @@ -0,0 +1,102 @@ +Metadata-Version: 2.1 +Name: bytecode +Version: 0.15.1 +Summary: Python module to generate and modify bytecode +Author-email: Victor Stinner +Maintainer-email: "Matthieu C. Dartiailh" +License: The MIT License (MIT) + Copyright Contributors to the bytecode project. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Project-URL: homepage, https://github.com/MatthieuDartiailh/bytecode +Project-URL: documentation, https://bytecode.readthedocs.io/en/latest/ +Project-URL: repository, https://github.com/MatthieuDartiailh/bytecode +Project-URL: changelog, https://github.com/MatthieuDartiailh/bytecode/blob/main/doc/changelog.rst +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Natural Language :: English +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: >=3.8 +Description-Content-Type: text/x-rst +License-File: COPYING +Requires-Dist: typing-extensions ; python_version < "3.10" + +******** +bytecode +******** + +.. image:: https://img.shields.io/pypi/v/bytecode.svg + :alt: Latest release on the Python Cheeseshop (PyPI) + :target: https://pypi.python.org/pypi/bytecode + +.. image:: https://github.com/MatthieuDartiailh/bytecode/workflows/Continuous%20Integration/badge.svg + :target: https://github.com/MatthieuDartiailh/bytecode/actions + :alt: Continuous integration + +.. image:: https://github.com/MatthieuDartiailh/bytecode/workflows/Documentation%20building/badge.svg + :target: https://github.com/MatthieuDartiailh/bytecode/actions + :alt: Documentation building + +.. image:: https://img.shields.io/codecov/c/github/MatthieuDartiailh/bytecode/master.svg + :alt: Code coverage of bytecode on codecov.io + :target: https://codecov.io/github/MatthieuDartiailh/bytecode + +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :alt: Code formatted using Black + :target: https://github.com/psf/black + +``bytecode`` is a Python module to generate and modify bytecode. + +* `bytecode project homepage at GitHub + `_ (code, bugs) +* `bytecode documentation + `_ +* `Download latest bytecode release at the Python Cheeseshop (PyPI) + `_ + +Install bytecode: ``python3 -m pip install bytecode``. It requires Python 3.8 +or newer. The latest release that supports Python 3.7 and 3.6 is 0.13.0. +The latest release that supports Python 3.5 is 0.12.0. For Python 2.7 support, +have a look at `dead-bytecode `_ +instead. + +Example executing ``print('Hello World!')``: + +.. code:: python + + from bytecode import Instr, Bytecode + + bytecode = Bytecode([Instr("LOAD_NAME", 'print'), + Instr("LOAD_CONST", 'Hello World!'), + Instr("CALL_FUNCTION", 1), + Instr("POP_TOP"), + Instr("LOAD_CONST", None), + Instr("RETURN_VALUE")]) + code = bytecode.to_code() + exec(code) diff --git a/lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/RECORD b/lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/RECORD new file mode 100644 index 0000000..49b55cf --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/RECORD @@ -0,0 +1,22 @@ +bytecode-0.15.1.dist-info/COPYING,sha256=15CDvwHVcioF_s6S_mWdkWdw96tvB21WZKc8jvc8N5M,1094 +bytecode-0.15.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +bytecode-0.15.1.dist-info/METADATA,sha256=btrMOPa27_H0V6neBiLPJiunLrto9ukEE-PWoTtFGvM,4627 +bytecode-0.15.1.dist-info/RECORD,, +bytecode-0.15.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +bytecode-0.15.1.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92 +bytecode-0.15.1.dist-info/top_level.txt,sha256=9BhdB7HqYZ-PvHNoWX6ilwLYWQqcgEOLwdb3aXm5Gys,9 +bytecode/__init__.py,sha256=lsE6qqd_1wYjGq6s3q1Rhz1AyAjf98F4iJSrfg01F3k,6919 +bytecode/__pycache__/__init__.cpython-311.pyc,, +bytecode/__pycache__/bytecode.cpython-311.pyc,, +bytecode/__pycache__/cfg.cpython-311.pyc,, +bytecode/__pycache__/concrete.cpython-311.pyc,, +bytecode/__pycache__/flags.cpython-311.pyc,, +bytecode/__pycache__/instr.cpython-311.pyc,, +bytecode/__pycache__/version.cpython-311.pyc,, +bytecode/bytecode.py,sha256=6oveflTRGnrzTQEP9Z0tp6ySwmXQ_DXIibdAGOZt5lY,11126 +bytecode/cfg.py,sha256=J0FOZD1n-LbPLGmPRggmj_1SxWZvcQQbuXeUDskRDv8,41785 +bytecode/concrete.py,sha256=NVsAef1Ya5MvhZfx0xKclP4eearg7vAixY2RpHtQFhk,52168 +bytecode/flags.py,sha256=eY4nrTIDkOBYswI-wXQ-p3mKfriH7pUNYaDien4OI6g,6189 +bytecode/instr.py,sha256=2fynmuZq46eXDyzIMS1e3wzGpXnm7BuY7rHGSsFkh7U,26777 +bytecode/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +bytecode/version.py,sha256=kz4YxQj6evqzVm2eaPEN9t8SwhJI1_YkLx-G2dMjhoI,519 diff --git a/lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/REQUESTED b/lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/WHEEL b/lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/WHEEL new file mode 100644 index 0000000..7e68873 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.41.2) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/top_level.txt b/lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/top_level.txt new file mode 100644 index 0000000..b37707e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/bytecode-0.15.1.dist-info/top_level.txt @@ -0,0 +1 @@ +bytecode diff --git a/lambdas/aws-dd-forwarder-3.127.0/bytecode/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/bytecode/__init__.py new file mode 100644 index 0000000..11eb7d6 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/bytecode/__init__.py @@ -0,0 +1,218 @@ +__all__ = [ + "Label", + "Instr", + "SetLineno", + "Bytecode", + "ConcreteInstr", + "ConcreteBytecode", + "ControlFlowGraph", + "CompilerFlags", + "Compare", + "BinaryOp", + "__version__", +] + +from io import StringIO +from typing import List, Union + +# import needed to use it in bytecode.py +from bytecode.bytecode import ( # noqa + BaseBytecode, + Bytecode, + _BaseBytecodeList, + _InstrList, +) + +# import needed to use it in bytecode.py +from bytecode.cfg import BasicBlock, ControlFlowGraph # noqa + +# import needed to use it in bytecode.py +from bytecode.concrete import _ConvertBytecodeToConcrete # noqa +from bytecode.concrete import ConcreteBytecode, ConcreteInstr +from bytecode.flags import CompilerFlags + +# import needed to use it in bytecode.py +from bytecode.instr import ( # noqa + UNSET, + BinaryOp, + CellVar, + Compare, + FreeVar, + Instr, + Intrinsic1Op, + Intrinsic2Op, + Label, + SetLineno, + TryBegin, + TryEnd, +) +from bytecode.version import __version__ + + +def format_bytecode( + bytecode: Union[Bytecode, ConcreteBytecode, ControlFlowGraph], + *, + lineno: bool = False, +) -> str: + try_begins: List[TryBegin] = [] + + def format_line(index, line): + nonlocal cur_lineno, prev_lineno + if lineno: + if cur_lineno != prev_lineno: + line = "L.% 3s % 3s: %s" % (cur_lineno, index, line) + prev_lineno = cur_lineno + else: + line = " % 3s: %s" % (index, line) + else: + line = line + return line + + def format_instr(instr, labels=None): + text = instr.name + arg = instr._arg + if arg is not UNSET: + if isinstance(arg, Label): + try: + arg = "<%s>" % labels[arg] + except KeyError: + arg = "" + elif isinstance(arg, BasicBlock): + try: + arg = "<%s>" % labels[id(arg)] + except KeyError: + arg = "" + else: + arg = repr(arg) + text = "%s %s" % (text, arg) + return text + + def format_try_begin(instr: TryBegin, labels: dict) -> str: + if isinstance(instr.target, Label): + try: + arg = "<%s>" % labels[instr.target] + except KeyError: + arg = "" + else: + try: + arg = "<%s>" % labels[id(instr.target)] + except KeyError: + arg = "" + line = "TryBegin %s -> %s [%s]" % ( + len(try_begins), + arg, + instr.stack_depth, + ) + (" last_i" if instr.push_lasti else "") + + # Track the seen try begin + try_begins.append(instr) + + return line + + def format_try_end(instr: TryEnd) -> str: + i = try_begins.index(instr.entry) if instr.entry in try_begins else "" + return "TryEnd (%s)" % i + + buffer = StringIO() + + indent = " " * 4 + + cur_lineno = bytecode.first_lineno + prev_lineno = None + + if isinstance(bytecode, ConcreteBytecode): + offset = 0 + for c_instr in bytecode: + fields = [] + if c_instr.lineno is not None: + cur_lineno = c_instr.lineno + if lineno: + fields.append(format_instr(c_instr)) + line = "".join(fields) + line = format_line(offset, line) + else: + fields.append("% 3s %s" % (offset, format_instr(c_instr))) + line = "".join(fields) + buffer.write(line + "\n") + + if isinstance(c_instr, ConcreteInstr): + offset += c_instr.size + + if bytecode.exception_table: + buffer.write("\n") + buffer.write("Exception table:\n") + for entry in bytecode.exception_table: + buffer.write( + f"{entry.start_offset} to {entry.stop_offset} -> " + f"{entry.target} [{entry.stack_depth}]" + + (" lasti" if entry.push_lasti else "") + + "\n" + ) + + elif isinstance(bytecode, Bytecode): + labels: dict[Label, str] = {} + for index, instr in enumerate(bytecode): + if isinstance(instr, Label): + labels[instr] = "label_instr%s" % index + + for index, instr in enumerate(bytecode): + if isinstance(instr, Label): + label = labels[instr] + line = "%s:" % label + if index != 0: + buffer.write("\n") + elif isinstance(instr, TryBegin): + line = indent + format_line(index, format_try_begin(instr, labels)) + indent += " " + elif isinstance(instr, TryEnd): + indent = indent[:-2] + line = indent + format_line(index, format_try_end(instr)) + else: + if instr.lineno is not None: + cur_lineno = instr.lineno + line = format_instr(instr, labels) + line = indent + format_line(index, line) + buffer.write(line + "\n") + buffer.write("\n") + + elif isinstance(bytecode, ControlFlowGraph): + cfg_labels = {} + for block_index, block in enumerate(bytecode, 1): + cfg_labels[id(block)] = "block%s" % block_index + + for block_index, block in enumerate(bytecode, 1): + buffer.write("%s:\n" % cfg_labels[id(block)]) + seen_instr = False + for index, instr in enumerate(block): + if isinstance(instr, TryBegin): + line = indent + format_line( + index, format_try_begin(instr, cfg_labels) + ) + indent += " " + elif isinstance(instr, TryEnd): + if seen_instr: + indent = indent[:-2] + line = indent + format_line(index, format_try_end(instr)) + else: + if isinstance(instr, Instr): + seen_instr = True + if instr.lineno is not None: + cur_lineno = instr.lineno + line = format_instr(instr, cfg_labels) + line = indent + format_line(index, line) + buffer.write(line + "\n") + if block.next_block is not None: + buffer.write(indent + "-> %s\n" % cfg_labels[id(block.next_block)]) + buffer.write("\n") + else: + raise TypeError("unknown bytecode class") + + return buffer.getvalue()[:-1] + + +def dump_bytecode( + bytecode: Union[Bytecode, ConcreteBytecode, ControlFlowGraph], + *, + lineno: bool = False, +) -> None: + print(format_bytecode(bytecode, lineno=lineno)) diff --git a/lambdas/aws-dd-forwarder-3.127.0/bytecode/bytecode.py b/lambdas/aws-dd-forwarder-3.127.0/bytecode/bytecode.py new file mode 100644 index 0000000..149bb37 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/bytecode/bytecode.py @@ -0,0 +1,330 @@ +# alias to keep the 'bytecode' variable free +import sys +import types +from abc import abstractmethod +from typing import ( + Any, + Dict, + Generic, + Iterator, + List, + Optional, + Sequence, + SupportsIndex, + TypeVar, + Union, + overload, +) + +import bytecode as _bytecode +from bytecode.flags import CompilerFlags, infer_flags +from bytecode.instr import ( + _UNSET, + UNSET, + BaseInstr, + Instr, + Label, + SetLineno, + TryBegin, + TryEnd, +) + + +class BaseBytecode: + def __init__(self) -> None: + self.argcount = 0 + self.posonlyargcount = 0 + self.kwonlyargcount = 0 + self.first_lineno = 1 + self.name = "" + self.qualname = self.name + self.filename = "" + self.docstring: Union[str, None, _UNSET] = UNSET + # We cannot recreate cellvars/freevars from instructions because of super() + # special-case, which involves an implicit __class__ cell/free variable + # We could try to detect it. + # CPython itself breaks if one aliases super so we could maybe make it work + # but it will require careful design and will be done later in the future. + self.cellvars: List[str] = [] + self.freevars: List[str] = [] + self._flags: CompilerFlags = CompilerFlags(0) + + def _copy_attr_from(self, bytecode: "BaseBytecode") -> None: + self.argcount = bytecode.argcount + self.posonlyargcount = bytecode.posonlyargcount + self.kwonlyargcount = bytecode.kwonlyargcount + self.flags = bytecode.flags + self.first_lineno = bytecode.first_lineno + self.name = bytecode.name + self.qualname = bytecode.qualname + self.filename = bytecode.filename + self.docstring = bytecode.docstring + self.cellvars = list(bytecode.cellvars) + self.freevars = list(bytecode.freevars) + + def __eq__(self, other: Any) -> bool: + if type(self) is not type(other): + return False + + if self.argcount != other.argcount: + return False + if self.posonlyargcount != other.posonlyargcount: + return False + if self.kwonlyargcount != other.kwonlyargcount: + return False + if self.flags != other.flags: + return False + if self.first_lineno != other.first_lineno: + return False + if self.filename != other.filename: + return False + if self.name != other.name: + return False + if self.qualname != other.qualname: + return False + if self.docstring != other.docstring: + return False + if self.cellvars != other.cellvars: + return False + if self.freevars != other.freevars: + return False + if self.compute_stacksize() != other.compute_stacksize(): + return False + + return True + + @property + def flags(self) -> CompilerFlags: + return self._flags + + @flags.setter + def flags(self, value: CompilerFlags) -> None: + if not isinstance(value, CompilerFlags): + value = CompilerFlags(value) + self._flags = value + + def update_flags(self, *, is_async: Optional[bool] = None) -> None: + # infer_flags reasonably only accept concrete subclasses + self.flags = infer_flags(self, is_async) # type: ignore + + @abstractmethod + def compute_stacksize(self, *, check_pre_and_post: bool = True) -> int: + raise NotImplementedError + + +T = TypeVar("T", bound="_BaseBytecodeList") +U = TypeVar("U") + + +class _BaseBytecodeList(BaseBytecode, list, Generic[U]): + """List subclass providing type stable slicing and copying.""" + + @overload + def __getitem__(self, index: SupportsIndex) -> U: + ... + + @overload + def __getitem__(self: T, index: slice) -> T: + ... + + def __getitem__(self, index): + value = super().__getitem__(index) + if isinstance(index, slice): + value = type(self)(value) + value._copy_attr_from(self) + + return value + + def copy(self: T) -> T: + # This is a list subclass and works + new = type(self)(super().copy()) # type: ignore + new._copy_attr_from(self) + return new + + def legalize(self) -> None: + """Check that all the element of the list are valid and remove SetLineno.""" + lineno_pos = [] + set_lineno = None + current_lineno = self.first_lineno + + for pos, instr in enumerate(self): + if isinstance(instr, SetLineno): + set_lineno = instr.lineno + lineno_pos.append(pos) + continue + # Filter out other pseudo instructions + if not isinstance(instr, BaseInstr): + continue + if set_lineno is not None: + instr.lineno = set_lineno + elif instr.lineno is UNSET: + instr.lineno = current_lineno + elif instr.lineno is not None: + current_lineno = instr.lineno + + for i in reversed(lineno_pos): + del self[i] + + def __iter__(self) -> Iterator[U]: + instructions = super().__iter__() + for instr in instructions: + self._check_instr(instr) + yield instr + + def _check_instr(self, instr): + raise NotImplementedError() + + +V = TypeVar("V") + + +class _InstrList(List[V]): + # Providing a stricter typing for this helper whose use is limited to the __eq__ + # implementation is more effort than it is worth. + def _flat(self) -> List: + instructions: List = [] + labels = {} + jumps = [] + try_begins: Dict[TryBegin, int] = {} + try_jumps = [] + + offset = 0 + instr: Any + for index, instr in enumerate(self): + if isinstance(instr, Label): + instructions.append("label_instr%s" % index) + labels[instr] = offset + elif isinstance(instr, TryBegin): + try_begins.setdefault(instr, len(try_begins)) + assert isinstance(instr.target, Label) + try_jumps.append((instr.target, len(instructions))) + instructions.append(instr) + elif isinstance(instr, TryEnd): + instructions.append(("TryEnd", try_begins[instr.entry])) + else: + if isinstance(instr, Instr) and isinstance(instr.arg, Label): + target_label = instr.arg + instr = _bytecode.ConcreteInstr( + instr.name, 0, location=instr.location + ) + jumps.append((target_label, instr)) + instructions.append(instr) + offset += 1 + + for target_label, instr in jumps: + instr.arg = labels[target_label] + + for target_label, index in try_jumps: + instr = instructions[index] + assert isinstance(instr, TryBegin) + instructions[index] = ( + "TryBegin", + try_begins[instr], + labels[target_label], + instr.push_lasti, + ) + + return instructions + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, _InstrList): + other = _InstrList(other) + + return self._flat() == other._flat() + + +class Bytecode( + _InstrList[Union[Instr, Label, TryBegin, TryEnd, SetLineno]], + _BaseBytecodeList[Union[Instr, Label, TryBegin, TryEnd, SetLineno]], +): + def __init__( + self, + instructions: Sequence[Union[Instr, Label, TryBegin, TryEnd, SetLineno]] = (), + ) -> None: + BaseBytecode.__init__(self) + self.argnames: List[str] = [] + for instr in instructions: + self._check_instr(instr) + self.extend(instructions) + + def __iter__(self) -> Iterator[Union[Instr, Label, TryBegin, TryEnd, SetLineno]]: + instructions = super().__iter__() + seen_try_begin = False + for instr in instructions: + self._check_instr(instr) + if isinstance(instr, TryBegin): + if seen_try_begin: + raise RuntimeError("TryBegin pseudo instructions cannot be nested.") + seen_try_begin = True + elif isinstance(instr, TryEnd): + seen_try_begin = False + yield instr + + def _check_instr(self, instr: Any) -> None: + if not isinstance(instr, (Label, SetLineno, Instr, TryBegin, TryEnd)): + raise ValueError( + "Bytecode must only contain Label, " + "SetLineno, and Instr objects, " + "but %s was found" % type(instr).__name__ + ) + + def _copy_attr_from(self, bytecode: BaseBytecode) -> None: + super()._copy_attr_from(bytecode) + if isinstance(bytecode, Bytecode): + self.argnames = bytecode.argnames + + @staticmethod + def from_code( + code: types.CodeType, + prune_caches: bool = True, + conserve_exception_block_stackdepth: bool = False, + ) -> "Bytecode": + concrete = _bytecode.ConcreteBytecode.from_code(code) + return concrete.to_bytecode( + prune_caches=prune_caches, + conserve_exception_block_stackdepth=conserve_exception_block_stackdepth, + ) + + def compute_stacksize(self, *, check_pre_and_post: bool = True) -> int: + cfg = _bytecode.ControlFlowGraph.from_bytecode(self) + return cfg.compute_stacksize(check_pre_and_post=check_pre_and_post) + + def to_code( + self, + compute_jumps_passes: Optional[int] = None, + stacksize: Optional[int] = None, + *, + check_pre_and_post: bool = True, + compute_exception_stack_depths: bool = True, + ) -> types.CodeType: + # Prevent reconverting the concrete bytecode to bytecode and cfg to do the + # calculation if we need to do it. + if stacksize is None or ( + sys.version_info >= (3, 11) and compute_exception_stack_depths + ): + cfg = _bytecode.ControlFlowGraph.from_bytecode(self) + stacksize = cfg.compute_stacksize( + check_pre_and_post=check_pre_and_post, + compute_exception_stack_depths=compute_exception_stack_depths, + ) + self = cfg.to_bytecode() + compute_exception_stack_depths = False # avoid redoing everything + bc = self.to_concrete_bytecode( + compute_jumps_passes=compute_jumps_passes, + compute_exception_stack_depths=compute_exception_stack_depths, + ) + return bc.to_code( + stacksize=stacksize, + compute_exception_stack_depths=compute_exception_stack_depths, + ) + + def to_concrete_bytecode( + self, + compute_jumps_passes: Optional[int] = None, + compute_exception_stack_depths: bool = True, + ) -> "_bytecode.ConcreteBytecode": + converter = _bytecode._ConvertBytecodeToConcrete(self) + return converter.to_concrete_bytecode( + compute_jumps_passes=compute_jumps_passes, + compute_exception_stack_depths=compute_exception_stack_depths, + ) diff --git a/lambdas/aws-dd-forwarder-3.127.0/bytecode/cfg.py b/lambdas/aws-dd-forwarder-3.127.0/bytecode/cfg.py new file mode 100644 index 0000000..7f554fa --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/bytecode/cfg.py @@ -0,0 +1,1061 @@ +import sys +import types +from collections import defaultdict +from dataclasses import dataclass +from typing import ( + Any, + Dict, + Generator, + Iterable, + Iterator, + List, + Optional, + Set, + SupportsIndex, + Tuple, + TypeVar, + Union, + overload, +) + +# alias to keep the 'bytecode' variable free +import bytecode as _bytecode +from bytecode.concrete import ConcreteInstr +from bytecode.flags import CompilerFlags +from bytecode.instr import UNSET, Instr, Label, SetLineno, TryBegin, TryEnd + +T = TypeVar("T", bound="BasicBlock") +U = TypeVar("U", bound="ControlFlowGraph") + + +class BasicBlock(_bytecode._InstrList[Union[Instr, SetLineno, TryBegin, TryEnd]]): + def __init__( + self, + instructions: Optional[ + Iterable[Union[Instr, SetLineno, TryBegin, TryEnd]] + ] = None, + ) -> None: + # a BasicBlock object, or None + self.next_block: Optional["BasicBlock"] = None + if instructions: + super().__init__(instructions) + + def __iter__(self) -> Iterator[Union[Instr, SetLineno, TryBegin, TryEnd]]: + index = 0 + while index < len(self): + instr = self[index] + index += 1 + + if not isinstance(instr, (SetLineno, Instr, TryBegin, TryEnd)): + raise ValueError( + "BasicBlock must only contain SetLineno and Instr objects, " + "but %s was found" % instr.__class__.__name__ + ) + + if isinstance(instr, Instr) and instr.has_jump(): + if index < len(self) and any( + isinstance(self[i], Instr) for i in range(index, len(self)) + ): + raise ValueError( + "Only the last instruction of a basic " "block can be a jump" + ) + + if not isinstance(instr.arg, BasicBlock): + raise ValueError( + "Jump target must a BasicBlock, got %s", + type(instr.arg).__name__, + ) + + if isinstance(instr, TryBegin): + if not isinstance(instr.target, BasicBlock): + raise ValueError( + "TryBegin target must a BasicBlock, got %s", + type(instr.target).__name__, + ) + + yield instr + + @overload + def __getitem__( + self, index: SupportsIndex + ) -> Union[Instr, SetLineno, TryBegin, TryEnd]: + ... + + @overload + def __getitem__(self: T, index: slice) -> T: + ... + + def __getitem__(self, index): + value = super().__getitem__(index) + if isinstance(index, slice): + value = type(self)(value) + value.next_block = self.next_block + + return value + + def get_last_non_artificial_instruction(self) -> Optional[Instr]: + for instr in reversed(self): + if isinstance(instr, Instr): + return instr + + return None + + def copy(self: T) -> T: + new = type(self)(super().copy()) + new.next_block = self.next_block + return new + + def legalize(self, first_lineno: int) -> int: + """Check that all the element of the list are valid and remove SetLineno.""" + lineno_pos = [] + set_lineno = None + current_lineno = first_lineno + + for pos, instr in enumerate(self): + if isinstance(instr, SetLineno): + set_lineno = current_lineno = instr.lineno + lineno_pos.append(pos) + continue + if isinstance(instr, (TryBegin, TryEnd)): + continue + + if set_lineno is not None: + instr.lineno = set_lineno + elif instr.lineno is UNSET: + instr.lineno = current_lineno + elif instr.lineno is not None: + current_lineno = instr.lineno + + for i in reversed(lineno_pos): + del self[i] + + return current_lineno + + def get_jump(self) -> Optional["BasicBlock"]: + if not self: + return None + + last_instr = self.get_last_non_artificial_instruction() + if last_instr is None or not last_instr.has_jump(): + return None + + target_block = last_instr.arg + assert isinstance(target_block, BasicBlock) + return target_block + + def get_trailing_try_end(self, index: int): + while index + 1 < len(self): + if isinstance(b := self[index + 1], TryEnd): + return b + index += 1 + + return None + + +def _update_size(pre_delta, post_delta, size, maxsize, minsize): + size += pre_delta + if size < 0: + msg = "Failed to compute stacksize, got negative size" + raise RuntimeError(msg) + size += post_delta + maxsize = max(maxsize, size) + minsize = min(minsize, size) + return size, maxsize, minsize + + +# We can never have nested TryBegin, so we can simply update the min stack size +# when we encounter one and use the number we have when we encounter the TryEnd + + +@dataclass +class _StackSizeComputationStorage: + """Common storage shared by the computers involved in computing CFG stack usage.""" + + #: Should we check that all stack operation are "safe" i.e. occurs while there + #: is a sufficient number of items on the stack. + check_pre_and_post: bool + + #: Id the blocks for which an analysis is under progress to avoid getting stuck + #: in recursions. + seen_blocks: Set[int] + + #: Sizes and exception handling status with which the analysis of the block + #: has been performed. Used to avoid running multiple times equivalent analysis. + blocks_startsizes: Dict[int, Set[Tuple[int, Optional[bool]]]] + + #: Track the encountered TryBegin pseudo-instruction to update their target + #: depth at the end of the calculation. + try_begins: List[TryBegin] + + #: Stacksize that should be used for exception blocks. This is the smallest size + #: with which this block was reached which is the only size that can be safely + #: restored. + exception_block_startsize: Dict[int, int] + + #: Largest stack size used in an exception block. We record the size corresponding + #: to the smallest start size for the block since the interpreter enforces that + #: we start with this size. + exception_block_maxsize: Dict[int, int] + + +class _StackSizeComputer: + """Helper computing the stack usage for a single block.""" + + #: Common storage shared by all helpers involved in the stack size computation + common: _StackSizeComputationStorage + + #: Block this helper is running the computation for. + block: BasicBlock + + #: Current stack usage. + size: int + + #: Maximal stack usage. + maxsize: int + + #: Minimal stack usage. This value is only relevant in between a TryBegin/TryEnd + #: pair and determine the startsize for the exception handling block associated + #: with the try begin. + minsize: int + + #: Flag indicating if the block analyzed is an exception handler (i.e. a target + #: of a TryBegin). + exception_handler: Optional[bool] + + #: TryBegin that was encountered before jumping to this block and for which + #: no try end was met yet. + pending_try_begin: Optional[TryBegin] + + def __init__( + self, + common: _StackSizeComputationStorage, + block: BasicBlock, + size: int, + maxsize: int, + minsize: int, + exception_handler: Optional[bool], + pending_try_begin: Optional[TryBegin], + ) -> None: + self.common = common + self.block = block + self.size = size + self.maxsize = maxsize + self.minsize = minsize + self.exception_handler = exception_handler + self.pending_try_begin = pending_try_begin + self._current_try_begin = pending_try_begin + + def run(self) -> Generator[Union["_StackSizeComputer", int], int, None]: + """Iterate over the block instructions to compute stack usage.""" + # Blocks are not hashable but in this particular context we know we won't be + # modifying blocks in place so we can safely use their id as hash rather than + # making them generally hashable which would be weird since they are list + # subclasses + block_id = id(self.block) + + # If the block is currently being visited (seen = True) or + # it was visited previously with parameters that makes the computation + # irrelevant return the maxsize. + fingerprint = (self.size, self.exception_handler) + if id(self.block) in self.common.seen_blocks or ( + not self._is_stacksize_computation_relevant(block_id, fingerprint) + ): + yield self.maxsize + + # Prevent recursive visit of block if two blocks are nested (jump from one + # to the other). + self.common.seen_blocks.add(block_id) + + # Track which size has been used to run an analysis to avoid re-running multiple + # times the same calculation. + self.common.blocks_startsizes[block_id].add(fingerprint) + + # If this block is an exception handler reached through the exception table + # we will push some extra objects on the stack before processing start. + if self.exception_handler is not None: + self._update_size(0, 1 + self.exception_handler) + # True is used to indicated that push_lasti is True, leading to pushing + # an extra object on the stack. + + for i, instr in enumerate(self.block): + # Ignore SetLineno + if isinstance(instr, (SetLineno)): + continue + + # When we encounter a TryBegin, we: + # - store it as the current TryBegin (since TryBegin cannot be nested) + # - record its existence to remember to update its stack size when + # the computation ends + # - update the minsize to the current size value since we need to + # know the minimal stack usage between the TryBegin/TryEnd pair to + # set the startsize of the exception handling block + # + # This approach does not require any special handling for with statements. + if isinstance(instr, TryBegin): + assert self._current_try_begin is None + self.common.try_begins.append(instr) + self._current_try_begin = instr + self.minsize = self.size + + continue + + elif isinstance(instr, TryEnd): + # When we encounter a TryEnd we can start the computation for the + # exception block using the minimum stack size encountered since + # the TryBegin matching this TryEnd. + + # TryBegin cannot be nested so a TryEnd should always match the + # current try begin. However inside the CFG some blocks may + # start with a TryEnd relevant only when reaching this block + # through a particular jump. So we are lenient here. + if instr.entry is not self._current_try_begin: + continue + + # Compute the stack usage of the exception handler + assert isinstance(instr.entry.target, BasicBlock) + yield from self._compute_exception_handler_stack_usage( + instr.entry.target, + instr.entry.push_lasti, + ) + self._current_try_begin = None + continue + + # For instructions with a jump first compute the stacksize required when the + # jump is taken. + if instr.has_jump(): + effect = ( + instr.pre_and_post_stack_effect(jump=True) + if self.common.check_pre_and_post + else (instr.stack_effect(jump=True), 0) + ) + taken_size, maxsize, minsize = _update_size( + *effect, self.size, self.maxsize, self.minsize + ) + + # Yield the parameters required to compute the stacksize required + # by the block to which the jump points to and resume when we now + # the maxsize. + assert isinstance(instr.arg, BasicBlock) + maxsize = yield _StackSizeComputer( + self.common, + instr.arg, + taken_size, + maxsize, + minsize, + None, + # Do not propagate the TryBegin if a final instruction is followed + # by a TryEnd. + None + if instr.is_final() and self.block.get_trailing_try_end(i) + else self._current_try_begin, + ) + + # Update the maximum used size by the usage implied by the following + # the jump + self.maxsize = max(self.maxsize, maxsize) + + # For unconditional jumps abort early since the other instruction will + # never be seen. + if instr.is_uncond_jump(): + # Check for TryEnd after the final instruction which is possible + # TryEnd being only pseudo instructions + if te := self.block.get_trailing_try_end(i): + # TryBegin cannot be nested + assert te.entry is self._current_try_begin + + assert isinstance(te.entry.target, BasicBlock) + yield from self._compute_exception_handler_stack_usage( + te.entry.target, + te.entry.push_lasti, + ) + + self.common.seen_blocks.remove(id(self.block)) + yield self.maxsize + + # jump=False: non-taken path of jumps, or any non-jump + effect = ( + instr.pre_and_post_stack_effect(jump=False) + if self.common.check_pre_and_post + else (instr.stack_effect(jump=False), 0) + ) + self._update_size(*effect) + + # Instruction is final (return, raise, ...) so any following instruction + # in the block is dead code. + if instr.is_final(): + # Check for TryEnd after the final instruction which is possible + # TryEnd being only pseudo instructions. + if te := self.block.get_trailing_try_end(i): + assert isinstance(te.entry.target, BasicBlock) + yield from self._compute_exception_handler_stack_usage( + te.entry.target, + te.entry.push_lasti, + ) + + self.common.seen_blocks.remove(id(self.block)) + + yield self.maxsize + + if self.block.next_block: + self.maxsize = yield _StackSizeComputer( + self.common, + self.block.next_block, + self.size, + self.maxsize, + self.minsize, + None, + self._current_try_begin, + ) + + self.common.seen_blocks.remove(id(self.block)) + + yield self.maxsize + + # --- Private API + + _current_try_begin: Optional[TryBegin] + + def _update_size(self, pre_delta: int, post_delta: int) -> None: + size, maxsize, minsize = _update_size( + pre_delta, post_delta, self.size, self.maxsize, self.minsize + ) + self.size = size + self.minsize = minsize + self.maxsize = maxsize + + def _compute_exception_handler_stack_usage( + self, block: BasicBlock, push_lasti: bool + ) -> Generator[Union["_StackSizeComputer", int], int, None]: + b_id = id(block) + if self.minsize < self.common.exception_block_startsize[b_id]: + block_size = yield _StackSizeComputer( + self.common, + block, + self.minsize, + self.maxsize, + self.minsize, + push_lasti, + None, + ) + # The entry cannot be smaller than abs(stc.minimal_entry_size) as otherwise + # we an underflow would have occured. + self.common.exception_block_startsize[b_id] = self.minsize + self.common.exception_block_maxsize[b_id] = block_size + + def _is_stacksize_computation_relevant( + self, block_id: int, fingerprint: Tuple[int, Optional[bool]] + ) -> bool: + if sys.version_info >= (3, 11): + # The computation is relevant if the block was not visited previously + # with the same starting size and exception handler status than the + # one in use + return fingerprint not in self.common.blocks_startsizes[block_id] + else: + # The computation is relevant if the block was only visited with smaller + # starting sizes than the one in use + if sizes := self.common.blocks_startsizes[block_id]: + return fingerprint[0] > max(f[0] for f in sizes) + else: + return True + + +class ControlFlowGraph(_bytecode.BaseBytecode): + def __init__(self) -> None: + super().__init__() + self._blocks: List[BasicBlock] = [] + self._block_index: Dict[int, int] = {} + self.argnames: List[str] = [] + + self.add_block() + + def legalize(self) -> None: + """Legalize all blocks.""" + current_lineno = self.first_lineno + for block in self._blocks: + current_lineno = block.legalize(current_lineno) + + def get_block_index(self, block: BasicBlock) -> int: + try: + return self._block_index[id(block)] + except KeyError: + raise ValueError("the block is not part of this bytecode") + + def _add_block(self, block: BasicBlock) -> None: + block_index = len(self._blocks) + self._blocks.append(block) + self._block_index[id(block)] = block_index + + def add_block( + self, instructions: Optional[Iterable[Union[Instr, SetLineno]]] = None + ) -> BasicBlock: + block = BasicBlock(instructions) + self._add_block(block) + return block + + def compute_stacksize( + self, + *, + check_pre_and_post: bool = True, + compute_exception_stack_depths: bool = True, + ) -> int: + """Compute the stack size by iterating through the blocks + + The implementation make use of a generator function to avoid issue with + deeply nested recursions. + + """ + # In the absence of any block return 0 + if not self: + return 0 + + # Create the common storage for the calculation + common = _StackSizeComputationStorage( + check_pre_and_post, + seen_blocks=set(), + blocks_startsizes={id(b): set() for b in self}, + exception_block_startsize=dict.fromkeys([id(b) for b in self], 32768), + exception_block_maxsize=dict.fromkeys([id(b) for b in self], -32768), + try_begins=[], + ) + + # Starting with Python 3.10, generator and coroutines start with one object + # on the stack (None, anything is an error). + initial_stack_size = 0 + if sys.version_info >= (3, 10) and self.flags & ( + CompilerFlags.GENERATOR + | CompilerFlags.COROUTINE + | CompilerFlags.ASYNC_GENERATOR + ): + initial_stack_size = 1 + + # Create a generator/coroutine responsible of dealing with the first block + coro = _StackSizeComputer( + common, self[0], initial_stack_size, 0, 0, None, None + ).run() + + # Create a list of generator that have not yet been exhausted + coroutines: List[Generator[Union[_StackSizeComputer, int], int, None]] = [] + + push_coroutine = coroutines.append + pop_coroutine = coroutines.pop + args = None + + try: + while True: + # Mypy does not seem to honor the fact that one must send None + # to a brand new generator irrespective of its send type. + args = coro.send(None) # type: ignore + + # Consume the stored generators as long as they return a simple + # integer that is to be used to resume the last stored generator. + while isinstance(args, int): + coro = pop_coroutine() + args = coro.send(args) + + # Otherwise we enter a new block and we store the generator under + # use and create a new one to process the new block + push_coroutine(coro) + coro = args.run() + + except IndexError: + # The exception occurs when all the generators have been exhausted + # in which case the last yielded value is the stacksize. + assert args is not None and isinstance(args, int) + + # Exception handling block size is reported separately since we need + # to report only the stack usage for the smallest start size for the + # block + args = max(args, *common.exception_block_maxsize.values()) + + # Check if there is dead code that may contain TryBegin/TryEnd pairs. + # For any such pair we set a huge size (the exception table format does not + # mandate a maximum value). We do so so that if the pair is fused with + # another it does not alter the computed size. + for block in self: + if not common.blocks_startsizes[id(block)]: + for i in block: + if isinstance(i, TryBegin) and i.stack_depth is UNSET: + i.stack_depth = 32768 + + # If requested update the TryBegin stack size + if compute_exception_stack_depths: + for tb in common.try_begins: + size = common.exception_block_startsize[id(tb.target)] + assert size >= 0 + tb.stack_depth = size + + return args + + def __repr__(self) -> str: + return "" % len(self._blocks) + + # Helper to obtain a flat list of instr, which does not refer to block at + # anymore. Used for comparison of different CFG. + def _get_instructions( + self, + ) -> List: + instructions: List = [] + try_begins: Dict[TryBegin, int] = {} + + for block in self: + for index, instr in enumerate(block): + if isinstance(instr, TryBegin): + assert isinstance(instr.target, BasicBlock) + try_begins.setdefault(instr, len(try_begins)) + instructions.append( + ( + "TryBegin", + try_begins[instr], + self.get_block_index(instr.target), + instr.push_lasti, + ) + ) + elif isinstance(instr, TryEnd): + instructions.append(("TryEnd", try_begins[instr.entry])) + elif isinstance(instr, Instr) and ( + instr.has_jump() or instr.is_final() + ): + if instr.has_jump(): + target_block = instr.arg + assert isinstance(target_block, BasicBlock) + # We use a concrete instr here to be able to use an integer as + # argument rather than a Label. This is fine for comparison + # purposes which is our sole goal here. + c_instr = ConcreteInstr( + instr.name, + self.get_block_index(target_block), + location=instr.location, + ) + instructions.append(c_instr) + else: + instructions.append(instr) + + if te := block.get_trailing_try_end(index): + instructions.append(("TryEnd", try_begins[te.entry])) + break + else: + instructions.append(instr) + + return instructions + + def __eq__(self, other: Any) -> bool: + if type(self) is not type(other): + return False + + if self.argnames != other.argnames: + return False + + instrs1 = self._get_instructions() + instrs2 = other._get_instructions() + if instrs1 != instrs2: + return False + # FIXME: compare block.next_block + + return super().__eq__(other) + + def __len__(self) -> int: + return len(self._blocks) + + def __iter__(self) -> Iterator[BasicBlock]: + return iter(self._blocks) + + @overload + def __getitem__(self, index: Union[int, BasicBlock]) -> BasicBlock: + ... + + @overload + def __getitem__(self: U, index: slice) -> U: + ... + + def __getitem__(self, index): + if isinstance(index, BasicBlock): + index = self.get_block_index(index) + return self._blocks[index] + + def __delitem__(self, index: Union[int, BasicBlock]) -> None: + if isinstance(index, BasicBlock): + index = self.get_block_index(index) + block = self._blocks[index] + del self._blocks[index] + del self._block_index[id(block)] + for index in range(index, len(self)): + block = self._blocks[index] + self._block_index[id(block)] -= 1 + + def split_block(self, block: BasicBlock, index: int) -> BasicBlock: + if not isinstance(block, BasicBlock): + raise TypeError("expected block") + block_index = self.get_block_index(block) + + if index < 0: + raise ValueError("index must be positive") + + block = self._blocks[block_index] + if index == 0: + return block + + if index > len(block): + raise ValueError("index out of the block") + + instructions = block[index:] + if not instructions: + if block_index + 1 < len(self): + return self[block_index + 1] + + del block[index:] + + block2 = BasicBlock(instructions) + block.next_block = block2 + + for block in self[block_index + 1 :]: + self._block_index[id(block)] += 1 + + self._blocks.insert(block_index + 1, block2) + self._block_index[id(block2)] = block_index + 1 + + return block2 + + def get_dead_blocks(self) -> List[BasicBlock]: + if not self: + return [] + + seen_block_ids = set() + stack = [self[0]] + while stack: + block = stack.pop() + if id(block) in seen_block_ids: + continue + seen_block_ids.add(id(block)) + for i in block: + if isinstance(i, Instr) and isinstance(i.arg, BasicBlock): + stack.append(i.arg) + elif isinstance(i, TryBegin): + assert isinstance(i.target, BasicBlock) + stack.append(i.target) + + return [b for b in self if id(b) not in seen_block_ids] + + @staticmethod + def from_bytecode(bytecode: _bytecode.Bytecode) -> "ControlFlowGraph": + # label => instruction index + label_to_block_index = {} + jumps = [] + try_end_locations = {} + for index, instr in enumerate(bytecode): + if isinstance(instr, Label): + label_to_block_index[instr] = index + elif isinstance(instr, Instr) and isinstance(instr.arg, Label): + jumps.append((index, instr.arg)) + elif isinstance(instr, TryBegin): + assert isinstance(instr.target, Label) + jumps.append((index, instr.target)) + elif isinstance(instr, TryEnd): + try_end_locations[instr.entry] = index + + # Figure out on which index block targeted by a label start + block_starts = {} + for target_index, target_label in jumps: + target_index = label_to_block_index[target_label] + block_starts[target_index] = target_label + + bytecode_blocks = ControlFlowGraph() + bytecode_blocks._copy_attr_from(bytecode) + bytecode_blocks.argnames = list(bytecode.argnames) + + # copy instructions, convert labels to block labels + block = bytecode_blocks[0] + labels = {} + jumping_instrs: List[Instr] = [] + # Map input TryBegin to CFG TryBegins (split across blocks may yield multiple + # TryBegin from a single in the bytecode). + try_begins: Dict[TryBegin, list[TryBegin]] = {} + # Storage for TryEnds that need to be inserted at the beginning of a block. + # We use a list because the same block can be reached through several paths + # with different active TryBegins + add_try_end: Dict[Label, List[TryEnd]] = defaultdict(list) + + # Track the currently active try begin + active_try_begin: Optional[TryBegin] = None + try_begin_inserted_in_block = False + last_instr: Optional[Instr] = None + for index, instr in enumerate(bytecode): + # Reference to the current block if we create a new one in the following. + old_block: BasicBlock | None = None + + # First we determine if we need to create a new block: + # - by checking the current instruction index + if index in block_starts: + old_label = block_starts[index] + # Create a new block if the last created one is not empty + # (of real instructions) + if index != 0 and (li := block.get_last_non_artificial_instruction()): + old_block = block + new_block = bytecode_blocks.add_block() + # If the last non artificial instruction is not final connect + # this block to the next. + if not li.is_final(): + block.next_block = new_block + block = new_block + if old_label is not None: + labels[old_label] = block + + # - by inspecting the last instr + elif block.get_last_non_artificial_instruction() and last_instr is not None: + # The last instruction is final but we did not create a block + # -> sounds like a block of dead code but we preserve it + if last_instr.is_final(): + old_block = block + block = bytecode_blocks.add_block() + + # We are dealing with a conditional jump + elif last_instr.has_jump(): + assert isinstance(last_instr.arg, Label) + old_block = block + new_block = bytecode_blocks.add_block() + block.next_block = new_block + block = new_block + + # If we created a new block, we check: + # - if the current instruction is a TryEnd and if the last instruction + # is final in which case we insert the TryEnd in the old block. + # - if we have a currently active TryBegin for which we may need to + # create a TryEnd in the previous block and a new TryBegin in the + # new one because the blocks are not connected. + if old_block is not None: + temp = try_begin_inserted_in_block + try_begin_inserted_in_block = False + + if old_block is not None and last_instr is not None: + # The last instruction is final, if the current instruction is a + # TryEnd insert it in the same block and move to the next instruction + if last_instr.is_final() and isinstance(instr, TryEnd): + assert active_try_begin + nte = instr.copy() + nte.entry = try_begins[active_try_begin][-1] + old_block.append(nte) + active_try_begin = None + continue + + # If we have an active TryBegin and last_instr is: + elif active_try_begin is not None: + # - a jump whose target is beyond the TryEnd of the active + # TryBegin: we remember TryEnd should be prepended to the + # target block. + if ( + last_instr.has_jump() + and active_try_begin in try_end_locations + and ( + # last_instr is a jump so arg is a Label + label_to_block_index[last_instr.arg] # type: ignore + >= try_end_locations[active_try_begin] + ) + ): + assert isinstance(last_instr.arg, Label) + add_try_end[last_instr.arg].append( + TryEnd(try_begins[active_try_begin][-1]) + ) + + # - final and the try begin originate from the current block: + # we insert a TryEnd in the old block and a new TryBegin in + # the new one since the blocks are disconnected. + if last_instr.is_final() and temp: + old_block.append(TryEnd(try_begins[active_try_begin][-1])) + new_tb = TryBegin( + active_try_begin.target, active_try_begin.push_lasti + ) + block.append(new_tb) + # Add this new TryBegin to the map to properly update + # the target. + try_begins[active_try_begin].append(new_tb) + try_begin_inserted_in_block = True + + last_instr = None + + if isinstance(instr, Label): + continue + + # don't copy SetLineno objects + if isinstance(instr, (Instr, TryBegin, TryEnd)): + new = instr.copy() + if isinstance(instr, TryBegin): + assert active_try_begin is None + active_try_begin = instr + try_begin_inserted_in_block = True + assert isinstance(new, TryBegin) + try_begins[instr] = [new] + elif isinstance(instr, TryEnd): + assert isinstance(new, TryEnd) + new.entry = try_begins[instr.entry][-1] + active_try_begin = None + try_begin_inserted_in_block = False + else: + last_instr = instr + if isinstance(instr.arg, Label): + assert isinstance(new, Instr) + jumping_instrs.append(new) + + instr = new + + block.append(instr) + + # Insert the necessary TryEnds at the beginning of block that were marked + # (if we did not already insert an equivalent TryEnd earlier). + for lab, tes in add_try_end.items(): + block = labels[lab] + existing_te_entries = set() + index = 0 + # We use a while loop since the block cannot yet be iterated on since + # jumps still use labels instead of blocks + while index < len(block): + i = block[index] + index += 1 + if isinstance(i, TryEnd): + existing_te_entries.add(i.entry) + else: + break + for te in tes: + if te.entry not in existing_te_entries: + labels[lab].insert(0, te) + existing_te_entries.add(te.entry) + + # Replace labels by block in jumping instructions + for instr in jumping_instrs: + label = instr.arg + assert isinstance(label, Label) + instr.arg = labels[label] + + # Replace labels by block in TryBegin + for b_tb, c_tbs in try_begins.items(): + label = b_tb.target + assert isinstance(label, Label) + for c_tb in c_tbs: + c_tb.target = labels[label] + + return bytecode_blocks + + def to_bytecode(self) -> _bytecode.Bytecode: + """Convert to Bytecode.""" + + used_blocks = set() + for block in self: + target_block = block.get_jump() + if target_block is not None: + used_blocks.add(id(target_block)) + + for tb in (i for i in block if isinstance(i, TryBegin)): + used_blocks.add(id(tb.target)) + + labels = {} + jumps = [] + try_begins = {} + seen_try_end: Set[TryBegin] = set() + instructions: List[Union[Instr, Label, TryBegin, TryEnd, SetLineno]] = [] + + # Track the last seen TryBegin and TryEnd to be able to fuse adjacent + # TryEnd/TryBegin pair which share the same target. + # In each case, we store the value found in the CFG and the value + # inserted in the bytecode. + last_try_begin: tuple[TryBegin, TryBegin] | None = None + last_try_end: tuple[TryEnd, TryEnd] | None = None + + for block in self: + if id(block) in used_blocks: + new_label = Label() + labels[id(block)] = new_label + instructions.append(new_label) + + for instr in block: + # don't copy SetLineno objects + if isinstance(instr, (Instr, TryBegin, TryEnd)): + new = instr.copy() + if isinstance(instr, TryBegin): + # If due to jumps and split TryBegin, we encounter a TryBegin + # while we still have a TryBegin ensure they can be fused. + if last_try_begin is not None: + cfg_tb, byt_tb = last_try_begin + assert instr.target is cfg_tb.target + assert instr.push_lasti == cfg_tb.push_lasti + byt_tb.stack_depth = min( + byt_tb.stack_depth, instr.stack_depth + ) + + # If the TryBegin share the target and push_lasti of the + # entry of an adjacent TryEnd, omit the new TryBegin that + # was inserted to allow analysis of the CFG and remove + # the already inserted TryEnd. + if last_try_end is not None: + cfg_te, byt_te = last_try_end + entry = cfg_te.entry + if ( + entry.target is instr.target + and entry.push_lasti == instr.push_lasti + ): + # If we did not yet compute the required stack depth + # keep the value as UNSET + if entry.stack_depth is UNSET: + assert instr.stack_depth is UNSET + byt_te.entry.stack_depth = UNSET + else: + byt_te.entry.stack_depth = min( + entry.stack_depth, instr.stack_depth + ) + try_begins[instr] = byt_te.entry + instructions.remove(byt_te) + continue + assert isinstance(new, TryBegin) + try_begins[instr] = new + last_try_begin = (instr, new) + last_try_end = None + elif isinstance(instr, TryEnd): + # Only keep the first seen TryEnd matching a TryBegin + assert isinstance(new, TryEnd) + if instr.entry in seen_try_end: + continue + seen_try_end.add(instr.entry) + new.entry = try_begins[instr.entry] + last_try_begin = None + last_try_end = (instr, new) + elif isinstance(instr.arg, BasicBlock): + assert isinstance(new, Instr) + jumps.append(new) + last_try_end = None + else: + last_try_end = None + + instr = new + + instructions.append(instr) + + # Map to new labels + for instr in jumps: + instr.arg = labels[id(instr.arg)] + + for tb in set(try_begins.values()): + tb.target = labels[id(tb.target)] + + bytecode = _bytecode.Bytecode() + bytecode._copy_attr_from(self) + bytecode.argnames = list(self.argnames) + bytecode[:] = instructions + + return bytecode + + def to_code( + self, + stacksize: Optional[int] = None, + *, + check_pre_and_post: bool = True, + compute_exception_stack_depths: bool = True, + ) -> types.CodeType: + """Convert to code.""" + if stacksize is None: + stacksize = self.compute_stacksize( + check_pre_and_post=check_pre_and_post, + compute_exception_stack_depths=compute_exception_stack_depths, + ) + bc = self.to_bytecode() + return bc.to_code( + stacksize=stacksize, + check_pre_and_post=False, + compute_exception_stack_depths=False, + ) diff --git a/lambdas/aws-dd-forwarder-3.127.0/bytecode/concrete.py b/lambdas/aws-dd-forwarder-3.127.0/bytecode/concrete.py new file mode 100644 index 0000000..4908e1c --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/bytecode/concrete.py @@ -0,0 +1,1419 @@ +import dis +import inspect +import opcode as _opcode +import struct +import sys +import types +from typing import ( + Any, + Dict, + Iterable, + Iterator, + List, + MutableSequence, + Optional, + Sequence, + Set, + Tuple, + Type, + TypeVar, + Union, +) + +# alias to keep the 'bytecode' variable free +import bytecode as _bytecode +from bytecode.flags import CompilerFlags +from bytecode.instr import ( + _UNSET, + BITFLAG2_INSTRUCTIONS, + BITFLAG_INSTRUCTIONS, + INTRINSIC, + INTRINSIC_1OP, + INTRINSIC_2OP, + PLACEHOLDER_LABEL, + UNSET, + BaseInstr, + CellVar, + Compare, + FreeVar, + Instr, + InstrArg, + InstrLocation, + Intrinsic1Op, + Intrinsic2Op, + Label, + SetLineno, + TryBegin, + TryEnd, + _check_arg_int, + const_key, + opcode_has_argument, +) + +# - jumps use instruction +# - lineno use bytes (dis.findlinestarts(code)) +# - dis displays bytes +OFFSET_AS_INSTRUCTION = sys.version_info >= (3, 10) + + +def _set_docstring(code: _bytecode.BaseBytecode, consts: Sequence) -> None: + if not consts: + return + first_const = consts[0] + if isinstance(first_const, str) or first_const is None: + code.docstring = first_const + + +T = TypeVar("T", bound="ConcreteInstr") + + +class ConcreteInstr(BaseInstr[int]): + """Concrete instruction. + + arg must be an integer in the range 0..2147483647. + + It has a read-only size attribute. + + """ + + # For ConcreteInstr the argument is always an integer + _arg: int + + __slots__ = ("_size", "_extended_args") + + def __init__( + self, + name: str, + arg: int = UNSET, + *, + lineno: Union[int, None, _UNSET] = UNSET, + location: Optional[InstrLocation] = None, + extended_args: Optional[int] = None, + ): + # Allow to remember a potentially meaningless EXTENDED_ARG emitted by + # Python to properly compute the size and avoid messing up the jump + # targets + self._extended_args = extended_args + super().__init__(name, arg, lineno=lineno, location=location) + + def _check_arg(self, name: str, opcode: int, arg: int) -> None: + if opcode_has_argument(opcode): + if arg is UNSET: + raise ValueError("operation %s requires an argument" % name) + + _check_arg_int(arg, name) + # opcode == 0 corresponds to CACHE instruction in 3.11+ and was unused before + elif opcode == 0: + arg = arg if arg is not UNSET else 0 + _check_arg_int(arg, name) + else: + if arg is not UNSET: + raise ValueError("operation %s has no argument" % name) + + def _set( + self, + name: str, + arg: int, + ) -> None: + super()._set(name, arg) + size = 2 + if arg is not UNSET: + while arg > 0xFF: + size += 2 + arg >>= 8 + if self._extended_args is not None: + size = 2 + 2 * self._extended_args + self._size = size + + @property + def size(self) -> int: + return self._size + + def _cmp_key(self) -> Tuple[Optional[InstrLocation], str, int]: + return (self._location, self._name, self._arg) + + def get_jump_target(self, instr_offset: int) -> Optional[int]: + # When a jump arg is zero the jump always points to the first non-CACHE + # opcode following the jump. The passed in offset is the offset at + # which the jump opcode starts. So to compute the target, we add to it + # the instruction size (accounting for extended args) and the + # number of caches expected to follow the jump instruction. + s = ( + (self._size // 2) if OFFSET_AS_INSTRUCTION else self._size + ) + self.use_cache_opcodes() + if self.is_forward_rel_jump(): + return instr_offset + s + self._arg + if self.is_backward_rel_jump(): + return instr_offset + s - self._arg + if self.is_abs_jump(): + return self._arg + return None + + def assemble(self) -> bytes: + if self._arg is UNSET: + return bytes((self._opcode, 0)) + + arg = self._arg + b = [self._opcode, arg & 0xFF] + while arg > 0xFF: + arg >>= 8 + b[:0] = [_opcode.EXTENDED_ARG, arg & 0xFF] + + if self._extended_args: + while len(b) < self._size: + b[:0] = [_opcode.EXTENDED_ARG, 0x00] + + return bytes(b) + + @classmethod + def disassemble(cls: Type[T], lineno: Optional[int], code: bytes, offset: int) -> T: + index = 2 * offset if OFFSET_AS_INSTRUCTION else offset + op = code[index] + if opcode_has_argument(op): + arg = code[index + 1] + else: + arg = UNSET + name = _opcode.opname[op] + return cls(name, arg, lineno=lineno) + + def use_cache_opcodes(self) -> int: + return ( + # Not supposed to be used but we need it + dis._inline_cache_entries[self._opcode] # type: ignore + if sys.version_info >= (3, 11) + else 0 + ) + + +class ExceptionTableEntry: + """Entry for a given line in the exception table. + + All offset are expressed in instructions not in bytes. + + """ + + #: Offset in instruction between the beginning of the bytecode and the beginning + #: of this entry. + start_offset: int + + #: Offset in instruction between the beginning of the bytecode and the end + #: of this entry. This offset is inclusive meaning that the instruction it points + #: to is included in the try/except handling. + stop_offset: int + + #: Offset in instruction to the first instruction of the exception handling block. + target: int + + #: Minimal stack depth in the block delineated by start and stop + #: offset of the exception table entry. Used to restore the stack (by + #: popping items) when entering the exception handling block. + stack_depth: int + + #: Should the offset, at which an exception was raised, be pushed on the stack + #: before the exception itself (which is pushed as a single value)). + push_lasti: bool + + __slots__ = ("start_offset", "stop_offset", "target", "stack_depth", "push_lasti") + + def __init__( + self, + start_offset: int, + stop_offset: int, + target: int, + stack_depth: int, + push_lasti: bool, + ) -> None: + self.start_offset = start_offset + self.stop_offset = stop_offset + self.target = target + self.stack_depth = stack_depth + self.push_lasti = push_lasti + + def __repr__(self) -> str: + return ( + "ExceptionTableEntry(" + f"start_offset={self.start_offset}, " + f"stop_offset={self.stop_offset}, " + f"target={self.target}, " + f"stack_depth={self.stack_depth}, " + f"push_lasti={self.push_lasti}" + ) + + +class ConcreteBytecode(_bytecode._BaseBytecodeList[Union[ConcreteInstr, SetLineno]]): + #: List of "constant" objects for the bytecode + consts: List + + #: List of names used by local variables. + names: List[str] + + #: List of names used by input variables. + varnames: List[str] + + #: Table describing portion of the bytecode in which exceptions are caught and + #: where there are handled. + #: Used only in Python 3.11+ + exception_table: List[ExceptionTableEntry] + + def __init__( + self, + instructions=(), + *, + consts: tuple = (), + names: Tuple[str, ...] = (), + varnames: Iterable[str] = (), + exception_table: Optional[List[ExceptionTableEntry]] = None, + ): + super().__init__() + self.consts = list(consts) + self.names = list(names) + self.varnames = list(varnames) + self.exception_table = exception_table or [] + for instr in instructions: + self._check_instr(instr) + self.extend(instructions) + + def __iter__(self) -> Iterator[Union[ConcreteInstr, SetLineno]]: + instructions = super().__iter__() + for instr in instructions: + self._check_instr(instr) + yield instr + + def _check_instr(self, instr: Any) -> None: + if not isinstance(instr, (ConcreteInstr, SetLineno)): + raise ValueError( + "ConcreteBytecode must only contain " + "ConcreteInstr and SetLineno objects, " + "but %s was found" % type(instr).__name__ + ) + + def _copy_attr_from(self, bytecode): + super()._copy_attr_from(bytecode) + if isinstance(bytecode, ConcreteBytecode): + self.consts = bytecode.consts + self.names = bytecode.names + self.varnames = bytecode.varnames + + def __repr__(self) -> str: + return "" % len(self) + + def __eq__(self, other: Any) -> bool: + if type(self) is not type(other): + return False + + const_keys1 = list(map(const_key, self.consts)) + const_keys2 = list(map(const_key, other.consts)) + if const_keys1 != const_keys2: + return False + + if self.names != other.names: + return False + if self.varnames != other.varnames: + return False + + return super().__eq__(other) + + @staticmethod + def from_code( + code: types.CodeType, *, extended_arg: bool = False + ) -> "ConcreteBytecode": + instructions: MutableSequence[Union[SetLineno, ConcreteInstr]] + # For Python 3.11+ we use dis to extract the detailed location information at + # reduced maintenance cost. + if sys.version_info >= (3, 11): + instructions = [ + # dis.get_instructions automatically handle extended arg which + # we do not want, so we fold back arguments to be between 0 and 255 + ConcreteInstr( + i.opname, + i.arg % 256 if i.arg is not None else UNSET, + location=InstrLocation.from_positions(i.positions) + if i.positions + else None, + ) + for i in dis.get_instructions(code, show_caches=True) + ] + else: + if sys.version_info >= (3, 10): + line_starts = dict( + (offset, lineno) for offset, _, lineno in code.co_lines() + ) + else: + line_starts = dict(dis.findlinestarts(code)) + + # find block starts + instructions = [] + offset = 0 + lineno: Optional[int] = code.co_firstlineno + while offset < (len(code.co_code) // (2 if OFFSET_AS_INSTRUCTION else 1)): + lineno_off = (2 * offset) if OFFSET_AS_INSTRUCTION else offset + if lineno_off in line_starts: + lineno = line_starts[lineno_off] + + instr = ConcreteInstr.disassemble(lineno, code.co_code, offset) + + instructions.append(instr) + offset += (instr.size // 2) if OFFSET_AS_INSTRUCTION else instr.size + + bytecode = ConcreteBytecode() + + # HINT : in some cases Python generate useless EXTENDED_ARG opcode + # with a value of zero. Such opcodes do not increases the size of the + # following opcode the way a normal EXTENDED_ARG does. As a + # consequence, they need to be tracked manually as otherwise the + # offsets in jump targets can end up being wrong. + if not extended_arg: + # The list is modified in place + bytecode._remove_extended_args(instructions) + + bytecode.name = code.co_name + bytecode.filename = code.co_filename + bytecode.flags = CompilerFlags(code.co_flags) + bytecode.argcount = code.co_argcount + bytecode.posonlyargcount = code.co_posonlyargcount + bytecode.kwonlyargcount = code.co_kwonlyargcount + bytecode.first_lineno = code.co_firstlineno + bytecode.names = list(code.co_names) + bytecode.consts = list(code.co_consts) + bytecode.varnames = list(code.co_varnames) + bytecode.freevars = list(code.co_freevars) + bytecode.cellvars = list(code.co_cellvars) + _set_docstring(bytecode, code.co_consts) + if sys.version_info >= (3, 11): + bytecode.exception_table = bytecode._parse_exception_table( + code.co_exceptiontable + ) + bytecode.qualname = code.co_qualname + else: + bytecode.qualname = bytecode.qualname + + bytecode[:] = instructions + return bytecode + + @staticmethod + def _normalize_lineno( + instructions: Sequence[Union[ConcreteInstr, SetLineno]], first_lineno: int + ) -> Iterator[Tuple[int, ConcreteInstr]]: + lineno = first_lineno + # For each instruction compute an "inherited" lineno used: + # - on 3.8 and 3.9 for which a lineno is mandatory + # - to infer a lineno on 3.10+ if no lineno was provided + for instr in instructions: + i_lineno = instr.lineno + # if instr.lineno is not set, it's inherited from the previous + # instruction, or from self.first_lineno + if i_lineno is not None and i_lineno is not UNSET: + lineno = i_lineno + + if isinstance(instr, ConcreteInstr): + yield (lineno, instr) + + def _assemble_code( + self, + ) -> Tuple[bytes, List[Tuple[int, int, int, Optional[InstrLocation]]]]: + offset = 0 + code_str = [] + linenos = [] + for lineno, instr in self._normalize_lineno(self, self.first_lineno): + code_str.append(instr.assemble()) + i_size = instr.size + linenos.append( + ( + (offset * 2) if OFFSET_AS_INSTRUCTION else offset, + i_size, + lineno, + instr.location, + ) + ) + offset += (i_size // 2) if OFFSET_AS_INSTRUCTION else i_size + + return (b"".join(code_str), linenos) + + # Used on 3.8 and 3.9 + @staticmethod + def _assemble_lnotab( + first_lineno: int, linenos: List[Tuple[int, int, int, Optional[InstrLocation]]] + ) -> bytes: + lnotab = [] + old_offset = 0 + old_lineno = first_lineno + for offset, _, lineno, _ in linenos: + dlineno = lineno - old_lineno + if dlineno == 0: + continue + old_lineno = lineno + + doff = offset - old_offset + old_offset = offset + + while doff > 255: + lnotab.append(b"\xff\x00") + doff -= 255 + + while dlineno < -128: + lnotab.append(struct.pack("Bb", doff, -128)) + doff = 0 + dlineno -= -128 + + while dlineno > 127: + lnotab.append(struct.pack("Bb", doff, 127)) + doff = 0 + dlineno -= 127 + + assert 0 <= doff <= 255 + assert -128 <= dlineno <= 127 + + lnotab.append(struct.pack("Bb", doff, dlineno)) + + return b"".join(lnotab) + + @staticmethod + def _pack_linetable( + linetable: List[bytes], doff: int, dlineno: Optional[int] + ) -> None: + if dlineno is not None: + # Ensure linenos are between -126 and +126, by using 127 lines jumps with + # a 0 byte offset + while dlineno < -127: + linetable.append(struct.pack("Bb", 0, -127)) + dlineno -= -127 + + while dlineno > 127: + linetable.append(struct.pack("Bb", 0, 127)) + dlineno -= 127 + + assert -127 <= dlineno <= 127 + else: + dlineno = -128 + + # Ensure offsets are less than 255. + # If an offset is larger, we first mark the line change with an offset of 254 + # then use as many 254 offset with no line change to reduce the offset to + # less than 254. + if doff > 254: + linetable.append(struct.pack("Bb", 254, dlineno)) + doff -= 254 + + while doff > 254: + linetable.append(b"\xfe\x00") + doff -= 254 + linetable.append(struct.pack("Bb", doff, 0)) + + else: + linetable.append(struct.pack("Bb", doff, dlineno)) + + assert 0 <= doff <= 254 + + # Used on 3.10 + def _assemble_linestable( + self, + first_lineno: int, + linenos: Iterable[Tuple[int, int, int, Optional[InstrLocation]]], + ) -> bytes: + if not linenos: + return b"" + + linetable: List[bytes] = [] + old_offset = 0 + + iter_in = iter(linenos) + + offset, i_size, old_lineno, old_location = next(iter_in) + if old_location is not None: + old_dlineno = ( + old_location.lineno - first_lineno + if old_location.lineno is not None + else None + ) + else: + old_dlineno = old_lineno - first_lineno + + for offset, i_size, lineno, location in iter_in: + if location is not None: + dlineno = ( + location.lineno - old_lineno + if location.lineno is not None + else None + ) + else: + dlineno = lineno - old_lineno + + if dlineno == 0 or (old_dlineno is None and dlineno is None): + continue + old_lineno = lineno + + doff = offset - old_offset + old_offset = offset + + self._pack_linetable(linetable, doff, old_dlineno) + old_dlineno = dlineno + + # Pack the line of the last instruction. + doff = offset + i_size - old_offset + self._pack_linetable(linetable, doff, old_dlineno) + + return b"".join(linetable) + + # The formats are describes in CPython/Objects/locations.md + @staticmethod + def _encode_location_varint(varint: int) -> bytearray: + encoded = bytearray() + # We encode on 6 bits + while True: + encoded.append(varint & 0x3F) + varint >>= 6 + if varint: + encoded[-1] |= 0x40 # bit 6 is set except on the last entry + else: + break + return encoded + + def _encode_location_svarint(self, svarint: int) -> bytearray: + if svarint < 0: + return self._encode_location_varint(((-svarint) << 1) | 1) + else: + return self._encode_location_varint(svarint << 1) + + # Python 3.11+ location format encoding + @staticmethod + def _pack_location_header(code: int, size: int) -> int: + return (1 << 7) + (code << 3) + (size - 1 if size <= 8 else 7) + + def _pack_location( + self, size: int, lineno: int, location: Optional[InstrLocation] + ) -> bytearray: + packed = bytearray() + + l_lineno: Optional[int] + # The location was not set so we infer a line. + if location is None: + l_lineno, end_lineno, col_offset, end_col_offset = ( + lineno, + None, + None, + None, + ) + else: + l_lineno, end_lineno, col_offset, end_col_offset = ( + location.lineno, + location.end_lineno, + location.col_offset, + location.end_col_offset, + ) + + # We have no location information so the code is 15 + if l_lineno is None: + packed.append(self._pack_location_header(15, size)) + + # No column info, code 13 + elif col_offset is None: + if end_lineno is not None and end_lineno != l_lineno: + raise ValueError( + "An instruction cannot have no column offset and span " + f"multiple lines (lineno: {l_lineno}, end lineno: {end_lineno}" + ) + packed.extend( + ( + self._pack_location_header(13, size), + *self._encode_location_svarint(l_lineno - lineno), + ) + ) + + # We enforce the end_lineno to be defined + else: + assert end_lineno is not None + assert end_col_offset is not None + + # Short forms + if ( + end_lineno == l_lineno + and l_lineno - lineno == 0 + and col_offset < 72 + and (end_col_offset - col_offset) <= 15 + ): + packed.extend( + ( + self._pack_location_header(col_offset // 8, size), + ((col_offset % 8) << 4) + (end_col_offset - col_offset), + ) + ) + + # One line form + elif ( + end_lineno == l_lineno + and l_lineno - lineno in (1, 2) + and col_offset < 256 + and end_col_offset < 256 + ): + packed.extend( + ( + self._pack_location_header(10 + l_lineno - lineno, size), + col_offset, + end_col_offset, + ) + ) + + # Long form + else: + packed.extend( + ( + self._pack_location_header(14, size), + *self._encode_location_svarint(l_lineno - lineno), + *self._encode_location_varint(end_lineno - l_lineno), + # When decoding in codeobject.c::advance_with_locations + # we remove 1 from the offset ... + *self._encode_location_varint(col_offset + 1), + *self._encode_location_varint(end_col_offset + 1), + ) + ) + + return packed + + def _push_locations( + self, + locations: List[bytearray], + size: int, + lineno: int, + location: InstrLocation, + ) -> int: + # We need the size in instruction not in bytes + size //= 2 + + # Repeatedly add element since we cannot cover more than 8 code + # elements. We recompute each time since in practice we will + # rarely loop. + while True: + locations.append(self._pack_location(size, lineno, location)) + # Update the lineno since if we need more than one entry the + # reference for the delta of the lineno change + lineno = location.lineno if location.lineno is not None else lineno + size -= 8 + if size < 1: + break + + return lineno + + def _assemble_locations( + self, + first_lineno: int, + linenos: Iterable[Tuple[int, int, int, Optional[InstrLocation]]], + ) -> bytes: + if not linenos: + return b"" + + locations: List[bytearray] = [] + + iter_in = iter(linenos) + + _, size, lineno, old_location = next(iter_in) + # Infer the line if location is None + old_location = old_location or InstrLocation(lineno, None, None, None) + lineno = first_lineno + + # We track the last set lineno to be able to compute deltas + for _, i_size, new_lineno, location in iter_in: + # Infer the line if location is None + location = location or InstrLocation(new_lineno, None, None, None) + + # Group together instruction with equivalent locations + if old_location.lineno and old_location == location: + size += i_size + continue + + lineno = self._push_locations(locations, size, lineno, old_location) + + size = i_size + old_location = location + + # Pack the line of the last instruction. + self._push_locations(locations, size, lineno, old_location) + + return b"".join(locations) + + @staticmethod + def _remove_extended_args( + instructions: MutableSequence[Union[SetLineno, ConcreteInstr]] + ) -> None: + # replace jump targets with blocks + # HINT : in some cases Python generate useless EXTENDED_ARG opcode + # with a value of zero. Such opcodes do not increases the size of the + # following opcode the way a normal EXTENDED_ARG does. As a + # consequence, they need to be tracked manually as otherwise the + # offsets in jump targets can end up being wrong. + nb_extended_args = 0 + extended_arg = None + index = 0 + while index < len(instructions): + instr = instructions[index] + + # Skip SetLineno meta instruction + if isinstance(instr, SetLineno): + index += 1 + continue + + if instr.name == "EXTENDED_ARG": + nb_extended_args += 1 + if extended_arg is not None: + extended_arg = (extended_arg << 8) + instr.arg + else: + extended_arg = instr.arg + + del instructions[index] + continue + + if extended_arg is not None: + arg = UNSET if instr.name == "NOP" else (extended_arg << 8) + instr.arg + extended_arg = None + + instr = ConcreteInstr( + instr.name, + arg, + location=instr.location, + extended_args=nb_extended_args, + ) + instructions[index] = instr + nb_extended_args = 0 + + index += 1 + + if extended_arg is not None: + raise ValueError("EXTENDED_ARG at the end of the code") + + # Taken and adapted from exception_handling_notes.txt in cpython/Objects + @staticmethod + def _parse_varint(except_table_iterator: Iterator[int]) -> int: + b = next(except_table_iterator) + val = b & 63 + while b & 64: + val <<= 6 + b = next(except_table_iterator) + val |= b & 63 + return val + + def _parse_exception_table( + self, exception_table: bytes + ) -> List[ExceptionTableEntry]: + assert sys.version_info >= (3, 11) + table = [] + iterator = iter(exception_table) + try: + while True: + start = self._parse_varint(iterator) + length = self._parse_varint(iterator) + end = start + length - 1 # Present as inclusive + target = self._parse_varint(iterator) + dl = self._parse_varint(iterator) + depth = dl >> 1 + lasti = bool(dl & 1) + table.append(ExceptionTableEntry(start, end, target, depth, lasti)) + except StopIteration: + return table + + @staticmethod + def _encode_varint(value: int, set_begin_marker: bool = False) -> Iterator[int]: + # Encode value as a varint on 7 bits (MSB should come first) and set + # the begin marker if requested. + temp: List[int] = [] + assert value >= 0 + while value: + temp.append(value & 63 | (64 if temp else 0)) + value >>= 6 + temp = temp or [0] + if set_begin_marker: + temp[-1] |= 128 + return reversed(temp) + + def _assemble_exception_table(self) -> bytes: + table = bytearray() + for entry in self.exception_table or []: + size = entry.stop_offset - entry.start_offset + 1 + depth = (entry.stack_depth << 1) + entry.push_lasti + table.extend(self._encode_varint(entry.start_offset, True)) + table.extend(self._encode_varint(size)) + table.extend(self._encode_varint(entry.target)) + table.extend(self._encode_varint(depth)) + + return bytes(table) + + def compute_stacksize(self, *, check_pre_and_post: bool = True) -> int: + bytecode = self.to_bytecode() + cfg = _bytecode.ControlFlowGraph.from_bytecode(bytecode) + return cfg.compute_stacksize(check_pre_and_post=check_pre_and_post) + + def to_code( + self, + stacksize: Optional[int] = None, + *, + check_pre_and_post: bool = True, + compute_exception_stack_depths: bool = True, + ) -> types.CodeType: + # Prevent reconverting the concrete bytecode to bytecode and cfg to do the + # calculation if we need to do it. + if stacksize is None or ( + sys.version_info >= (3, 11) and compute_exception_stack_depths + ): + cfg = _bytecode.ControlFlowGraph.from_bytecode(self.to_bytecode()) + stacksize = cfg.compute_stacksize( + check_pre_and_post=check_pre_and_post, + compute_exception_stack_depths=compute_exception_stack_depths, + ) + self = cfg.to_bytecode().to_concrete_bytecode( + compute_exception_stack_depths=False + ) + + # Assemble the code string after round tripping to CFG if necessary. + code_str, linenos = self._assemble_code() + + lnotab = ( + self._assemble_locations(self.first_lineno, linenos) + if sys.version_info >= (3, 11) + else ( + self._assemble_linestable(self.first_lineno, linenos) + if sys.version_info >= (3, 10) + else self._assemble_lnotab(self.first_lineno, linenos) + ) + ) + nlocals = len(self.varnames) + + if sys.version_info >= (3, 11): + return types.CodeType( + self.argcount, + self.posonlyargcount, + self.kwonlyargcount, + nlocals, + stacksize, + int(self.flags), + code_str, + tuple(self.consts), + tuple(self.names), + tuple(self.varnames), + self.filename, + self.name, + self.qualname, + self.first_lineno, + lnotab, + self._assemble_exception_table(), + tuple(self.freevars), + tuple(self.cellvars), + ) + else: + return types.CodeType( + self.argcount, + self.posonlyargcount, + self.kwonlyargcount, + nlocals, + stacksize, + int(self.flags), + code_str, + tuple(self.consts), + tuple(self.names), + tuple(self.varnames), + self.filename, + self.name, + self.first_lineno, + lnotab, + tuple(self.freevars), + tuple(self.cellvars), + ) + + def to_bytecode( + self, + prune_caches: bool = True, + conserve_exception_block_stackdepth: bool = False, + ) -> _bytecode.Bytecode: + # On 3.11 we generate pseudo-instruction from the exception table + + # Copy instruction and remove extended args if any (in-place) + c_instructions = self[:] + self._remove_extended_args(c_instructions) + + # Find jump targets + jump_targets: Set[int] = set() + offset = 0 + for c_instr in c_instructions: + if isinstance(c_instr, SetLineno): + continue + target = c_instr.get_jump_target(offset) + if target is not None: + jump_targets.add(target) + offset += (c_instr.size // 2) if OFFSET_AS_INSTRUCTION else c_instr.size + + # On 3.11+ we need to also look at the exception table for jump targets + for ex_entry in self.exception_table: + jump_targets.add(ex_entry.target) + + # Create look up dict to find entries based on either exception handling + # block exit or entry offsets. Several blocks can end on the same instruction + # so we store a list of entry per offset. + ex_start: Dict[int, ExceptionTableEntry] = {} + ex_end: Dict[int, List[ExceptionTableEntry]] = {} + for entry in self.exception_table: + # Ensure we do not have more than one entry with identical starting + # offsets + assert entry.start_offset not in ex_start + ex_start[entry.start_offset] = entry + ex_end.setdefault(entry.stop_offset, []).append(entry) + + # Create labels and instructions + jumps: List[Tuple[int, int]] = [] + instructions: List[Union[Instr, Label, TryBegin, TryEnd, SetLineno]] = [] + labels = {} + tb_instrs: Dict[ExceptionTableEntry, TryBegin] = {} + offset = 0 + # In Python 3.11+ cell and varnames can be shared and are indexed in a single + # array. + # As a consequence, the instruction argument can be either: + # - < len(varnames): the name is shared an we can directly use + # the index to access the name in cellvars + # - > len(varnames): the name is not shared and is offset by the + # number unshared varname. + # Free vars are never shared and correspond to index larger than the + # largest cell var. + # See PyCode_NewWithPosOnlyArgs + if sys.version_info >= (3, 11): + cells_lookup = self.varnames + [ + n for n in self.cellvars if n not in self.varnames + ] + ncells = len(cells_lookup) + else: + ncells = len(self.cellvars) + cells_lookup = self.cellvars + + for lineno, c_instr in self._normalize_lineno( + c_instructions, self.first_lineno + ): + if offset in jump_targets: + label = Label() + labels[offset] = label + instructions.append(label) + + # Handle TryBegin pseudo instructions + if offset in ex_start: + entry = ex_start[offset] + tb_instr = TryBegin( + Label(), + entry.push_lasti, + entry.stack_depth if conserve_exception_block_stackdepth else UNSET, + ) + # Per entry store the pseudo instruction associated + tb_instrs[entry] = tb_instr + instructions.append(tb_instr) + + jump_target = c_instr.get_jump_target(offset) + size = c_instr.size + # If an instruction uses extended args, those appear before the instruction + # causing the instruction to appear at offset that accounts for extended + # args. So we first update the offset to account for extended args, then + # record the instruction offset and then add the instruction itself to the + # offset. + offset += (size // 2 - 1) if OFFSET_AS_INSTRUCTION else (size - 2) + current_instr_offset = offset + offset += 1 if OFFSET_AS_INSTRUCTION else 2 + + # on Python 3.11+ remove CACHE opcodes if we are requested to do so. + # We are careful to first advance the offset and check that the CACHE + # is not a jump target. It should never be the case but we double check. + if prune_caches and c_instr.name == "CACHE": + assert jump_target is None + + # We may need to insert a TryEnd after a CACHE so we need to run the + # through the last block. + else: + arg: InstrArg + c_arg = c_instr.arg + # FIXME: better error reporting + if c_instr.opcode in _opcode.hasconst: + arg = self.consts[c_arg] + elif c_instr.opcode in _opcode.haslocal: + arg = self.varnames[c_arg] + elif c_instr.opcode in _opcode.hasname: + if c_instr.name in BITFLAG_INSTRUCTIONS: + arg = (bool(c_arg & 1), self.names[c_arg >> 1]) + elif c_instr.name in BITFLAG2_INSTRUCTIONS: + arg = (bool(c_arg & 1), bool(c_arg & 2), self.names[c_arg >> 2]) + else: + arg = self.names[c_arg] + elif c_instr.opcode in _opcode.hasfree: + if c_arg < ncells: + name = cells_lookup[c_arg] + arg = CellVar(name) + else: + name = self.freevars[c_arg - ncells] + arg = FreeVar(name) + elif c_instr.opcode in _opcode.hascompare: + arg = Compare( + (c_arg >> 4) if sys.version_info >= (3, 12) else c_arg + ) + elif c_instr.opcode in INTRINSIC_1OP: + arg = Intrinsic1Op(c_arg) + elif c_instr.opcode in INTRINSIC_2OP: + arg = Intrinsic2Op(c_arg) + else: + arg = c_arg + + location = c_instr.location or InstrLocation(lineno, None, None, None) + + if jump_target is not None: + arg = PLACEHOLDER_LABEL + instr_index = len(instructions) + jumps.append((instr_index, jump_target)) + + instructions.append(Instr(c_instr.name, arg, location=location)) + + # We now insert the TryEnd entries + if current_instr_offset in ex_end: + entries = ex_end[current_instr_offset] + for entry in reversed(entries): + instructions.append(TryEnd(tb_instrs[entry])) + + # Replace jump targets with labels + for index, jump_target in jumps: + instr = instructions[index] + assert isinstance(instr, Instr) and instr.arg is PLACEHOLDER_LABEL + # FIXME: better error reporting on missing label + instr.arg = labels[jump_target] + + # Set the label for TryBegin + for entry, tb in tb_instrs.items(): + tb.target = labels[entry.target] + + bytecode = _bytecode.Bytecode() + bytecode._copy_attr_from(self) + + nargs = bytecode.argcount + bytecode.kwonlyargcount + nargs += bytecode.posonlyargcount + if bytecode.flags & inspect.CO_VARARGS: + nargs += 1 + if bytecode.flags & inspect.CO_VARKEYWORDS: + nargs += 1 + bytecode.argnames = self.varnames[:nargs] + _set_docstring(bytecode, self.consts) + + bytecode.extend(instructions) + return bytecode + + +class _ConvertBytecodeToConcrete: + # XXX document attributes + + #: Default number of passes of compute_jumps() before giving up. Refer to + #: assemble_jump_offsets() in compile.c for background. + _compute_jumps_passes = 10 + + def __init__(self, code: _bytecode.Bytecode) -> None: + assert isinstance(code, _bytecode.Bytecode) + self.bytecode = code + + # temporary variables + self.instructions: List[ConcreteInstr] = [] + self.jumps: List[Tuple[int, Label, ConcreteInstr]] = [] + self.labels: Dict[Label, int] = {} + self.exception_handling_blocks: Dict[TryBegin, ExceptionTableEntry] = {} + self.required_caches = 0 + self.seen_manual_cache = False + + # used to build ConcreteBytecode() object + self.consts_indices: Dict[Union[bytes, Tuple[type, int]], int] = {} + self.consts_list: List[Any] = [] + self.names: List[str] = [] + self.varnames: List[str] = [] + + def add_const(self, value: Any) -> int: + key = const_key(value) + if key in self.consts_indices: + return self.consts_indices[key] + index = len(self.consts_indices) + self.consts_indices[key] = index + self.consts_list.append(value) + return index + + @staticmethod + def add(names: List[str], name: str) -> int: + try: + index = names.index(name) + except ValueError: + index = len(names) + names.append(name) + return index + + def concrete_instructions(self) -> None: + lineno = self.bytecode.first_lineno + # Track instruction (index) using cell vars and free vars to be able to update + # the index used once all the names are known. + cell_instrs: List[int] = [] + free_instrs: List[int] = [] + + for instr in self.bytecode: + # Enforce proper use of CACHE opcode on Python 3.11+ by checking we get the + # number we expect or directly generate the needed ones. + if isinstance(instr, Instr) and instr.name == "CACHE": + if not self.required_caches: + raise RuntimeError("Found a CACHE opcode when none was expected.") + self.seen_manual_cache = True + self.required_caches -= 1 + + elif self.required_caches: + if not self.seen_manual_cache: + # We preserve the location of the instruction requiring the + # presence of cache instructions + self.instructions.extend( + [ + ConcreteInstr( + "CACHE", 0, location=self.instructions[-1].location + ) + for i in range(self.required_caches) + ] + ) + self.required_caches = 0 + self.seen_manual_cache = False + else: + raise RuntimeError( + "Found some manual opcode but less than expected. " + f"Missing {self.required_caches} CACHE opcodes." + ) + + if isinstance(instr, Label): + self.labels[instr] = len(self.instructions) + continue + + if isinstance(instr, SetLineno): + lineno = instr.lineno + continue + + if isinstance(instr, TryBegin): + # We expect the stack depth to have be provided or computed earlier + assert instr.stack_depth is not UNSET + # NOTE here we store the index of the instruction at which the + # exception table entry starts. This is not the final value we want, + # we want the offset in the bytecode but that requires to compute + # the jumps first to resolve any possible extended arg needed in a + # jump. + self.exception_handling_blocks[instr] = ExceptionTableEntry( + len(self.instructions), 0, 0, instr.stack_depth, instr.push_lasti + ) + continue + + # Do not handle TryEnd before we insert possible CACHE opcode + if isinstance(instr, TryEnd): + entry = self.exception_handling_blocks[instr.entry] + # The TryEnd is located after the last opcode in the exception entry + # so we move the offset by one. We choose one so that the end does + # encompass a possible EXTENDED_ARG + entry.stop_offset = len(self.instructions) - 1 + continue + + assert isinstance(instr, Instr) + + if instr.lineno is not UNSET and instr.lineno is not None: + lineno = instr.lineno + elif instr.lineno is UNSET: + instr.lineno = lineno + + arg = instr.arg + is_jump = False + if isinstance(arg, Label): + label = arg + # fake value, real value is set in compute_jumps() + arg = 0 + is_jump = True + elif instr.opcode in _opcode.hasconst: + arg = self.add_const(arg) + elif instr.opcode in _opcode.haslocal: + assert isinstance(arg, str) + arg = self.add(self.varnames, arg) + elif instr.opcode in _opcode.hasname: + if instr.name in BITFLAG_INSTRUCTIONS: + assert ( + isinstance(arg, tuple) + and len(arg) == 2 + and isinstance(arg[0], bool) + and isinstance(arg[1], str) + ), arg + index = self.add(self.names, arg[1]) + arg = int(arg[0]) + (index << 1) + elif instr.name in BITFLAG2_INSTRUCTIONS: + assert ( + isinstance(arg, tuple) + and len(arg) == 3 + and isinstance(arg[0], bool) + and isinstance(arg[1], bool) + and isinstance(arg[2], str) + ), arg + index = self.add(self.names, arg[2]) + arg = int(arg[0]) + 2 * int(arg[1]) + (index << 2) + else: + assert isinstance(arg, str), f"Got {arg}, expected a str" + arg = self.add(self.names, arg) + elif instr.opcode in _opcode.hasfree: + if isinstance(arg, CellVar): + cell_instrs.append(len(self.instructions)) + arg = self.bytecode.cellvars.index(arg.name) + else: + assert isinstance(arg, FreeVar) + free_instrs.append(len(self.instructions)) + arg = self.bytecode.freevars.index(arg.name) + elif instr.opcode in _opcode.hascompare: + if isinstance(arg, Compare): + # In Python 3.12 the 4 lowest bits are used for caching + # See compare_masks in compile.c + if sys.version_info >= (3, 12): + arg = arg._get_mask() + (arg.value << 4) + else: + arg = arg.value + elif instr.opcode in INTRINSIC: + if isinstance(arg, (Intrinsic1Op, Intrinsic2Op)): + arg = arg.value + + # The above should have performed all the necessary conversion + assert isinstance(arg, int) + c_instr = ConcreteInstr(instr.name, arg, location=instr.location) + if is_jump: + self.jumps.append((len(self.instructions), label, c_instr)) + + # If the instruction expect some cache + if sys.version_info >= (3, 11): + self.required_caches = c_instr.use_cache_opcodes() + self.seen_manual_cache = False + + self.instructions.append(c_instr) + + # On Python 3.11 varnames and cells can share some names. Wind the shared + # names and update the arg argument of instructions using cell vars. + # We also track by how much to offset free vars which are stored in a + # contiguous array after the cell vars + if sys.version_info >= (3, 11): + # Map naive cell index to shared index + shared_name_indexes: Dict[int, int] = {} + n_shared = 0 + n_unshared = 0 + for i, name in enumerate(self.bytecode.cellvars): + if name in self.varnames: + shared_name_indexes[i] = self.varnames.index(name) + n_shared += 1 + else: + shared_name_indexes[i] = len(self.varnames) + n_unshared + n_unshared += 1 + + for index in cell_instrs: + c_instr = self.instructions[index] + c_instr.arg = shared_name_indexes[c_instr.arg] + + free_offset = len(self.varnames) + len(self.bytecode.cellvars) - n_shared + else: + free_offset = len(self.bytecode.cellvars) + + for index in free_instrs: + c_instr = self.instructions[index] + c_instr.arg += free_offset + + def compute_jumps(self) -> bool: + # For labels we need the offset before the instruction at a given index but for + # exception table entries we need the offset of the instruction which can differ + # in the presence of extended args... + label_offsets = [] + instruction_offsets = [] + offset = 0 + for index, instr in enumerate(self.instructions): + label_offsets.append(offset) + # If an instruction uses extended args, those appear before the instruction + # causing the instruction to appear at offset that accounts for extended + # args. + offset += ( + (instr.size // 2 - 1) if OFFSET_AS_INSTRUCTION else (instr.size - 2) + ) + instruction_offsets.append(offset) + offset += 1 if OFFSET_AS_INSTRUCTION else 2 + # needed if a label is at the end + label_offsets.append(offset) + + # FIXME may need some extra check to validate jump forward vs jump backward + # fix argument of jump instructions: resolve labels + modified = False + for index, label, instr in self.jumps: + target_index = self.labels[label] + target_offset = label_offsets[target_index] + + # FIXME use opcode + # Under 3.12+, FOR_ITER, SEND jump is increased by 1 implicitely + # to skip over END_FOR, END_SEND see Python/instrumentation.c + if sys.version_info >= (3, 12) and instr.name in ("FOR_ITER", "SEND"): + target_offset -= 1 + + if instr.is_forward_rel_jump(): + instr_offset = label_offsets[index] + target_offset -= instr_offset + ( + instr.size // 2 if OFFSET_AS_INSTRUCTION else instr.size + ) + elif instr.is_backward_rel_jump(): + instr_offset = label_offsets[index] + target_offset = ( + instr_offset + + (instr.size // 2 if OFFSET_AS_INSTRUCTION else instr.size) + - target_offset + ) + + old_size = instr.size + # FIXME: better error report if target_offset is negative + instr.arg = target_offset + if instr.size != old_size: + modified = True + + # If a jump required an extended arg hence invalidating the calculation + # we return early before filling the exception table entries + if modified: + return modified + + # Resolve labels for exception handling entries + for tb, entry in self.exception_handling_blocks.items(): + # Set the offset for the start and end offset from the instruction + # index stored when assembling the concrete instructions. + entry.start_offset = instruction_offsets[entry.start_offset] + entry.stop_offset = instruction_offsets[entry.stop_offset] + + # Set the offset to the target instruction + lb = tb.target + assert isinstance(lb, Label) + target_index = self.labels[lb] + target_offset = label_offsets[target_index] + entry.target = target_offset + + return False + + def to_concrete_bytecode( + self, + compute_jumps_passes: Optional[int] = None, + compute_exception_stack_depths: bool = True, + ) -> ConcreteBytecode: + if sys.version_info >= (3, 11) and compute_exception_stack_depths: + cfg = _bytecode.ControlFlowGraph.from_bytecode(self.bytecode) + cfg.compute_stacksize(compute_exception_stack_depths=True) + self.bytecode = cfg.to_bytecode() + + if compute_jumps_passes is None: + compute_jumps_passes = self._compute_jumps_passes + + first_const = self.bytecode.docstring + if first_const is not UNSET: + self.add_const(first_const) + + self.varnames.extend(self.bytecode.argnames) + + self.concrete_instructions() + for pas in range(0, compute_jumps_passes): + modified = self.compute_jumps() + if not modified: + break + else: + raise RuntimeError( + "compute_jumps() failed to converge after" " %d passes" % (pas + 1) + ) + + concrete = ConcreteBytecode( + self.instructions, + consts=tuple(self.consts_list), + names=tuple(self.names), + varnames=self.varnames, + exception_table=list(self.exception_handling_blocks.values()), + ) + concrete._copy_attr_from(self.bytecode) + return concrete diff --git a/lambdas/aws-dd-forwarder-3.127.0/bytecode/flags.py b/lambdas/aws-dd-forwarder-3.127.0/bytecode/flags.py new file mode 100644 index 0000000..039150f --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/bytecode/flags.py @@ -0,0 +1,187 @@ +import opcode +import sys +from enum import IntFlag +from typing import Optional, Union + +# alias to keep the 'bytecode' variable free +import bytecode as _bytecode + + +class CompilerFlags(IntFlag): + """Possible values of the co_flags attribute of Code object. + + Note: We do not rely on inspect values here as some of them are missing and + furthermore would be version dependent. + + """ + + OPTIMIZED = 0x00001 # noqa + NEWLOCALS = 0x00002 # noqa + VARARGS = 0x00004 # noqa + VARKEYWORDS = 0x00008 # noqa + NESTED = 0x00010 # noqa + GENERATOR = 0x00020 # noqa + NOFREE = 0x00040 # noqa + # New in Python 3.5 + # Used for coroutines defined using async def ie native coroutine + COROUTINE = 0x00080 # noqa + # Used for coroutines defined as a generator and then decorated using + # types.coroutine + ITERABLE_COROUTINE = 0x00100 # noqa + # New in Python 3.6 + # Generator defined in an async def function + ASYNC_GENERATOR = 0x00200 # noqa + + # __future__ flags + # future flags changed in Python 3.9 + if sys.version_info < (3, 9): + FUTURE_GENERATOR_STOP = 0x80000 # noqa + FUTURE_ANNOTATIONS = 0x100000 + else: + FUTURE_GENERATOR_STOP = 0x800000 # noqa + FUTURE_ANNOTATIONS = 0x1000000 + + +def infer_flags( + bytecode: Union[ + "_bytecode.Bytecode", "_bytecode.ConcreteBytecode", "_bytecode.ControlFlowGraph" + ], + is_async: Optional[bool] = None, +): + """Infer the proper flags for a bytecode based on the instructions. + + Because the bytecode does not have enough context to guess if a function + is asynchronous the algorithm tries to be conservative and will never turn + a previously async code into a sync one. + + Parameters + ---------- + bytecode : Bytecode | ConcreteBytecode | ControlFlowGraph + Bytecode for which to infer the proper flags + is_async : bool | None, optional + Force the code to be marked as asynchronous if True, prevent it from + being marked as asynchronous if False and simply infer the best + solution based on the opcode and the existing flag if None. + + """ + flags = CompilerFlags(0) + if not isinstance( + bytecode, + (_bytecode.Bytecode, _bytecode.ConcreteBytecode, _bytecode.ControlFlowGraph), + ): + msg = ( + "Expected a Bytecode, ConcreteBytecode or ControlFlowGraph " + "instance not %s" + ) + raise ValueError(msg % bytecode) + + instructions = ( + bytecode._get_instructions() + if isinstance(bytecode, _bytecode.ControlFlowGraph) + else bytecode + ) + instr_names = { + i.name + for i in instructions + if not isinstance( + i, + ( + _bytecode.SetLineno, + _bytecode.Label, + _bytecode.TryBegin, + _bytecode.TryEnd, + ), + ) + } + + # Identify optimized code + if not (instr_names & {"STORE_NAME", "LOAD_NAME", "DELETE_NAME"}): + flags |= CompilerFlags.OPTIMIZED + + # Check for free variables + if not (instr_names & {opcode.opname[i] for i in opcode.hasfree}): + flags |= CompilerFlags.NOFREE + + # Copy flags for which we cannot infer the right value + flags |= bytecode.flags & ( + CompilerFlags.NEWLOCALS + | CompilerFlags.VARARGS + | CompilerFlags.VARKEYWORDS + | CompilerFlags.NESTED + ) + + sure_generator = instr_names & {"YIELD_VALUE"} + maybe_generator = instr_names & {"YIELD_VALUE", "YIELD_FROM"} + + sure_async = instr_names & { + "GET_AWAITABLE", + "GET_AITER", + "GET_ANEXT", + "BEFORE_ASYNC_WITH", + "SETUP_ASYNC_WITH", + "END_ASYNC_FOR", + "ASYNC_GEN_WRAP", # New in 3.11 + } + + # If performing inference or forcing an async behavior, first inspect + # the flags since this is the only way to identify iterable coroutines + if is_async in (None, True): + if bytecode.flags & CompilerFlags.COROUTINE: + if sure_generator: + flags |= CompilerFlags.ASYNC_GENERATOR + else: + flags |= CompilerFlags.COROUTINE + elif bytecode.flags & CompilerFlags.ITERABLE_COROUTINE: + if sure_async: + msg = ( + "The ITERABLE_COROUTINE flag is set but bytecode that" + "can only be used in async functions have been " + "detected. Please unset that flag before performing " + "inference." + ) + raise ValueError(msg) + flags |= CompilerFlags.ITERABLE_COROUTINE + elif bytecode.flags & CompilerFlags.ASYNC_GENERATOR: + if not sure_generator: + flags |= CompilerFlags.COROUTINE + else: + flags |= CompilerFlags.ASYNC_GENERATOR + + # If the code was not asynchronous before determine if it should now be + # asynchronous based on the opcode and the is_async argument. + else: + if sure_async: + # YIELD_FROM is not allowed in async generator + if sure_generator: + flags |= CompilerFlags.ASYNC_GENERATOR + else: + flags |= CompilerFlags.COROUTINE + + elif maybe_generator: + if is_async: + if sure_generator: + flags |= CompilerFlags.ASYNC_GENERATOR + else: + flags |= CompilerFlags.COROUTINE + else: + flags |= CompilerFlags.GENERATOR + + elif is_async: + flags |= CompilerFlags.COROUTINE + + # If the code should not be asynchronous, check first it is possible and + # next set the GENERATOR flag if relevant + else: + if sure_async: + raise ValueError( + "The is_async argument is False but bytecodes " + "that can only be used in async functions have " + "been detected." + ) + + if maybe_generator: + flags |= CompilerFlags.GENERATOR + + flags |= bytecode.flags & CompilerFlags.FUTURE_GENERATOR_STOP + + return flags diff --git a/lambdas/aws-dd-forwarder-3.127.0/bytecode/instr.py b/lambdas/aws-dd-forwarder-3.127.0/bytecode/instr.py new file mode 100644 index 0000000..e927cdf --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/bytecode/instr.py @@ -0,0 +1,878 @@ +import dis +import enum +import opcode as _opcode +import sys +from abc import abstractmethod +from dataclasses import dataclass +from marshal import dumps as _dumps +from typing import Any, Callable, Dict, Generic, Optional, Tuple, TypeVar, Union + +try: + from typing import TypeGuard +except ImportError: + from typing_extensions import TypeGuard # type: ignore + +import bytecode as _bytecode + +# --- Instruction argument tools and + +MIN_INSTRUMENTED_OPCODE = getattr(_opcode, "MIN_INSTRUMENTED_OPCODE", 256) + +# Instructions relying on a bit to modify its behavior. +# The lowest bit is used to encode custom behavior. +BITFLAG_INSTRUCTIONS = ( + ("LOAD_GLOBAL", "LOAD_ATTR") + if sys.version_info >= (3, 12) + else ("LOAD_GLOBAL",) + if sys.version_info >= (3, 11) + else () +) + +BITFLAG2_INSTRUCTIONS = ("LOAD_SUPER_ATTR",) if sys.version_info >= (3, 12) else () + +# Intrinsic related opcodes +INTRINSIC_1OP = ( + (_opcode.opmap["CALL_INTRINSIC_1"],) if sys.version_info >= (3, 12) else () +) +INTRINSIC_2OP = ( + (_opcode.opmap["CALL_INTRINSIC_2"],) if sys.version_info >= (3, 12) else () +) +INTRINSIC = INTRINSIC_1OP + INTRINSIC_2OP + + +# Used for COMPARE_OP opcode argument +@enum.unique +class Compare(enum.IntEnum): + LT = 0 + LE = 1 + EQ = 2 + NE = 3 + GT = 4 + GE = 5 + if sys.version_info < (3, 9): + IN = 6 + NOT_IN = 7 + IS = 8 + IS_NOT = 9 + EXC_MATCH = 10 + + if sys.version_info >= (3, 12): + + def _get_mask(self): + if self == Compare.EQ: + return 8 + elif self == Compare.NE: + return 1 + 2 + 4 + elif self == Compare.LT: + return 2 + elif self == Compare.LE: + return 2 + 8 + elif self == Compare.GT: + return 4 + elif self == Compare.GE: + return 4 + 8 + + +# Used for BINARY_OP under Python 3.11+ +@enum.unique +class BinaryOp(enum.IntEnum): + ADD = 0 + AND = 1 + FLOOR_DIVIDE = 2 + LSHIFT = 3 + MATRIX_MULTIPLY = 4 + MULTIPLY = 5 + REMAINDER = 6 + OR = 7 + POWER = 8 + RSHIFT = 9 + SUBTRACT = 10 + TRUE_DIVIDE = 11 + XOR = 12 + INPLACE_ADD = 13 + INPLACE_AND = 14 + INPLACE_FLOOR_DIVIDE = 15 + INPLACE_LSHIFT = 16 + INPLACE_MATRIX_MULTIPLY = 17 + INPLACE_MULTIPLY = 18 + INPLACE_REMAINDER = 19 + INPLACE_OR = 20 + INPLACE_POWER = 21 + INPLACE_RSHIFT = 22 + INPLACE_SUBTRACT = 23 + INPLACE_TRUE_DIVIDE = 24 + INPLACE_XOR = 25 + + +@enum.unique +class Intrinsic1Op(enum.IntEnum): + INTRINSIC_1_INVALID = 0 + INTRINSIC_PRINT = 1 + INTRINSIC_IMPORT_STAR = 2 + INTRINSIC_STOPITERATION_ERROR = 3 + INTRINSIC_ASYNC_GEN_WRAP = 4 + INTRINSIC_UNARY_POSITIVE = 5 + INTRINSIC_LIST_TO_TUPLE = 6 + INTRINSIC_TYPEVAR = 7 + INTRINSIC_PARAMSPEC = 8 + INTRINSIC_TYPEVARTUPLE = 9 + INTRINSIC_SUBSCRIPT_GENERIC = 10 + INTRINSIC_TYPEALIAS = 11 + + +@enum.unique +class Intrinsic2Op(enum.IntEnum): + INTRINSIC_2_INVALID = 0 + INTRINSIC_PREP_RERAISE_STAR = 1 + INTRINSIC_TYPEVAR_WITH_BOUND = 2 + INTRINSIC_TYPEVAR_WITH_CONSTRAINTS = 3 + INTRINSIC_SET_FUNCTION_TYPE_PARAMS = 4 + + +# This make type checking happy but means it won't catch attempt to manipulate an unset +# statically. We would need guard on object attribute narrowed down through methods +class _UNSET(int): + instance = None + + def __new__(cls): + if cls.instance is None: + cls.instance = super().__new__(cls) + return cls.instance + + def __eq__(self, other) -> bool: + return self is other + + +for op in [ + "__abs__", + "__add__", + "__and__", + "__bool__", + "__ceil__", + "__divmod__", + "__float__", + "__floor__", + "__floordiv__", + "__ge__", + "__gt__", + "__hash__", + "__index__", + "__int__", + "__invert__", + "__le__", + "__lshift__", + "__lt__", + "__mod__", + "__mul__", + "__ne__", + "__neg__", + "__or__", + "__pos__", + "__pow__", + "__radd__", + "__rand__", + "__rdivmod__", + "__rfloordiv__", + "__rlshift__", + "__rmod__", + "__rmul__", + "__ror__", + "__round__", + "__rpow__", + "__rrshift__", + "__rshift__", + "__rsub__", + "__rtruediv__", + "__rxor__", + "__sub__", + "__truediv__", + "__trunc__", + "__xor__", +]: + setattr(_UNSET, op, lambda *args: NotImplemented) + + +UNSET = _UNSET() + + +def const_key(obj: Any) -> Union[bytes, Tuple[type, int]]: + try: + return _dumps(obj) + except ValueError: + # For other types, we use the object identifier as an unique identifier + # to ensure that they are seen as unequal. + return (type(obj), id(obj)) + + +class Label: + __slots__ = () + + +#: Placeholder label temporarily used when performing some conversions +#: concrete -> bytecode +PLACEHOLDER_LABEL = Label() + + +class _Variable: + __slots__ = ("name",) + + def __init__(self, name: str) -> None: + self.name: str = name + + def __eq__(self, other: Any) -> bool: + if type(self) is not type(other): + return False + return self.name == other.name + + def __str__(self) -> str: + return self.name + + def __repr__(self) -> str: + return "<%s %r>" % (self.__class__.__name__, self.name) + + +class CellVar(_Variable): + __slots__ = () + + +class FreeVar(_Variable): + __slots__ = () + + +def _check_arg_int(arg: Any, name: str) -> TypeGuard[int]: + if not isinstance(arg, int): + raise TypeError( + "operation %s argument must be an int, " + "got %s" % (name, type(arg).__name__) + ) + + if not (0 <= arg <= 2147483647): + raise ValueError( + "operation %s argument must be in " "the range 0..2,147,483,647" % name + ) + + return True + + +if sys.version_info >= (3, 12): + + def opcode_has_argument(opcode: int) -> bool: + return opcode in dis.hasarg + +else: + + def opcode_has_argument(opcode: int) -> bool: + return opcode >= dis.HAVE_ARGUMENT + + +# --- Instruction stack effect impact + +# We split the stack effect between the manipulations done on the stack before +# executing the instruction (fetching the elements that are going to be used) +# and what is pushed back on the stack after the execution is complete. + +# Stack effects that do not depend on the argument of the instruction +STATIC_STACK_EFFECTS: Dict[str, Tuple[int, int]] = { + "ROT_TWO": (-2, 2), + "ROT_THREE": (-3, 3), + "ROT_FOUR": (-4, 4), + "DUP_TOP": (-1, 2), + "DUP_TOP_TWO": (-2, 4), + "GET_LEN": (-1, 2), + "GET_ITER": (-1, 1), + "GET_YIELD_FROM_ITER": (-1, 1), + "GET_AWAITABLE": (-1, 1), + "GET_AITER": (-1, 1), + "GET_ANEXT": (-1, 2), + "LIST_TO_TUPLE": (-1, 1), + "LIST_EXTEND": (-2, 1), + "SET_UPDATE": (-2, 1), + "DICT_UPDATE": (-2, 1), + "DICT_MERGE": (-2, 1), + "COMPARE_OP": (-2, 1), + "IS_OP": (-2, 1), + "CONTAINS_OP": (-2, 1), + "IMPORT_NAME": (-2, 1), + "ASYNC_GEN_WRAP": (-1, 1), + "PUSH_EXC_INFO": (-1, 2), + # Pop TOS and push TOS.__aexit__ and result of TOS.__aenter__() + "BEFORE_ASYNC_WITH": (-1, 2), + # Replace TOS based on TOS and TOS1 + "IMPORT_FROM": (-1, 2), + "COPY_DICT_WITHOUT_KEYS": (-2, 2), + # Call a function at position 7 (4 3.11+) on the stack and push the return value + "WITH_EXCEPT_START": (-4, 5) if sys.version_info >= (3, 11) else (-7, 8), + # Starting with Python 3.11 MATCH_CLASS does not push a boolean anymore + "MATCH_CLASS": (-3, 1 if sys.version_info >= (3, 11) else 2), + "MATCH_MAPPING": (-1, 2), + "MATCH_SEQUENCE": (-1, 2), + "MATCH_KEYS": (-2, 3 if sys.version_info >= (3, 11) else 4), + "CHECK_EXC_MATCH": (-2, 2), # (TOS1, TOS) -> (TOS1, bool) + "CHECK_EG_MATCH": (-2, 2), # (TOS, TOS1) -> non-matched, matched or TOS1, None) + "PREP_RERAISE_STAR": (-2, 1), # (TOS1, TOS) -> new exception group) + **{k: (-1, 1) for k in (o for o in _opcode.opmap if (o.startswith("UNARY_")))}, + **{ + k: (-2, 1) + for k in ( + o + for o in _opcode.opmap + if (o.startswith("BINARY_") or o.startswith("INPLACE_")) + ) + }, + # Python 3.12 changes not covered by dis.stack_effect + "BINARY_SLICE": (-3, 1), + # "STORE_SLICE" handled by dis.stack_effect + "LOAD_FROM_DICT_OR_GLOBALS": (-1, 1), + "LOAD_FROM_DICT_OR_DEREF": (-1, 1), + "LOAD_INTRISIC_1": (-1, 1), + "LOAD_INTRISIC_2": (-2, 1), +} + + +DYNAMIC_STACK_EFFECTS: Dict[ + str, Callable[[int, Any, Optional[bool]], Tuple[int, int]] +] = { + # PRECALL pops all arguments (as per its stack effect) and leaves + # the callable and either self or NULL + # CALL pops the 2 above items and push the return + # (when PRECALL does not exist it pops more as encoded by the effect) + "CALL": lambda effect, arg, jump: ( + -2 - arg if sys.version_info >= (3, 12) else -2, + 1, + ), + # 3.12 changed the behavior of LOAD_ATTR + "LOAD_ATTR": lambda effect, arg, jump: (-1, 1 + effect), + "LOAD_SUPER_ATTR": lambda effect, arg, jump: (-3, 3 + effect), + "SWAP": lambda effect, arg, jump: (-arg, arg), + "COPY": lambda effect, arg, jump: (-arg, arg + effect), + "ROT_N": lambda effect, arg, jump: (-arg, arg), + "SET_ADD": lambda effect, arg, jump: (-arg, arg - 1), + "LIST_APPEND": lambda effect, arg, jump: (-arg, arg - 1), + "MAP_ADD": lambda effect, arg, jump: (-arg, arg - 2), + "FORMAT_VALUE": lambda effect, arg, jump: (effect - 1, 1), + # FOR_ITER needs TOS to be an iterator, hence a prerequisite of 1 on the stack + "FOR_ITER": lambda effect, arg, jump: (effect, 0) if jump else (-1, 2), + **{ + # Instr(UNPACK_* , n) pops 1 and pushes n + k: lambda effect, arg, jump: (-1, effect + 1) + for k in ( + "UNPACK_SEQUENCE", + "UNPACK_EX", + ) + }, + **{ + k: lambda effect, arg, jump: (effect - 1, 1) + for k in ( + "MAKE_FUNCTION", + "CALL_FUNCTION", + "CALL_FUNCTION_EX", + "CALL_FUNCTION_KW", + "CALL_METHOD", + *(o for o in _opcode.opmap if o.startswith("BUILD_")), + ) + }, +} + + +# --- Instruction location + + +def _check_location( + location: Optional[int], location_name: str, min_value: int +) -> None: + if location is None: + return + if not isinstance(location, int): + raise TypeError(f"{location_name} must be an int, got {type(location)}") + if location < min_value: + raise ValueError( + f"invalid {location_name}, expected >= {min_value}, got {location}" + ) + + +@dataclass(frozen=True) +class InstrLocation: + """Location information for an instruction.""" + + #: Lineno at which the instruction corresponds. + #: Optional so that a location of None in an instruction encode an unset value. + lineno: Optional[int] + + #: End lineno at which the instruction corresponds (Python 3.11+ only) + end_lineno: Optional[int] + + #: Column offset at which the instruction corresponds (Python 3.11+ only) + col_offset: Optional[int] + + #: End column offset at which the instruction corresponds (Python 3.11+ only) + end_col_offset: Optional[int] + + __slots__ = ["lineno", "end_lineno", "col_offset", "end_col_offset"] + + def __init__( + self, + lineno: Optional[int], + end_lineno: Optional[int], + col_offset: Optional[int], + end_col_offset: Optional[int], + ) -> None: + # Needed because we want the class to be frozen + object.__setattr__(self, "lineno", lineno) + object.__setattr__(self, "end_lineno", end_lineno) + object.__setattr__(self, "col_offset", col_offset) + object.__setattr__(self, "end_col_offset", end_col_offset) + # In Python 3.11 0 is a valid lineno for some instructions (RESUME for example) + _check_location(lineno, "lineno", 0 if sys.version_info >= (3, 11) else 1) + _check_location(end_lineno, "end_lineno", 1) + _check_location(col_offset, "col_offset", 0) + _check_location(end_col_offset, "end_col_offset", 0) + if end_lineno: + if lineno is None: + raise ValueError("End lineno specified with no lineno.") + elif lineno > end_lineno: + raise ValueError( + f"End lineno {end_lineno} cannot be smaller than lineno {lineno}." + ) + + if col_offset is not None or end_col_offset is not None: + if lineno is None or end_lineno is None: + raise ValueError( + "Column offsets were specified but lineno information are " + f"incomplete. Lineno: {lineno}, end lineno: {end_lineno}." + ) + if end_col_offset is not None: + if col_offset is None: + raise ValueError( + "End column offset specified with no column offset." + ) + # Column offset must be increasing inside a signle line but + # have no relations between different lines. + elif lineno == end_lineno and col_offset > end_col_offset: + raise ValueError( + f"End column offset {end_col_offset} cannot be smaller than " + f"column offset: {col_offset}." + ) + else: + raise ValueError( + "No end column offset was specified but a column offset was given." + ) + + @classmethod + def from_positions(cls, position: "dis.Positions") -> "InstrLocation": # type: ignore + return InstrLocation( + position.lineno, + position.end_lineno, + position.col_offset, + position.end_col_offset, + ) + + +class SetLineno: + __slots__ = ("_lineno",) + + def __init__(self, lineno: int) -> None: + # In Python 3.11 0 is a valid lineno for some instructions (RESUME for example) + _check_location(lineno, "lineno", 0 if sys.version_info >= (3, 11) else 1) + self._lineno: int = lineno + + @property + def lineno(self) -> int: + return self._lineno + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, SetLineno): + return False + return self._lineno == other._lineno + + +# --- Pseudo instructions used to represent exception handling (3.11+) + + +class TryBegin: + __slots__ = ("target", "push_lasti", "stack_depth") + + def __init__( + self, + target: Union[Label, "_bytecode.BasicBlock"], + push_lasti: bool, + stack_depth: Union[int, _UNSET] = UNSET, + ) -> None: + self.target: Union[Label, "_bytecode.BasicBlock"] = target + self.push_lasti: bool = push_lasti + self.stack_depth: Union[int, _UNSET] = stack_depth + + def copy(self) -> "TryBegin": + return TryBegin(self.target, self.push_lasti, self.stack_depth) + + +class TryEnd: + __slots__ = "entry" + + def __init__(self, entry: TryBegin) -> None: + self.entry: TryBegin = entry + + def copy(self) -> "TryEnd": + return TryEnd(self.entry) + + +T = TypeVar("T", bound="BaseInstr") +A = TypeVar("A", bound=object) + + +class BaseInstr(Generic[A]): + """Abstract instruction.""" + + __slots__ = ("_name", "_opcode", "_arg", "_location") + + # Work around an issue with the default value of arg + def __init__( + self, + name: str, + arg: A = UNSET, # type: ignore + *, + lineno: Union[int, None, _UNSET] = UNSET, + location: Optional[InstrLocation] = None, + ) -> None: + self._set(name, arg) + if location: + self._location = location + elif lineno is UNSET: + self._location = None + else: + self._location = InstrLocation(lineno, None, None, None) + + # Work around an issue with the default value of arg + def set(self, name: str, arg: A = UNSET) -> None: # type: ignore + """Modify the instruction in-place. + + Replace name and arg attributes. Don't modify lineno. + + """ + self._set(name, arg) + + def require_arg(self) -> bool: + """Does the instruction require an argument?""" + return opcode_has_argument(self._opcode) + + @property + def name(self) -> str: + return self._name + + @name.setter + def name(self, name: str) -> None: + self._set(name, self._arg) + + @property + def opcode(self) -> int: + return self._opcode + + @opcode.setter + def opcode(self, op: int) -> None: + if not isinstance(op, int): + raise TypeError("operator code must be an int") + if 0 <= op <= 255: + name = _opcode.opname[op] + valid = name != "<%r>" % op + else: + valid = False + if not valid: + raise ValueError("invalid operator code") + + self._set(name, self._arg) + + @property + def arg(self) -> A: + return self._arg + + @arg.setter + def arg(self, arg: A): + self._set(self._name, arg) + + @property + def lineno(self) -> Union[int, _UNSET, None]: + return self._location.lineno if self._location is not None else UNSET + + @lineno.setter + def lineno(self, lineno: Union[int, _UNSET, None]) -> None: + loc = self._location + if loc and ( + loc.end_lineno is not None + or loc.col_offset is not None + or loc.end_col_offset is not None + ): + raise RuntimeError( + "The lineno of an instruction with detailed location information " + "cannot be set." + ) + + if lineno is UNSET: + self._location = None + else: + self._location = InstrLocation(lineno, None, None, None) + + @property + def location(self) -> Optional[InstrLocation]: + return self._location + + @location.setter + def location(self, location: Optional[InstrLocation]) -> None: + if location and not isinstance(location, InstrLocation): + raise TypeError( + "The instr location must be an instance of InstrLocation or None." + ) + self._location = location + + def stack_effect(self, jump: Optional[bool] = None) -> int: + if not self.require_arg(): + arg = None + # 3.11 where LOAD_GLOBAL arg encode whether or we push a null + # 3.12 does the same for LOAD_ATTR + elif self.name in BITFLAG_INSTRUCTIONS and isinstance(self._arg, tuple): + assert len(self._arg) == 2 + arg = self._arg[0] + # 3.12 does a similar trick for LOAD_SUPER_ATTR + elif self.name in BITFLAG2_INSTRUCTIONS and isinstance(self._arg, tuple): + assert len(self._arg) == 3 + arg = self._arg[0] + elif not isinstance(self._arg, int) or self._opcode in _opcode.hasconst: + # Argument is either a non-integer or an integer constant, + # not oparg. + arg = 0 + else: + arg = self._arg + + return dis.stack_effect(self._opcode, arg, jump=jump) + + def pre_and_post_stack_effect(self, jump: Optional[bool] = None) -> Tuple[int, int]: + # Allow to check that execution will not cause a stack underflow + _effect = self.stack_effect(jump=jump) + + n = self.name + if n in STATIC_STACK_EFFECTS: + return STATIC_STACK_EFFECTS[n] + elif n in DYNAMIC_STACK_EFFECTS: + return DYNAMIC_STACK_EFFECTS[n](_effect, self.arg, jump) + else: + # For instruction with no special value we simply consider the effect apply + # before execution + return (_effect, 0) + + def copy(self: T) -> T: + return self.__class__(self._name, self._arg, location=self._location) + + def has_jump(self) -> bool: + return self._has_jump(self._opcode) + + def is_cond_jump(self) -> bool: + """Is a conditional jump?""" + # Ex: POP_JUMP_IF_TRUE, JUMP_IF_FALSE_OR_POP + # IN 3.11+ the JUMP and the IF are no necessary adjacent in the name. + name = self._name + return "JUMP_" in name and "IF_" in name + + def is_uncond_jump(self) -> bool: + """Is an unconditional jump?""" + # JUMP_BACKWARD has been introduced in 3.11+ + # JUMP_ABSOLUTE was removed in 3.11+ + return self.name in { + "JUMP_FORWARD", + "JUMP_ABSOLUTE", + "JUMP_BACKWARD", + "JUMP_BACKWARD_NO_INTERRUPT", + } + + def is_abs_jump(self) -> bool: + """Is an absolute jump.""" + return self._opcode in _opcode.hasjabs + + def is_forward_rel_jump(self) -> bool: + """Is a forward relative jump.""" + return self._opcode in _opcode.hasjrel and "BACKWARD" not in self._name + + def is_backward_rel_jump(self) -> bool: + """Is a backward relative jump.""" + return self._opcode in _opcode.hasjrel and "BACKWARD" in self._name + + def is_final(self) -> bool: + if self._name in { + "RETURN_VALUE", + "RETURN_CONST", + "RAISE_VARARGS", + "RERAISE", + "BREAK_LOOP", + "CONTINUE_LOOP", + }: + return True + if self.is_uncond_jump(): + return True + return False + + def __repr__(self) -> str: + if self._arg is not UNSET: + return "<%s arg=%r location=%s>" % (self._name, self._arg, self._location) + else: + return "<%s location=%s>" % (self._name, self._location) + + def __eq__(self, other: Any) -> bool: + if type(self) is not type(other): + return False + return self._cmp_key() == other._cmp_key() + + # --- Private API + + _name: str + + _location: Optional[InstrLocation] + + _opcode: int + + _arg: A + + def _set(self, name: str, arg: A) -> None: + if not isinstance(name, str): + raise TypeError("operation name must be a str") + try: + opcode = _opcode.opmap[name] + except KeyError: + raise ValueError(f"invalid operation name: {name}") + + if opcode >= MIN_INSTRUMENTED_OPCODE: + raise ValueError( + f"operation {name} is an instrumented or pseudo opcode. " + "Only base opcodes are supported" + ) + + self._check_arg(name, opcode, arg) + + self._name = name + self._opcode = opcode + self._arg = arg + + @staticmethod + def _has_jump(opcode) -> bool: + return opcode in _opcode.hasjrel or opcode in _opcode.hasjabs + + @abstractmethod + def _check_arg(self, name: str, opcode: int, arg: A) -> None: + pass + + @abstractmethod + def _cmp_key(self) -> Tuple[Optional[InstrLocation], str, Any]: + pass + + +InstrArg = Union[ + int, + str, + Label, + CellVar, + FreeVar, + "_bytecode.BasicBlock", + Compare, + Tuple[bool, str], + Tuple[bool, bool, str], +] + + +class Instr(BaseInstr[InstrArg]): + __slots__ = () + + def _cmp_key(self) -> Tuple[Optional[InstrLocation], str, Any]: + arg: Any = self._arg + if self._opcode in _opcode.hasconst: + arg = const_key(arg) + return (self._location, self._name, arg) + + def _check_arg(self, name: str, opcode: int, arg: InstrArg) -> None: + if name == "EXTENDED_ARG": + raise ValueError( + "only concrete instruction can contain EXTENDED_ARG, " + "highlevel instruction can represent arbitrary argument without it" + ) + + if opcode_has_argument(opcode): + if arg is UNSET: + raise ValueError("operation %s requires an argument" % name) + else: + if arg is not UNSET: + raise ValueError("operation %s has no argument" % name) + + if self._has_jump(opcode): + if not isinstance(arg, (Label, _bytecode.BasicBlock)): + raise TypeError( + "operation %s argument type must be " + "Label or BasicBlock, got %s" % (name, type(arg).__name__) + ) + + elif opcode in _opcode.hasfree: + if not isinstance(arg, (CellVar, FreeVar)): + raise TypeError( + "operation %s argument must be CellVar " + "or FreeVar, got %s" % (name, type(arg).__name__) + ) + + elif opcode in _opcode.haslocal or opcode in _opcode.hasname: + if name in BITFLAG_INSTRUCTIONS: + if not ( + isinstance(arg, tuple) + and len(arg) == 2 + and isinstance(arg[0], bool) + and isinstance(arg[1], str) + ): + raise TypeError( + "operation %s argument must be a tuple[bool, str], " + "got %s (value=%s)" % (name, type(arg).__name__, str(arg)) + ) + + elif name in BITFLAG2_INSTRUCTIONS: + if not ( + isinstance(arg, tuple) + and len(arg) == 3 + and isinstance(arg[0], bool) + and isinstance(arg[1], bool) + and isinstance(arg[2], str) + ): + raise TypeError( + "operation %s argument must be a tuple[bool, bool, str], " + "got %s (value=%s)" % (name, type(arg).__name__, str(arg)) + ) + + elif not isinstance(arg, str): + raise TypeError( + "operation %s argument must be a str, " + "got %s" % (name, type(arg).__name__) + ) + + elif opcode in _opcode.hasconst: + if isinstance(arg, Label): + raise ValueError( + "label argument cannot be used " "in %s operation" % name + ) + if isinstance(arg, _bytecode.BasicBlock): + raise ValueError( + "block argument cannot be used " "in %s operation" % name + ) + + elif opcode in _opcode.hascompare: + if not isinstance(arg, Compare): + raise TypeError( + "operation %s argument type must be " + "Compare, got %s" % (name, type(arg).__name__) + ) + + elif opcode in INTRINSIC_1OP: + if not isinstance(arg, Intrinsic1Op): + raise TypeError( + "operation %s argument type must be " + "Intrinsic1Op, got %s" % (name, type(arg).__name__) + ) + + elif opcode in INTRINSIC_2OP: + if not isinstance(arg, Intrinsic2Op): + raise TypeError( + "operation %s argument type must be " + "Intrinsic2Op, got %s" % (name, type(arg).__name__) + ) + + elif opcode_has_argument(opcode): + _check_arg_int(arg, name) diff --git a/lambdas/aws-dd-forwarder-3.127.0/bytecode/py.typed b/lambdas/aws-dd-forwarder-3.127.0/bytecode/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/bytecode/version.py b/lambdas/aws-dd-forwarder-3.127.0/bytecode/version.py new file mode 100644 index 0000000..2d91554 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/bytecode/version.py @@ -0,0 +1,19 @@ +# This file is auto-generated by setuptools-scm do NOT edit it. + +from collections import namedtuple + +#: A namedtuple of the version info for the current release. +_version_info = namedtuple("_version_info", "major minor micro status") + +parts = "0.15.1".split(".", 3) +version_info = _version_info( + int(parts[0]), + int(parts[1]), + int(parts[2]), + parts[3] if len(parts) == 4 else "", +) + +# Remove everything but the 'version_info' from this module. +del namedtuple, _version_info, parts + +__version__ = "0.15.1" diff --git a/lambdas/aws-dd-forwarder-3.127.0/caching/base_tags_cache.py b/lambdas/aws-dd-forwarder-3.127.0/caching/base_tags_cache.py new file mode 100644 index 0000000..c38aa00 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/caching/base_tags_cache.py @@ -0,0 +1,172 @@ +import json +import logging +import os +from random import randint +from time import time + +import boto3 +from botocore.exceptions import ClientError + +from caching.common import get_last_modified_time +from settings import ( + DD_S3_BUCKET_NAME, + DD_S3_CACHE_DIRNAME, + DD_S3_CACHE_LOCK_TTL_SECONDS, + DD_TAGS_CACHE_TTL_SECONDS, +) +from telemetry import send_forwarder_internal_metrics + +JITTER_MIN = 1 +JITTER_MAX = 100 +DD_TAGS_CACHE_TTL_SECONDS = DD_TAGS_CACHE_TTL_SECONDS + randint(JITTER_MIN, JITTER_MAX) + + +class BaseTagsCache(object): + def __init__( + self, + prefix, + cache_filename, + cache_lock_filename, + tags_ttl_seconds=DD_TAGS_CACHE_TTL_SECONDS, + ): + self.cache_dirname = DD_S3_CACHE_DIRNAME + self.tags_ttl_seconds = tags_ttl_seconds + self.tags_by_id = {} + self.last_tags_fetch_time = 0 + self.cache_prefix = prefix + self.cache_filename = cache_filename + self.cache_lock_filename = cache_lock_filename + self.logger = logging.getLogger() + self.logger.setLevel( + logging.getLevelName(os.environ.get("DD_LOG_LEVEL", "INFO").upper()) + ) + self.resource_tagging_client = boto3.client("resourcegroupstaggingapi") + self.s3_client = boto3.resource("s3") + + def get_resources_paginator(self): + return self.resource_tagging_client.get_paginator("get_resources") + + def get_cache_name_with_prefix(self): + return f"{self.cache_dirname}/{self.cache_prefix}_{self.cache_filename}" + + def get_cache_lock_with_prefix(self): + return f"{self.cache_dirname}/{self.cache_prefix}_{self.cache_lock_filename}" + + def write_cache_to_s3(self, data): + """Writes tags cache to s3""" + try: + self.logger.debug("Trying to write data to s3: {}".format(data)) + s3_object = self.s3_client.Object( + DD_S3_BUCKET_NAME, self.get_cache_name_with_prefix() + ) + s3_object.put(Body=(bytes(json.dumps(data).encode("UTF-8")))) + except ClientError: + send_forwarder_internal_metrics("s3_cache_write_failure") + self.logger.debug("Unable to write new cache to S3", exc_info=True) + + def acquire_s3_cache_lock(self): + """Acquire cache lock""" + cache_lock_object = self.s3_client.Object( + DD_S3_BUCKET_NAME, self.get_cache_lock_with_prefix() + ) + try: + file_content = cache_lock_object.get() + + # check lock file expiration + last_modified_unix_time = get_last_modified_time(file_content) + if last_modified_unix_time + DD_S3_CACHE_LOCK_TTL_SECONDS >= time(): + return False + except Exception: + self.logger.debug("Unable to get cache lock file") + + # lock file doesn't exist, create file to acquire lock + try: + cache_lock_object.put(Body=(bytes("lock".encode("UTF-8")))) + send_forwarder_internal_metrics("s3_cache_lock_acquired") + self.logger.debug("S3 cache lock acquired") + except ClientError: + self.logger.debug("Unable to write S3 cache lock file", exc_info=True) + return False + + return True + + def release_s3_cache_lock(self): + """Release cache lock""" + try: + cache_lock_object = self.s3_client.Object( + DD_S3_BUCKET_NAME, self.get_cache_lock_with_prefix() + ) + cache_lock_object.delete() + send_forwarder_internal_metrics("s3_cache_lock_released") + self.logger.debug("S3 cache lock released") + except ClientError: + send_forwarder_internal_metrics("s3_cache_lock_release_failure") + self.logger.debug("Unable to release S3 cache lock", exc_info=True) + + def get_cache_from_s3(self): + """Retrieves tags cache from s3 and returns the body along with + the last modified datetime for the cache""" + cache_object = self.s3_client.Object( + DD_S3_BUCKET_NAME, self.get_cache_name_with_prefix() + ) + try: + file_content = cache_object.get() + tags_cache = json.loads(file_content["Body"].read().decode("utf-8")) + last_modified_unix_time = get_last_modified_time(file_content) + except: + send_forwarder_internal_metrics("s3_cache_fetch_failure") + self.logger.debug("Unable to fetch cache from S3", exc_info=True) + return {}, -1 + + return tags_cache, last_modified_unix_time + + def _refresh(self): + """Populate the tags in the local cache by getting cache from s3 + If cache not in s3, then cache is built using build_tags_cache + """ + self.last_tags_fetch_time = time() + + # If the custom tag fetch env var is not set to true do not fetch + if not self.should_fetch_tags(): + self.logger.debug( + "Not fetching custom tags because the env variable for the cache {} is not set to true".format( + self.cache_filename + ) + ) + return + + tags_fetched, last_modified = self.get_cache_from_s3() + + if self._is_expired(last_modified): + send_forwarder_internal_metrics("s3_cache_expired") + self.logger.debug("S3 cache expired, rebuilding cache") + lock_acquired = self.acquire_s3_cache_lock() + if lock_acquired: + success, new_tags_fetched = self.build_tags_cache() + if success: + self.tags_by_id = new_tags_fetched + self.write_cache_to_s3(self.tags_by_id) + elif tags_fetched != {}: + self.tags_by_id = tags_fetched + + self.release_s3_cache_lock() + # s3 cache fetch succeeded and isn't expired + elif last_modified > -1: + self.tags_by_id = tags_fetched + + def _is_expired(self, last_modified=None): + """Returns bool for whether the fetch TTL has expired""" + if not last_modified: + last_modified = self.last_tags_fetch_time + + earliest_time_to_refetch_tags = last_modified + self.tags_ttl_seconds + return time() > earliest_time_to_refetch_tags + + def should_fetch_tags(self): + raise Exception("SHOULD FETCH TAGS MUST BE DEFINED FOR TAGS CACHES") + + def get(self, key): + raise Exception("GET TAGS MUST BE DEFINED FOR TAGS CACHES") + + def build_tags_cache(self): + raise Exception("BUILD TAGS MUST BE DEFINED FOR TAGS CACHES") diff --git a/lambdas/aws-dd-forwarder-3.127.0/caching/cache_layer.py b/lambdas/aws-dd-forwarder-3.127.0/caching/cache_layer.py new file mode 100644 index 0000000..eef6a53 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/caching/cache_layer.py @@ -0,0 +1,24 @@ +from caching.cloudwatch_log_group_cache import CloudwatchLogGroupTagsCache +from caching.step_functions_cache import StepFunctionsTagsCache +from caching.s3_tags_cache import S3TagsCache +from caching.lambda_cache import LambdaTagsCache + + +class CacheLayer: + def __init__(self, prefix): + self._cloudwatch_log_group_cache = CloudwatchLogGroupTagsCache(prefix) + self._s3_tags_cache = S3TagsCache(prefix) + self._step_functions_cache = StepFunctionsTagsCache(prefix) + self._lambda_cache = LambdaTagsCache(prefix) + + def get_cloudwatch_log_group_tags_cache(self): + return self._cloudwatch_log_group_cache + + def get_s3_tags_cache(self): + return self._s3_tags_cache + + def get_step_functions_tags_cache(self): + return self._step_functions_cache + + def get_lambda_tags_cache(self): + return self._lambda_cache diff --git a/lambdas/aws-dd-forwarder-3.127.0/caching/cloudwatch_log_group_cache.py b/lambdas/aws-dd-forwarder-3.127.0/caching/cloudwatch_log_group_cache.py new file mode 100644 index 0000000..f20d9a5 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/caching/cloudwatch_log_group_cache.py @@ -0,0 +1,194 @@ +import json +import logging +import os +from random import randint +from time import time + +import boto3 +from botocore.config import Config + +from caching.common import sanitize_aws_tag_string +from settings import ( + DD_S3_BUCKET_NAME, + DD_S3_CACHE_DIRNAME, + DD_S3_LOG_GROUP_CACHE_DIRNAME, + DD_TAGS_CACHE_TTL_SECONDS, +) +from telemetry import send_forwarder_internal_metrics + + +class CloudwatchLogGroupTagsCache: + def __init__( + self, + prefix, + ): + self.cache_dirname = f"{DD_S3_CACHE_DIRNAME}/{DD_S3_LOG_GROUP_CACHE_DIRNAME}" + self.cache_ttl_seconds = DD_TAGS_CACHE_TTL_SECONDS + self.bucket_name = DD_S3_BUCKET_NAME + self.cache_prefix = prefix + self.tags_by_log_group = {} + # We need to use the standard retry mode for the Cloudwatch Logs client that defaults to 3 retries + self.cloudwatch_logs_client = boto3.client( + "logs", config=Config(retries={"mode": "standard"}) + ) + self.s3_client = boto3.client("s3") + + self.logger = logging.getLogger() + self.logger.setLevel( + logging.getLevelName(os.environ.get("DD_LOG_LEVEL", "INFO").upper()) + ) + + # Initialize the cache + if self._should_fetch_tags(): + self._build_tags_cache() + + def get(self, log_group_arn): + """Get the tags for the Cloudwatch Log Group from the cache + + Will refetch the tags if they are out of date, or a log group is encountered + which isn't in the tag list + + Args: + key (str): the key we're getting tags from the cache for + + Returns: + log_group_tags (str[]): the list of "key:value" Datadog tag strings + """ + # If the custom tag fetch env var is not set to true do not fetch tags + if not self._should_fetch_tags(): + self.logger.debug( + "Not fetching custom tags because the env variable DD_FETCH_LOG_GROUP_TAGS is " + "not set to true" + ) + return [] + + return self._fetch_log_group_tags(log_group_arn) + + def _should_fetch_tags(self): + return os.environ.get("DD_FETCH_LOG_GROUP_TAGS", "false").lower() == "true" + + def _build_tags_cache(self): + try: + prefix = self._get_cache_file_prefix() + response = self.s3_client.list_objects_v2( + Bucket=DD_S3_BUCKET_NAME, Prefix=prefix + ) + cache_files = [content["Key"] for content in response.get("Contents", [])] + for cache_file in cache_files: + log_group_tags, last_modified = self._get_log_group_tags_from_cache( + cache_file + ) + if log_group_tags and not self._is_expired(last_modified): + log_group = cache_file.split("/")[-1].split(".")[0] + self.tags_by_log_group[log_group] = { + "tags": log_group_tags, + "last_modified": last_modified, + } + self.logger.debug( + f"loggroup_tags_cache initialized successfully {self.tags_by_log_group}" + ) + except Exception: + self.logger.exception("failed to build log group tags cache", exc_info=True) + + def _fetch_log_group_tags(self, log_group_arn): + # first, check in-memory cache + log_group_tags_struct = self.tags_by_log_group.get(log_group_arn, None) + if log_group_tags_struct and not self._is_expired( + log_group_tags_struct.get("last_modified", None) + ): + send_forwarder_internal_metrics("loggroup_local_cache_hit") + return log_group_tags_struct.get("tags", []) + + # then, check cache file, update and return + cache_file_name = self._get_cache_file_name(log_group_arn) + log_group_tags, last_modified = self._get_log_group_tags_from_cache( + cache_file_name + ) + if log_group_tags and not self._is_expired(last_modified): + self.tags_by_log_group[log_group_arn] = { + "tags": log_group_tags, + "last_modified": time(), + } + send_forwarder_internal_metrics("loggroup_s3_cache_hit") + return log_group_tags + + # finally, make an api call, update and return + log_group_tags = self._get_log_group_tags(log_group_arn) or [] + self._update_log_group_tags_cache(log_group_arn, log_group_tags) + self.tags_by_log_group[log_group_arn] = { + "tags": log_group_tags, + "last_modified": time(), + } + + return log_group_tags + + def _get_log_group_tags_from_cache(self, cache_file_name): + try: + response = self.s3_client.get_object( + Bucket=self.bucket_name, Key=cache_file_name + ) + tags_cache = json.loads(response.get("Body").read().decode("utf-8")) + last_modified_unix_time = int(response.get("LastModified").timestamp()) + except Exception: + send_forwarder_internal_metrics("loggroup_cache_fetch_failure") + self.logger.exception( + "Failed to get log group tags from cache", exc_info=True + ) + return None, -1 + + return tags_cache, last_modified_unix_time + + def _update_log_group_tags_cache(self, log_group, tags): + cache_file_name = self._get_cache_file_name(log_group) + try: + self.s3_client.put_object( + Bucket=self.bucket_name, + Key=cache_file_name, + Body=(bytes(json.dumps(tags).encode("UTF-8"))), + ) + except Exception: + send_forwarder_internal_metrics("loggroup_cache_write_failure") + self.logger.exception( + "Failed to update log group tags cache", exc_info=True + ) + + def _is_expired(self, last_modified): + if not last_modified: + return True + + # add a random number of seconds to avoid having all tags refetched at the same time + earliest_time_to_refetch_tags = ( + last_modified + self.cache_ttl_seconds + randint(1, 100) + ) + return time() > earliest_time_to_refetch_tags + + def _get_cache_file_name(self, log_group_arn): + log_group_name = log_group_arn.replace("/", "_").replace(":", "_") + return f"{self._get_cache_file_prefix()}/{log_group_name}.json" + + def _get_cache_file_prefix(self): + return f"{self.cache_dirname}/{self.cache_prefix}" + + def _get_log_group_tags(self, log_group_arn): + response = None + try: + send_forwarder_internal_metrics("list_tags_log_group_api_call") + response = self.cloudwatch_logs_client.list_tags_for_resource( + resourceArn=log_group_arn + ) + except Exception: + self.logger.exception("Failed to get log group tags", exc_info=True) + formatted_tags = None + if response is not None: + formatted_tags = [ + ( + "{key}:{value}".format( + key=sanitize_aws_tag_string(k, remove_colons=True), + value=sanitize_aws_tag_string(v, remove_leading_digits=False), + ) + if v + else sanitize_aws_tag_string(k, remove_colons=True) + ) + for k, v in response["tags"].items() + ] + return formatted_tags diff --git a/lambdas/aws-dd-forwarder-3.127.0/caching/common.py b/lambdas/aws-dd-forwarder-3.127.0/caching/common.py new file mode 100644 index 0000000..7d7db88 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/caching/common.py @@ -0,0 +1,103 @@ +import os +import datetime +import logging +import re +from collections import defaultdict + +logger = logging.getLogger() +logger.setLevel(logging.getLevelName(os.environ.get("DD_LOG_LEVEL", "INFO").upper())) + + +_other_chars = r"\w:\-\.\/" +Sanitize = re.compile(r"[^%s]" % _other_chars, re.UNICODE).sub +Dedupe = re.compile(r"_+", re.UNICODE).sub +FixInit = re.compile(r"^[_\d]*", re.UNICODE).sub + + +def get_last_modified_time(s3_file): + last_modified_str = s3_file["ResponseMetadata"]["HTTPHeaders"]["last-modified"] + last_modified_date = datetime.datetime.strptime( + last_modified_str, "%a, %d %b %Y %H:%M:%S %Z" + ) + last_modified_unix_time = int(last_modified_date.strftime("%s")) + return last_modified_unix_time + + +def parse_get_resources_response_for_tags_by_arn(get_resources_page): + """Parses a page of GetResources response for the mapping from ARN to tags + + Args: + get_resources_page (dict[]>): one page of the GetResources response. + Partial example: + {"ResourceTagMappingList": [{ + 'ResourceARN': 'arn:aws:lambda:us-east-1:123497598159:function:my-test-lambda', + 'Tags': [{'Key': 'stage', 'Value': 'dev'}, {'Key': 'team', 'Value': 'serverless'}] + }]} + + Returns: + tags_by_arn (dict): Lambda tag lists keyed by ARN + """ + tags_by_arn = defaultdict(list) + + aws_resouce_tag_mappings = get_resources_page["ResourceTagMappingList"] + for aws_resource_tag_mapping in aws_resouce_tag_mappings: + function_arn = aws_resource_tag_mapping["ResourceARN"] + lowercase_function_arn = function_arn.lower() + + raw_aws_tags = aws_resource_tag_mapping["Tags"] + tags = map(get_dd_tag_string_from_aws_dict, raw_aws_tags) + + tags_by_arn[lowercase_function_arn] += tags + + return tags_by_arn + + +def get_dd_tag_string_from_aws_dict(aws_key_value_tag_dict): + """Converts the AWS dict tag format to the dd key:value string format and truncates to 200 characters + + Args: + aws_key_value_tag_dict (dict): the dict the GetResources endpoint returns for a tag + ex: { "Key": "creator", "Value": "swf"} + + Returns: + key:value colon-separated string built from the dict + ex: "creator:swf" + """ + key = sanitize_aws_tag_string(aws_key_value_tag_dict["Key"], remove_colons=True) + value = sanitize_aws_tag_string( + aws_key_value_tag_dict.get("Value"), remove_leading_digits=False + ) + # Value is optional in DD and AWS + if not value: + return key + return f"{key}:{value}"[0:200] + + +def sanitize_aws_tag_string(tag, remove_colons=False, remove_leading_digits=True): + """Convert characters banned from DD but allowed in AWS tags to underscores""" + global Sanitize, Dedupe, FixInit + + # 1. Replace colons with _ + # 2. Convert to all lowercase unicode string + # 3. Convert bad characters to underscores + # 4. Dedupe contiguous underscores + # 5. Remove initial underscores/digits such that the string + # starts with an alpha char + # FIXME: tag normalization incorrectly supports tags starting + # with a ':', but this behavior should be phased out in future + # as it results in unqueryable data. See dogweb/#11193 + # 6. Strip trailing underscores + + if len(tag) == 0: + # if tag is empty, nothing to do + return tag + + if remove_colons: + tag = tag.replace(":", "_") + tag = Dedupe("_", Sanitize("_", tag.lower())) + if remove_leading_digits: + first_char = tag[0] + if first_char == "_" or "0" <= first_char <= "9": + tag = FixInit("", tag) + tag = tag.rstrip("_") + return tag diff --git a/lambdas/aws-dd-forwarder-3.127.0/caching/lambda_cache.py b/lambdas/aws-dd-forwarder-3.127.0/caching/lambda_cache.py new file mode 100644 index 0000000..e1d28e0 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/caching/lambda_cache.py @@ -0,0 +1,90 @@ +import os + +from botocore.exceptions import ClientError + +from caching.base_tags_cache import BaseTagsCache +from caching.common import parse_get_resources_response_for_tags_by_arn +from settings import ( + DD_S3_LAMBDA_CACHE_FILENAME, + DD_S3_LAMBDA_CACHE_LOCK_FILENAME, + GET_RESOURCES_LAMBDA_FILTER, +) +from telemetry import send_forwarder_internal_metrics + + +class LambdaTagsCache(BaseTagsCache): + def __init__(self, prefix): + super().__init__( + prefix, DD_S3_LAMBDA_CACHE_FILENAME, DD_S3_LAMBDA_CACHE_LOCK_FILENAME + ) + + def should_fetch_tags(self): + return os.environ.get("DD_FETCH_LAMBDA_TAGS", "false").lower() == "true" + + def build_tags_cache(self): + """Makes API calls to GetResources to get the live tags of the account's Lambda functions + + Returns an empty dict instead of fetching custom tags if the tag fetch env variable is not set to true + + Returns: + tags_by_arn_cache (dict): each Lambda's tags in a dict keyed by ARN + """ + tags_fetch_success = False + tags_by_arn_cache = {} + resource_paginator = self.get_resources_paginator() + + try: + for page in resource_paginator.paginate( + ResourceTypeFilters=[GET_RESOURCES_LAMBDA_FILTER], ResourcesPerPage=100 + ): + send_forwarder_internal_metrics("get_resources_api_calls") + page_tags_by_arn = parse_get_resources_response_for_tags_by_arn(page) + tags_by_arn_cache.update(page_tags_by_arn) + tags_fetch_success = True + + except ClientError as e: + self.logger.exception( + "Encountered a ClientError when trying to fetch tags. You may need to give " + "this Lambda's role the 'tag:GetResources' permission" + ) + additional_tags = [ + f"http_status_code:{e.response['ResponseMetadata']['HTTPStatusCode']}" + ] + send_forwarder_internal_metrics( + "client_error", additional_tags=additional_tags + ) + tags_fetch_success = False + + self.logger.debug( + "Built this tags cache from GetResources API calls: %s", tags_by_arn_cache + ) + + return tags_fetch_success, tags_by_arn_cache + + def get(self, key): + """Get the tags for the Lambda function from the cache + + Will refetch the tags if they are out of date, or a lambda arn is encountered + which isn't in the tag list + + Note: the ARNs in the cache have been lowercased, so resource_arn must be lowercased + + Args: + key (str): the key we're getting tags from the cache for + + Returns: + lambda_tags (str[]): the list of "key:value" Datadog tag strings + """ + if not self.should_fetch_tags(): + self.logger.debug( + "Not fetching lambda function tags because the env variable DD_FETCH_LAMBDA_TAGS is " + "not set to true" + ) + return [] + + if self._is_expired(): + send_forwarder_internal_metrics("local_lambda_cache_expired") + self.logger.debug("Local cache expired, fetching cache from S3") + self._refresh() + + return self.tags_by_id.get(key, []) diff --git a/lambdas/aws-dd-forwarder-3.127.0/caching/s3_tags_cache.py b/lambdas/aws-dd-forwarder-3.127.0/caching/s3_tags_cache.py new file mode 100644 index 0000000..b60c873 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/caching/s3_tags_cache.py @@ -0,0 +1,64 @@ +from botocore.exceptions import ClientError +from caching.base_tags_cache import BaseTagsCache +from caching.common import parse_get_resources_response_for_tags_by_arn +from telemetry import send_forwarder_internal_metrics +from settings import ( + DD_S3_TAGS_CACHE_FILENAME, + DD_S3_TAGS_CACHE_LOCK_FILENAME, + GET_RESOURCES_S3_FILTER, +) + + +class S3TagsCache(BaseTagsCache): + def __init__(self, prefix): + super().__init__( + prefix, DD_S3_TAGS_CACHE_FILENAME, DD_S3_TAGS_CACHE_LOCK_FILENAME + ) + + def should_fetch_tags(self): + return True + + def build_tags_cache(self): + """Makes API calls to GetResources to get the live tags of the account's S3 buckets + Returns an empty dict instead of fetching custom tags if the tag fetch env variable is not set to true + Returns: + tags_by_arn_cache (dict): each S3 bucket's tags in a dict keyed by ARN + """ + tags_fetch_success = False + tags_by_arn_cache = {} + resource_paginator = self.get_resources_paginator() + + try: + for page in resource_paginator.paginate( + ResourceTypeFilters=[GET_RESOURCES_S3_FILTER], ResourcesPerPage=100 + ): + send_forwarder_internal_metrics("get_s3_resources_api_calls") + page_tags_by_arn = parse_get_resources_response_for_tags_by_arn(page) + tags_by_arn_cache.update(page_tags_by_arn) + tags_fetch_success = True + except ClientError as e: + self.logger.exception( + "Encountered a ClientError when trying to fetch tags. You may need to give " + "this Lambda's role the 'tag:GetResources' permission" + ) + additional_tags = [ + f"http_status_code:{e.response['ResponseMetadata']['HTTPStatusCode']}" + ] + send_forwarder_internal_metrics( + "client_error", additional_tags=additional_tags + ) + tags_fetch_success = False + + self.logger.debug( + "Built this tags cache from GetResources API calls: %s", tags_by_arn_cache + ) + + return tags_fetch_success, tags_by_arn_cache + + def get(self, bucket_arn): + if self._is_expired(): + send_forwarder_internal_metrics("local_s3_tags_cache_expired") + self.logger.debug("Local cache expired, fetching cache from S3") + self._refresh() + + return self.tags_by_id.get(bucket_arn, []) diff --git a/lambdas/aws-dd-forwarder-3.127.0/caching/step_functions_cache.py b/lambdas/aws-dd-forwarder-3.127.0/caching/step_functions_cache.py new file mode 100644 index 0000000..4b2c497 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/caching/step_functions_cache.py @@ -0,0 +1,144 @@ +import os +from botocore.exceptions import ClientError +from caching.base_tags_cache import BaseTagsCache +from caching.common import ( + sanitize_aws_tag_string, + parse_get_resources_response_for_tags_by_arn, +) +from telemetry import send_forwarder_internal_metrics +from settings import ( + DD_S3_STEP_FUNCTIONS_CACHE_FILENAME, + DD_S3_STEP_FUNCTIONS_CACHE_LOCK_FILENAME, + GET_RESOURCES_STEP_FUNCTIONS_FILTER, +) + + +class StepFunctionsTagsCache(BaseTagsCache): + def __init__(self, prefix): + super().__init__( + prefix, + DD_S3_STEP_FUNCTIONS_CACHE_FILENAME, + DD_S3_STEP_FUNCTIONS_CACHE_LOCK_FILENAME, + ) + + def should_fetch_tags(self): + return os.environ.get("DD_FETCH_STEP_FUNCTIONS_TAGS", "false").lower() == "true" + + def build_tags_cache(self): + """Makes API calls to GetResources to get the live tags of the account's Step Functions + Returns an empty dict instead of fetching custom tags if the tag fetch env variable is not + set to true. + Returns: + tags_by_arn_cache (dict): each Lambda's tags in a dict keyed by ARN + """ + tags_fetch_success = False + tags_by_arn_cache = {} + get_resources_paginator = self.get_resources_paginator() + + try: + for page in get_resources_paginator.paginate( + ResourceTypeFilters=[GET_RESOURCES_STEP_FUNCTIONS_FILTER], + ResourcesPerPage=100, + ): + send_forwarder_internal_metrics( + "step_functions_get_resources_api_calls" + ) + page_tags_by_arn = parse_get_resources_response_for_tags_by_arn(page) + tags_by_arn_cache.update(page_tags_by_arn) + tags_fetch_success = True + + except ClientError as e: + self.logger.exception( + "Encountered a ClientError when trying to fetch tags. You may need to give " + "this Lambda's role the 'tag:GetResources' permission" + ) + additional_tags = [ + f"http_status_code:{e.response['ResponseMetadata']['HTTPStatusCode']}" + ] + send_forwarder_internal_metrics( + "client_error", additional_tags=additional_tags + ) + + self.logger.debug( + "All Step Functions tags refreshed: {}".format(tags_by_arn_cache) + ) + + return tags_fetch_success, tags_by_arn_cache + + def get(self, state_machine_arn): + """Get the tags for the Step Functions from the cache + + Will re-fetch the tags if they are out of date, or a log group is encountered + which isn't in the tag list + + Args: + state_machine_arn (str): the key we're getting tags from the cache for + + Returns: + state_machine_tags (List[str]): the list of "key:value" Datadog tag strings + """ + if self._is_expired(): + send_forwarder_internal_metrics("local_step_functions_tags_cache_expired") + self.logger.debug( # noqa: F821 + "Local cache expired for Step Functions tags. Fetching cache from S3" + ) + self._refresh() + + state_machine_tags = self.tags_by_id.get(state_machine_arn, None) + if state_machine_tags is None: + # If the custom tag fetch env var is not set to true do not fetch + if not self.should_fetch_tags(): + self.logger.debug( + "Not fetching custom tags because the env variable DD_FETCH_STEP_FUNCTIONS_TAGS" + " is not set to true" + ) + return [] + state_machine_tags = self._get_state_machine_tags(state_machine_arn) or [] + self.tags_by_id[state_machine_arn] = state_machine_tags + + return state_machine_tags + + def _get_state_machine_tags(self, state_machine_arn: str): + """Return a list of tags of a state machine in dd format (max 200 chars) + + Example response from get source api: + { + "ResourceTagMappingList": [ + { + "ResourceARN": "arn:aws:states:us-east-1:1234567890:stateMachine:example-machine", + "Tags": [ + { + "Key": "ENV", + "Value": "staging" + } + ] + } + ] + } + + Args: + state_machine_arn (str): the key we're getting tags from the cache for + Returns: + state_machine_arn (List[str]): e.g. ["k1:v1", "k2:v2"] + """ + response = None + formatted_tags = [] + + try: + send_forwarder_internal_metrics("get_state_machine_tags") + response = self.resource_tagging_client.get_resources( + ResourceARNList=[state_machine_arn] + ) + except Exception as e: + self.logger.exception(f"Failed to get Step Functions tags due to {e}") + + if response and len(response.get("ResourceTagMappingList", {})) > 0: + resource_dict = response.get("ResourceTagMappingList")[0] + for a_tag in resource_dict.get("Tags", []): + key = sanitize_aws_tag_string(a_tag["Key"], remove_colons=True) + value = sanitize_aws_tag_string( + a_tag.get("Value"), remove_leading_digits=False + ) + formatted_tags.append(f"{key}:{value}"[:200]) # same logic as lambda + + return formatted_tags diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattr/.DS_Store b/lambdas/aws-dd-forwarder-3.127.0/cattr/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..d86ee78a2df942869f542add7ddcfcb8b72b86c2 GIT binary patch literal 6148 zcmeHKu};H447E!UMO`{F-Y@hILKVKCD^j2-G(*EhNDOTEX-s?sU&Hg+Dx@I;3j$FGSWw2Qo2>EOOqXrdz71p*yMu-fsAf49MNh zaa$F2^o+avxW8(@TdwQacAwH7@oZyRZuW5lPwbbPm+RNd@qOF4{f2kf46-;wu8;VQ z)fDN0?)!P3zTxB}kE8r{mfygum)D3_Kc9rdT$}-Cz!~^+44`I<%;t(dIs?vtGjL!) zz7GK^SQsY7{OQ1uTL9n`<}8>?FCj6(urN%DSb?yH0yUJa#b6DGJ(yo%m=raf*qRTv zli4~H&ZlGlkll$3MIW63XQ0o(i9Rl*{$Gvv|NS6;at54%f5iY#%XPWJD_LzFyqwh9 s0KI{Vh+k4XgkX|OF?^*IUqG|K9%KS643i=(5dR~PY4E`r_)!Ml0o%S%EdT%j literal 0 HcmV?d00001 diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattr/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/cattr/__init__.py new file mode 100644 index 0000000..6c262fe --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattr/__init__.py @@ -0,0 +1,25 @@ +from .converters import BaseConverter, Converter, GenConverter, UnstructureStrategy +from .gen import override + +__all__ = ( + "global_converter", + "unstructure", + "structure", + "structure_attrs_fromtuple", + "structure_attrs_fromdict", + "UnstructureStrategy", + "BaseConverter", + "Converter", + "GenConverter", + "override", +) +from cattrs import global_converter + +unstructure = global_converter.unstructure +structure = global_converter.structure +structure_attrs_fromtuple = global_converter.structure_attrs_fromtuple +structure_attrs_fromdict = global_converter.structure_attrs_fromdict +register_structure_hook = global_converter.register_structure_hook +register_structure_hook_func = global_converter.register_structure_hook_func +register_unstructure_hook = global_converter.register_unstructure_hook +register_unstructure_hook_func = global_converter.register_unstructure_hook_func diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattr/converters.py b/lambdas/aws-dd-forwarder-3.127.0/cattr/converters.py new file mode 100644 index 0000000..4434fe5 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattr/converters.py @@ -0,0 +1,8 @@ +from cattrs.converters import ( + BaseConverter, + Converter, + GenConverter, + UnstructureStrategy, +) + +__all__ = ["BaseConverter", "Converter", "GenConverter", "UnstructureStrategy"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattr/disambiguators.py b/lambdas/aws-dd-forwarder-3.127.0/cattr/disambiguators.py new file mode 100644 index 0000000..f10797a --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattr/disambiguators.py @@ -0,0 +1,3 @@ +from cattrs.disambiguators import create_uniq_field_dis_func + +__all__ = ["create_uniq_field_dis_func"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattr/dispatch.py b/lambdas/aws-dd-forwarder-3.127.0/cattr/dispatch.py new file mode 100644 index 0000000..2474247 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattr/dispatch.py @@ -0,0 +1,3 @@ +from cattrs.dispatch import FunctionDispatch, MultiStrategyDispatch + +__all__ = ["FunctionDispatch", "MultiStrategyDispatch"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattr/errors.py b/lambdas/aws-dd-forwarder-3.127.0/cattr/errors.py new file mode 100644 index 0000000..af092e9 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattr/errors.py @@ -0,0 +1,15 @@ +from cattrs.errors import ( + BaseValidationError, + ClassValidationError, + ForbiddenExtraKeysError, + IterableValidationError, + StructureHandlerNotFoundError, +) + +__all__ = [ + "BaseValidationError", + "ClassValidationError", + "ForbiddenExtraKeysError", + "IterableValidationError", + "StructureHandlerNotFoundError", +] diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattr/gen.py b/lambdas/aws-dd-forwarder-3.127.0/cattr/gen.py new file mode 100644 index 0000000..b1f63b5 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattr/gen.py @@ -0,0 +1,21 @@ +from cattrs.cols import iterable_unstructure_factory as make_iterable_unstructure_fn +from cattrs.gen import ( + make_dict_structure_fn, + make_dict_unstructure_fn, + make_hetero_tuple_unstructure_fn, + make_mapping_structure_fn, + make_mapping_unstructure_fn, + override, +) +from cattrs.gen._consts import AttributeOverride + +__all__ = [ + "AttributeOverride", + "make_dict_structure_fn", + "make_dict_unstructure_fn", + "make_hetero_tuple_unstructure_fn", + "make_iterable_unstructure_fn", + "make_mapping_structure_fn", + "make_mapping_unstructure_fn", + "override", +] diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/__init__.py new file mode 100644 index 0000000..fa6ad35 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/__init__.py @@ -0,0 +1,3 @@ +from cattrs.preconf import validate_datetime + +__all__ = ["validate_datetime"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/bson.py b/lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/bson.py new file mode 100644 index 0000000..4ac9743 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/bson.py @@ -0,0 +1,5 @@ +"""Preconfigured converters for bson.""" + +from cattrs.preconf.bson import BsonConverter, configure_converter, make_converter + +__all__ = ["BsonConverter", "configure_converter", "make_converter"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/json.py b/lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/json.py new file mode 100644 index 0000000..d590bd6 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/json.py @@ -0,0 +1,5 @@ +"""Preconfigured converters for the stdlib json.""" + +from cattrs.preconf.json import JsonConverter, configure_converter, make_converter + +__all__ = ["configure_converter", "JsonConverter", "make_converter"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/msgpack.py b/lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/msgpack.py new file mode 100644 index 0000000..1a579d6 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/msgpack.py @@ -0,0 +1,5 @@ +"""Preconfigured converters for msgpack.""" + +from cattrs.preconf.msgpack import MsgpackConverter, configure_converter, make_converter + +__all__ = ["configure_converter", "make_converter", "MsgpackConverter"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/orjson.py b/lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/orjson.py new file mode 100644 index 0000000..4450990 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/orjson.py @@ -0,0 +1,5 @@ +"""Preconfigured converters for orjson.""" + +from cattrs.preconf.orjson import OrjsonConverter, configure_converter, make_converter + +__all__ = ["configure_converter", "make_converter", "OrjsonConverter"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/pyyaml.py b/lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/pyyaml.py new file mode 100644 index 0000000..63d39f1 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/pyyaml.py @@ -0,0 +1,5 @@ +"""Preconfigured converters for pyyaml.""" + +from cattrs.preconf.pyyaml import PyyamlConverter, configure_converter, make_converter + +__all__ = ["configure_converter", "make_converter", "PyyamlConverter"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/tomlkit.py b/lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/tomlkit.py new file mode 100644 index 0000000..6add731 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/tomlkit.py @@ -0,0 +1,5 @@ +"""Preconfigured converters for tomlkit.""" + +from cattrs.preconf.tomlkit import TomlkitConverter, configure_converter, make_converter + +__all__ = ["configure_converter", "make_converter", "TomlkitConverter"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/ujson.py b/lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/ujson.py new file mode 100644 index 0000000..ef85c47 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattr/preconf/ujson.py @@ -0,0 +1,5 @@ +"""Preconfigured converters for ujson.""" + +from cattrs.preconf.ujson import UjsonConverter, configure_converter, make_converter + +__all__ = ["configure_converter", "make_converter", "UjsonConverter"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattr/py.typed b/lambdas/aws-dd-forwarder-3.127.0/cattr/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs-24.1.2.dist-info/INSTALLER b/lambdas/aws-dd-forwarder-3.127.0/cattrs-24.1.2.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs-24.1.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs-24.1.2.dist-info/METADATA b/lambdas/aws-dd-forwarder-3.127.0/cattrs-24.1.2.dist-info/METADATA new file mode 100644 index 0000000..0c6a750 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs-24.1.2.dist-info/METADATA @@ -0,0 +1,161 @@ +Metadata-Version: 2.3 +Name: cattrs +Version: 24.1.2 +Summary: Composable complex class support for attrs and dataclasses. +Project-URL: Homepage, https://catt.rs +Project-URL: Changelog, https://catt.rs/en/latest/history.html +Project-URL: Bug Tracker, https://github.com/python-attrs/cattrs/issues +Project-URL: Repository, https://github.com/python-attrs/cattrs +Project-URL: Documentation, https://catt.rs/en/stable/ +Author-email: Tin Tvrtkovic +License: MIT +License-File: LICENSE +Keywords: attrs,dataclasses,serialization +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Typing :: Typed +Requires-Python: >=3.8 +Requires-Dist: attrs>=23.1.0 +Requires-Dist: exceptiongroup>=1.1.1; python_version < '3.11' +Requires-Dist: typing-extensions!=4.6.3,>=4.1.0; python_version < '3.11' +Provides-Extra: bson +Requires-Dist: pymongo>=4.4.0; extra == 'bson' +Provides-Extra: cbor2 +Requires-Dist: cbor2>=5.4.6; extra == 'cbor2' +Provides-Extra: msgpack +Requires-Dist: msgpack>=1.0.5; extra == 'msgpack' +Provides-Extra: msgspec +Requires-Dist: msgspec>=0.18.5; (implementation_name == 'cpython') and extra == 'msgspec' +Provides-Extra: orjson +Requires-Dist: orjson>=3.9.2; (implementation_name == 'cpython') and extra == 'orjson' +Provides-Extra: pyyaml +Requires-Dist: pyyaml>=6.0; extra == 'pyyaml' +Provides-Extra: tomlkit +Requires-Dist: tomlkit>=0.11.8; extra == 'tomlkit' +Provides-Extra: ujson +Requires-Dist: ujson>=5.7.0; extra == 'ujson' +Description-Content-Type: text/markdown + +# *cattrs*: Flexible Object Serialization and Validation + +*Because validation belongs to the edges.* + +[![Documentation](https://img.shields.io/badge/Docs-Read%20The%20Docs-black)](https://catt.rs/) +[![License: MIT](https://img.shields.io/badge/license-MIT-C06524)](https://github.com/hynek/stamina/blob/main/LICENSE) +[![PyPI](https://img.shields.io/pypi/v/cattrs.svg)](https://pypi.python.org/pypi/cattrs) +[![Supported Python Versions](https://img.shields.io/pypi/pyversions/cattrs.svg)](https://github.com/python-attrs/cattrs) +[![Downloads](https://static.pepy.tech/badge/cattrs/month)](https://pepy.tech/project/cattrs) +[![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/Tinche/22405310d6a663164d894a2beab4d44d/raw/covbadge.json)](https://github.com/python-attrs/cattrs/actions/workflows/main.yml) + +--- + + + +**cattrs** is a Swiss Army knife for (un)structuring and validating data in Python. +In practice, that means it converts **unstructured dictionaries** into **proper classes** and back, while **validating** their contents. + + + + +## Example + + + +_cattrs_ works best with [_attrs_](https://www.attrs.org/) classes, and [dataclasses](https://docs.python.org/3/library/dataclasses.html) where simple (un-)structuring works out of the box, even for nested data, without polluting your data model with serialization details: + +```python +>>> from attrs import define +>>> from cattrs import structure, unstructure +>>> @define +... class C: +... a: int +... b: list[str] +>>> instance = structure({'a': 1, 'b': ['x', 'y']}, C) +>>> instance +C(a=1, b=['x', 'y']) +>>> unstructure(instance) +{'a': 1, 'b': ['x', 'y']} +``` + + + + +Have a look at [*Why *cattrs*?*](https://catt.rs/en/latest/why.html) for more examples! + + + +## Features + +### Recursive Unstructuring + +- _attrs_ classes and dataclasses are converted into dictionaries in a way similar to `attrs.asdict()`, or into tuples in a way similar to `attrs.astuple()`. +- Enumeration instances are converted to their values. +- Other types are let through without conversion. This includes types such as integers, dictionaries, lists and instances of non-_attrs_ classes. +- Custom converters for any type can be registered using `register_unstructure_hook`. + + +### Recursive Structuring + +Converts unstructured data into structured data, recursively, according to your specification given as a type. +The following types are supported: + +- `typing.Optional[T]` and its 3.10+ form, `T | None`. +- `list[T]`, `typing.List[T]`, `typing.MutableSequence[T]`, `typing.Sequence[T]` convert to a lists. +- `tuple` and `typing.Tuple` (both variants, `tuple[T, ...]` and `tuple[X, Y, Z]`). +- `set[T]`, `typing.MutableSet[T]`, and `typing.Set[T]` convert to a sets. +- `frozenset[T]`, and `typing.FrozenSet[T]` convert to a frozensets. +- `dict[K, V]`, `typing.Dict[K, V]`, `typing.MutableMapping[K, V]`, and `typing.Mapping[K, V]` convert to a dictionaries. +- [`typing.TypedDict`](https://docs.python.org/3/library/typing.html#typing.TypedDict), ordinary and generic. +- [`typing.NewType`](https://docs.python.org/3/library/typing.html#newtype) +- [PEP 695 type aliases](https://docs.python.org/3/library/typing.html#type-aliases) on 3.12+ +- _attrs_ classes with simple attributes and the usual `__init__`[^simple]. +- All _attrs_ classes and dataclasses with the usual `__init__`, if their complex attributes have type metadata. +- Unions of supported _attrs_ classes, given that all of the classes have a unique field. +- Unions of anything, if you provide a disambiguation function for it. +- Custom converters for any type can be registered using `register_structure_hook`. + +[^simple]: Simple attributes are attributes that can be assigned unstructured data, like numbers, strings, and collections of unstructured data. + + +### Batteries Included + +_cattrs_ comes with pre-configured converters for a number of serialization libraries, including JSON (standard library, [_orjson_](https://pypi.org/project/orjson/), [UltraJSON](https://pypi.org/project/ujson/)), [_msgpack_](https://pypi.org/project/msgpack/), [_cbor2_](https://pypi.org/project/cbor2/), [_bson_](https://pypi.org/project/bson/), [PyYAML](https://pypi.org/project/PyYAML/), [_tomlkit_](https://pypi.org/project/tomlkit/) and [_msgspec_](https://pypi.org/project/msgspec/) (supports only JSON at this time). + +For details, see the [cattrs.preconf package](https://catt.rs/en/stable/preconf.html). + + +## Design Decisions + +_cattrs_ is based on a few fundamental design decisions: + +- Un/structuring rules are separate from the models. + This allows models to have a one-to-many relationship with un/structuring rules, and to create un/structuring rules for models which you do not own and you cannot change. + (_cattrs_ can be configured to use un/structuring rules from models using the [`use_class_methods` strategy](https://catt.rs/en/latest/strategies.html#using-class-specific-structure-and-unstructure-methods).) +- Invent as little as possible; reuse existing ordinary Python instead. + For example, _cattrs_ did not have a custom exception type to group exceptions until the sanctioned Python [`exceptiongroups`](https://docs.python.org/3/library/exceptions.html#ExceptionGroup). + A side-effect of this design decision is that, in a lot of cases, when you're solving _cattrs_ problems you're actually learning Python instead of learning _cattrs_. +- Resist the temptation to guess. + If there are two ways of solving a problem, _cattrs_ should refuse to guess and let the user configure it themselves. + +A foolish consistency is the hobgoblin of little minds, so these decisions can and are sometimes broken, but they have proven to be a good foundation. + + + + +## Credits + +Major credits to Hynek Schlawack for creating [attrs](https://attrs.org) and its predecessor, [characteristic](https://github.com/hynek/characteristic). + +_cattrs_ is tested with [Hypothesis](http://hypothesis.readthedocs.io/en/latest/), by David R. MacIver. + +_cattrs_ is benchmarked using [perf](https://github.com/haypo/perf) and [pytest-benchmark](https://pytest-benchmark.readthedocs.io/en/latest/index.html). + +This package was created with [Cookiecutter](https://github.com/audreyr/cookiecutter) and the [`audreyr/cookiecutter-pypackage`](https://github.com/audreyr/cookiecutter-pypackage) project template. diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs-24.1.2.dist-info/RECORD b/lambdas/aws-dd-forwarder-3.127.0/cattrs-24.1.2.dist-info/RECORD new file mode 100644 index 0000000..f7b3ae3 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs-24.1.2.dist-info/RECORD @@ -0,0 +1,96 @@ +cattr/__init__.py,sha256=pODFKaZ7MisyHe_XPc9X6KKG73mqduHUvQO142XwijY,906 +cattr/__pycache__/__init__.cpython-311.pyc,, +cattr/__pycache__/converters.cpython-311.pyc,, +cattr/__pycache__/disambiguators.cpython-311.pyc,, +cattr/__pycache__/dispatch.cpython-311.pyc,, +cattr/__pycache__/errors.cpython-311.pyc,, +cattr/__pycache__/gen.cpython-311.pyc,, +cattr/converters.py,sha256=rQhY4J8r7QTZh5WICuFe4GWO1v0DS3DgQ9r569zd6jg,192 +cattr/disambiguators.py,sha256=ugD1fq1Z5x1pGu5P1lMzcT-IEi1q7IfQJIHEdmg62vM,103 +cattr/dispatch.py,sha256=uVEOgHWR9Hn5tm-wIw-bDccqrxJByVi8yRKaYyvL67k,125 +cattr/errors.py,sha256=V4RhoCObwGrlaM3oyn1H_FYxGR8iAB9dG5NxFDYM548,343 +cattr/gen.py,sha256=hWyKoZ_d2D36Jz_npspyGw8s9pWtUA69sXf0R3uOvgM,597 +cattr/preconf/__init__.py,sha256=NqPE7uhVfcP-PggkUpsbfAutMo8oHjcoB1cvjgLft-s,78 +cattr/preconf/__pycache__/__init__.cpython-311.pyc,, +cattr/preconf/__pycache__/bson.cpython-311.pyc,, +cattr/preconf/__pycache__/json.cpython-311.pyc,, +cattr/preconf/__pycache__/msgpack.cpython-311.pyc,, +cattr/preconf/__pycache__/orjson.cpython-311.pyc,, +cattr/preconf/__pycache__/pyyaml.cpython-311.pyc,, +cattr/preconf/__pycache__/tomlkit.cpython-311.pyc,, +cattr/preconf/__pycache__/ujson.cpython-311.pyc,, +cattr/preconf/bson.py,sha256=Bn4hJxac7OthGg_CR4LCPeBp_fz4kx3QniBVOZhguGs,195 +cattr/preconf/json.py,sha256=HBxWOTqKI7HOlmt-GnN6_wjQz1VphRi70sAOEbx0A2Y,206 +cattr/preconf/msgpack.py,sha256=VXqynPel11_lX8uTg84-u27LQhCqL1OoiF-lTqnoAkQ,207 +cattr/preconf/orjson.py,sha256=fs8qDPDYSBba9D8ib9Df1WVZ8iZaRPQq7kDigAxp14E,203 +cattr/preconf/pyyaml.py,sha256=lhuKwHrcvr16WOtdW4Q0mgIRzB90v1hwZkFXtPKOvAw,203 +cattr/preconf/tomlkit.py,sha256=rk393txIBHeWR66LfnATPh9Im1EFAHPJvSEGGSP2c-8,207 +cattr/preconf/ujson.py,sha256=r6ufraKDqmKdetNZUKxLYVSGmuJ-ckc-UjGYvCamr9k,199 +cattr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +cattrs-24.1.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +cattrs-24.1.2.dist-info/METADATA,sha256=Dw1BXPd1jf0ooO8yiPhPNKrkXvGklnIuiYPdELv-Ohk,8420 +cattrs-24.1.2.dist-info/RECORD,, +cattrs-24.1.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +cattrs-24.1.2.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87 +cattrs-24.1.2.dist-info/licenses/LICENSE,sha256=9fudHt43qIykf0IMSZ3KD0oFvJk-Esd9I1IKrSkcAb8,1074 +cattrs/__init__.py,sha256=peO0_Q9AEguPCMjXlRH-Nj0CahcCw5CJmpnpKxsWKSQ,1835 +cattrs/__pycache__/__init__.cpython-311.pyc,, +cattrs/__pycache__/_compat.cpython-311.pyc,, +cattrs/__pycache__/_generics.cpython-311.pyc,, +cattrs/__pycache__/cols.cpython-311.pyc,, +cattrs/__pycache__/converters.cpython-311.pyc,, +cattrs/__pycache__/disambiguators.cpython-311.pyc,, +cattrs/__pycache__/dispatch.cpython-311.pyc,, +cattrs/__pycache__/errors.cpython-311.pyc,, +cattrs/__pycache__/fns.cpython-311.pyc,, +cattrs/__pycache__/v.cpython-311.pyc,, +cattrs/_compat.py,sha256=DmHUZNi_MnI2UKvNPxwr77zuMs5tl3zDM4rdJK7kJiI,17620 +cattrs/_generics.py,sha256=ymyDdLjXoYi_XPBA_f_-xJC7Bc8RGqoUcdlwTbB7xl8,718 +cattrs/cols.py,sha256=sB9NTOp8pGLMUxVicSHWpcX_4czrD1g5MdCJO0Ko5s0,8433 +cattrs/converters.py,sha256=nMxuapDj3Q75oW4sVXnYdIeHhodwzLNUcDcaIfKMLQM,53916 +cattrs/disambiguators.py,sha256=ljl73QtSB3MAGcl7-phAUR66b4yx_1ORYLb5fUgW8bY,6825 +cattrs/dispatch.py,sha256=fEE100tCqcqC_wl5y2FCdVEocLOuDlys0sduJrTfmB4,6810 +cattrs/errors.py,sha256=rHps9Qp7SoRafb2VuAkMbhsQf4pq87gX1SzM-jluMsE,4070 +cattrs/fns.py,sha256=xQceStzW4qLiMTJgGM-pVUudGwHm0Hin8oCYe1feS5c,633 +cattrs/gen/__init__.py,sha256=yBOs4V1SQ6RAPFSGyIkwi4ZEU7fqA_nQrH6ujgT88eI,38527 +cattrs/gen/__pycache__/__init__.cpython-311.pyc,, +cattrs/gen/__pycache__/_consts.cpython-311.pyc,, +cattrs/gen/__pycache__/_generics.cpython-311.pyc,, +cattrs/gen/__pycache__/_lc.cpython-311.pyc,, +cattrs/gen/__pycache__/_shared.cpython-311.pyc,, +cattrs/gen/__pycache__/typeddicts.cpython-311.pyc,, +cattrs/gen/_consts.py,sha256=ZwT_m2J3S7p-UjltpbA1WtfQZLNj9KhmFYCAv6Zl-g0,511 +cattrs/gen/_generics.py,sha256=_DyXCGql2QIxGhAv3_B1hsi80uPK8PhK2hhZa95YOlo,3011 +cattrs/gen/_lc.py,sha256=ktP5F9oOUo4HpZ4-hlLliLPzr8XjFi31EXMl8YMMs-g,906 +cattrs/gen/_shared.py,sha256=4yX9-TD5yyVzDWlSjkECrQV5B82xHUeBt9n2N5UgOAE,2064 +cattrs/gen/typeddicts.py,sha256=C3Bp8tNM-MI7L7KO0X3sfwSkG5d0ua3j7qDtvcCEBQk,22004 +cattrs/preconf/__init__.py,sha256=dfkUXoU47ZJfmoKX9FsnARKqLlgJeBjMxORMzxrbKbs,604 +cattrs/preconf/__pycache__/__init__.cpython-311.pyc,, +cattrs/preconf/__pycache__/bson.cpython-311.pyc,, +cattrs/preconf/__pycache__/cbor2.cpython-311.pyc,, +cattrs/preconf/__pycache__/json.cpython-311.pyc,, +cattrs/preconf/__pycache__/msgpack.cpython-311.pyc,, +cattrs/preconf/__pycache__/msgspec.cpython-311.pyc,, +cattrs/preconf/__pycache__/orjson.cpython-311.pyc,, +cattrs/preconf/__pycache__/pyyaml.cpython-311.pyc,, +cattrs/preconf/__pycache__/tomlkit.cpython-311.pyc,, +cattrs/preconf/__pycache__/ujson.cpython-311.pyc,, +cattrs/preconf/bson.py,sha256=uBRpTVfwGZ-qfuDYGwsl8eXokVAmcVBedKQPGUmamhc,3656 +cattrs/preconf/cbor2.py,sha256=ANfQUXgs7pyU5-4_2hYmcqUxzQZhWhFzrk_0y6b1yYw,1635 +cattrs/preconf/json.py,sha256=CSU5RosdYyg6cIOpaohgZVfdMtOtKjZlSg837fW4fTw,2035 +cattrs/preconf/msgpack.py,sha256=cgwX_ARi_swQjG6hwa9j-n7FUynLNWIMVLouz_VoTuw,1753 +cattrs/preconf/msgspec.py,sha256=f8J04RXv8UErKAwwzVs1cMbvoM-9erMmmF49zKBbCDo,6343 +cattrs/preconf/orjson.py,sha256=RZ8DI-4K7Xi0QdpIihT9I3Cm-O8Aq8_MTt2R3a4fgEk,3241 +cattrs/preconf/pyyaml.py,sha256=Ga96zLypn2DglTgbrb9h3jcuH-caur_UQI1ADo-ynUA,2298 +cattrs/preconf/tomlkit.py,sha256=2k-BN0ZW3faWmHcMQ1bCvsKCClhdgSjTe056O1xEc4o,3060 +cattrs/preconf/ujson.py,sha256=JBh5dWluwMwKhAJPINJhpse_aQ1p9hzrGo8BuvmG6S0,1863 +cattrs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +cattrs/strategies/__init__.py,sha256=nkZWCzSRYcS-75FMfk52mioZSuWykaN8hB39Vig5Xkg,339 +cattrs/strategies/__pycache__/__init__.cpython-311.pyc,, +cattrs/strategies/__pycache__/_class_methods.cpython-311.pyc,, +cattrs/strategies/__pycache__/_subclasses.cpython-311.pyc,, +cattrs/strategies/__pycache__/_unions.cpython-311.pyc,, +cattrs/strategies/_class_methods.py,sha256=vfiE3wKm04oc-3T9hchsIyhVzpMpJRdgTbujKsWyVpQ,2597 +cattrs/strategies/_subclasses.py,sha256=zzhLl7fSZlmlBuBY-rPX7L1d_C5tiDFDBmUTeRpG2uI,9204 +cattrs/strategies/_unions.py,sha256=l8CjVVFAwftkBa47g3m2KgtQ_b42Wnv-KwYY_LHReCA,9166 +cattrs/v.py,sha256=cTYt0EW8yr-gzKynw4_XjFv3RLpAF8IebvOb612l9QE,4399 diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs-24.1.2.dist-info/REQUESTED b/lambdas/aws-dd-forwarder-3.127.0/cattrs-24.1.2.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs-24.1.2.dist-info/WHEEL b/lambdas/aws-dd-forwarder-3.127.0/cattrs-24.1.2.dist-info/WHEEL new file mode 100644 index 0000000..cdd68a4 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs-24.1.2.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.25.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs-24.1.2.dist-info/licenses/LICENSE b/lambdas/aws-dd-forwarder-3.127.0/cattrs-24.1.2.dist-info/licenses/LICENSE new file mode 100644 index 0000000..340022c --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs-24.1.2.dist-info/licenses/LICENSE @@ -0,0 +1,11 @@ + +MIT License + +Copyright (c) 2016, Tin Tvrtković + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/.DS_Store b/lambdas/aws-dd-forwarder-3.127.0/cattrs/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5aad9a2de44ee5958d21f27c0be89c9727ce63be GIT binary patch literal 6148 zcmeHKu};G<5Ixf%s&?tfm|y716sqtAS&>ScmK3RJDhd*8{v=|)8Ax7KOt z@XezY74V@q<};ujJ%P~-ehDoe;CCUb;wlyw;dXS*2*abXAy3OZ)lORh+}UjXp-1(q zfGVI0>=oeiAw^@%1g%GRbilbM05HL@GxYhFf^$;AOwfA749xgcpiedO#4tV`idN^-~v4{s#Ab^{Rj>a8RJx< literal 0 HcmV?d00001 diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/__init__.py new file mode 100644 index 0000000..db49636 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/__init__.py @@ -0,0 +1,55 @@ +from typing import Final + +from .converters import BaseConverter, Converter, GenConverter, UnstructureStrategy +from .errors import ( + AttributeValidationNote, + BaseValidationError, + ClassValidationError, + ForbiddenExtraKeysError, + IterableValidationError, + IterableValidationNote, + StructureHandlerNotFoundError, +) +from .gen import override +from .v import transform_error + +__all__ = [ + "structure", + "unstructure", + "get_structure_hook", + "get_unstructure_hook", + "register_structure_hook_func", + "register_structure_hook", + "register_unstructure_hook_func", + "register_unstructure_hook", + "structure_attrs_fromdict", + "structure_attrs_fromtuple", + "global_converter", + "BaseConverter", + "Converter", + "AttributeValidationNote", + "BaseValidationError", + "ClassValidationError", + "ForbiddenExtraKeysError", + "GenConverter", + "IterableValidationError", + "IterableValidationNote", + "override", + "StructureHandlerNotFoundError", + "transform_error", + "UnstructureStrategy", +] + +#: The global converter. Prefer creating your own if customizations are required. +global_converter: Final = Converter() + +unstructure = global_converter.unstructure +structure = global_converter.structure +structure_attrs_fromtuple = global_converter.structure_attrs_fromtuple +structure_attrs_fromdict = global_converter.structure_attrs_fromdict +register_structure_hook = global_converter.register_structure_hook +register_structure_hook_func = global_converter.register_structure_hook_func +register_unstructure_hook = global_converter.register_unstructure_hook +register_unstructure_hook_func = global_converter.register_unstructure_hook_func +get_structure_hook: Final = global_converter.get_structure_hook +get_unstructure_hook: Final = global_converter.get_unstructure_hook diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/_compat.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/_compat.py new file mode 100644 index 0000000..027ef47 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/_compat.py @@ -0,0 +1,578 @@ +import sys +from collections import deque +from collections.abc import Mapping as AbcMapping +from collections.abc import MutableMapping as AbcMutableMapping +from collections.abc import MutableSet as AbcMutableSet +from collections.abc import Set as AbcSet +from dataclasses import MISSING, Field, is_dataclass +from dataclasses import fields as dataclass_fields +from functools import partial +from inspect import signature as _signature +from typing import AbstractSet as TypingAbstractSet +from typing import ( + Any, + Deque, + Dict, + Final, + FrozenSet, + List, + Literal, + NewType, + Optional, + Protocol, + Tuple, + Type, + Union, + get_args, + get_origin, + get_type_hints, +) +from typing import Mapping as TypingMapping +from typing import MutableMapping as TypingMutableMapping +from typing import MutableSequence as TypingMutableSequence +from typing import MutableSet as TypingMutableSet +from typing import Sequence as TypingSequence +from typing import Set as TypingSet + +from attrs import NOTHING, Attribute, Factory, resolve_types +from attrs import fields as attrs_fields +from attrs import fields_dict as attrs_fields_dict + +__all__ = [ + "ANIES", + "adapted_fields", + "fields_dict", + "ExceptionGroup", + "ExtensionsTypedDict", + "get_type_alias_base", + "has", + "is_type_alias", + "is_typeddict", + "TypeAlias", + "TypedDict", +] + +try: + from typing_extensions import TypedDict as ExtensionsTypedDict +except ImportError: # pragma: no cover + ExtensionsTypedDict = None + +if sys.version_info >= (3, 11): + from builtins import ExceptionGroup +else: + from exceptiongroup import ExceptionGroup + +try: + from typing_extensions import is_typeddict as _is_typeddict +except ImportError: # pragma: no cover + assert sys.version_info >= (3, 10) + from typing import is_typeddict as _is_typeddict + +try: + from typing_extensions import TypeAlias +except ImportError: # pragma: no cover + assert sys.version_info >= (3, 11) + from typing import TypeAlias + +LITERALS = {Literal} +try: + from typing_extensions import Literal as teLiteral + + LITERALS.add(teLiteral) +except ImportError: # pragma: no cover + pass + +# On some Python versions, `typing_extensions.Any` is different than +# `typing.Any`. +try: + from typing_extensions import Any as teAny + + ANIES = frozenset([Any, teAny]) +except ImportError: # pragma: no cover + ANIES = frozenset([Any]) + +NoneType = type(None) + + +def is_optional(typ: Type) -> bool: + return is_union_type(typ) and NoneType in typ.__args__ and len(typ.__args__) == 2 + + +def is_typeddict(cls): + """Thin wrapper around typing(_extensions).is_typeddict""" + return _is_typeddict(getattr(cls, "__origin__", cls)) + + +def is_type_alias(type: Any) -> bool: + """Is this a PEP 695 type alias?""" + return False + + +def get_type_alias_base(type: Any) -> Any: + """ + What is this a type alias of? + + Works only on 3.12+. + """ + return type.__value__ + + +def has(cls): + return hasattr(cls, "__attrs_attrs__") or hasattr(cls, "__dataclass_fields__") + + +def has_with_generic(cls): + """Test whether the class if a normal or generic attrs or dataclass.""" + return has(cls) or has(get_origin(cls)) + + +def fields(type): + try: + return type.__attrs_attrs__ + except AttributeError: + return dataclass_fields(type) + + +def fields_dict(type) -> Dict[str, Union[Attribute, Field]]: + """Return the fields_dict for attrs and dataclasses.""" + if is_dataclass(type): + return {f.name: f for f in dataclass_fields(type)} + return attrs_fields_dict(type) + + +def adapted_fields(cl) -> List[Attribute]: + """Return the attrs format of `fields()` for attrs and dataclasses.""" + if is_dataclass(cl): + attrs = dataclass_fields(cl) + if any(isinstance(a.type, str) for a in attrs): + # Do this conditionally in case `get_type_hints` fails, so + # users can resolve on their own first. + type_hints = get_type_hints(cl) + else: + type_hints = {} + return [ + Attribute( + attr.name, + ( + attr.default + if attr.default is not MISSING + else ( + Factory(attr.default_factory) + if attr.default_factory is not MISSING + else NOTHING + ) + ), + None, + True, + None, + True, + attr.init, + True, + type=type_hints.get(attr.name, attr.type), + alias=attr.name, + ) + for attr in attrs + ] + attribs = attrs_fields(cl) + if any(isinstance(a.type, str) for a in attribs): + # PEP 563 annotations - need to be resolved. + resolve_types(cl) + attribs = attrs_fields(cl) + return attribs + + +def is_subclass(obj: type, bases) -> bool: + """A safe version of issubclass (won't raise).""" + try: + return issubclass(obj, bases) + except TypeError: + return False + + +def is_hetero_tuple(type: Any) -> bool: + origin = getattr(type, "__origin__", None) + return origin is tuple and ... not in type.__args__ + + +def is_protocol(type: Any) -> bool: + return is_subclass(type, Protocol) and getattr(type, "_is_protocol", False) + + +def is_bare_final(type) -> bool: + return type is Final + + +def get_final_base(type) -> Optional[type]: + """Return the base of the Final annotation, if it is Final.""" + if type is Final: + return Any + if type.__class__ is _GenericAlias and type.__origin__ is Final: + return type.__args__[0] + return None + + +OriginAbstractSet = AbcSet +OriginMutableSet = AbcMutableSet + +signature = _signature + +if sys.version_info >= (3, 10): + signature = partial(_signature, eval_str=True) + +if sys.version_info >= (3, 9): + from collections import Counter + from collections.abc import MutableSequence as AbcMutableSequence + from collections.abc import MutableSet as AbcMutableSet + from collections.abc import Sequence as AbcSequence + from collections.abc import Set as AbcSet + from types import GenericAlias + from typing import ( + Annotated, + Generic, + TypedDict, + Union, + _AnnotatedAlias, + _GenericAlias, + _SpecialGenericAlias, + _UnionGenericAlias, + ) + from typing import Counter as TypingCounter + + try: + # Not present on 3.9.0, so we try carefully. + from typing import _LiteralGenericAlias + + def is_literal(type) -> bool: + return type in LITERALS or ( + isinstance( + type, (_GenericAlias, _LiteralGenericAlias, _SpecialGenericAlias) + ) + and type.__origin__ in LITERALS + ) + + except ImportError: # pragma: no cover + + def is_literal(_) -> bool: + return False + + Set = AbcSet + AbstractSet = AbcSet + MutableSet = AbcMutableSet + Sequence = AbcSequence + MutableSequence = AbcMutableSequence + MutableMapping = AbcMutableMapping + Mapping = AbcMapping + FrozenSetSubscriptable = frozenset + TupleSubscriptable = tuple + + def is_annotated(type) -> bool: + return getattr(type, "__class__", None) is _AnnotatedAlias + + def is_tuple(type): + return ( + type in (Tuple, tuple) + or (type.__class__ is _GenericAlias and is_subclass(type.__origin__, Tuple)) + or (getattr(type, "__origin__", None) is tuple) + ) + + if sys.version_info >= (3, 12): + from typing import TypeAliasType + + def is_type_alias(type: Any) -> bool: + """Is this a PEP 695 type alias?""" + return isinstance(type, TypeAliasType) + + if sys.version_info >= (3, 10): + + def is_union_type(obj): + from types import UnionType + + return ( + obj is Union + or (isinstance(obj, _UnionGenericAlias) and obj.__origin__ is Union) + or isinstance(obj, UnionType) + ) + + def get_newtype_base(typ: Any) -> Optional[type]: + if typ is NewType or isinstance(typ, NewType): + return typ.__supertype__ + return None + + if sys.version_info >= (3, 11): + from typing import NotRequired, Required + else: + from typing_extensions import NotRequired, Required + + else: + from typing_extensions import NotRequired, Required + + def is_union_type(obj): + return ( + obj is Union + or isinstance(obj, _UnionGenericAlias) + and obj.__origin__ is Union + ) + + def get_newtype_base(typ: Any) -> Optional[type]: + supertype = getattr(typ, "__supertype__", None) + if ( + supertype is not None + and getattr(typ, "__qualname__", "") == "NewType..new_type" + and typ.__module__ in ("typing", "typing_extensions") + ): + return supertype + return None + + def get_notrequired_base(type) -> "Union[Any, Literal[NOTHING]]": + if is_annotated(type): + # Handle `Annotated[NotRequired[int]]` + type = get_args(type)[0] + if get_origin(type) in (NotRequired, Required): + return get_args(type)[0] + return NOTHING + + def is_sequence(type: Any) -> bool: + """A predicate function for sequences. + + Matches lists, sequences, mutable sequences, deques and homogenous + tuples. + """ + origin = getattr(type, "__origin__", None) + return ( + type + in ( + List, + list, + TypingSequence, + TypingMutableSequence, + AbcMutableSequence, + tuple, + Tuple, + deque, + Deque, + ) + or ( + type.__class__ is _GenericAlias + and ( + (origin is not tuple) + and is_subclass(origin, TypingSequence) + or origin is tuple + and type.__args__[1] is ... + ) + ) + or (origin in (list, deque, AbcMutableSequence, AbcSequence)) + or (origin is tuple and type.__args__[1] is ...) + ) + + def is_deque(type): + return ( + type in (deque, Deque) + or (type.__class__ is _GenericAlias and is_subclass(type.__origin__, deque)) + or (getattr(type, "__origin__", None) is deque) + ) + + def is_mutable_set(type: Any) -> bool: + """A predicate function for (mutable) sets. + + Matches built-in sets and sets from the typing module. + """ + return ( + type in (TypingSet, TypingMutableSet, set) + or ( + type.__class__ is _GenericAlias + and is_subclass(type.__origin__, TypingMutableSet) + ) + or (getattr(type, "__origin__", None) in (set, AbcMutableSet, AbcSet)) + ) + + def is_frozenset(type: Any) -> bool: + """A predicate function for frozensets. + + Matches built-in frozensets and frozensets from the typing module. + """ + return ( + type in (FrozenSet, frozenset) + or ( + type.__class__ is _GenericAlias + and is_subclass(type.__origin__, FrozenSet) + ) + or (getattr(type, "__origin__", None) is frozenset) + ) + + def is_bare(type): + return isinstance(type, _SpecialGenericAlias) or ( + not hasattr(type, "__origin__") and not hasattr(type, "__args__") + ) + + def is_mapping(type: Any) -> bool: + """A predicate function for mappings.""" + return ( + type in (dict, Dict, TypingMapping, TypingMutableMapping, AbcMutableMapping) + or ( + type.__class__ is _GenericAlias + and is_subclass(type.__origin__, TypingMapping) + ) + or is_subclass( + getattr(type, "__origin__", type), (dict, AbcMutableMapping, AbcMapping) + ) + ) + + def is_counter(type): + return ( + type in (Counter, TypingCounter) + or getattr(type, "__origin__", None) is Counter + ) + + def is_generic(type) -> bool: + """Whether `type` is a generic type.""" + # Inheriting from protocol will inject `Generic` into the MRO + # without `__orig_bases__`. + return isinstance(type, (_GenericAlias, GenericAlias)) or ( + is_subclass(type, Generic) and hasattr(type, "__orig_bases__") + ) + + def copy_with(type, args): + """Replace a generic type's arguments.""" + if is_annotated(type): + # typing.Annotated requires a special case. + return Annotated[args] + if isinstance(args, tuple) and len(args) == 1: + # Some annotations can't handle 1-tuples. + args = args[0] + return type.__origin__[args] + + def get_full_type_hints(obj, globalns=None, localns=None): + return get_type_hints(obj, globalns, localns, include_extras=True) + +else: + # 3.8 + Set = TypingSet + AbstractSet = TypingAbstractSet + MutableSet = TypingMutableSet + + Sequence = TypingSequence + MutableSequence = TypingMutableSequence + MutableMapping = TypingMutableMapping + Mapping = TypingMapping + FrozenSetSubscriptable = FrozenSet + TupleSubscriptable = Tuple + + from collections import Counter as ColCounter + from typing import Counter, Generic, TypedDict, Union, _GenericAlias + + from typing_extensions import Annotated, NotRequired, Required + from typing_extensions import get_origin as te_get_origin + + def is_annotated(type) -> bool: + return te_get_origin(type) is Annotated + + def is_tuple(type): + return type in (Tuple, tuple) or ( + type.__class__ is _GenericAlias and is_subclass(type.__origin__, Tuple) + ) + + def is_union_type(obj): + return ( + obj is Union or isinstance(obj, _GenericAlias) and obj.__origin__ is Union + ) + + def get_newtype_base(typ: Any) -> Optional[type]: + supertype = getattr(typ, "__supertype__", None) + if ( + supertype is not None + and getattr(typ, "__qualname__", "") == "NewType..new_type" + and typ.__module__ in ("typing", "typing_extensions") + ): + return supertype + return None + + def is_sequence(type: Any) -> bool: + return type in (List, list, Tuple, tuple) or ( + type.__class__ is _GenericAlias + and ( + type.__origin__ not in (Union, Tuple, tuple) + and is_subclass(type.__origin__, TypingSequence) + ) + or (type.__origin__ in (Tuple, tuple) and type.__args__[1] is ...) + ) + + def is_deque(type: Any) -> bool: + return ( + type in (deque, Deque) + or (type.__class__ is _GenericAlias and is_subclass(type.__origin__, deque)) + or type.__origin__ is deque + ) + + def is_mutable_set(type) -> bool: + return type in (set, TypingAbstractSet) or ( + type.__class__ is _GenericAlias + and is_subclass(type.__origin__, (MutableSet, TypingAbstractSet)) + ) + + def is_frozenset(type): + return type is frozenset or ( + type.__class__ is _GenericAlias and is_subclass(type.__origin__, FrozenSet) + ) + + def is_mapping(type: Any) -> bool: + """A predicate function for mappings.""" + return ( + type in (TypingMapping, dict) + or ( + type.__class__ is _GenericAlias + and is_subclass(type.__origin__, TypingMapping) + ) + or is_subclass( + getattr(type, "__origin__", type), (dict, AbcMutableMapping, AbcMapping) + ) + ) + + bare_generic_args = { + List.__args__, + TypingSequence.__args__, + TypingMapping.__args__, + Dict.__args__, + TypingMutableSequence.__args__, + Tuple.__args__, + None, # non-parametrized containers do not have `__args__ attribute in py3.7-8 + } + + def is_bare(type): + return getattr(type, "__args__", None) in bare_generic_args + + def is_counter(type): + return ( + type in (Counter, ColCounter) + or getattr(type, "__origin__", None) is ColCounter + ) + + def is_literal(type) -> bool: + return type in LITERALS or ( + isinstance(type, _GenericAlias) and type.__origin__ in LITERALS + ) + + def is_generic(obj): + return isinstance(obj, _GenericAlias) or ( + is_subclass(obj, Generic) and hasattr(obj, "__orig_bases__") + ) + + def copy_with(type, args): + """Replace a generic type's arguments.""" + return type.copy_with(args) + + def get_notrequired_base(type) -> "Union[Any, Literal[NOTHING]]": + if is_annotated(type): + # Handle `Annotated[NotRequired[int]]` + type = get_origin(type) + + if get_origin(type) in (NotRequired, Required): + return get_args(type)[0] + return NOTHING + + def get_full_type_hints(obj, globalns=None, localns=None): + return get_type_hints(obj, globalns, localns) + + +def is_generic_attrs(type) -> bool: + """Return True for both specialized (A[int]) and unspecialized (A) generics.""" + return is_generic(type) and has(type.__origin__) diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/_generics.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/_generics.py new file mode 100644 index 0000000..c473f43 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/_generics.py @@ -0,0 +1,24 @@ +from typing import Any, Mapping + +from ._compat import copy_with, get_args, is_annotated, is_generic + + +def deep_copy_with(t, mapping: Mapping[str, Any]): + args = get_args(t) + rest = () + if is_annotated(t) and args: + # If we're dealing with `Annotated`, we only map the first type parameter + rest = tuple(args[1:]) + args = (args[0],) + new_args = ( + tuple( + ( + mapping[a.__name__] + if hasattr(a, "__name__") and a.__name__ in mapping + else (deep_copy_with(a, mapping) if is_generic(a) else a) + ) + for a in args + ) + + rest + ) + return copy_with(t, new_args) if new_args != args else t diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/cols.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/cols.py new file mode 100644 index 0000000..8ff5c0f --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/cols.py @@ -0,0 +1,289 @@ +"""Utility functions for collections.""" + +from __future__ import annotations + +from sys import version_info +from typing import ( + TYPE_CHECKING, + Any, + Iterable, + Literal, + NamedTuple, + Tuple, + TypeVar, + get_type_hints, +) + +from attrs import NOTHING, Attribute + +from ._compat import ANIES, is_bare, is_frozenset, is_mapping, is_sequence, is_subclass +from ._compat import is_mutable_set as is_set +from .dispatch import StructureHook, UnstructureHook +from .errors import IterableValidationError, IterableValidationNote +from .fns import identity +from .gen import ( + AttributeOverride, + already_generating, + make_dict_structure_fn_from_attrs, + make_dict_unstructure_fn_from_attrs, + make_hetero_tuple_unstructure_fn, + mapping_structure_factory, +) +from .gen import make_iterable_unstructure_fn as iterable_unstructure_factory + +if TYPE_CHECKING: + from .converters import BaseConverter + +__all__ = [ + "is_any_set", + "is_frozenset", + "is_namedtuple", + "is_mapping", + "is_set", + "is_sequence", + "iterable_unstructure_factory", + "list_structure_factory", + "namedtuple_structure_factory", + "namedtuple_unstructure_factory", + "namedtuple_dict_structure_factory", + "namedtuple_dict_unstructure_factory", + "mapping_structure_factory", +] + + +def is_any_set(type) -> bool: + """A predicate function for both mutable and frozensets.""" + return is_set(type) or is_frozenset(type) + + +if version_info[:2] >= (3, 9): + + def is_namedtuple(type: Any) -> bool: + """A predicate function for named tuples.""" + + if is_subclass(type, tuple): + for cl in type.mro(): + orig_bases = cl.__dict__.get("__orig_bases__", ()) + if NamedTuple in orig_bases: + return True + return False + +else: + + def is_namedtuple(type: Any) -> bool: + """A predicate function for named tuples.""" + # This is tricky. It may not be possible for this function to be 100% + # accurate, since it doesn't seem like we can distinguish between tuple + # subclasses and named tuples reliably. + + if is_subclass(type, tuple): + for cl in type.mro(): + if cl is tuple: + # No point going further. + break + if "_fields" in cl.__dict__: + return True + return False + + +def _is_passthrough(type: type[tuple], converter: BaseConverter) -> bool: + """If all fields would be passed through, this class should not be processed + either. + """ + return all( + converter.get_unstructure_hook(t) == identity + for t in type.__annotations__.values() + ) + + +T = TypeVar("T") + + +def list_structure_factory(type: type, converter: BaseConverter) -> StructureHook: + """A hook factory for structuring lists. + + Converts any given iterable into a list. + """ + + if is_bare(type) or type.__args__[0] in ANIES: + + def structure_list(obj: Iterable[T], _: type = type) -> list[T]: + return list(obj) + + return structure_list + + elem_type = type.__args__[0] + + try: + handler = converter.get_structure_hook(elem_type) + except RecursionError: + # Break the cycle by using late binding. + handler = converter.structure + + if converter.detailed_validation: + + def structure_list( + obj: Iterable[T], _: type = type, _handler=handler, _elem_type=elem_type + ) -> list[T]: + errors = [] + res = [] + ix = 0 # Avoid `enumerate` for performance. + for e in obj: + try: + res.append(handler(e, _elem_type)) + except Exception as e: + msg = IterableValidationNote( + f"Structuring {type} @ index {ix}", ix, elem_type + ) + e.__notes__ = [*getattr(e, "__notes__", []), msg] + errors.append(e) + finally: + ix += 1 + if errors: + raise IterableValidationError( + f"While structuring {type!r}", errors, type + ) + + return res + + else: + + def structure_list( + obj: Iterable[T], _: type = type, _handler=handler, _elem_type=elem_type + ) -> list[T]: + return [_handler(e, _elem_type) for e in obj] + + return structure_list + + +def namedtuple_unstructure_factory( + cl: type[tuple], converter: BaseConverter, unstructure_to: Any = None +) -> UnstructureHook: + """A hook factory for unstructuring namedtuples. + + :param unstructure_to: Force unstructuring to this type, if provided. + """ + + if unstructure_to is None and _is_passthrough(cl, converter): + return identity + + return make_hetero_tuple_unstructure_fn( + cl, + converter, + unstructure_to=tuple if unstructure_to is None else unstructure_to, + type_args=tuple(cl.__annotations__.values()), + ) + + +def namedtuple_structure_factory( + cl: type[tuple], converter: BaseConverter +) -> StructureHook: + """A hook factory for structuring namedtuples from iterables.""" + # We delegate to the existing infrastructure for heterogenous tuples. + hetero_tuple_type = Tuple[tuple(cl.__annotations__.values())] + base_hook = converter.get_structure_hook(hetero_tuple_type) + return lambda v, _: cl(*base_hook(v, hetero_tuple_type)) + + +def _namedtuple_to_attrs(cl: type[tuple]) -> list[Attribute]: + """Generate pseudo attributes for a namedtuple.""" + return [ + Attribute( + name, + cl._field_defaults.get(name, NOTHING), + None, + False, + False, + False, + True, + False, + type=a, + alias=name, + ) + for name, a in get_type_hints(cl).items() + ] + + +def namedtuple_dict_structure_factory( + cl: type[tuple], + converter: BaseConverter, + detailed_validation: bool | Literal["from_converter"] = "from_converter", + forbid_extra_keys: bool = False, + use_linecache: bool = True, + /, + **kwargs: AttributeOverride, +) -> StructureHook: + """A hook factory for hooks structuring namedtuples from dictionaries. + + :param forbid_extra_keys: Whether the hook should raise a `ForbiddenExtraKeysError` + if unknown keys are encountered. + :param use_linecache: Whether to store the source code in the Python linecache. + + .. versionadded:: 24.1.0 + """ + try: + working_set = already_generating.working_set + except AttributeError: + working_set = set() + already_generating.working_set = working_set + else: + if cl in working_set: + raise RecursionError() + + working_set.add(cl) + + try: + return make_dict_structure_fn_from_attrs( + _namedtuple_to_attrs(cl), + cl, + converter, + _cattrs_forbid_extra_keys=forbid_extra_keys, + _cattrs_use_detailed_validation=detailed_validation, + _cattrs_use_linecache=use_linecache, + **kwargs, + ) + finally: + working_set.remove(cl) + if not working_set: + del already_generating.working_set + + +def namedtuple_dict_unstructure_factory( + cl: type[tuple], + converter: BaseConverter, + omit_if_default: bool = False, + use_linecache: bool = True, + /, + **kwargs: AttributeOverride, +) -> UnstructureHook: + """A hook factory for hooks unstructuring namedtuples to dictionaries. + + :param omit_if_default: When true, attributes equal to their default values + will be omitted in the result dictionary. + :param use_linecache: Whether to store the source code in the Python linecache. + + .. versionadded:: 24.1.0 + """ + try: + working_set = already_generating.working_set + except AttributeError: + working_set = set() + already_generating.working_set = working_set + if cl in working_set: + raise RecursionError() + + working_set.add(cl) + + try: + return make_dict_unstructure_fn_from_attrs( + _namedtuple_to_attrs(cl), + cl, + converter, + _cattrs_omit_if_default=omit_if_default, + _cattrs_use_linecache=use_linecache, + **kwargs, + ) + finally: + working_set.remove(cl) + if not working_set: + del already_generating.working_set diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/converters.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/converters.py new file mode 100644 index 0000000..1490ec2 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/converters.py @@ -0,0 +1,1419 @@ +from __future__ import annotations + +from collections import Counter, deque +from collections.abc import Mapping as AbcMapping +from collections.abc import MutableMapping as AbcMutableMapping +from collections.abc import MutableSet as AbcMutableSet +from dataclasses import Field +from enum import Enum +from inspect import Signature +from inspect import signature as inspect_signature +from pathlib import Path +from typing import Any, Callable, Iterable, Optional, Tuple, TypeVar, overload + +from attrs import Attribute, resolve_types +from attrs import has as attrs_has + +from ._compat import ( + ANIES, + FrozenSetSubscriptable, + Mapping, + MutableMapping, + MutableSequence, + NoneType, + OriginAbstractSet, + OriginMutableSet, + Sequence, + Set, + TypeAlias, + fields, + get_final_base, + get_newtype_base, + get_origin, + get_type_alias_base, + has, + has_with_generic, + is_annotated, + is_bare, + is_counter, + is_deque, + is_frozenset, + is_generic, + is_generic_attrs, + is_hetero_tuple, + is_literal, + is_mapping, + is_mutable_set, + is_optional, + is_protocol, + is_sequence, + is_tuple, + is_type_alias, + is_typeddict, + is_union_type, + signature, +) +from .cols import ( + is_namedtuple, + iterable_unstructure_factory, + list_structure_factory, + namedtuple_structure_factory, + namedtuple_unstructure_factory, +) +from .disambiguators import create_default_dis_func, is_supported_union +from .dispatch import ( + HookFactory, + MultiStrategyDispatch, + StructuredValue, + StructureHook, + TargetType, + UnstructuredValue, + UnstructureHook, +) +from .errors import ( + IterableValidationError, + IterableValidationNote, + StructureHandlerNotFoundError, +) +from .fns import Predicate, identity, raise_error +from .gen import ( + AttributeOverride, + DictStructureFn, + HeteroTupleUnstructureFn, + IterableUnstructureFn, + MappingStructureFn, + MappingUnstructureFn, + make_dict_structure_fn, + make_dict_unstructure_fn, + make_hetero_tuple_unstructure_fn, + make_mapping_structure_fn, + make_mapping_unstructure_fn, +) +from .gen.typeddicts import make_dict_structure_fn as make_typeddict_dict_struct_fn +from .gen.typeddicts import make_dict_unstructure_fn as make_typeddict_dict_unstruct_fn + +__all__ = ["UnstructureStrategy", "BaseConverter", "Converter", "GenConverter"] + +T = TypeVar("T") +V = TypeVar("V") + +UnstructureHookFactory = TypeVar( + "UnstructureHookFactory", bound=HookFactory[UnstructureHook] +) + +# The Extended factory also takes a converter. +ExtendedUnstructureHookFactory: TypeAlias = Callable[[TargetType, T], UnstructureHook] + +# This typevar for the BaseConverter. +AnyUnstructureHookFactoryBase = TypeVar( + "AnyUnstructureHookFactoryBase", + bound="HookFactory[UnstructureHook] | ExtendedUnstructureHookFactory[BaseConverter]", +) + +# This typevar for the Converter. +AnyUnstructureHookFactory = TypeVar( + "AnyUnstructureHookFactory", + bound="HookFactory[UnstructureHook] | ExtendedUnstructureHookFactory[Converter]", +) + +StructureHookFactory = TypeVar("StructureHookFactory", bound=HookFactory[StructureHook]) + +# The Extended factory also takes a converter. +ExtendedStructureHookFactory: TypeAlias = Callable[[TargetType, T], StructureHook] + +# This typevar for the BaseConverter. +AnyStructureHookFactoryBase = TypeVar( + "AnyStructureHookFactoryBase", + bound="HookFactory[StructureHook] | ExtendedStructureHookFactory[BaseConverter]", +) + +# This typevar for the Converter. +AnyStructureHookFactory = TypeVar( + "AnyStructureHookFactory", + bound="HookFactory[StructureHook] | ExtendedStructureHookFactory[Converter]", +) + +UnstructureHookT = TypeVar("UnstructureHookT", bound=UnstructureHook) +StructureHookT = TypeVar("StructureHookT", bound=StructureHook) + + +class UnstructureStrategy(Enum): + """`attrs` classes unstructuring strategies.""" + + AS_DICT = "asdict" + AS_TUPLE = "astuple" + + +def is_literal_containing_enums(typ: type) -> bool: + return is_literal(typ) and any(isinstance(val, Enum) for val in typ.__args__) + + +def _is_extended_factory(factory: Callable) -> bool: + """Does this factory also accept a converter arg?""" + # We use the original `inspect.signature` to not evaluate string + # annotations. + sig = inspect_signature(factory) + return ( + len(sig.parameters) >= 2 + and (list(sig.parameters.values())[1]).default is Signature.empty + ) + + +class BaseConverter: + """Converts between structured and unstructured data.""" + + __slots__ = ( + "_unstructure_func", + "_unstructure_attrs", + "_structure_attrs", + "_dict_factory", + "_union_struct_registry", + "_structure_func", + "_prefer_attrib_converters", + "detailed_validation", + "_struct_copy_skip", + "_unstruct_copy_skip", + ) + + def __init__( + self, + dict_factory: Callable[[], Any] = dict, + unstruct_strat: UnstructureStrategy = UnstructureStrategy.AS_DICT, + prefer_attrib_converters: bool = False, + detailed_validation: bool = True, + unstructure_fallback_factory: HookFactory[UnstructureHook] = lambda _: identity, + structure_fallback_factory: HookFactory[StructureHook] = lambda _: raise_error, + ) -> None: + """ + :param detailed_validation: Whether to use a slightly slower mode for detailed + validation errors. + :param unstructure_fallback_factory: A hook factory to be called when no + registered unstructuring hooks match. + :param structure_fallback_factory: A hook factory to be called when no + registered structuring hooks match. + + .. versionadded:: 23.2.0 *unstructure_fallback_factory* + .. versionadded:: 23.2.0 *structure_fallback_factory* + """ + unstruct_strat = UnstructureStrategy(unstruct_strat) + self._prefer_attrib_converters = prefer_attrib_converters + + self.detailed_validation = detailed_validation + self._union_struct_registry: dict[Any, Callable[[Any, type[T]], T]] = {} + + # Create a per-instance cache. + if unstruct_strat is UnstructureStrategy.AS_DICT: + self._unstructure_attrs = self.unstructure_attrs_asdict + self._structure_attrs = self.structure_attrs_fromdict + else: + self._unstructure_attrs = self.unstructure_attrs_astuple + self._structure_attrs = self.structure_attrs_fromtuple + + self._unstructure_func = MultiStrategyDispatch( + unstructure_fallback_factory, self + ) + self._unstructure_func.register_cls_list( + [(bytes, identity), (str, identity), (Path, str)] + ) + self._unstructure_func.register_func_list( + [ + ( + is_protocol, + lambda o: self.unstructure(o, unstructure_as=o.__class__), + ), + ( + lambda t: get_final_base(t) is not None, + lambda t: self.get_unstructure_hook(get_final_base(t)), + True, + ), + ( + is_type_alias, + lambda t: self.get_unstructure_hook(get_type_alias_base(t)), + True, + ), + (is_mapping, self._unstructure_mapping), + (is_sequence, self._unstructure_seq), + (is_mutable_set, self._unstructure_seq), + (is_frozenset, self._unstructure_seq), + (lambda t: issubclass(t, Enum), self._unstructure_enum), + (has, self._unstructure_attrs), + (is_union_type, self._unstructure_union), + (lambda t: t in ANIES, self.unstructure), + ] + ) + + # Per-instance register of to-attrs converters. + # Singledispatch dispatches based on the first argument, so we + # store the function and switch the arguments in self.loads. + self._structure_func = MultiStrategyDispatch(structure_fallback_factory, self) + self._structure_func.register_func_list( + [ + ( + lambda cl: cl in ANIES or cl is Optional or cl is None, + lambda v, _: v, + ), + (is_generic_attrs, self._gen_structure_generic, True), + (lambda t: get_newtype_base(t) is not None, self._structure_newtype), + (is_type_alias, self._find_type_alias_structure_hook, True), + ( + lambda t: get_final_base(t) is not None, + self._structure_final_factory, + True, + ), + (is_literal, self._structure_simple_literal), + (is_literal_containing_enums, self._structure_enum_literal), + (is_sequence, list_structure_factory, "extended"), + (is_deque, self._structure_deque), + (is_mutable_set, self._structure_set), + (is_frozenset, self._structure_frozenset), + (is_tuple, self._structure_tuple), + (is_namedtuple, namedtuple_structure_factory, "extended"), + (is_mapping, self._structure_dict), + (is_supported_union, self._gen_attrs_union_structure, True), + (is_optional, self._structure_optional), + ( + lambda t: is_union_type(t) and t in self._union_struct_registry, + self._union_struct_registry.__getitem__, + True, + ), + (has, self._structure_attrs), + ] + ) + # Strings are sequences. + self._structure_func.register_cls_list( + [ + (str, self._structure_call), + (bytes, self._structure_call), + (int, self._structure_call), + (float, self._structure_call), + (Enum, self._structure_call), + (Path, self._structure_call), + ] + ) + + self._dict_factory = dict_factory + + self._unstruct_copy_skip = self._unstructure_func.get_num_fns() + self._struct_copy_skip = self._structure_func.get_num_fns() + + def unstructure(self, obj: Any, unstructure_as: Any = None) -> Any: + return self._unstructure_func.dispatch( + obj.__class__ if unstructure_as is None else unstructure_as + )(obj) + + @property + def unstruct_strat(self) -> UnstructureStrategy: + """The default way of unstructuring ``attrs`` classes.""" + return ( + UnstructureStrategy.AS_DICT + if self._unstructure_attrs == self.unstructure_attrs_asdict + else UnstructureStrategy.AS_TUPLE + ) + + @overload + def register_unstructure_hook(self, cls: UnstructureHookT) -> UnstructureHookT: ... + + @overload + def register_unstructure_hook(self, cls: Any, func: UnstructureHook) -> None: ... + + def register_unstructure_hook( + self, cls: Any = None, func: UnstructureHook | None = None + ) -> Callable[[UnstructureHook]] | None: + """Register a class-to-primitive converter function for a class. + + The converter function should take an instance of the class and return + its Python equivalent. + + May also be used as a decorator. When used as a decorator, the first + argument annotation from the decorated function will be used as the + type to register the hook for. + + .. versionchanged:: 24.1.0 + This method may now be used as a decorator. + """ + if func is None: + # Autodetecting decorator. + func = cls + sig = signature(func) + cls = next(iter(sig.parameters.values())).annotation + self.register_unstructure_hook(cls, func) + + return func + + if attrs_has(cls): + resolve_types(cls) + if is_union_type(cls): + self._unstructure_func.register_func_list([(lambda t: t == cls, func)]) + elif get_newtype_base(cls) is not None: + # This is a newtype, so we handle it specially. + self._unstructure_func.register_func_list([(lambda t: t is cls, func)]) + else: + self._unstructure_func.register_cls_list([(cls, func)]) + return None + + def register_unstructure_hook_func( + self, check_func: Predicate, func: UnstructureHook + ) -> None: + """Register a class-to-primitive converter function for a class, using + a function to check if it's a match. + """ + self._unstructure_func.register_func_list([(check_func, func)]) + + @overload + def register_unstructure_hook_factory( + self, predicate: Predicate + ) -> Callable[[AnyUnstructureHookFactoryBase], AnyUnstructureHookFactoryBase]: ... + + @overload + def register_unstructure_hook_factory( + self, predicate: Predicate, factory: UnstructureHookFactory + ) -> UnstructureHookFactory: ... + + @overload + def register_unstructure_hook_factory( + self, + predicate: Predicate, + factory: ExtendedUnstructureHookFactory[BaseConverter], + ) -> ExtendedUnstructureHookFactory[BaseConverter]: ... + + def register_unstructure_hook_factory(self, predicate, factory=None): + """ + Register a hook factory for a given predicate. + + The hook factory may expose an additional required parameter. In this case, + the current converter will be provided to the hook factory as that + parameter. + + May also be used as a decorator. + + :param predicate: A function that, given a type, returns whether the factory + can produce a hook for that type. + :param factory: A callable that, given a type, produces an unstructuring + hook for that type. This unstructuring hook will be cached. + + .. versionchanged:: 24.1.0 + This method may now be used as a decorator. + The factory may also receive the converter as a second, required argument. + """ + if factory is None: + + def decorator(factory): + # Is this an extended factory (takes a converter too)? + if _is_extended_factory(factory): + self._unstructure_func.register_func_list( + [(predicate, factory, "extended")] + ) + else: + self._unstructure_func.register_func_list( + [(predicate, factory, True)] + ) + + return decorator + + self._unstructure_func.register_func_list( + [ + ( + predicate, + factory, + "extended" if _is_extended_factory(factory) else True, + ) + ] + ) + return factory + + def get_unstructure_hook( + self, type: Any, cache_result: bool = True + ) -> UnstructureHook: + """Get the unstructure hook for the given type. + + This hook can be manually called, or composed with other functions + and re-registered. + + If no hook is registered, the converter unstructure fallback factory + will be used to produce one. + + :param cache: Whether to cache the returned hook. + + .. versionadded:: 24.1.0 + """ + return ( + self._unstructure_func.dispatch(type) + if cache_result + else self._unstructure_func.dispatch_without_caching(type) + ) + + @overload + def register_structure_hook(self, cl: StructureHookT) -> StructureHookT: ... + + @overload + def register_structure_hook(self, cl: Any, func: StructureHook) -> None: ... + + def register_structure_hook( + self, cl: Any, func: StructureHook | None = None + ) -> None: + """Register a primitive-to-class converter function for a type. + + The converter function should take two arguments: + * a Python object to be converted, + * the type to convert to + + and return the instance of the class. The type may seem redundant, but + is sometimes needed (for example, when dealing with generic classes). + + This method may be used as a decorator. In this case, the decorated + hook must have a return type annotation, and this annotation will be used + as the type for the hook. + + .. versionchanged:: 24.1.0 + This method may now be used as a decorator. + """ + if func is None: + # The autodetecting decorator. + func = cl + sig = signature(func) + self.register_structure_hook(sig.return_annotation, func) + return func + + if attrs_has(cl): + resolve_types(cl) + if is_union_type(cl): + self._union_struct_registry[cl] = func + self._structure_func.clear_cache() + elif get_newtype_base(cl) is not None: + # This is a newtype, so we handle it specially. + self._structure_func.register_func_list([(lambda t: t is cl, func)]) + else: + self._structure_func.register_cls_list([(cl, func)]) + return None + + def register_structure_hook_func( + self, check_func: Predicate, func: StructureHook + ) -> None: + """Register a class-to-primitive converter function for a class, using + a function to check if it's a match. + """ + self._structure_func.register_func_list([(check_func, func)]) + + @overload + def register_structure_hook_factory( + self, predicate: Predicate + ) -> Callable[[AnyStructureHookFactoryBase], AnyStructureHookFactoryBase]: ... + + @overload + def register_structure_hook_factory( + self, predicate: Predicate, factory: StructureHookFactory + ) -> StructureHookFactory: ... + + @overload + def register_structure_hook_factory( + self, predicate: Predicate, factory: ExtendedStructureHookFactory[BaseConverter] + ) -> ExtendedStructureHookFactory[BaseConverter]: ... + + def register_structure_hook_factory(self, predicate, factory=None): + """ + Register a hook factory for a given predicate. + + The hook factory may expose an additional required parameter. In this case, + the current converter will be provided to the hook factory as that + parameter. + + May also be used as a decorator. + + :param predicate: A function that, given a type, returns whether the factory + can produce a hook for that type. + :param factory: A callable that, given a type, produces a structuring + hook for that type. This structuring hook will be cached. + + .. versionchanged:: 24.1.0 + This method may now be used as a decorator. + The factory may also receive the converter as a second, required argument. + """ + if factory is None: + # Decorator use. + def decorator(factory): + # Is this an extended factory (takes a converter too)? + if _is_extended_factory(factory): + self._structure_func.register_func_list( + [(predicate, factory, "extended")] + ) + else: + self._structure_func.register_func_list( + [(predicate, factory, True)] + ) + + return decorator + self._structure_func.register_func_list( + [ + ( + predicate, + factory, + "extended" if _is_extended_factory(factory) else True, + ) + ] + ) + return factory + + def structure(self, obj: UnstructuredValue, cl: type[T]) -> T: + """Convert unstructured Python data structures to structured data.""" + return self._structure_func.dispatch(cl)(obj, cl) + + def get_structure_hook(self, type: Any, cache_result: bool = True) -> StructureHook: + """Get the structure hook for the given type. + + This hook can be manually called, or composed with other functions + and re-registered. + + If no hook is registered, the converter structure fallback factory + will be used to produce one. + + :param cache: Whether to cache the returned hook. + + .. versionadded:: 24.1.0 + """ + return ( + self._structure_func.dispatch(type) + if cache_result + else self._structure_func.dispatch_without_caching(type) + ) + + # Classes to Python primitives. + def unstructure_attrs_asdict(self, obj: Any) -> dict[str, Any]: + """Our version of `attrs.asdict`, so we can call back to us.""" + attrs = fields(obj.__class__) + dispatch = self._unstructure_func.dispatch + rv = self._dict_factory() + for a in attrs: + name = a.name + v = getattr(obj, name) + rv[name] = dispatch(a.type or v.__class__)(v) + return rv + + def unstructure_attrs_astuple(self, obj: Any) -> tuple[Any, ...]: + """Our version of `attrs.astuple`, so we can call back to us.""" + attrs = fields(obj.__class__) + dispatch = self._unstructure_func.dispatch + res = [] + for a in attrs: + name = a.name + v = getattr(obj, name) + res.append(dispatch(a.type or v.__class__)(v)) + return tuple(res) + + def _unstructure_enum(self, obj: Enum) -> Any: + """Convert an enum to its value.""" + return obj.value + + def _unstructure_seq(self, seq: Sequence[T]) -> Sequence[T]: + """Convert a sequence to primitive equivalents.""" + # We can reuse the sequence class, so tuples stay tuples. + dispatch = self._unstructure_func.dispatch + return seq.__class__(dispatch(e.__class__)(e) for e in seq) + + def _unstructure_mapping(self, mapping: Mapping[T, V]) -> Mapping[T, V]: + """Convert a mapping of attr classes to primitive equivalents.""" + + # We can reuse the mapping class, so dicts stay dicts and OrderedDicts + # stay OrderedDicts. + dispatch = self._unstructure_func.dispatch + return mapping.__class__( + (dispatch(k.__class__)(k), dispatch(v.__class__)(v)) + for k, v in mapping.items() + ) + + # note: Use UnionType when 3.11 is released as + # the behaviour of @final is changed. This would + # affect how we can support UnionType in ._compat.py + def _unstructure_union(self, obj: Any) -> Any: + """ + Unstructure an object as a union. + + By default, just unstructures the instance. + """ + return self._unstructure_func.dispatch(obj.__class__)(obj) + + # Python primitives to classes. + + def _gen_structure_generic(self, cl: type[T]) -> DictStructureFn[T]: + """Create and return a hook for structuring generics.""" + return make_dict_structure_fn( + cl, self, _cattrs_prefer_attrib_converters=self._prefer_attrib_converters + ) + + def _gen_attrs_union_structure( + self, cl: Any, use_literals: bool = True + ) -> Callable[[Any, type[T]], type[T] | None]: + """ + Generate a structuring function for a union of attrs classes (and maybe None). + + :param use_literals: Whether to consider literal fields. + """ + dis_fn = self._get_dis_func(cl, use_literals=use_literals) + has_none = NoneType in cl.__args__ + + if has_none: + + def structure_attrs_union(obj, _) -> cl: + if obj is None: + return None + return self.structure(obj, dis_fn(obj)) + + else: + + def structure_attrs_union(obj, _): + return self.structure(obj, dis_fn(obj)) + + return structure_attrs_union + + @staticmethod + def _structure_call(obj: Any, cl: type[T]) -> Any: + """Just call ``cl`` with the given ``obj``. + + This is just an optimization on the ``_structure_default`` case, when + we know we can skip the ``if`` s. Use for ``str``, ``bytes``, ``enum``, + etc. + """ + return cl(obj) + + @staticmethod + def _structure_simple_literal(val, type): + if val not in type.__args__: + raise Exception(f"{val} not in literal {type}") + return val + + @staticmethod + def _structure_enum_literal(val, type): + vals = {(x.value if isinstance(x, Enum) else x): x for x in type.__args__} + try: + return vals[val] + except KeyError: + raise Exception(f"{val} not in literal {type}") from None + + def _structure_newtype(self, val: UnstructuredValue, type) -> StructuredValue: + base = get_newtype_base(type) + return self.get_structure_hook(base)(val, base) + + def _find_type_alias_structure_hook(self, type: Any) -> StructureHook: + base = get_type_alias_base(type) + res = self.get_structure_hook(base) + if res == self._structure_call: + # we need to replace the type arg of `structure_call` + return lambda v, _, __base=base: __base(v) + return lambda v, _, __base=base: res(v, __base) + + def _structure_final_factory(self, type): + base = get_final_base(type) + res = self.get_structure_hook(base) + return lambda v, _, __base=base: res(v, __base) + + # Attrs classes. + + def structure_attrs_fromtuple(self, obj: tuple[Any, ...], cl: type[T]) -> T: + """Load an attrs class from a sequence (tuple).""" + conv_obj = [] # A list of converter parameters. + for a, value in zip(fields(cl), obj): + # We detect the type by the metadata. + converted = self._structure_attribute(a, value) + conv_obj.append(converted) + + return cl(*conv_obj) + + def _structure_attribute(self, a: Attribute | Field, value: Any) -> Any: + """Handle an individual attrs attribute.""" + type_ = a.type + attrib_converter = getattr(a, "converter", None) + if self._prefer_attrib_converters and attrib_converter: + # A attrib converter is defined on this attribute, and + # prefer_attrib_converters is set to give these priority over registered + # structure hooks. So, pass through the raw value, which attrs will flow + # into the converter + return value + if type_ is None: + # No type metadata. + return value + + try: + return self._structure_func.dispatch(type_)(value, type_) + except StructureHandlerNotFoundError: + if attrib_converter: + # Return the original value and fallback to using an attrib converter. + return value + raise + + def structure_attrs_fromdict(self, obj: Mapping[str, Any], cl: type[T]) -> T: + """Instantiate an attrs class from a mapping (dict).""" + # For public use. + + conv_obj = {} # Start with a fresh dict, to ignore extra keys. + for a in fields(cl): + try: + val = obj[a.name] + except KeyError: + continue + + # try .alias and .name because this code also supports dataclasses! + conv_obj[getattr(a, "alias", a.name)] = self._structure_attribute(a, val) + + return cl(**conv_obj) + + def _structure_deque(self, obj: Iterable[T], cl: Any) -> deque[T]: + """Convert an iterable to a potentially generic deque.""" + if is_bare(cl) or cl.__args__[0] in ANIES: + res = deque(obj) + else: + elem_type = cl.__args__[0] + handler = self._structure_func.dispatch(elem_type) + if self.detailed_validation: + errors = [] + res = deque() + ix = 0 # Avoid `enumerate` for performance. + for e in obj: + try: + res.append(handler(e, elem_type)) + except Exception as e: + msg = IterableValidationNote( + f"Structuring {cl} @ index {ix}", ix, elem_type + ) + e.__notes__ = [*getattr(e, "__notes__", []), msg] + errors.append(e) + finally: + ix += 1 + if errors: + raise IterableValidationError( + f"While structuring {cl!r}", errors, cl + ) + else: + res = deque(handler(e, elem_type) for e in obj) + return res + + def _structure_set( + self, obj: Iterable[T], cl: Any, structure_to: type = set + ) -> Set[T]: + """Convert an iterable into a potentially generic set.""" + if is_bare(cl) or cl.__args__[0] in ANIES: + return structure_to(obj) + elem_type = cl.__args__[0] + handler = self._structure_func.dispatch(elem_type) + if self.detailed_validation: + errors = [] + res = set() + ix = 0 + for e in obj: + try: + res.add(handler(e, elem_type)) + except Exception as exc: + msg = IterableValidationNote( + f"Structuring {structure_to.__name__} @ element {e!r}", + ix, + elem_type, + ) + exc.__notes__ = [*getattr(exc, "__notes__", []), msg] + errors.append(exc) + finally: + ix += 1 + if errors: + raise IterableValidationError(f"While structuring {cl!r}", errors, cl) + return res if structure_to is set else structure_to(res) + if structure_to is set: + return {handler(e, elem_type) for e in obj} + return structure_to([handler(e, elem_type) for e in obj]) + + def _structure_frozenset( + self, obj: Iterable[T], cl: Any + ) -> FrozenSetSubscriptable[T]: + """Convert an iterable into a potentially generic frozenset.""" + return self._structure_set(obj, cl, structure_to=frozenset) + + def _structure_dict(self, obj: Mapping[T, V], cl: Any) -> dict[T, V]: + """Convert a mapping into a potentially generic dict.""" + if is_bare(cl) or cl.__args__ == (Any, Any): + return dict(obj) + key_type, val_type = cl.__args__ + + if self.detailed_validation: + key_handler = self._structure_func.dispatch(key_type) + val_handler = self._structure_func.dispatch(val_type) + errors = [] + res = {} + + for k, v in obj.items(): + try: + value = val_handler(v, val_type) + except Exception as exc: + msg = IterableValidationNote( + f"Structuring mapping value @ key {k!r}", k, val_type + ) + exc.__notes__ = [*getattr(exc, "__notes__", []), msg] + errors.append(exc) + continue + + try: + key = key_handler(k, key_type) + res[key] = value + except Exception as exc: + msg = IterableValidationNote( + f"Structuring mapping key @ key {k!r}", k, key_type + ) + exc.__notes__ = [*getattr(exc, "__notes__", []), msg] + errors.append(exc) + + if errors: + raise IterableValidationError(f"While structuring {cl!r}", errors, cl) + return res + + if key_type in ANIES: + val_conv = self._structure_func.dispatch(val_type) + return {k: val_conv(v, val_type) for k, v in obj.items()} + if val_type in ANIES: + key_conv = self._structure_func.dispatch(key_type) + return {key_conv(k, key_type): v for k, v in obj.items()} + key_conv = self._structure_func.dispatch(key_type) + val_conv = self._structure_func.dispatch(val_type) + return {key_conv(k, key_type): val_conv(v, val_type) for k, v in obj.items()} + + def _structure_optional(self, obj, union): + if obj is None: + return None + union_params = union.__args__ + other = union_params[0] if union_params[1] is NoneType else union_params[1] + # We can't actually have a Union of a Union, so this is safe. + return self._structure_func.dispatch(other)(obj, other) + + def _structure_tuple(self, obj: Any, tup: type[T]) -> T: + """Deal with structuring into a tuple.""" + tup_params = None if tup in (Tuple, tuple) else tup.__args__ + has_ellipsis = tup_params and tup_params[-1] is Ellipsis + if tup_params is None or (has_ellipsis and tup_params[0] in ANIES): + # Just a Tuple. (No generic information.) + return tuple(obj) + if has_ellipsis: + # We're dealing with a homogenous tuple, Tuple[int, ...] + tup_type = tup_params[0] + conv = self._structure_func.dispatch(tup_type) + if self.detailed_validation: + errors = [] + res = [] + ix = 0 + for e in obj: + try: + res.append(conv(e, tup_type)) + except Exception as exc: + msg = IterableValidationNote( + f"Structuring {tup} @ index {ix}", ix, tup_type + ) + exc.__notes__ = [*getattr(exc, "__notes__", []), msg] + errors.append(exc) + finally: + ix += 1 + if errors: + raise IterableValidationError( + f"While structuring {tup!r}", errors, tup + ) + return tuple(res) + return tuple(conv(e, tup_type) for e in obj) + + # We're dealing with a heterogenous tuple. + exp_len = len(tup_params) + try: + len_obj = len(obj) + except TypeError: + pass # most likely an unsized iterator, eg generator + else: + if len_obj > exp_len: + exp_len = len_obj + if self.detailed_validation: + errors = [] + res = [] + for ix, (t, e) in enumerate(zip(tup_params, obj)): + try: + conv = self._structure_func.dispatch(t) + res.append(conv(e, t)) + except Exception as exc: + msg = IterableValidationNote( + f"Structuring {tup} @ index {ix}", ix, t + ) + exc.__notes__ = [*getattr(exc, "__notes__", []), msg] + errors.append(exc) + if len(res) < exp_len: + problem = "Not enough" if len(res) < len(tup_params) else "Too many" + exc = ValueError(f"{problem} values in {obj!r} to structure as {tup!r}") + msg = f"Structuring {tup}" + exc.__notes__ = [*getattr(exc, "__notes__", []), msg] + errors.append(exc) + if errors: + raise IterableValidationError(f"While structuring {tup!r}", errors, tup) + return tuple(res) + + res = tuple( + [self._structure_func.dispatch(t)(e, t) for t, e in zip(tup_params, obj)] + ) + if len(res) < exp_len: + problem = "Not enough" if len(res) < len(tup_params) else "Too many" + raise ValueError(f"{problem} values in {obj!r} to structure as {tup!r}") + return res + + def _get_dis_func( + self, + union: Any, + use_literals: bool = True, + overrides: dict[str, AttributeOverride] | None = None, + ) -> Callable[[Any], type]: + """Fetch or try creating a disambiguation function for a union.""" + union_types = union.__args__ + if NoneType in union_types: + # We support unions of attrs classes and NoneType higher in the + # logic. + union_types = tuple(e for e in union_types if e is not NoneType) + + # TODO: technically both disambiguators could support TypedDicts and + # dataclasses... + if not all(has(get_origin(e) or e) for e in union_types): + raise StructureHandlerNotFoundError( + "Only unions of attrs classes supported " + "currently. Register a structure hook manually.", + type_=union, + ) + + return create_default_dis_func( + self, + *union_types, + use_literals=use_literals, + overrides=overrides if overrides is not None else "from_converter", + ) + + def __deepcopy__(self, _) -> BaseConverter: + return self.copy() + + def copy( + self, + dict_factory: Callable[[], Any] | None = None, + unstruct_strat: UnstructureStrategy | None = None, + prefer_attrib_converters: bool | None = None, + detailed_validation: bool | None = None, + ) -> BaseConverter: + """Create a copy of the converter, keeping all existing custom hooks. + + :param detailed_validation: Whether to use a slightly slower mode for detailed + validation errors. + """ + res = self.__class__( + dict_factory if dict_factory is not None else self._dict_factory, + ( + unstruct_strat + if unstruct_strat is not None + else ( + UnstructureStrategy.AS_DICT + if self._unstructure_attrs == self.unstructure_attrs_asdict + else UnstructureStrategy.AS_TUPLE + ) + ), + ( + prefer_attrib_converters + if prefer_attrib_converters is not None + else self._prefer_attrib_converters + ), + ( + detailed_validation + if detailed_validation is not None + else self.detailed_validation + ), + ) + + self._unstructure_func.copy_to(res._unstructure_func, self._unstruct_copy_skip) + self._structure_func.copy_to(res._structure_func, self._struct_copy_skip) + + return res + + +class Converter(BaseConverter): + """A converter which generates specialized un/structuring functions.""" + + __slots__ = ( + "omit_if_default", + "forbid_extra_keys", + "type_overrides", + "_unstruct_collection_overrides", + ) + + def __init__( + self, + dict_factory: Callable[[], Any] = dict, + unstruct_strat: UnstructureStrategy = UnstructureStrategy.AS_DICT, + omit_if_default: bool = False, + forbid_extra_keys: bool = False, + type_overrides: Mapping[type, AttributeOverride] = {}, + unstruct_collection_overrides: Mapping[type, Callable] = {}, + prefer_attrib_converters: bool = False, + detailed_validation: bool = True, + unstructure_fallback_factory: HookFactory[UnstructureHook] = lambda _: identity, + structure_fallback_factory: HookFactory[StructureHook] = lambda _: raise_error, + ): + """ + :param detailed_validation: Whether to use a slightly slower mode for detailed + validation errors. + :param unstructure_fallback_factory: A hook factory to be called when no + registered unstructuring hooks match. + :param structure_fallback_factory: A hook factory to be called when no + registered structuring hooks match. + + .. versionadded:: 23.2.0 *unstructure_fallback_factory* + .. versionadded:: 23.2.0 *structure_fallback_factory* + """ + super().__init__( + dict_factory=dict_factory, + unstruct_strat=unstruct_strat, + prefer_attrib_converters=prefer_attrib_converters, + detailed_validation=detailed_validation, + unstructure_fallback_factory=unstructure_fallback_factory, + structure_fallback_factory=structure_fallback_factory, + ) + self.omit_if_default = omit_if_default + self.forbid_extra_keys = forbid_extra_keys + self.type_overrides = dict(type_overrides) + + unstruct_collection_overrides = { + get_origin(k) or k: v for k, v in unstruct_collection_overrides.items() + } + + self._unstruct_collection_overrides = unstruct_collection_overrides + + # Do a little post-processing magic to make things easier for users. + co = unstruct_collection_overrides + + # abc.Set overrides, if defined, apply to abc.MutableSets and sets + if OriginAbstractSet in co: + if OriginMutableSet not in co: + co[OriginMutableSet] = co[OriginAbstractSet] + co[AbcMutableSet] = co[OriginAbstractSet] # For 3.8 compatibility. + if FrozenSetSubscriptable not in co: + co[FrozenSetSubscriptable] = co[OriginAbstractSet] + + # abc.MutableSet overrrides, if defined, apply to sets + if OriginMutableSet in co and set not in co: + co[set] = co[OriginMutableSet] + + if FrozenSetSubscriptable in co: + co[frozenset] = co[FrozenSetSubscriptable] # For 3.8 compatibility. + + # abc.Sequence overrides, if defined, can apply to MutableSequences, lists and + # tuples + if Sequence in co: + if MutableSequence not in co: + co[MutableSequence] = co[Sequence] + if tuple not in co: + co[tuple] = co[Sequence] + + # abc.MutableSequence overrides, if defined, can apply to lists + if MutableSequence in co: + if list not in co: + co[list] = co[MutableSequence] + if deque not in co: + co[deque] = co[MutableSequence] + + # abc.Mapping overrides, if defined, can apply to MutableMappings + if Mapping in co and MutableMapping not in co: + co[MutableMapping] = co[Mapping] + + # abc.MutableMapping overrides, if defined, can apply to dicts + if MutableMapping in co and dict not in co: + co[dict] = co[MutableMapping] + + # builtins.dict overrides, if defined, can apply to counters + if dict in co and Counter not in co: + co[Counter] = co[dict] + + if unstruct_strat is UnstructureStrategy.AS_DICT: + # Override the attrs handler. + self.register_unstructure_hook_factory( + has_with_generic, self.gen_unstructure_attrs_fromdict + ) + self.register_structure_hook_factory( + has_with_generic, self.gen_structure_attrs_fromdict + ) + self.register_unstructure_hook_factory( + is_annotated, self.gen_unstructure_annotated + ) + self.register_unstructure_hook_factory( + is_hetero_tuple, self.gen_unstructure_hetero_tuple + ) + self.register_unstructure_hook_factory(is_namedtuple)( + namedtuple_unstructure_factory + ) + self.register_unstructure_hook_factory( + is_sequence, self.gen_unstructure_iterable + ) + self.register_unstructure_hook_factory(is_mapping, self.gen_unstructure_mapping) + self.register_unstructure_hook_factory( + is_mutable_set, + lambda cl: self.gen_unstructure_iterable(cl, unstructure_to=set), + ) + self.register_unstructure_hook_factory( + is_frozenset, + lambda cl: self.gen_unstructure_iterable(cl, unstructure_to=frozenset), + ) + self.register_unstructure_hook_factory( + is_optional, self.gen_unstructure_optional + ) + self.register_unstructure_hook_factory( + is_typeddict, self.gen_unstructure_typeddict + ) + self.register_unstructure_hook_factory( + lambda t: get_newtype_base(t) is not None, + lambda t: self.get_unstructure_hook(get_newtype_base(t)), + ) + + self.register_structure_hook_factory(is_annotated, self.gen_structure_annotated) + self.register_structure_hook_factory(is_mapping, self.gen_structure_mapping) + self.register_structure_hook_factory(is_counter, self.gen_structure_counter) + self.register_structure_hook_factory(is_typeddict, self.gen_structure_typeddict) + self.register_structure_hook_factory( + lambda t: get_newtype_base(t) is not None, self.get_structure_newtype + ) + + # We keep these so we can more correctly copy the hooks. + self._struct_copy_skip = self._structure_func.get_num_fns() + self._unstruct_copy_skip = self._unstructure_func.get_num_fns() + + @overload + def register_unstructure_hook_factory( + self, predicate: Predicate + ) -> Callable[[AnyUnstructureHookFactory], AnyUnstructureHookFactory]: ... + + @overload + def register_unstructure_hook_factory( + self, predicate: Predicate, factory: UnstructureHookFactory + ) -> UnstructureHookFactory: ... + + @overload + def register_unstructure_hook_factory( + self, predicate: Predicate, factory: ExtendedUnstructureHookFactory[Converter] + ) -> ExtendedUnstructureHookFactory[Converter]: ... + + def register_unstructure_hook_factory(self, predicate, factory=None): + # This dummy wrapper is required due to how `@overload` works. + return super().register_unstructure_hook_factory(predicate, factory) + + @overload + def register_structure_hook_factory( + self, predicate: Predicate + ) -> Callable[[AnyStructureHookFactory], AnyStructureHookFactory]: ... + + @overload + def register_structure_hook_factory( + self, predicate: Predicate, factory: StructureHookFactory + ) -> StructureHookFactory: ... + + @overload + def register_structure_hook_factory( + self, predicate: Predicate, factory: ExtendedStructureHookFactory[Converter] + ) -> ExtendedStructureHookFactory[Converter]: ... + + def register_structure_hook_factory(self, predicate, factory=None): + # This dummy wrapper is required due to how `@overload` works. + return super().register_structure_hook_factory(predicate, factory) + + def get_structure_newtype(self, type: type[T]) -> Callable[[Any, Any], T]: + base = get_newtype_base(type) + handler = self.get_structure_hook(base) + return lambda v, _: handler(v, base) + + def gen_unstructure_annotated(self, type): + origin = type.__origin__ + return self.get_unstructure_hook(origin) + + def gen_structure_annotated(self, type) -> Callable: + """A hook factory for annotated types.""" + origin = type.__origin__ + hook = self.get_structure_hook(origin) + return lambda v, _: hook(v, origin) + + def gen_unstructure_typeddict(self, cl: Any) -> Callable[[dict], dict]: + """Generate a TypedDict unstructure function. + + Also apply converter-scored modifications. + """ + return make_typeddict_dict_unstruct_fn(cl, self) + + def gen_unstructure_attrs_fromdict( + self, cl: type[T] + ) -> Callable[[T], dict[str, Any]]: + origin = get_origin(cl) + attribs = fields(origin or cl) + if attrs_has(cl) and any(isinstance(a.type, str) for a in attribs): + # PEP 563 annotations - need to be resolved. + resolve_types(cl) + attrib_overrides = { + a.name: self.type_overrides[a.type] + for a in attribs + if a.type in self.type_overrides + } + + return make_dict_unstructure_fn( + cl, self, _cattrs_omit_if_default=self.omit_if_default, **attrib_overrides + ) + + def gen_unstructure_optional(self, cl: type[T]) -> Callable[[T], Any]: + """Generate an unstructuring hook for optional types.""" + union_params = cl.__args__ + other = union_params[0] if union_params[1] is NoneType else union_params[1] + + if isinstance(other, TypeVar): + handler = self.unstructure + else: + handler = self.get_unstructure_hook(other) + + def unstructure_optional(val, _handler=handler): + return None if val is None else _handler(val) + + return unstructure_optional + + def gen_structure_typeddict(self, cl: Any) -> Callable[[dict, Any], dict]: + """Generate a TypedDict structure function. + + Also apply converter-scored modifications. + """ + return make_typeddict_dict_struct_fn( + cl, self, _cattrs_detailed_validation=self.detailed_validation + ) + + def gen_structure_attrs_fromdict( + self, cl: type[T] + ) -> Callable[[Mapping[str, Any], Any], T]: + attribs = fields(get_origin(cl) or cl if is_generic(cl) else cl) + if attrs_has(cl) and any(isinstance(a.type, str) for a in attribs): + # PEP 563 annotations - need to be resolved. + resolve_types(cl) + attrib_overrides = { + a.name: self.type_overrides[a.type] + for a in attribs + if a.type in self.type_overrides + } + return make_dict_structure_fn( + cl, + self, + _cattrs_forbid_extra_keys=self.forbid_extra_keys, + _cattrs_prefer_attrib_converters=self._prefer_attrib_converters, + _cattrs_detailed_validation=self.detailed_validation, + **attrib_overrides, + ) + + def gen_unstructure_iterable( + self, cl: Any, unstructure_to: Any = None + ) -> IterableUnstructureFn: + unstructure_to = self._unstruct_collection_overrides.get( + get_origin(cl) or cl, unstructure_to or list + ) + h = iterable_unstructure_factory(cl, self, unstructure_to=unstructure_to) + self._unstructure_func.register_cls_list([(cl, h)], direct=True) + return h + + def gen_unstructure_hetero_tuple( + self, cl: Any, unstructure_to: Any = None + ) -> HeteroTupleUnstructureFn: + unstructure_to = self._unstruct_collection_overrides.get( + get_origin(cl) or cl, unstructure_to or tuple + ) + h = make_hetero_tuple_unstructure_fn(cl, self, unstructure_to=unstructure_to) + self._unstructure_func.register_cls_list([(cl, h)], direct=True) + return h + + def gen_unstructure_mapping( + self, + cl: Any, + unstructure_to: Any = None, + key_handler: Callable[[Any, Any | None], Any] | None = None, + ) -> MappingUnstructureFn: + unstructure_to = self._unstruct_collection_overrides.get( + get_origin(cl) or cl, unstructure_to or dict + ) + h = make_mapping_unstructure_fn( + cl, self, unstructure_to=unstructure_to, key_handler=key_handler + ) + self._unstructure_func.register_cls_list([(cl, h)], direct=True) + return h + + def gen_structure_counter(self, cl: Any) -> MappingStructureFn[T]: + h = make_mapping_structure_fn( + cl, + self, + structure_to=Counter, + val_type=int, + detailed_validation=self.detailed_validation, + ) + self._structure_func.register_cls_list([(cl, h)], direct=True) + return h + + def gen_structure_mapping(self, cl: Any) -> MappingStructureFn[T]: + structure_to = get_origin(cl) or cl + if structure_to in ( + MutableMapping, + AbcMutableMapping, + Mapping, + AbcMapping, + ): # These default to dicts + structure_to = dict + h = make_mapping_structure_fn( + cl, self, structure_to, detailed_validation=self.detailed_validation + ) + self._structure_func.register_cls_list([(cl, h)], direct=True) + return h + + def copy( + self, + dict_factory: Callable[[], Any] | None = None, + unstruct_strat: UnstructureStrategy | None = None, + omit_if_default: bool | None = None, + forbid_extra_keys: bool | None = None, + type_overrides: Mapping[type, AttributeOverride] | None = None, + unstruct_collection_overrides: Mapping[type, Callable] | None = None, + prefer_attrib_converters: bool | None = None, + detailed_validation: bool | None = None, + ) -> Converter: + """Create a copy of the converter, keeping all existing custom hooks. + + :param detailed_validation: Whether to use a slightly slower mode for detailed + validation errors. + """ + res = self.__class__( + dict_factory if dict_factory is not None else self._dict_factory, + ( + unstruct_strat + if unstruct_strat is not None + else ( + UnstructureStrategy.AS_DICT + if self._unstructure_attrs == self.unstructure_attrs_asdict + else UnstructureStrategy.AS_TUPLE + ) + ), + omit_if_default if omit_if_default is not None else self.omit_if_default, + ( + forbid_extra_keys + if forbid_extra_keys is not None + else self.forbid_extra_keys + ), + type_overrides if type_overrides is not None else self.type_overrides, + ( + unstruct_collection_overrides + if unstruct_collection_overrides is not None + else self._unstruct_collection_overrides + ), + ( + prefer_attrib_converters + if prefer_attrib_converters is not None + else self._prefer_attrib_converters + ), + ( + detailed_validation + if detailed_validation is not None + else self.detailed_validation + ), + ) + + self._unstructure_func.copy_to( + res._unstructure_func, skip=self._unstruct_copy_skip + ) + self._structure_func.copy_to(res._structure_func, skip=self._struct_copy_skip) + + return res + + +GenConverter: TypeAlias = Converter diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/disambiguators.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/disambiguators.py new file mode 100644 index 0000000..ad36ae3 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/disambiguators.py @@ -0,0 +1,205 @@ +"""Utilities for union (sum type) disambiguation.""" + +from __future__ import annotations + +from collections import defaultdict +from dataclasses import MISSING +from functools import reduce +from operator import or_ +from typing import TYPE_CHECKING, Any, Callable, Literal, Mapping, Union + +from attrs import NOTHING, Attribute, AttrsInstance + +from ._compat import ( + NoneType, + adapted_fields, + fields_dict, + get_args, + get_origin, + has, + is_literal, + is_union_type, +) +from .gen import AttributeOverride + +if TYPE_CHECKING: + from .converters import BaseConverter + +__all__ = ["is_supported_union", "create_default_dis_func"] + + +def is_supported_union(typ: Any) -> bool: + """Whether the type is a union of attrs classes.""" + return is_union_type(typ) and all( + e is NoneType or has(get_origin(e) or e) for e in typ.__args__ + ) + + +def create_default_dis_func( + converter: BaseConverter, + *classes: type[AttrsInstance], + use_literals: bool = True, + overrides: ( + dict[str, AttributeOverride] | Literal["from_converter"] + ) = "from_converter", +) -> Callable[[Mapping[Any, Any]], type[Any] | None]: + """Given attrs classes or dataclasses, generate a disambiguation function. + + The function is based on unique fields without defaults or unique values. + + :param use_literals: Whether to try using fields annotated as literals for + disambiguation. + :param overrides: Attribute overrides to apply. + + .. versionchanged:: 24.1.0 + Dataclasses are now supported. + """ + if len(classes) < 2: + raise ValueError("At least two classes required.") + + if overrides == "from_converter": + overrides = [ + getattr(converter.get_structure_hook(c), "overrides", {}) for c in classes + ] + else: + overrides = [overrides for _ in classes] + + # first, attempt for unique values + if use_literals: + # requirements for a discriminator field: + # (... TODO: a single fallback is OK) + # - it must always be enumerated + cls_candidates = [ + { + at.name + for at in adapted_fields(get_origin(cl) or cl) + if is_literal(at.type) + } + for cl in classes + ] + + # literal field names common to all members + discriminators: set[str] = cls_candidates[0] + for possible_discriminators in cls_candidates: + discriminators &= possible_discriminators + + best_result = None + best_discriminator = None + for discriminator in discriminators: + # maps Literal values (strings, ints...) to classes + mapping = defaultdict(list) + + for cl in classes: + for key in get_args( + fields_dict(get_origin(cl) or cl)[discriminator].type + ): + mapping[key].append(cl) + + if best_result is None or max(len(v) for v in mapping.values()) <= max( + len(v) for v in best_result.values() + ): + best_result = mapping + best_discriminator = discriminator + + if ( + best_result + and best_discriminator + and max(len(v) for v in best_result.values()) != len(classes) + ): + final_mapping = { + k: v[0] if len(v) == 1 else Union[tuple(v)] + for k, v in best_result.items() + } + + def dis_func(data: Mapping[Any, Any]) -> type | None: + if not isinstance(data, Mapping): + raise ValueError("Only input mappings are supported.") + return final_mapping[data[best_discriminator]] + + return dis_func + + # next, attempt for unique keys + + # NOTE: This could just as well work with just field availability and not + # uniqueness, returning Unions ... it doesn't do that right now. + cls_and_attrs = [ + (cl, *_usable_attribute_names(cl, override)) + for cl, override in zip(classes, overrides) + ] + # For each class, attempt to generate a single unique required field. + uniq_attrs_dict: dict[str, type] = {} + + # We start from classes with the largest number of unique fields + # so we can do easy picks first, making later picks easier. + cls_and_attrs.sort(key=lambda c_a: len(c_a[1]), reverse=True) + + fallback = None # If none match, try this. + + for cl, cl_reqs, back_map in cls_and_attrs: + # We do not have to consider classes we've already processed, since + # they will have been eliminated by the match dictionary already. + other_classes = [ + c_and_a + for c_and_a in cls_and_attrs + if c_and_a[0] is not cl and c_and_a[0] not in uniq_attrs_dict.values() + ] + other_reqs = reduce(or_, (c_a[1] for c_a in other_classes), set()) + uniq = cl_reqs - other_reqs + + # We want a unique attribute with no default. + cl_fields = fields_dict(get_origin(cl) or cl) + for maybe_renamed_attr_name in uniq: + orig_name = back_map[maybe_renamed_attr_name] + if cl_fields[orig_name].default in (NOTHING, MISSING): + break + else: + if fallback is None: + fallback = cl + continue + raise TypeError(f"{cl} has no usable non-default attributes") + uniq_attrs_dict[maybe_renamed_attr_name] = cl + + if fallback is None: + + def dis_func(data: Mapping[Any, Any]) -> type[AttrsInstance] | None: + if not isinstance(data, Mapping): + raise ValueError("Only input mappings are supported") + for k, v in uniq_attrs_dict.items(): + if k in data: + return v + raise ValueError("Couldn't disambiguate") + + else: + + def dis_func(data: Mapping[Any, Any]) -> type[AttrsInstance] | None: + if not isinstance(data, Mapping): + raise ValueError("Only input mappings are supported") + for k, v in uniq_attrs_dict.items(): + if k in data: + return v + return fallback + + return dis_func + + +create_uniq_field_dis_func = create_default_dis_func + + +def _overriden_name(at: Attribute, override: AttributeOverride | None) -> str: + if override is None or override.rename is None: + return at.name + return override.rename + + +def _usable_attribute_names( + cl: type[Any], overrides: dict[str, AttributeOverride] +) -> tuple[set[str], dict[str, str]]: + """Return renamed fields and a mapping to original field names.""" + res = set() + mapping = {} + + for at in adapted_fields(get_origin(cl) or cl): + res.add(n := _overriden_name(at, overrides.get(at.name))) + mapping[n] = at.name + + return res, mapping diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/dispatch.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/dispatch.py new file mode 100644 index 0000000..3d746db --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/dispatch.py @@ -0,0 +1,194 @@ +from __future__ import annotations + +from functools import lru_cache, singledispatch +from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, TypeVar + +from attrs import Factory, define + +from ._compat import TypeAlias +from .fns import Predicate + +if TYPE_CHECKING: + from .converters import BaseConverter + +TargetType: TypeAlias = Any +UnstructuredValue: TypeAlias = Any +StructuredValue: TypeAlias = Any + +StructureHook: TypeAlias = Callable[[UnstructuredValue, TargetType], StructuredValue] +UnstructureHook: TypeAlias = Callable[[StructuredValue], UnstructuredValue] + +Hook = TypeVar("Hook", StructureHook, UnstructureHook) +HookFactory: TypeAlias = Callable[[TargetType], Hook] + + +@define +class _DispatchNotFound: + """A dummy object to help signify a dispatch not found.""" + + +@define +class FunctionDispatch: + """ + FunctionDispatch is similar to functools.singledispatch, but + instead dispatches based on functions that take the type of the + first argument in the method, and return True or False. + + objects that help determine dispatch should be instantiated objects. + + :param converter: A converter to be used for factories that require converters. + + .. versionchanged:: 24.1.0 + Support for factories that require converters, hence this requires a + converter when creating. + """ + + _converter: BaseConverter + _handler_pairs: list[tuple[Predicate, Callable[[Any, Any], Any], bool, bool]] = ( + Factory(list) + ) + + def register( + self, + predicate: Predicate, + func: Callable[..., Any], + is_generator=False, + takes_converter=False, + ) -> None: + self._handler_pairs.insert(0, (predicate, func, is_generator, takes_converter)) + + def dispatch(self, typ: Any) -> Callable[..., Any] | None: + """ + Return the appropriate handler for the object passed. + """ + for can_handle, handler, is_generator, takes_converter in self._handler_pairs: + # can handle could raise an exception here + # such as issubclass being called on an instance. + # it's easier to just ignore that case. + try: + ch = can_handle(typ) + except Exception: # noqa: S112 + continue + if ch: + if is_generator: + if takes_converter: + return handler(typ, self._converter) + return handler(typ) + + return handler + return None + + def get_num_fns(self) -> int: + return len(self._handler_pairs) + + def copy_to(self, other: FunctionDispatch, skip: int = 0) -> None: + other._handler_pairs = self._handler_pairs[:-skip] + other._handler_pairs + + +@define(init=False) +class MultiStrategyDispatch(Generic[Hook]): + """ + MultiStrategyDispatch uses a combination of exact-match dispatch, + singledispatch, and FunctionDispatch. + + :param converter: A converter to be used for factories that require converters. + :param fallback_factory: A hook factory to be called when a hook cannot be + produced. + + .. versionchanged:: 23.2.0 + Fallbacks are now factories. + .. versionchanged:: 24.1.0 + Support for factories that require converters, hence this requires a + converter when creating. + """ + + _fallback_factory: HookFactory[Hook] + _converter: BaseConverter + _direct_dispatch: dict[TargetType, Hook] + _function_dispatch: FunctionDispatch + _single_dispatch: Any + dispatch: Callable[[TargetType, BaseConverter], Hook] + + def __init__( + self, fallback_factory: HookFactory[Hook], converter: BaseConverter + ) -> None: + self._fallback_factory = fallback_factory + self._direct_dispatch = {} + self._function_dispatch = FunctionDispatch(converter) + self._single_dispatch = singledispatch(_DispatchNotFound) + self.dispatch = lru_cache(maxsize=None)(self.dispatch_without_caching) + + def dispatch_without_caching(self, typ: TargetType) -> Hook: + """Dispatch on the type but without caching the result.""" + try: + dispatch = self._single_dispatch.dispatch(typ) + if dispatch is not _DispatchNotFound: + return dispatch + except Exception: # noqa: S110 + pass + + direct_dispatch = self._direct_dispatch.get(typ) + if direct_dispatch is not None: + return direct_dispatch + + res = self._function_dispatch.dispatch(typ) + return res if res is not None else self._fallback_factory(typ) + + def register_cls_list(self, cls_and_handler, direct: bool = False) -> None: + """Register a class to direct or singledispatch.""" + for cls, handler in cls_and_handler: + if direct: + self._direct_dispatch[cls] = handler + else: + self._single_dispatch.register(cls, handler) + self.clear_direct() + self.dispatch.cache_clear() + + def register_func_list( + self, + pred_and_handler: list[ + tuple[Predicate, Any] + | tuple[Predicate, Any, bool] + | tuple[Predicate, Callable[[Any, BaseConverter], Any], Literal["extended"]] + ], + ): + """ + Register a predicate function to determine if the handler + should be used for the type. + + :param pred_and_handler: The list of predicates and their associated + handlers. If a handler is registered in `extended` mode, it's a + factory that requires a converter. + """ + for tup in pred_and_handler: + if len(tup) == 2: + func, handler = tup + self._function_dispatch.register(func, handler) + else: + func, handler, is_gen = tup + if is_gen == "extended": + self._function_dispatch.register( + func, handler, is_generator=is_gen, takes_converter=True + ) + else: + self._function_dispatch.register(func, handler, is_generator=is_gen) + self.clear_direct() + self.dispatch.cache_clear() + + def clear_direct(self) -> None: + """Clear the direct dispatch.""" + self._direct_dispatch.clear() + + def clear_cache(self) -> None: + """Clear all caches.""" + self._direct_dispatch.clear() + self.dispatch.cache_clear() + + def get_num_fns(self) -> int: + return self._function_dispatch.get_num_fns() + + def copy_to(self, other: MultiStrategyDispatch, skip: int = 0) -> None: + self._function_dispatch.copy_to(other._function_dispatch, skip=skip) + for cls, fn in self._single_dispatch.registry.items(): + other._single_dispatch.register(cls, fn) + other.clear_cache() diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/errors.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/errors.py new file mode 100644 index 0000000..9148bf1 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/errors.py @@ -0,0 +1,129 @@ +from typing import Any, List, Optional, Set, Tuple, Type, Union + +from cattrs._compat import ExceptionGroup + + +class StructureHandlerNotFoundError(Exception): + """ + Error raised when structuring cannot find a handler for converting inputs into + :attr:`type_`. + """ + + def __init__(self, message: str, type_: Type) -> None: + super().__init__(message) + self.type_ = type_ + + +class BaseValidationError(ExceptionGroup): + cl: Type + + def __new__(cls, message, excs, cl: Type): + obj = super().__new__(cls, message, excs) + obj.cl = cl + return obj + + def derive(self, excs): + return ClassValidationError(self.message, excs, self.cl) + + +class IterableValidationNote(str): + """Attached as a note to an exception when an iterable element fails structuring.""" + + index: Union[int, str] # Ints for list indices, strs for dict keys + type: Any + + def __new__( + cls, string: str, index: Union[int, str], type: Any + ) -> "IterableValidationNote": + instance = str.__new__(cls, string) + instance.index = index + instance.type = type + return instance + + def __getnewargs__(self) -> Tuple[str, Union[int, str], Any]: + return (str(self), self.index, self.type) + + +class IterableValidationError(BaseValidationError): + """Raised when structuring an iterable.""" + + def group_exceptions( + self, + ) -> Tuple[List[Tuple[Exception, IterableValidationNote]], List[Exception]]: + """Split the exceptions into two groups: with and without validation notes.""" + excs_with_notes = [] + other_excs = [] + for subexc in self.exceptions: + if hasattr(subexc, "__notes__"): + for note in subexc.__notes__: + if note.__class__ is IterableValidationNote: + excs_with_notes.append((subexc, note)) + break + else: + other_excs.append(subexc) + else: + other_excs.append(subexc) + + return excs_with_notes, other_excs + + +class AttributeValidationNote(str): + """Attached as a note to an exception when an attribute fails structuring.""" + + name: str + type: Any + + def __new__(cls, string: str, name: str, type: Any) -> "AttributeValidationNote": + instance = str.__new__(cls, string) + instance.name = name + instance.type = type + return instance + + def __getnewargs__(self) -> Tuple[str, str, Any]: + return (str(self), self.name, self.type) + + +class ClassValidationError(BaseValidationError): + """Raised when validating a class if any attributes are invalid.""" + + def group_exceptions( + self, + ) -> Tuple[List[Tuple[Exception, AttributeValidationNote]], List[Exception]]: + """Split the exceptions into two groups: with and without validation notes.""" + excs_with_notes = [] + other_excs = [] + for subexc in self.exceptions: + if hasattr(subexc, "__notes__"): + for note in subexc.__notes__: + if note.__class__ is AttributeValidationNote: + excs_with_notes.append((subexc, note)) + break + else: + other_excs.append(subexc) + else: + other_excs.append(subexc) + + return excs_with_notes, other_excs + + +class ForbiddenExtraKeysError(Exception): + """ + Raised when `forbid_extra_keys` is activated and such extra keys are detected + during structuring. + + The attribute `extra_fields` is a sequence of those extra keys, which were the + cause of this error, and `cl` is the class which was structured with those extra + keys. + """ + + def __init__( + self, message: Optional[str], cl: Type, extra_fields: Set[str] + ) -> None: + self.cl = cl + self.extra_fields = extra_fields + cln = cl.__name__ + + super().__init__( + message + or f"Extra fields in constructor for {cln}: {', '.join(extra_fields)}" + ) diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/fns.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/fns.py new file mode 100644 index 0000000..748cfb3 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/fns.py @@ -0,0 +1,22 @@ +"""Useful internal functions.""" + +from typing import Any, Callable, NoReturn, Type, TypeVar + +from ._compat import TypeAlias +from .errors import StructureHandlerNotFoundError + +T = TypeVar("T") + +Predicate: TypeAlias = Callable[[Any], bool] +"""A predicate function determines if a type can be handled.""" + + +def identity(obj: T) -> T: + """The identity function.""" + return obj + + +def raise_error(_, cl: Type) -> NoReturn: + """At the bottom of the condition stack, we explode if we can't handle it.""" + msg = f"Unsupported type: {cl!r}. Register a structure hook for it." + raise StructureHandlerNotFoundError(msg, type_=cl) diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/gen/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/gen/__init__.py new file mode 100644 index 0000000..97d2876 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/gen/__init__.py @@ -0,0 +1,1053 @@ +from __future__ import annotations + +import re +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Final, + Iterable, + Literal, + Mapping, + Tuple, + TypeVar, +) + +from attrs import NOTHING, Attribute, Factory, resolve_types + +from .._compat import ( + ANIES, + TypeAlias, + adapted_fields, + get_args, + get_origin, + is_annotated, + is_bare, + is_bare_final, + is_generic, +) +from .._generics import deep_copy_with +from ..dispatch import UnstructureHook +from ..errors import ( + AttributeValidationNote, + ClassValidationError, + ForbiddenExtraKeysError, + IterableValidationError, + IterableValidationNote, + StructureHandlerNotFoundError, +) +from ..fns import identity +from ._consts import AttributeOverride, already_generating, neutral +from ._generics import generate_mapping +from ._lc import generate_unique_filename +from ._shared import find_structure_handler + +if TYPE_CHECKING: + from ..converters import BaseConverter + +__all__ = [ + "make_dict_unstructure_fn", + "make_dict_structure_fn", + "make_iterable_unstructure_fn", + "make_hetero_tuple_unstructure_fn", + "make_mapping_unstructure_fn", + "make_mapping_structure_fn", + "make_dict_unstructure_fn_from_attrs", + "make_dict_structure_fn_from_attrs", +] + + +def override( + omit_if_default: bool | None = None, + rename: str | None = None, + omit: bool | None = None, + struct_hook: Callable[[Any, Any], Any] | None = None, + unstruct_hook: Callable[[Any], Any] | None = None, +) -> AttributeOverride: + """Override how a particular field is handled. + + :param omit: Whether to skip the field or not. `None` means apply default handling. + """ + return AttributeOverride(omit_if_default, rename, omit, struct_hook, unstruct_hook) + + +T = TypeVar("T") + + +def make_dict_unstructure_fn_from_attrs( + attrs: list[Attribute], + cl: type, + converter: BaseConverter, + typevar_map: dict[str, Any] = {}, + _cattrs_omit_if_default: bool = False, + _cattrs_use_linecache: bool = True, + _cattrs_use_alias: bool = False, + _cattrs_include_init_false: bool = False, + **kwargs: AttributeOverride, +) -> Callable[[T], dict[str, Any]]: + """ + Generate a specialized dict unstructuring function for a list of attributes. + + Usually used as a building block by more specialized hook factories. + + Any provided overrides are attached to the generated function under the + `overrides` attribute. + + :param cl: The class for which the function is generated; used mostly for its name, + module name and qualname. + :param _cattrs_omit_if_default: if true, attributes equal to their default values + will be omitted in the result dictionary. + :param _cattrs_use_alias: If true, the attribute alias will be used as the + dictionary key by default. + :param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False` + will be included. + + .. versionadded:: 24.1.0 + """ + + fn_name = "unstructure_" + cl.__name__ + globs = {} + lines = [] + invocation_lines = [] + internal_arg_parts = {} + + for a in attrs: + attr_name = a.name + override = kwargs.get(attr_name, neutral) + if override.omit: + continue + if override.omit is None and not a.init and not _cattrs_include_init_false: + continue + if override.rename is None: + kn = attr_name if not _cattrs_use_alias else a.alias + else: + kn = override.rename + d = a.default + + # For each attribute, we try resolving the type here and now. + # If a type is manually overwritten, this function should be + # regenerated. + handler = None + if override.unstruct_hook is not None: + handler = override.unstruct_hook + else: + if a.type is not None: + t = a.type + if isinstance(t, TypeVar): + if t.__name__ in typevar_map: + t = typevar_map[t.__name__] + else: + handler = converter.unstructure + elif is_generic(t) and not is_bare(t) and not is_annotated(t): + t = deep_copy_with(t, typevar_map) + + if handler is None: + if ( + is_bare_final(t) + and a.default is not NOTHING + and not isinstance(a.default, Factory) + ): + # This is a special case where we can use the + # type of the default to dispatch on. + t = a.default.__class__ + try: + handler = converter.get_unstructure_hook(t, cache_result=False) + except RecursionError: + # There's a circular reference somewhere down the line + handler = converter.unstructure + else: + handler = converter.unstructure + + is_identity = handler == identity + + if not is_identity: + unstruct_handler_name = f"__c_unstr_{attr_name}" + globs[unstruct_handler_name] = handler + internal_arg_parts[unstruct_handler_name] = handler + invoke = f"{unstruct_handler_name}(instance.{attr_name})" + else: + invoke = f"instance.{attr_name}" + + if d is not NOTHING and ( + (_cattrs_omit_if_default and override.omit_if_default is not False) + or override.omit_if_default + ): + def_name = f"__c_def_{attr_name}" + + if isinstance(d, Factory): + globs[def_name] = d.factory + internal_arg_parts[def_name] = d.factory + if d.takes_self: + lines.append(f" if instance.{attr_name} != {def_name}(instance):") + else: + lines.append(f" if instance.{attr_name} != {def_name}():") + lines.append(f" res['{kn}'] = {invoke}") + else: + globs[def_name] = d + internal_arg_parts[def_name] = d + lines.append(f" if instance.{attr_name} != {def_name}:") + lines.append(f" res['{kn}'] = {invoke}") + + else: + # No default or no override. + invocation_lines.append(f"'{kn}': {invoke},") + + internal_arg_line = ", ".join([f"{i}={i}" for i in internal_arg_parts]) + if internal_arg_line: + internal_arg_line = f", {internal_arg_line}" + for k, v in internal_arg_parts.items(): + globs[k] = v + + total_lines = ( + [f"def {fn_name}(instance{internal_arg_line}):"] + + [" res = {"] + + [f" {line}" for line in invocation_lines] + + [" }"] + + lines + + [" return res"] + ) + script = "\n".join(total_lines) + fname = generate_unique_filename( + cl, "unstructure", lines=total_lines if _cattrs_use_linecache else [] + ) + + eval(compile(script, fname, "exec"), globs) + + res = globs[fn_name] + res.overrides = kwargs + + return res + + +def make_dict_unstructure_fn( + cl: type[T], + converter: BaseConverter, + _cattrs_omit_if_default: bool = False, + _cattrs_use_linecache: bool = True, + _cattrs_use_alias: bool = False, + _cattrs_include_init_false: bool = False, + **kwargs: AttributeOverride, +) -> Callable[[T], dict[str, Any]]: + """ + Generate a specialized dict unstructuring function for an attrs class or a + dataclass. + + Any provided overrides are attached to the generated function under the + `overrides` attribute. + + :param _cattrs_omit_if_default: if true, attributes equal to their default values + will be omitted in the result dictionary. + :param _cattrs_use_alias: If true, the attribute alias will be used as the + dictionary key by default. + :param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False` + will be included. + + .. versionadded:: 23.2.0 *_cattrs_use_alias* + .. versionadded:: 23.2.0 *_cattrs_include_init_false* + """ + origin = get_origin(cl) + attrs = adapted_fields(origin or cl) # type: ignore + + if any(isinstance(a.type, str) for a in attrs): + # PEP 563 annotations - need to be resolved. + resolve_types(cl) + + mapping = {} + if is_generic(cl): + mapping = generate_mapping(cl, mapping) + + for base in getattr(origin, "__orig_bases__", ()): + if is_generic(base) and not str(base).startswith("typing.Generic"): + mapping = generate_mapping(base, mapping) + break + if origin is not None: + cl = origin + + # We keep track of what we're generating to help with recursive + # class graphs. + try: + working_set = already_generating.working_set + except AttributeError: + working_set = set() + already_generating.working_set = working_set + if cl in working_set: + raise RecursionError() + + working_set.add(cl) + + try: + return make_dict_unstructure_fn_from_attrs( + attrs, + cl, + converter, + mapping, + _cattrs_omit_if_default=_cattrs_omit_if_default, + _cattrs_use_linecache=_cattrs_use_linecache, + _cattrs_use_alias=_cattrs_use_alias, + _cattrs_include_init_false=_cattrs_include_init_false, + **kwargs, + ) + finally: + working_set.remove(cl) + if not working_set: + del already_generating.working_set + + +DictStructureFn = Callable[[Mapping[str, Any], Any], T] + + +def make_dict_structure_fn_from_attrs( + attrs: list[Attribute], + cl: type, + converter: BaseConverter, + typevar_map: dict[str, Any] = {}, + _cattrs_forbid_extra_keys: bool | Literal["from_converter"] = "from_converter", + _cattrs_use_linecache: bool = True, + _cattrs_prefer_attrib_converters: ( + bool | Literal["from_converter"] + ) = "from_converter", + _cattrs_detailed_validation: bool | Literal["from_converter"] = "from_converter", + _cattrs_use_alias: bool = False, + _cattrs_include_init_false: bool = False, + **kwargs: AttributeOverride, +) -> DictStructureFn[T]: + """ + Generate a specialized dict structuring function for a list of attributes. + + Usually used as a building block by more specialized hook factories. + + Any provided overrides are attached to the generated function under the + `overrides` attribute. + + :param _cattrs_forbid_extra_keys: Whether the structuring function should raise a + `ForbiddenExtraKeysError` if unknown keys are encountered. + :param _cattrs_use_linecache: Whether to store the source code in the Python + linecache. + :param _cattrs_prefer_attrib_converters: If an _attrs_ converter is present on a + field, use it instead of processing the field normally. + :param _cattrs_detailed_validation: Whether to use a slower mode that produces + more detailed errors. + :param _cattrs_use_alias: If true, the attribute alias will be used as the + dictionary key by default. + :param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False` + will be included. + + .. versionadded:: 24.1.0 + """ + + cl_name = cl.__name__ + fn_name = "structure_" + cl_name + + # We have generic parameters and need to generate a unique name for the function + for p in getattr(cl, "__parameters__", ()): + # This is nasty, I am not sure how best to handle `typing.List[str]` or + # `TClass[int, int]` as a parameter type here + try: + name_base = typevar_map[p.__name__] + except KeyError: + pn = p.__name__ + raise StructureHandlerNotFoundError( + f"Missing type for generic argument {pn}, specify it when structuring.", + p, + ) from None + name = getattr(name_base, "__name__", None) or str(name_base) + # `<>` can be present in lambdas + # `|` can be present in unions + name = re.sub(r"[\[\.\] ,<>]", "_", name) + name = re.sub(r"\|", "u", name) + fn_name += f"_{name}" + + internal_arg_parts = {"__cl": cl} + globs = {} + lines = [] + post_lines = [] + pi_lines = [] # post instantiation lines + invocation_lines = [] + + allowed_fields = set() + if _cattrs_forbid_extra_keys == "from_converter": + # BaseConverter doesn't have it so we're careful. + _cattrs_forbid_extra_keys = getattr(converter, "forbid_extra_keys", False) + if _cattrs_detailed_validation == "from_converter": + _cattrs_detailed_validation = converter.detailed_validation + if _cattrs_prefer_attrib_converters == "from_converter": + _cattrs_prefer_attrib_converters = converter._prefer_attrib_converters + + if _cattrs_forbid_extra_keys: + globs["__c_a"] = allowed_fields + globs["__c_feke"] = ForbiddenExtraKeysError + + if _cattrs_detailed_validation: + lines.append(" res = {}") + lines.append(" errors = []") + invocation_lines.append("**res,") + internal_arg_parts["__c_cve"] = ClassValidationError + internal_arg_parts["__c_avn"] = AttributeValidationNote + for a in attrs: + an = a.name + override = kwargs.get(an, neutral) + if override.omit: + continue + if override.omit is None and not a.init and not _cattrs_include_init_false: + continue + t = a.type + if isinstance(t, TypeVar): + t = typevar_map.get(t.__name__, t) + elif is_generic(t) and not is_bare(t) and not is_annotated(t): + t = deep_copy_with(t, typevar_map) + + # For each attribute, we try resolving the type here and now. + # If a type is manually overwritten, this function should be + # regenerated. + if override.struct_hook is not None: + # If the user has requested an override, just use that. + handler = override.struct_hook + else: + handler = find_structure_handler( + a, t, converter, _cattrs_prefer_attrib_converters + ) + + struct_handler_name = f"__c_structure_{an}" + if handler is not None: + internal_arg_parts[struct_handler_name] = handler + + ian = a.alias + if override.rename is None: + kn = an if not _cattrs_use_alias else a.alias + else: + kn = override.rename + + allowed_fields.add(kn) + i = " " + + if not a.init: + if a.default is not NOTHING: + pi_lines.append(f"{i}if '{kn}' in o:") + i = f"{i} " + pi_lines.append(f"{i}try:") + i = f"{i} " + type_name = f"__c_type_{an}" + internal_arg_parts[type_name] = t + if handler is not None: + if handler == converter._structure_call: + internal_arg_parts[struct_handler_name] = t + pi_lines.append( + f"{i}instance.{an} = {struct_handler_name}(o['{kn}'])" + ) + else: + tn = f"__c_type_{an}" + internal_arg_parts[tn] = t + pi_lines.append( + f"{i}instance.{an} = {struct_handler_name}(o['{kn}'], {tn})" + ) + else: + pi_lines.append(f"{i}instance.{an} = o['{kn}']") + i = i[:-2] + pi_lines.append(f"{i}except Exception as e:") + i = f"{i} " + pi_lines.append( + f'{i}e.__notes__ = getattr(e, \'__notes__\', []) + [__c_avn("Structuring class {cl.__qualname__} @ attribute {an}", "{an}", __c_type_{an})]' + ) + pi_lines.append(f"{i}errors.append(e)") + + else: + if a.default is not NOTHING: + lines.append(f"{i}if '{kn}' in o:") + i = f"{i} " + lines.append(f"{i}try:") + i = f"{i} " + type_name = f"__c_type_{an}" + internal_arg_parts[type_name] = t + if handler: + if handler == converter._structure_call: + internal_arg_parts[struct_handler_name] = t + lines.append( + f"{i}res['{ian}'] = {struct_handler_name}(o['{kn}'])" + ) + else: + tn = f"__c_type_{an}" + internal_arg_parts[tn] = t + lines.append( + f"{i}res['{ian}'] = {struct_handler_name}(o['{kn}'], {tn})" + ) + else: + lines.append(f"{i}res['{ian}'] = o['{kn}']") + i = i[:-2] + lines.append(f"{i}except Exception as e:") + i = f"{i} " + lines.append( + f'{i}e.__notes__ = getattr(e, \'__notes__\', []) + [__c_avn("Structuring class {cl.__qualname__} @ attribute {an}", "{an}", __c_type_{an})]' + ) + lines.append(f"{i}errors.append(e)") + + if _cattrs_forbid_extra_keys: + post_lines += [ + " unknown_fields = set(o.keys()) - __c_a", + " if unknown_fields:", + " errors.append(__c_feke('', __cl, unknown_fields))", + ] + + post_lines.append( + f" if errors: raise __c_cve('While structuring ' + {cl_name!r}, errors, __cl)" + ) + if not pi_lines: + instantiation_lines = ( + [" try:"] + + [" return __cl("] + + [f" {line}" for line in invocation_lines] + + [" )"] + + [ + f" except Exception as exc: raise __c_cve('While structuring ' + {cl_name!r}, [exc], __cl)" + ] + ) + else: + instantiation_lines = ( + [" try:"] + + [" instance = __cl("] + + [f" {line}" for line in invocation_lines] + + [" )"] + + [ + f" except Exception as exc: raise __c_cve('While structuring ' + {cl_name!r}, [exc], __cl)" + ] + ) + pi_lines.append(" return instance") + else: + non_required = [] + # The first loop deals with required args. + for a in attrs: + an = a.name + override = kwargs.get(an, neutral) + if override.omit: + continue + if override.omit is None and not a.init and not _cattrs_include_init_false: + continue + if a.default is not NOTHING: + non_required.append(a) + continue + t = a.type + if isinstance(t, TypeVar): + t = typevar_map.get(t.__name__, t) + elif is_generic(t) and not is_bare(t) and not is_annotated(t): + t = deep_copy_with(t, typevar_map) + + # For each attribute, we try resolving the type here and now. + # If a type is manually overwritten, this function should be + # regenerated. + if override.struct_hook is not None: + # If the user has requested an override, just use that. + handler = override.struct_hook + else: + handler = find_structure_handler( + a, t, converter, _cattrs_prefer_attrib_converters + ) + + if override.rename is None: + kn = an if not _cattrs_use_alias else a.alias + else: + kn = override.rename + allowed_fields.add(kn) + + if not a.init: + if handler is not None: + struct_handler_name = f"__c_structure_{an}" + internal_arg_parts[struct_handler_name] = handler + if handler == converter._structure_call: + internal_arg_parts[struct_handler_name] = t + pi_line = f" instance.{an} = {struct_handler_name}(o['{kn}'])" + else: + tn = f"__c_type_{an}" + internal_arg_parts[tn] = t + pi_line = ( + f" instance.{an} = {struct_handler_name}(o['{kn}'], {tn})" + ) + else: + pi_line = f" instance.{an} = o['{kn}']" + + pi_lines.append(pi_line) + else: + if handler: + struct_handler_name = f"__c_structure_{an}" + internal_arg_parts[struct_handler_name] = handler + if handler == converter._structure_call: + internal_arg_parts[struct_handler_name] = t + invocation_line = f"{struct_handler_name}(o['{kn}'])," + else: + tn = f"__c_type_{an}" + internal_arg_parts[tn] = t + invocation_line = f"{struct_handler_name}(o['{kn}'], {tn})," + else: + invocation_line = f"o['{kn}']," + + if a.kw_only: + invocation_line = f"{a.alias}={invocation_line}" + invocation_lines.append(invocation_line) + + # The second loop is for optional args. + if non_required: + invocation_lines.append("**res,") + lines.append(" res = {}") + + for a in non_required: + an = a.name + override = kwargs.get(an, neutral) + t = a.type + if isinstance(t, TypeVar): + t = typevar_map.get(t.__name__, t) + elif is_generic(t) and not is_bare(t) and not is_annotated(t): + t = deep_copy_with(t, typevar_map) + + # For each attribute, we try resolving the type here and now. + # If a type is manually overwritten, this function should be + # regenerated. + if override.struct_hook is not None: + # If the user has requested an override, just use that. + handler = override.struct_hook + else: + handler = find_structure_handler( + a, t, converter, _cattrs_prefer_attrib_converters + ) + + struct_handler_name = f"__c_structure_{an}" + internal_arg_parts[struct_handler_name] = handler + + if override.rename is None: + kn = an if not _cattrs_use_alias else a.alias + else: + kn = override.rename + allowed_fields.add(kn) + if not a.init: + pi_lines.append(f" if '{kn}' in o:") + if handler: + if handler == converter._structure_call: + internal_arg_parts[struct_handler_name] = t + pi_lines.append( + f" instance.{an} = {struct_handler_name}(o['{kn}'])" + ) + else: + tn = f"__c_type_{an}" + internal_arg_parts[tn] = t + pi_lines.append( + f" instance.{an} = {struct_handler_name}(o['{kn}'], {tn})" + ) + else: + pi_lines.append(f" instance.{an} = o['{kn}']") + else: + post_lines.append(f" if '{kn}' in o:") + if handler: + if handler == converter._structure_call: + internal_arg_parts[struct_handler_name] = t + post_lines.append( + f" res['{a.alias}'] = {struct_handler_name}(o['{kn}'])" + ) + else: + tn = f"__c_type_{an}" + internal_arg_parts[tn] = t + post_lines.append( + f" res['{a.alias}'] = {struct_handler_name}(o['{kn}'], {tn})" + ) + else: + post_lines.append(f" res['{a.alias}'] = o['{kn}']") + if not pi_lines: + instantiation_lines = ( + [" return __cl("] + + [f" {line}" for line in invocation_lines] + + [" )"] + ) + else: + instantiation_lines = ( + [" instance = __cl("] + + [f" {line}" for line in invocation_lines] + + [" )"] + ) + pi_lines.append(" return instance") + + if _cattrs_forbid_extra_keys: + post_lines += [ + " unknown_fields = set(o.keys()) - __c_a", + " if unknown_fields:", + " raise __c_feke('', __cl, unknown_fields)", + ] + + # At the end, we create the function header. + internal_arg_line = ", ".join([f"{i}={i}" for i in internal_arg_parts]) + for k, v in internal_arg_parts.items(): + globs[k] = v + + total_lines = [ + f"def {fn_name}(o, _, {internal_arg_line}):", + *lines, + *post_lines, + *instantiation_lines, + *pi_lines, + ] + + script = "\n".join(total_lines) + fname = generate_unique_filename( + cl, "structure", lines=total_lines if _cattrs_use_linecache else [] + ) + + eval(compile(script, fname, "exec"), globs) + + res = globs[fn_name] + res.overrides = kwargs + + return res + + +def make_dict_structure_fn( + cl: type[T], + converter: BaseConverter, + _cattrs_forbid_extra_keys: bool | Literal["from_converter"] = "from_converter", + _cattrs_use_linecache: bool = True, + _cattrs_prefer_attrib_converters: ( + bool | Literal["from_converter"] + ) = "from_converter", + _cattrs_detailed_validation: bool | Literal["from_converter"] = "from_converter", + _cattrs_use_alias: bool = False, + _cattrs_include_init_false: bool = False, + **kwargs: AttributeOverride, +) -> DictStructureFn[T]: + """ + Generate a specialized dict structuring function for an attrs class or + dataclass. + + Any provided overrides are attached to the generated function under the + `overrides` attribute. + + :param _cattrs_forbid_extra_keys: Whether the structuring function should raise a + `ForbiddenExtraKeysError` if unknown keys are encountered. + :param _cattrs_use_linecache: Whether to store the source code in the Python + linecache. + :param _cattrs_prefer_attrib_converters: If an _attrs_ converter is present on a + field, use it instead of processing the field normally. + :param _cattrs_detailed_validation: Whether to use a slower mode that produces + more detailed errors. + :param _cattrs_use_alias: If true, the attribute alias will be used as the + dictionary key by default. + :param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False` + will be included. + + .. versionadded:: 23.2.0 *_cattrs_use_alias* + .. versionadded:: 23.2.0 *_cattrs_include_init_false* + .. versionchanged:: 23.2.0 + The `_cattrs_forbid_extra_keys` and `_cattrs_detailed_validation` parameters + take their values from the given converter by default. + .. versionchanged:: 24.1.0 + The `_cattrs_prefer_attrib_converters` parameter takes its value from the given + converter by default. + """ + + mapping = {} + if is_generic(cl): + base = get_origin(cl) + mapping = generate_mapping(cl, mapping) + if base is not None: + cl = base + + for base in getattr(cl, "__orig_bases__", ()): + if is_generic(base) and not str(base).startswith("typing.Generic"): + mapping = generate_mapping(base, mapping) + break + + attrs = adapted_fields(cl) + + if any(isinstance(a.type, str) for a in attrs): + # PEP 563 annotations - need to be resolved. + resolve_types(cl) + + # We keep track of what we're generating to help with recursive + # class graphs. + try: + working_set = already_generating.working_set + except AttributeError: + working_set = set() + already_generating.working_set = working_set + else: + if cl in working_set: + raise RecursionError() + + working_set.add(cl) + + try: + return make_dict_structure_fn_from_attrs( + attrs, + cl, + converter, + mapping, + _cattrs_forbid_extra_keys=_cattrs_forbid_extra_keys, + _cattrs_use_linecache=_cattrs_use_linecache, + _cattrs_prefer_attrib_converters=_cattrs_prefer_attrib_converters, + _cattrs_detailed_validation=_cattrs_detailed_validation, + _cattrs_use_alias=_cattrs_use_alias, + _cattrs_include_init_false=_cattrs_include_init_false, + **kwargs, + ) + finally: + working_set.remove(cl) + if not working_set: + del already_generating.working_set + + +IterableUnstructureFn = Callable[[Iterable[Any]], Any] + + +#: A type alias for heterogeneous tuple unstructure hooks. +HeteroTupleUnstructureFn: TypeAlias = Callable[[Tuple[Any, ...]], Any] + + +def make_hetero_tuple_unstructure_fn( + cl: Any, + converter: BaseConverter, + unstructure_to: Any = None, + type_args: tuple | None = None, +) -> HeteroTupleUnstructureFn: + """Generate a specialized unstructure function for a heterogenous tuple. + + :param type_args: If provided, override the type arguments. + """ + fn_name = "unstructure_tuple" + + type_args = get_args(cl) if type_args is None else type_args + + # We can do the dispatch here and now. + handlers = [converter.get_unstructure_hook(type_arg) for type_arg in type_args] + + globs = {f"__cattr_u_{i}": h for i, h in enumerate(handlers)} + if unstructure_to is not tuple: + globs["__cattr_seq_cl"] = unstructure_to or cl + lines = [] + + lines.append(f"def {fn_name}(tup):") + if unstructure_to is not tuple: + lines.append(" res = __cattr_seq_cl((") + else: + lines.append(" res = (") + for i in range(len(handlers)): + if handlers[i] == identity: + lines.append(f" tup[{i}],") + else: + lines.append(f" __cattr_u_{i}(tup[{i}]),") + + if unstructure_to is not tuple: + lines.append(" ))") + else: + lines.append(" )") + + total_lines = [*lines, " return res"] + + eval(compile("\n".join(total_lines), "", "exec"), globs) + + return globs[fn_name] + + +MappingUnstructureFn = Callable[[Mapping[Any, Any]], Any] + + +def make_mapping_unstructure_fn( + cl: Any, + converter: BaseConverter, + unstructure_to: Any = None, + key_handler: Callable[[Any, Any | None], Any] | None = None, +) -> MappingUnstructureFn: + """Generate a specialized unstructure function for a mapping.""" + kh = key_handler or converter.unstructure + val_handler = converter.unstructure + + fn_name = "unstructure_mapping" + + # Let's try fishing out the type args. + if getattr(cl, "__args__", None) is not None: + args = get_args(cl) + if len(args) == 2: + key_arg, val_arg = args + else: + # Probably a Counter + key_arg, val_arg = args, Any + # We can do the dispatch here and now. + kh = key_handler or converter.get_unstructure_hook(key_arg, cache_result=False) + if kh == identity: + kh = None + + val_handler = converter.get_unstructure_hook(val_arg, cache_result=False) + if val_handler == identity: + val_handler = None + + globs = { + "__cattr_mapping_cl": unstructure_to or cl, + "__cattr_k_u": kh, + "__cattr_v_u": val_handler, + } + + k_u = "__cattr_k_u(k)" if kh is not None else "k" + v_u = "__cattr_v_u(v)" if val_handler is not None else "v" + + lines = [] + + lines.append(f"def {fn_name}(mapping):") + lines.append( + f" res = __cattr_mapping_cl(({k_u}, {v_u}) for k, v in mapping.items())" + ) + + total_lines = [*lines, " return res"] + + eval(compile("\n".join(total_lines), "", "exec"), globs) + + return globs[fn_name] + + +MappingStructureFn = Callable[[Mapping[Any, Any], Any], T] + + +# This factory is here for backwards compatibility and circular imports. +def mapping_structure_factory( + cl: type[T], + converter: BaseConverter, + structure_to: type = dict, + key_type=NOTHING, + val_type=NOTHING, + detailed_validation: bool = True, +) -> MappingStructureFn[T]: + """Generate a specialized structure function for a mapping.""" + fn_name = "structure_mapping" + + globs: dict[str, type] = {"__cattr_mapping_cl": structure_to} + + lines = [] + internal_arg_parts = {} + + # Let's try fishing out the type args. + if not is_bare(cl): + args = get_args(cl) + if len(args) == 2: + key_arg_cand, val_arg_cand = args + if key_type is NOTHING: + key_type = key_arg_cand + if val_type is NOTHING: + val_type = val_arg_cand + else: + if key_type is not NOTHING and val_type is NOTHING: + (val_type,) = args + elif key_type is NOTHING and val_type is not NOTHING: + (key_type,) = args + else: + # Probably a Counter + (key_type,) = args + val_type = Any + + is_bare_dict = val_type in ANIES and key_type in ANIES + if not is_bare_dict: + # We can do the dispatch here and now. + key_handler = converter.get_structure_hook(key_type, cache_result=False) + if key_handler == converter._structure_call: + key_handler = key_type + + val_handler = converter.get_structure_hook(val_type, cache_result=False) + if val_handler == converter._structure_call: + val_handler = val_type + + globs["__cattr_k_t"] = key_type + globs["__cattr_v_t"] = val_type + globs["__cattr_k_s"] = key_handler + globs["__cattr_v_s"] = val_handler + k_s = ( + "__cattr_k_s(k, __cattr_k_t)" + if key_handler != key_type + else "__cattr_k_s(k)" + ) + v_s = ( + "__cattr_v_s(v, __cattr_v_t)" + if val_handler != val_type + else "__cattr_v_s(v)" + ) + else: + is_bare_dict = True + + if is_bare_dict: + # No args, it's a bare dict. + lines.append(" res = dict(mapping)") + else: + if detailed_validation: + internal_arg_parts["IterableValidationError"] = IterableValidationError + internal_arg_parts["IterableValidationNote"] = IterableValidationNote + internal_arg_parts["val_type"] = ( + val_type if val_type is not NOTHING else Any + ) + internal_arg_parts["key_type"] = ( + key_type if key_type is not NOTHING else Any + ) + globs["enumerate"] = enumerate + + lines.append(" res = {}; errors = []") + lines.append(" for k, v in mapping.items():") + lines.append(" try:") + lines.append(f" value = {v_s}") + lines.append(" except Exception as e:") + lines.append( + " e.__notes__ = getattr(e, '__notes__', []) + [IterableValidationNote(f'Structuring mapping value @ key {k!r}', k, val_type)]" + ) + lines.append(" errors.append(e)") + lines.append(" continue") + lines.append(" try:") + lines.append(f" key = {k_s}") + lines.append(" res[key] = value") + lines.append(" except Exception as e:") + lines.append( + " e.__notes__ = getattr(e, '__notes__', []) + [IterableValidationNote(f'Structuring mapping key @ key {k!r}', k, key_type)]" + ) + lines.append(" errors.append(e)") + lines.append(" if errors:") + lines.append( + f" raise IterableValidationError('While structuring ' + {repr(cl)!r}, errors, __cattr_mapping_cl)" + ) + else: + lines.append(f" res = {{{k_s}: {v_s} for k, v in mapping.items()}}") + if structure_to is not dict: + lines.append(" res = __cattr_mapping_cl(res)") + + internal_arg_line = ", ".join([f"{i}={i}" for i in internal_arg_parts]) + if internal_arg_line: + internal_arg_line = f", {internal_arg_line}" + for k, v in internal_arg_parts.items(): + globs[k] = v + + def_line = f"def {fn_name}(mapping, _{internal_arg_line}):" + total_lines = [def_line, *lines, " return res"] + script = "\n".join(total_lines) + + eval(compile(script, "", "exec"), globs) + + return globs[fn_name] + + +make_mapping_structure_fn: Final = mapping_structure_factory + + +# This factory is here for backwards compatibility and circular imports. +def iterable_unstructure_factory( + cl: Any, converter: BaseConverter, unstructure_to: Any = None +) -> UnstructureHook: + """A hook factory for unstructuring iterables. + + :param unstructure_to: Force unstructuring to this type, if provided. + """ + handler = converter.unstructure + + # Let's try fishing out the type args + # Unspecified tuples have `__args__` as empty tuples, so guard + # against IndexError. + if getattr(cl, "__args__", None) not in (None, ()): + type_arg = cl.__args__[0] + if isinstance(type_arg, TypeVar): + type_arg = getattr(type_arg, "__default__", Any) + handler = converter.get_unstructure_hook(type_arg, cache_result=False) + if handler == identity: + # Save ourselves the trouble of iterating over it all. + return unstructure_to or cl + + def unstructure_iterable(iterable, _seq_cl=unstructure_to or cl, _hook=handler): + return _seq_cl(_hook(i) for i in iterable) + + return unstructure_iterable + + +make_iterable_unstructure_fn: Final = iterable_unstructure_factory diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/gen/_consts.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/gen/_consts.py new file mode 100644 index 0000000..a6dcd03 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/gen/_consts.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +from threading import local +from typing import Any, Callable + +from attrs import frozen + + +@frozen +class AttributeOverride: + omit_if_default: bool | None = None + rename: str | None = None + omit: bool | None = None # Omit the field completely. + struct_hook: Callable[[Any, Any], Any] | None = None # Structure hook to use. + unstruct_hook: Callable[[Any], Any] | None = None # Structure hook to use. + + +neutral = AttributeOverride() +already_generating = local() diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/gen/_generics.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/gen/_generics.py new file mode 100644 index 0000000..069c48c --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/gen/_generics.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +from typing import TypeVar + +from .._compat import get_args, get_origin, is_generic + + +def _tvar_has_default(tvar) -> bool: + """Does `tvar` have a default? + + In CPython 3.13+ and typing_extensions>=4.12.0: + - TypeVars have a `no_default()` method for detecting + if a TypeVar has a default + - TypeVars with `default=None` have `__default__` set to `None` + - TypeVars with no `default` parameter passed + have `__default__` set to `typing(_extensions).NoDefault + + On typing_exensions<4.12.0: + - TypeVars do not have a `no_default()` method for detecting + if a TypeVar has a default + - TypeVars with `default=None` have `__default__` set to `NoneType` + - TypeVars with no `default` parameter passed + have `__default__` set to `typing(_extensions).NoDefault + """ + try: + return tvar.has_default() + except AttributeError: + # compatibility for typing_extensions<4.12.0 + return getattr(tvar, "__default__", None) is not None + + +def generate_mapping(cl: type, old_mapping: dict[str, type] = {}) -> dict[str, type]: + """Generate a mapping of typevars to actual types for a generic class.""" + mapping = dict(old_mapping) + + origin = get_origin(cl) + + if origin is not None: + # To handle the cases where classes in the typing module are using + # the GenericAlias structure but aren't a Generic and hence + # end up in this function but do not have an `__parameters__` + # attribute. These classes are interface types, for example + # `typing.Hashable`. + parameters = getattr(get_origin(cl), "__parameters__", None) + if parameters is None: + return dict(old_mapping) + + for p, t in zip(parameters, get_args(cl)): + if isinstance(t, TypeVar): + continue + mapping[p.__name__] = t + + elif is_generic(cl): + # Origin is None, so this may be a subclass of a generic class. + orig_bases = cl.__orig_bases__ + for base in orig_bases: + if not hasattr(base, "__args__"): + continue + base_args = base.__args__ + if hasattr(base.__origin__, "__parameters__"): + base_params = base.__origin__.__parameters__ + elif any(_tvar_has_default(base_arg) for base_arg in base_args): + # TypeVar with a default e.g. PEP 696 + # https://www.python.org/dev/peps/pep-0696/ + # Extract the defaults for the TypeVars and insert + # them into the mapping + mapping_params = [ + (base_arg, base_arg.__default__) + for base_arg in base_args + if _tvar_has_default(base_arg) + ] + base_params, base_args = zip(*mapping_params) + else: + continue + + for param, arg in zip(base_params, base_args): + mapping[param.__name__] = arg + + return mapping diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/gen/_lc.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/gen/_lc.py new file mode 100644 index 0000000..04843cd --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/gen/_lc.py @@ -0,0 +1,29 @@ +"""Line-cache functionality.""" + +import linecache +from typing import List + + +def generate_unique_filename(cls: type, func_name: str, lines: List[str] = []) -> str: + """ + Create a "filename" suitable for a function being generated. + + If *lines* are provided, insert them in the first free spot or stop + if a duplicate is found. + """ + extra = "" + count = 1 + + while True: + unique_filename = "".format( + func_name, cls.__module__, getattr(cls, "__qualname__", cls.__name__), extra + ) + if not lines: + return unique_filename + cache_line = (len("\n".join(lines)), None, lines, unique_filename) + if linecache.cache.setdefault(unique_filename, cache_line) == cache_line: + return unique_filename + + # Looks like this spot is taken. Try again. + count += 1 + extra = f"-{count}" diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/gen/_shared.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/gen/_shared.py new file mode 100644 index 0000000..4e63143 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/gen/_shared.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from attrs import NOTHING, Attribute, Factory + +from .._compat import is_bare_final +from ..dispatch import StructureHook +from ..fns import raise_error + +if TYPE_CHECKING: + from ..converters import BaseConverter + + +def find_structure_handler( + a: Attribute, type: Any, c: BaseConverter, prefer_attrs_converters: bool = False +) -> StructureHook | None: + """Find the appropriate structure handler to use. + + Return `None` if no handler should be used. + """ + try: + if a.converter is not None and prefer_attrs_converters: + # If the user as requested to use attrib converters, use nothing + # so it falls back to that. + handler = None + elif ( + a.converter is not None and not prefer_attrs_converters and type is not None + ): + handler = c.get_structure_hook(type, cache_result=False) + if handler == raise_error: + handler = None + elif type is not None: + if ( + is_bare_final(type) + and a.default is not NOTHING + and not isinstance(a.default, Factory) + ): + # This is a special case where we can use the + # type of the default to dispatch on. + type = a.default.__class__ + handler = c.get_structure_hook(type, cache_result=False) + if handler == c._structure_call: + # Finals can't really be used with _structure_call, so + # we wrap it so the rest of the toolchain doesn't get + # confused. + + def handler(v, _, _h=handler): + return _h(v, type) + + else: + handler = c.get_structure_hook(type, cache_result=False) + else: + handler = c.structure + return handler + except RecursionError: + # This means we're dealing with a reference cycle, so use late binding. + return c.structure diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/gen/typeddicts.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/gen/typeddicts.py new file mode 100644 index 0000000..5614d6f --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/gen/typeddicts.py @@ -0,0 +1,611 @@ +from __future__ import annotations + +import re +import sys +from typing import TYPE_CHECKING, Any, Callable, Literal, TypeVar + +from attrs import NOTHING, Attribute + +try: + from inspect import get_annotations + + def get_annots(cl) -> dict[str, Any]: + return get_annotations(cl, eval_str=True) + +except ImportError: + # https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older + def get_annots(cl) -> dict[str, Any]: + if isinstance(cl, type): + ann = cl.__dict__.get("__annotations__", {}) + else: + ann = getattr(cl, "__annotations__", {}) + return ann + + +try: + from typing_extensions import _TypedDictMeta +except ImportError: + _TypedDictMeta = None + +from .._compat import ( + TypedDict, + get_full_type_hints, + get_notrequired_base, + get_origin, + is_annotated, + is_bare, + is_generic, +) +from .._generics import deep_copy_with +from ..errors import ( + AttributeValidationNote, + ClassValidationError, + ForbiddenExtraKeysError, + StructureHandlerNotFoundError, +) +from ..fns import identity +from . import AttributeOverride +from ._consts import already_generating, neutral +from ._generics import generate_mapping +from ._lc import generate_unique_filename +from ._shared import find_structure_handler + +if TYPE_CHECKING: + from ..converters import BaseConverter + +__all__ = ["make_dict_unstructure_fn", "make_dict_structure_fn"] + +T = TypeVar("T", bound=TypedDict) + + +def make_dict_unstructure_fn( + cl: type[T], + converter: BaseConverter, + _cattrs_use_linecache: bool = True, + **kwargs: AttributeOverride, +) -> Callable[[T], dict[str, Any]]: + """ + Generate a specialized dict unstructuring function for a TypedDict. + + :param cl: A `TypedDict` class. + :param converter: A Converter instance to use for unstructuring nested fields. + :param kwargs: A mapping of field names to an `AttributeOverride`, for + customization. + :param _cattrs_detailed_validation: Whether to store the generated code in the + _linecache_, for easier debugging and better stack traces. + """ + origin = get_origin(cl) + attrs = _adapted_fields(origin or cl) # type: ignore + req_keys = _required_keys(origin or cl) + + mapping = {} + if is_generic(cl): + mapping = generate_mapping(cl, mapping) + + for base in getattr(origin, "__orig_bases__", ()): + if is_generic(base) and not str(base).startswith("typing.Generic"): + mapping = generate_mapping(base, mapping) + break + + # It's possible for origin to be None if this is a subclass + # of a generic class. + if origin is not None: + cl = origin + + cl_name = cl.__name__ + fn_name = "unstructure_typeddict_" + cl_name + globs = {} + lines = [] + internal_arg_parts = {} + + # We keep track of what we're generating to help with recursive + # class graphs. + try: + working_set = already_generating.working_set + except AttributeError: + working_set = set() + already_generating.working_set = working_set + if cl in working_set: + raise RecursionError() + working_set.add(cl) + + try: + # We want to short-circuit in certain cases and return the identity + # function. + # We short-circuit if all of these are true: + # * no attributes have been overridden + # * all attributes resolve to `converter._unstructure_identity` + for a in attrs: + attr_name = a.name + override = kwargs.get(attr_name, neutral) + if override != neutral: + break + handler = None + t = a.type + + if isinstance(t, TypeVar): + if t.__name__ in mapping: + t = mapping[t.__name__] + else: + # Unbound typevars use late binding. + handler = converter.unstructure + elif is_generic(t) and not is_bare(t) and not is_annotated(t): + t = deep_copy_with(t, mapping) + + if handler is None: + nrb = get_notrequired_base(t) + if nrb is not NOTHING: + t = nrb + try: + handler = converter.get_unstructure_hook(t) + except RecursionError: + # There's a circular reference somewhere down the line + handler = converter.unstructure + is_identity = handler == identity + if not is_identity: + break + else: + # We've not broken the loop. + return identity + + for ix, a in enumerate(attrs): + attr_name = a.name + override = kwargs.get(attr_name, neutral) + if override.omit: + lines.append(f" res.pop('{attr_name}', None)") + continue + if override.rename is not None: + # We also need to pop when renaming, since we're copying + # the original. + lines.append(f" res.pop('{attr_name}', None)") + kn = attr_name if override.rename is None else override.rename + attr_required = attr_name in req_keys + + # For each attribute, we try resolving the type here and now. + # If a type is manually overwritten, this function should be + # regenerated. + handler = None + if override.unstruct_hook is not None: + handler = override.unstruct_hook + else: + t = a.type + + if isinstance(t, TypeVar): + if t.__name__ in mapping: + t = mapping[t.__name__] + else: + handler = converter.unstructure + elif is_generic(t) and not is_bare(t) and not is_annotated(t): + t = deep_copy_with(t, mapping) + + if handler is None: + nrb = get_notrequired_base(t) + if nrb is not NOTHING: + t = nrb + try: + handler = converter.get_unstructure_hook(t) + except RecursionError: + # There's a circular reference somewhere down the line + handler = converter.unstructure + + is_identity = handler == identity + + if not is_identity: + unstruct_handler_name = f"__c_unstr_{ix}" + globs[unstruct_handler_name] = handler + internal_arg_parts[unstruct_handler_name] = handler + invoke = f"{unstruct_handler_name}(instance['{attr_name}'])" + elif override.rename is None: + # We're not doing anything to this attribute, so + # it'll already be present in the input dict. + continue + else: + # Probably renamed, we just fetch it. + invoke = f"instance['{attr_name}']" + + if attr_required: + # No default or no override. + lines.append(f" res['{kn}'] = {invoke}") + else: + lines.append(f" if '{attr_name}' in instance: res['{kn}'] = {invoke}") + + internal_arg_line = ", ".join([f"{i}={i}" for i in internal_arg_parts]) + if internal_arg_line: + internal_arg_line = f", {internal_arg_line}" + for k, v in internal_arg_parts.items(): + globs[k] = v + + total_lines = [ + f"def {fn_name}(instance{internal_arg_line}):", + " res = instance.copy()", + *lines, + " return res", + ] + script = "\n".join(total_lines) + + fname = generate_unique_filename( + cl, "unstructure", lines=total_lines if _cattrs_use_linecache else [] + ) + + eval(compile(script, fname, "exec"), globs) + finally: + working_set.remove(cl) + if not working_set: + del already_generating.working_set + + return globs[fn_name] + + +def make_dict_structure_fn( + cl: Any, + converter: BaseConverter, + _cattrs_forbid_extra_keys: bool | Literal["from_converter"] = "from_converter", + _cattrs_use_linecache: bool = True, + _cattrs_detailed_validation: bool | Literal["from_converter"] = "from_converter", + **kwargs: AttributeOverride, +) -> Callable[[dict, Any], Any]: + """Generate a specialized dict structuring function for typed dicts. + + :param cl: A `TypedDict` class. + :param converter: A Converter instance to use for structuring nested fields. + :param kwargs: A mapping of field names to an `AttributeOverride`, for + customization. + :param _cattrs_detailed_validation: Whether to use a slower mode that produces + more detailed errors. + :param _cattrs_forbid_extra_keys: Whether the structuring function should raise a + `ForbiddenExtraKeysError` if unknown keys are encountered. + :param _cattrs_detailed_validation: Whether to store the generated code in the + _linecache_, for easier debugging and better stack traces. + + .. versionchanged:: 23.2.0 + The `_cattrs_forbid_extra_keys` and `_cattrs_detailed_validation` parameters + take their values from the given converter by default. + """ + + mapping = {} + if is_generic(cl): + base = get_origin(cl) + mapping = generate_mapping(cl, mapping) + if base is not None: + # It's possible for this to be a subclass of a generic, + # so no origin. + cl = base + + for base in getattr(cl, "__orig_bases__", ()): + if is_generic(base) and not str(base).startswith("typing.Generic"): + mapping = generate_mapping(base, mapping) + break + + cl_name = cl.__name__ + fn_name = "structure_" + cl_name + + # We have generic parameters and need to generate a unique name for the function + for p in getattr(cl, "__parameters__", ()): + try: + name_base = mapping[p.__name__] + except KeyError: + pn = p.__name__ + raise StructureHandlerNotFoundError( + f"Missing type for generic argument {pn}, specify it when structuring.", + p, + ) from None + name = getattr(name_base, "__name__", None) or str(name_base) + # `<>` can be present in lambdas + # `|` can be present in unions + name = re.sub(r"[\[\.\] ,<>]", "_", name) + name = re.sub(r"\|", "u", name) + fn_name += f"_{name}" + + internal_arg_parts = {"__cl": cl} + globs = {} + lines = [] + post_lines = [] + + attrs = _adapted_fields(cl) + req_keys = _required_keys(cl) + + allowed_fields = set() + if _cattrs_forbid_extra_keys == "from_converter": + # BaseConverter doesn't have it so we're careful. + _cattrs_forbid_extra_keys = getattr(converter, "forbid_extra_keys", False) + if _cattrs_detailed_validation == "from_converter": + _cattrs_detailed_validation = converter.detailed_validation + + if _cattrs_forbid_extra_keys: + globs["__c_a"] = allowed_fields + globs["__c_feke"] = ForbiddenExtraKeysError + + lines.append(" res = o.copy()") + + if _cattrs_detailed_validation: + lines.append(" errors = []") + internal_arg_parts["__c_cve"] = ClassValidationError + internal_arg_parts["__c_avn"] = AttributeValidationNote + for ix, a in enumerate(attrs): + an = a.name + attr_required = an in req_keys + override = kwargs.get(an, neutral) + if override.omit: + continue + t = a.type + + if isinstance(t, TypeVar): + t = mapping.get(t.__name__, t) + elif is_generic(t) and not is_bare(t) and not is_annotated(t): + t = deep_copy_with(t, mapping) + + nrb = get_notrequired_base(t) + if nrb is not NOTHING: + t = nrb + + if is_generic(t) and not is_bare(t) and not is_annotated(t): + t = deep_copy_with(t, mapping) + + # For each attribute, we try resolving the type here and now. + # If a type is manually overwritten, this function should be + # regenerated. + if override.struct_hook is not None: + # If the user has requested an override, just use that. + handler = override.struct_hook + else: + handler = find_structure_handler(a, t, converter) + + struct_handler_name = f"__c_structure_{ix}" + internal_arg_parts[struct_handler_name] = handler + + kn = an if override.rename is None else override.rename + allowed_fields.add(kn) + i = " " + if not attr_required: + lines.append(f"{i}if '{kn}' in o:") + i = f"{i} " + lines.append(f"{i}try:") + i = f"{i} " + + tn = f"__c_type_{ix}" + internal_arg_parts[tn] = t + + if handler == converter._structure_call: + internal_arg_parts[struct_handler_name] = t + lines.append(f"{i}res['{an}'] = {struct_handler_name}(o['{kn}'])") + else: + lines.append(f"{i}res['{an}'] = {struct_handler_name}(o['{kn}'], {tn})") + if override.rename is not None: + lines.append(f"{i}del res['{kn}']") + i = i[:-2] + lines.append(f"{i}except Exception as e:") + i = f"{i} " + lines.append( + f'{i}e.__notes__ = [*getattr(e, \'__notes__\', []), __c_avn("Structuring typeddict {cl.__qualname__} @ attribute {an}", "{an}", {tn})]' + ) + lines.append(f"{i}errors.append(e)") + + if _cattrs_forbid_extra_keys: + post_lines += [ + " unknown_fields = o.keys() - __c_a", + " if unknown_fields:", + " errors.append(__c_feke('', __cl, unknown_fields))", + ] + + post_lines.append( + f" if errors: raise __c_cve('While structuring ' + {cl.__name__!r}, errors, __cl)" + ) + else: + non_required = [] + + # The first loop deals with required args. + for ix, a in enumerate(attrs): + an = a.name + attr_required = an in req_keys + override = kwargs.get(an, neutral) + if override.omit: + continue + if not attr_required: + non_required.append((ix, a)) + continue + + t = a.type + + if isinstance(t, TypeVar): + t = mapping.get(t.__name__, t) + elif is_generic(t) and not is_bare(t) and not is_annotated(t): + t = deep_copy_with(t, mapping) + + nrb = get_notrequired_base(t) + if nrb is not NOTHING: + t = nrb + + if override.struct_hook is not None: + handler = override.struct_hook + else: + # For each attribute, we try resolving the type here and now. + # If a type is manually overwritten, this function should be + # regenerated. + handler = converter.get_structure_hook(t) + + kn = an if override.rename is None else override.rename + allowed_fields.add(kn) + + struct_handler_name = f"__c_structure_{ix}" + internal_arg_parts[struct_handler_name] = handler + if handler == converter._structure_call: + internal_arg_parts[struct_handler_name] = t + invocation_line = f" res['{an}'] = {struct_handler_name}(o['{kn}'])" + else: + tn = f"__c_type_{ix}" + internal_arg_parts[tn] = t + invocation_line = ( + f" res['{an}'] = {struct_handler_name}(o['{kn}'], {tn})" + ) + + lines.append(invocation_line) + if override.rename is not None: + lines.append(f" del res['{override.rename}']") + + # The second loop is for optional args. + if non_required: + for ix, a in non_required: + an = a.name + override = kwargs.get(an, neutral) + t = a.type + + nrb = get_notrequired_base(t) + if nrb is not NOTHING: + t = nrb + + if isinstance(t, TypeVar): + t = mapping.get(t.__name__, t) + elif is_generic(t) and not is_bare(t) and not is_annotated(t): + t = deep_copy_with(t, mapping) + + if override.struct_hook is not None: + handler = override.struct_hook + else: + # For each attribute, we try resolving the type here and now. + # If a type is manually overwritten, this function should be + # regenerated. + handler = converter.get_structure_hook(t) + + struct_handler_name = f"__c_structure_{ix}" + internal_arg_parts[struct_handler_name] = handler + + ian = an + kn = an if override.rename is None else override.rename + allowed_fields.add(kn) + post_lines.append(f" if '{kn}' in o:") + if handler == converter._structure_call: + internal_arg_parts[struct_handler_name] = t + post_lines.append( + f" res['{ian}'] = {struct_handler_name}(o['{kn}'])" + ) + else: + tn = f"__c_type_{ix}" + internal_arg_parts[tn] = t + post_lines.append( + f" res['{ian}'] = {struct_handler_name}(o['{kn}'], {tn})" + ) + if override.rename is not None: + lines.append(f" res.pop('{override.rename}', None)") + + if _cattrs_forbid_extra_keys: + post_lines += [ + " unknown_fields = o.keys() - __c_a", + " if unknown_fields:", + " raise __c_feke('', __cl, unknown_fields)", + ] + + # At the end, we create the function header. + internal_arg_line = ", ".join([f"{i}={i}" for i in internal_arg_parts]) + for k, v in internal_arg_parts.items(): + globs[k] = v + + total_lines = [ + f"def {fn_name}(o, _, {internal_arg_line}):", + *lines, + *post_lines, + " return res", + ] + + script = "\n".join(total_lines) + fname = generate_unique_filename( + cl, "structure", lines=total_lines if _cattrs_use_linecache else [] + ) + + eval(compile(script, fname, "exec"), globs) + return globs[fn_name] + + +def _adapted_fields(cls: Any) -> list[Attribute]: + annotations = get_annots(cls) + hints = get_full_type_hints(cls) + return [ + Attribute( + n, + NOTHING, + None, + False, + False, + False, + False, + False, + type=hints[n] if n in hints else annotations[n], + ) + for n, a in annotations.items() + ] + + +def _is_extensions_typeddict(cls) -> bool: + if _TypedDictMeta is None: + return False + return cls.__class__ is _TypedDictMeta or ( + is_generic(cls) and (cls.__origin__.__class__ is _TypedDictMeta) + ) + + +if sys.version_info >= (3, 11): + + def _required_keys(cls: type) -> set[str]: + return cls.__required_keys__ + +elif sys.version_info >= (3, 9): + from typing_extensions import Annotated, NotRequired, Required, get_args + + # Note that there is no `typing.Required` on 3.9 and 3.10, only in + # `typing_extensions`. Therefore, `typing.TypedDict` will not honor this + # annotation, only `typing_extensions.TypedDict`. + + def _required_keys(cls: type) -> set[str]: + """Our own processor for required keys.""" + if _is_extensions_typeddict(cls): + return cls.__required_keys__ + + # We vendor a part of the typing_extensions logic for + # gathering required keys. *sigh* + own_annotations = cls.__dict__.get("__annotations__", {}) + required_keys = set() + # On 3.8 - 3.10, typing.TypedDict doesn't put typeddict superclasses + # in the MRO, therefore we cannot handle non-required keys properly + # in some situations. Oh well. + for key in getattr(cls, "__required_keys__", []): + annotation_type = own_annotations[key] + annotation_origin = get_origin(annotation_type) + if annotation_origin is Annotated: + annotation_args = get_args(annotation_type) + if annotation_args: + annotation_type = annotation_args[0] + annotation_origin = get_origin(annotation_type) + + if annotation_origin is NotRequired: + pass + elif cls.__total__: + required_keys.add(key) + return required_keys + +else: + from typing_extensions import Annotated, NotRequired, Required, get_args + + # On 3.8, typing.TypedDicts do not have __required_keys__. + + def _required_keys(cls: type) -> set[str]: + """Our own processor for required keys.""" + if _is_extensions_typeddict(cls): + return cls.__required_keys__ + + own_annotations = cls.__dict__.get("__annotations__", {}) + required_keys = set() + for key in own_annotations: + annotation_type = own_annotations[key] + + if is_annotated(annotation_type): + # If this is `Annotated`, we need to get the origin twice. + annotation_type = get_origin(annotation_type) + + annotation_origin = get_origin(annotation_type) + + if annotation_origin is Required: + required_keys.add(key) + elif annotation_origin is NotRequired: + pass + elif cls.__total__: + required_keys.add(key) + return required_keys diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/__init__.py new file mode 100644 index 0000000..876576d --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/__init__.py @@ -0,0 +1,27 @@ +import sys +from datetime import datetime +from typing import Any, Callable, TypeVar + +if sys.version_info[:2] < (3, 10): + from typing_extensions import ParamSpec +else: + from typing import ParamSpec + + +def validate_datetime(v, _): + if not isinstance(v, datetime): + raise Exception(f"Expected datetime, got {v}") + return v + + +T = TypeVar("T") +P = ParamSpec("P") + + +def wrap(_: Callable[P, Any]) -> Callable[[Callable[..., T]], Callable[P, T]]: + """Wrap a `Converter` `__init__` in a type-safe way.""" + + def impl(x: Callable[..., T]) -> Callable[P, T]: + return x + + return impl diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/bson.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/bson.py new file mode 100644 index 0000000..e73d131 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/bson.py @@ -0,0 +1,106 @@ +"""Preconfigured converters for bson.""" + +from base64 import b85decode, b85encode +from datetime import date, datetime +from typing import Any, Type, TypeVar, Union + +from bson import DEFAULT_CODEC_OPTIONS, CodecOptions, Int64, ObjectId, decode, encode + +from cattrs._compat import AbstractSet, is_mapping +from cattrs.gen import make_mapping_structure_fn + +from ..converters import BaseConverter, Converter +from ..dispatch import StructureHook +from ..strategies import configure_union_passthrough +from . import validate_datetime, wrap + +T = TypeVar("T") + + +class Base85Bytes(bytes): + """A subclass to help with binary key encoding/decoding.""" + + +class BsonConverter(Converter): + def dumps( + self, + obj: Any, + unstructure_as: Any = None, + check_keys: bool = False, + codec_options: CodecOptions = DEFAULT_CODEC_OPTIONS, + ) -> bytes: + return encode( + self.unstructure(obj, unstructure_as=unstructure_as), + check_keys=check_keys, + codec_options=codec_options, + ) + + def loads( + self, + data: bytes, + cl: Type[T], + codec_options: CodecOptions = DEFAULT_CODEC_OPTIONS, + ) -> T: + return self.structure(decode(data, codec_options=codec_options), cl) + + +def configure_converter(converter: BaseConverter): + """ + Configure the converter for use with the bson library. + + * sets are serialized as lists + * byte mapping keys are base85-encoded into strings when unstructuring, and reverse + * non-string, non-byte mapping keys are coerced into strings when unstructuring + * a deserialization hook is registered for bson.ObjectId by default + """ + + def gen_unstructure_mapping(cl: Any, unstructure_to=None): + key_handler = str + args = getattr(cl, "__args__", None) + if args: + if issubclass(args[0], str): + key_handler = None + elif issubclass(args[0], bytes): + + def key_handler(k): + return b85encode(k).decode("utf8") + + return converter.gen_unstructure_mapping( + cl, unstructure_to=unstructure_to, key_handler=key_handler + ) + + def gen_structure_mapping(cl: Any) -> StructureHook: + args = getattr(cl, "__args__", None) + if args and issubclass(args[0], bytes): + h = make_mapping_structure_fn(cl, converter, key_type=Base85Bytes) + else: + h = make_mapping_structure_fn(cl, converter) + return h + + converter.register_structure_hook(Base85Bytes, lambda v, _: b85decode(v)) + converter.register_unstructure_hook_factory(is_mapping, gen_unstructure_mapping) + converter.register_structure_hook_factory(is_mapping, gen_structure_mapping) + + converter.register_structure_hook(ObjectId, lambda v, _: ObjectId(v)) + configure_union_passthrough( + Union[str, bool, int, float, None, bytes, datetime, ObjectId, Int64], converter + ) + + # datetime inherits from date, so identity unstructure hook used + # here to prevent the date unstructure hook running. + converter.register_unstructure_hook(datetime, lambda v: v) + converter.register_structure_hook(datetime, validate_datetime) + converter.register_unstructure_hook(date, lambda v: v.isoformat()) + converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v)) + + +@wrap(BsonConverter) +def make_converter(*args: Any, **kwargs: Any) -> BsonConverter: + kwargs["unstruct_collection_overrides"] = { + AbstractSet: list, + **kwargs.get("unstruct_collection_overrides", {}), + } + res = BsonConverter(*args, **kwargs) + configure_converter(res) + + return res diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/cbor2.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/cbor2.py new file mode 100644 index 0000000..73a9a97 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/cbor2.py @@ -0,0 +1,50 @@ +"""Preconfigured converters for cbor2.""" + +from datetime import date, datetime, timezone +from typing import Any, Type, TypeVar, Union + +from cbor2 import dumps, loads + +from cattrs._compat import AbstractSet + +from ..converters import BaseConverter, Converter +from ..strategies import configure_union_passthrough +from . import wrap + +T = TypeVar("T") + + +class Cbor2Converter(Converter): + def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> bytes: + return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs) + + def loads(self, data: bytes, cl: Type[T], **kwargs: Any) -> T: + return self.structure(loads(data, **kwargs), cl) + + +def configure_converter(converter: BaseConverter): + """ + Configure the converter for use with the cbor2 library. + + * datetimes are serialized as timestamp floats + * sets are serialized as lists + """ + converter.register_unstructure_hook(datetime, lambda v: v.timestamp()) + converter.register_structure_hook( + datetime, lambda v, _: datetime.fromtimestamp(v, timezone.utc) + ) + converter.register_unstructure_hook(date, lambda v: v.isoformat()) + converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v)) + configure_union_passthrough(Union[str, bool, int, float, None, bytes], converter) + + +@wrap(Cbor2Converter) +def make_converter(*args: Any, **kwargs: Any) -> Cbor2Converter: + kwargs["unstruct_collection_overrides"] = { + AbstractSet: list, + **kwargs.get("unstruct_collection_overrides", {}), + } + res = Cbor2Converter(*args, **kwargs) + configure_converter(res) + + return res diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/json.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/json.py new file mode 100644 index 0000000..acc82ae --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/json.py @@ -0,0 +1,56 @@ +"""Preconfigured converters for the stdlib json.""" + +from base64 import b85decode, b85encode +from datetime import date, datetime +from json import dumps, loads +from typing import Any, Type, TypeVar, Union + +from .._compat import AbstractSet, Counter +from ..converters import BaseConverter, Converter +from ..strategies import configure_union_passthrough +from . import wrap + +T = TypeVar("T") + + +class JsonConverter(Converter): + def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> str: + return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs) + + def loads(self, data: Union[bytes, str], cl: Type[T], **kwargs: Any) -> T: + return self.structure(loads(data, **kwargs), cl) + + +def configure_converter(converter: BaseConverter): + """ + Configure the converter for use with the stdlib json module. + + * bytes are serialized as base85 strings + * datetimes are serialized as ISO 8601 + * counters are serialized as dicts + * sets are serialized as lists + * union passthrough is configured for unions of strings, bools, ints, + floats and None + """ + converter.register_unstructure_hook( + bytes, lambda v: (b85encode(v) if v else b"").decode("utf8") + ) + converter.register_structure_hook(bytes, lambda v, _: b85decode(v)) + converter.register_unstructure_hook(datetime, lambda v: v.isoformat()) + converter.register_structure_hook(datetime, lambda v, _: datetime.fromisoformat(v)) + converter.register_unstructure_hook(date, lambda v: v.isoformat()) + converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v)) + configure_union_passthrough(Union[str, bool, int, float, None], converter) + + +@wrap(JsonConverter) +def make_converter(*args: Any, **kwargs: Any) -> JsonConverter: + kwargs["unstruct_collection_overrides"] = { + AbstractSet: list, + Counter: dict, + **kwargs.get("unstruct_collection_overrides", {}), + } + res = JsonConverter(*args, **kwargs) + configure_converter(res) + + return res diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/msgpack.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/msgpack.py new file mode 100644 index 0000000..dd7c369 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/msgpack.py @@ -0,0 +1,54 @@ +"""Preconfigured converters for msgpack.""" + +from datetime import date, datetime, time, timezone +from typing import Any, Type, TypeVar, Union + +from msgpack import dumps, loads + +from cattrs._compat import AbstractSet + +from ..converters import BaseConverter, Converter +from ..strategies import configure_union_passthrough +from . import wrap + +T = TypeVar("T") + + +class MsgpackConverter(Converter): + def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> bytes: + return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs) + + def loads(self, data: bytes, cl: Type[T], **kwargs: Any) -> T: + return self.structure(loads(data, **kwargs), cl) + + +def configure_converter(converter: BaseConverter): + """ + Configure the converter for use with the msgpack library. + + * datetimes are serialized as timestamp floats + * sets are serialized as lists + """ + converter.register_unstructure_hook(datetime, lambda v: v.timestamp()) + converter.register_structure_hook( + datetime, lambda v, _: datetime.fromtimestamp(v, timezone.utc) + ) + converter.register_unstructure_hook( + date, lambda v: datetime.combine(v, time(tzinfo=timezone.utc)).timestamp() + ) + converter.register_structure_hook( + date, lambda v, _: datetime.fromtimestamp(v, timezone.utc).date() + ) + configure_union_passthrough(Union[str, bool, int, float, None, bytes], converter) + + +@wrap(MsgpackConverter) +def make_converter(*args: Any, **kwargs: Any) -> MsgpackConverter: + kwargs["unstruct_collection_overrides"] = { + AbstractSet: list, + **kwargs.get("unstruct_collection_overrides", {}), + } + res = MsgpackConverter(*args, **kwargs) + configure_converter(res) + + return res diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/msgspec.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/msgspec.py new file mode 100644 index 0000000..6ef84d7 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/msgspec.py @@ -0,0 +1,185 @@ +"""Preconfigured converters for msgspec.""" + +from __future__ import annotations + +from base64 import b64decode +from datetime import date, datetime +from enum import Enum +from functools import partial +from typing import Any, Callable, TypeVar, Union, get_type_hints + +from attrs import has as attrs_has +from attrs import resolve_types +from msgspec import Struct, convert, to_builtins +from msgspec.json import Encoder, decode + +from .._compat import ( + fields, + get_args, + get_origin, + has, + is_bare, + is_mapping, + is_sequence, +) +from ..cols import is_namedtuple +from ..converters import BaseConverter, Converter +from ..dispatch import UnstructureHook +from ..fns import identity +from ..gen import make_hetero_tuple_unstructure_fn +from ..strategies import configure_union_passthrough +from . import wrap + +T = TypeVar("T") + +__all__ = ["MsgspecJsonConverter", "configure_converter", "make_converter"] + + +class MsgspecJsonConverter(Converter): + """A converter specialized for the _msgspec_ library.""" + + #: The msgspec encoder for dumping. + encoder: Encoder = Encoder() + + def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> bytes: + """Unstructure and encode `obj` into JSON bytes.""" + return self.encoder.encode( + self.unstructure(obj, unstructure_as=unstructure_as), **kwargs + ) + + def get_dumps_hook( + self, unstructure_as: Any, **kwargs: Any + ) -> Callable[[Any], bytes]: + """Produce a `dumps` hook for the given type.""" + unstruct_hook = self.get_unstructure_hook(unstructure_as) + if unstruct_hook in (identity, to_builtins): + return self.encoder.encode + return self.dumps + + def loads(self, data: bytes, cl: type[T], **kwargs: Any) -> T: + """Decode and structure `cl` from the provided JSON bytes.""" + return self.structure(decode(data, **kwargs), cl) + + def get_loads_hook(self, cl: type[T]) -> Callable[[bytes], T]: + """Produce a `loads` hook for the given type.""" + return partial(self.loads, cl=cl) + + +def configure_converter(converter: Converter) -> None: + """Configure the converter for the msgspec library. + + * bytes are serialized as base64 strings, directly by msgspec + * datetimes and dates are passed through to be serialized as RFC 3339 directly + * enums are passed through to msgspec directly + * union passthrough configured for str, bool, int, float and None + """ + configure_passthroughs(converter) + + converter.register_unstructure_hook(Struct, to_builtins) + converter.register_unstructure_hook(Enum, to_builtins) + + converter.register_structure_hook(Struct, convert) + converter.register_structure_hook(bytes, lambda v, _: b64decode(v)) + converter.register_structure_hook(datetime, lambda v, _: convert(v, datetime)) + converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v)) + configure_union_passthrough(Union[str, bool, int, float, None], converter) + + +@wrap(MsgspecJsonConverter) +def make_converter(*args: Any, **kwargs: Any) -> MsgspecJsonConverter: + res = MsgspecJsonConverter(*args, **kwargs) + configure_converter(res) + return res + + +def configure_passthroughs(converter: Converter) -> None: + """Configure optimizing passthroughs. + + A passthrough is when we let msgspec handle something automatically. + """ + converter.register_unstructure_hook(bytes, to_builtins) + converter.register_unstructure_hook_factory(is_mapping, mapping_unstructure_factory) + converter.register_unstructure_hook_factory(is_sequence, seq_unstructure_factory) + converter.register_unstructure_hook_factory(has, attrs_unstructure_factory) + converter.register_unstructure_hook_factory( + is_namedtuple, namedtuple_unstructure_factory + ) + + +def seq_unstructure_factory(type, converter: Converter) -> UnstructureHook: + """The msgspec unstructure hook factory for sequences.""" + if is_bare(type): + type_arg = Any + else: + args = get_args(type) + type_arg = args[0] + handler = converter.get_unstructure_hook(type_arg, cache_result=False) + + if handler in (identity, to_builtins): + return handler + return converter.gen_unstructure_iterable(type) + + +def mapping_unstructure_factory(type, converter: BaseConverter) -> UnstructureHook: + """The msgspec unstructure hook factory for mappings.""" + if is_bare(type): + key_arg = Any + val_arg = Any + key_handler = converter.get_unstructure_hook(key_arg, cache_result=False) + value_handler = converter.get_unstructure_hook(val_arg, cache_result=False) + else: + args = get_args(type) + if len(args) == 2: + key_arg, val_arg = args + else: + # Probably a Counter + key_arg, val_arg = args, Any + key_handler = converter.get_unstructure_hook(key_arg, cache_result=False) + value_handler = converter.get_unstructure_hook(val_arg, cache_result=False) + + if key_handler in (identity, to_builtins) and value_handler in ( + identity, + to_builtins, + ): + return to_builtins + return converter.gen_unstructure_mapping(type) + + +def attrs_unstructure_factory(type: Any, converter: Converter) -> UnstructureHook: + """Choose whether to use msgspec handling or our own.""" + origin = get_origin(type) + attribs = fields(origin or type) + if attrs_has(type) and any(isinstance(a.type, str) for a in attribs): + resolve_types(type) + attribs = fields(origin or type) + + if any( + attr.name.startswith("_") + or ( + converter.get_unstructure_hook(attr.type, cache_result=False) + not in (identity, to_builtins) + ) + for attr in attribs + ): + return converter.gen_unstructure_attrs_fromdict(type) + + return to_builtins + + +def namedtuple_unstructure_factory( + type: type[tuple], converter: BaseConverter +) -> UnstructureHook: + """A hook factory for unstructuring namedtuples, modified for msgspec.""" + + if all( + converter.get_unstructure_hook(t) in (identity, to_builtins) + for t in get_type_hints(type).values() + ): + return identity + + return make_hetero_tuple_unstructure_fn( + type, + converter, + unstructure_to=tuple, + type_args=tuple(get_type_hints(type).values()), + ) diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/orjson.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/orjson.py new file mode 100644 index 0000000..4b595bc --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/orjson.py @@ -0,0 +1,95 @@ +"""Preconfigured converters for orjson.""" + +from base64 import b85decode, b85encode +from datetime import date, datetime +from enum import Enum +from functools import partial +from typing import Any, Type, TypeVar, Union + +from orjson import dumps, loads + +from .._compat import AbstractSet, is_mapping +from ..cols import is_namedtuple, namedtuple_unstructure_factory +from ..converters import BaseConverter, Converter +from ..fns import identity +from ..strategies import configure_union_passthrough +from . import wrap + +T = TypeVar("T") + + +class OrjsonConverter(Converter): + def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> bytes: + return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs) + + def loads(self, data: Union[bytes, bytearray, memoryview, str], cl: Type[T]) -> T: + return self.structure(loads(data), cl) + + +def configure_converter(converter: BaseConverter): + """ + Configure the converter for use with the orjson library. + + * bytes are serialized as base85 strings + * datetimes and dates are passed through to be serialized as RFC 3339 by orjson + * typed namedtuples are serialized as lists + * sets are serialized as lists + * string enum mapping keys have special handling + * mapping keys are coerced into strings when unstructuring + + .. versionchanged: 24.1.0 + Add support for typed namedtuples. + """ + converter.register_unstructure_hook( + bytes, lambda v: (b85encode(v) if v else b"").decode("utf8") + ) + converter.register_structure_hook(bytes, lambda v, _: b85decode(v)) + + converter.register_structure_hook(datetime, lambda v, _: datetime.fromisoformat(v)) + converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v)) + + def gen_unstructure_mapping(cl: Any, unstructure_to=None): + key_handler = str + args = getattr(cl, "__args__", None) + if args: + if issubclass(args[0], str) and issubclass(args[0], Enum): + + def key_handler(v): + return v.value + + else: + # It's possible the handler for the key type has been overridden. + # (For example base85 encoding for bytes.) + # In that case, we want to use the override. + + kh = converter.get_unstructure_hook(args[0]) + if kh != identity: + key_handler = kh + + return converter.gen_unstructure_mapping( + cl, unstructure_to=unstructure_to, key_handler=key_handler + ) + + converter._unstructure_func.register_func_list( + [ + (is_mapping, gen_unstructure_mapping, True), + ( + is_namedtuple, + partial(namedtuple_unstructure_factory, unstructure_to=tuple), + "extended", + ), + ] + ) + configure_union_passthrough(Union[str, bool, int, float, None], converter) + + +@wrap(OrjsonConverter) +def make_converter(*args: Any, **kwargs: Any) -> OrjsonConverter: + kwargs["unstruct_collection_overrides"] = { + AbstractSet: list, + **kwargs.get("unstruct_collection_overrides", {}), + } + res = OrjsonConverter(*args, **kwargs) + configure_converter(res) + + return res diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/pyyaml.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/pyyaml.py new file mode 100644 index 0000000..7374625 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/pyyaml.py @@ -0,0 +1,72 @@ +"""Preconfigured converters for pyyaml.""" + +from datetime import date, datetime +from functools import partial +from typing import Any, Type, TypeVar, Union + +from yaml import safe_dump, safe_load + +from .._compat import FrozenSetSubscriptable +from ..cols import is_namedtuple, namedtuple_unstructure_factory +from ..converters import BaseConverter, Converter +from ..strategies import configure_union_passthrough +from . import validate_datetime, wrap + +T = TypeVar("T") + + +def validate_date(v, _): + if not isinstance(v, date): + raise ValueError(f"Expected date, got {v}") + return v + + +class PyyamlConverter(Converter): + def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> str: + return safe_dump(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs) + + def loads(self, data: str, cl: Type[T]) -> T: + return self.structure(safe_load(data), cl) + + +def configure_converter(converter: BaseConverter): + """ + Configure the converter for use with the pyyaml library. + + * frozensets are serialized as lists + * string enums are converted into strings explicitly + * datetimes and dates are validated + * typed namedtuples are serialized as lists + + .. versionchanged: 24.1.0 + Add support for typed namedtuples. + """ + converter.register_unstructure_hook( + str, lambda v: v if v.__class__ is str else v.value + ) + + # datetime inherits from date, so identity unstructure hook used + # here to prevent the date unstructure hook running. + converter.register_unstructure_hook(datetime, lambda v: v) + converter.register_structure_hook(datetime, validate_datetime) + converter.register_structure_hook(date, validate_date) + + converter.register_unstructure_hook_factory(is_namedtuple)( + partial(namedtuple_unstructure_factory, unstructure_to=tuple) + ) + + configure_union_passthrough( + Union[str, bool, int, float, None, bytes, datetime, date], converter + ) + + +@wrap(PyyamlConverter) +def make_converter(*args: Any, **kwargs: Any) -> PyyamlConverter: + kwargs["unstruct_collection_overrides"] = { + FrozenSetSubscriptable: list, + **kwargs.get("unstruct_collection_overrides", {}), + } + res = PyyamlConverter(*args, **kwargs) + configure_converter(res) + + return res diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/tomlkit.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/tomlkit.py new file mode 100644 index 0000000..0d0180b --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/tomlkit.py @@ -0,0 +1,87 @@ +"""Preconfigured converters for tomlkit.""" + +from base64 import b85decode, b85encode +from datetime import date, datetime +from enum import Enum +from operator import attrgetter +from typing import Any, Type, TypeVar, Union + +from tomlkit import dumps, loads +from tomlkit.items import Float, Integer, String + +from cattrs._compat import AbstractSet, is_mapping + +from ..converters import BaseConverter, Converter +from ..strategies import configure_union_passthrough +from . import validate_datetime, wrap + +T = TypeVar("T") +_enum_value_getter = attrgetter("_value_") + + +class TomlkitConverter(Converter): + def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> str: + return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs) + + def loads(self, data: str, cl: Type[T]) -> T: + return self.structure(loads(data), cl) + + +def configure_converter(converter: BaseConverter): + """ + Configure the converter for use with the tomlkit library. + + * bytes are serialized as base85 strings + * sets are serialized as lists + * tuples are serializas as lists + * mapping keys are coerced into strings when unstructuring + """ + converter.register_structure_hook(bytes, lambda v, _: b85decode(v)) + converter.register_unstructure_hook( + bytes, lambda v: (b85encode(v) if v else b"").decode("utf8") + ) + + def gen_unstructure_mapping(cl: Any, unstructure_to=None): + key_handler = str + args = getattr(cl, "__args__", None) + if args: + # Currently, tomlkit has inconsistent behavior on 3.11 + # so we paper over it here. + # https://github.com/sdispater/tomlkit/issues/237 + if issubclass(args[0], str): + key_handler = _enum_value_getter if issubclass(args[0], Enum) else None + elif issubclass(args[0], bytes): + + def key_handler(k: bytes): + return b85encode(k).decode("utf8") + + return converter.gen_unstructure_mapping( + cl, unstructure_to=unstructure_to, key_handler=key_handler + ) + + converter._unstructure_func.register_func_list( + [(is_mapping, gen_unstructure_mapping, True)] + ) + + # datetime inherits from date, so identity unstructure hook used + # here to prevent the date unstructure hook running. + converter.register_unstructure_hook(datetime, lambda v: v) + converter.register_structure_hook(datetime, validate_datetime) + converter.register_unstructure_hook(date, lambda v: v.isoformat()) + converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v)) + configure_union_passthrough( + Union[str, String, bool, int, Integer, float, Float], converter + ) + + +@wrap(TomlkitConverter) +def make_converter(*args: Any, **kwargs: Any) -> TomlkitConverter: + kwargs["unstruct_collection_overrides"] = { + AbstractSet: list, + tuple: list, + **kwargs.get("unstruct_collection_overrides", {}), + } + res = TomlkitConverter(*args, **kwargs) + configure_converter(res) + + return res diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/ujson.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/ujson.py new file mode 100644 index 0000000..7256d52 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/preconf/ujson.py @@ -0,0 +1,55 @@ +"""Preconfigured converters for ujson.""" + +from base64 import b85decode, b85encode +from datetime import date, datetime +from typing import Any, AnyStr, Type, TypeVar, Union + +from ujson import dumps, loads + +from cattrs._compat import AbstractSet + +from ..converters import BaseConverter, Converter +from ..strategies import configure_union_passthrough +from . import wrap + +T = TypeVar("T") + + +class UjsonConverter(Converter): + def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> str: + return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs) + + def loads(self, data: AnyStr, cl: Type[T], **kwargs: Any) -> T: + return self.structure(loads(data, **kwargs), cl) + + +def configure_converter(converter: BaseConverter): + """ + Configure the converter for use with the ujson library. + + * bytes are serialized as base64 strings + * datetimes are serialized as ISO 8601 + * sets are serialized as lists + """ + converter.register_unstructure_hook( + bytes, lambda v: (b85encode(v) if v else b"").decode("utf8") + ) + converter.register_structure_hook(bytes, lambda v, _: b85decode(v)) + + converter.register_unstructure_hook(datetime, lambda v: v.isoformat()) + converter.register_structure_hook(datetime, lambda v, _: datetime.fromisoformat(v)) + converter.register_unstructure_hook(date, lambda v: v.isoformat()) + converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v)) + configure_union_passthrough(Union[str, bool, int, float, None], converter) + + +@wrap(UjsonConverter) +def make_converter(*args: Any, **kwargs: Any) -> UjsonConverter: + kwargs["unstruct_collection_overrides"] = { + AbstractSet: list, + **kwargs.get("unstruct_collection_overrides", {}), + } + res = UjsonConverter(*args, **kwargs) + configure_converter(res) + + return res diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/py.typed b/lambdas/aws-dd-forwarder-3.127.0/cattrs/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/strategies/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/strategies/__init__.py new file mode 100644 index 0000000..9caf073 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/strategies/__init__.py @@ -0,0 +1,12 @@ +"""High level strategies for converters.""" + +from ._class_methods import use_class_methods +from ._subclasses import include_subclasses +from ._unions import configure_tagged_union, configure_union_passthrough + +__all__ = [ + "configure_tagged_union", + "configure_union_passthrough", + "include_subclasses", + "use_class_methods", +] diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/strategies/_class_methods.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/strategies/_class_methods.py new file mode 100644 index 0000000..c2b6325 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/strategies/_class_methods.py @@ -0,0 +1,64 @@ +"""Strategy for using class-specific (un)structuring methods.""" + +from inspect import signature +from typing import Any, Callable, Optional, Type, TypeVar + +from .. import BaseConverter + +T = TypeVar("T") + + +def use_class_methods( + converter: BaseConverter, + structure_method_name: Optional[str] = None, + unstructure_method_name: Optional[str] = None, +) -> None: + """ + Configure the converter such that dedicated methods are used for (un)structuring + the instance of a class if such methods are available. The default (un)structuring + will be applied if such an (un)structuring methods cannot be found. + + :param converter: The `Converter` on which this strategy is applied. You can use + :class:`cattrs.BaseConverter` or any other derived class. + :param structure_method_name: Optional string with the name of the class method + which should be used for structuring. If not provided, no class method will be + used for structuring. + :param unstructure_method_name: Optional string with the name of the class method + which should be used for unstructuring. If not provided, no class method will + be used for unstructuring. + + If you want to (un)structured nested objects, just append a converter parameter + to your (un)structuring methods and you will receive the converter there. + + .. versionadded:: 23.2.0 + """ + + if structure_method_name: + + def make_class_method_structure(cl: Type[T]) -> Callable[[Any, Type[T]], T]: + fn = getattr(cl, structure_method_name) + n_parameters = len(signature(fn).parameters) + if n_parameters == 1: + return lambda v, _: fn(v) + if n_parameters == 2: + return lambda v, _: fn(v, converter) + raise TypeError("Provide a class method with one or two arguments.") + + converter.register_structure_hook_factory( + lambda t: hasattr(t, structure_method_name), make_class_method_structure + ) + + if unstructure_method_name: + + def make_class_method_unstructure(cl: Type[T]) -> Callable[[T], T]: + fn = getattr(cl, unstructure_method_name) + n_parameters = len(signature(fn).parameters) + if n_parameters == 1: + return fn + if n_parameters == 2: + return lambda self_: fn(self_, converter) + raise TypeError("Provide a method with no or one argument.") + + converter.register_unstructure_hook_factory( + lambda t: hasattr(t, unstructure_method_name), make_class_method_unstructure + ) diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/strategies/_subclasses.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/strategies/_subclasses.py new file mode 100644 index 0000000..06a92af --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/strategies/_subclasses.py @@ -0,0 +1,238 @@ +"""Strategies for customizing subclass behaviors.""" + +from __future__ import annotations + +from gc import collect +from typing import Any, Callable, TypeVar, Union + +from ..converters import BaseConverter +from ..gen import AttributeOverride, make_dict_structure_fn, make_dict_unstructure_fn +from ..gen._consts import already_generating + + +def _make_subclasses_tree(cl: type) -> list[type]: + return [cl] + [ + sscl for scl in cl.__subclasses__() for sscl in _make_subclasses_tree(scl) + ] + + +def _has_subclasses(cl: type, given_subclasses: tuple[type, ...]) -> bool: + """Whether the given class has subclasses from `given_subclasses`.""" + actual = set(cl.__subclasses__()) + given = set(given_subclasses) + return bool(actual & given) + + +def _get_union_type(cl: type, given_subclasses_tree: tuple[type]) -> type | None: + actual_subclass_tree = tuple(_make_subclasses_tree(cl)) + class_tree = tuple(set(actual_subclass_tree) & set(given_subclasses_tree)) + return Union[class_tree] if len(class_tree) >= 2 else None + + +C = TypeVar("C", bound=BaseConverter) + + +def include_subclasses( + cl: type, + converter: C, + subclasses: tuple[type, ...] | None = None, + union_strategy: Callable[[Any, C], Any] | None = None, + overrides: dict[str, AttributeOverride] | None = None, +) -> None: + """ + Configure the converter so that the attrs/dataclass `cl` is un/structured as if it + was a union of itself and all its subclasses that are defined at the time when this + strategy is applied. + + :param cl: A base `attrs` or `dataclass` class. + :param converter: The `Converter` on which this strategy is applied. Do note that + the strategy does not work for a :class:`cattrs.BaseConverter`. + :param subclasses: A tuple of sublcasses whose ancestor is `cl`. If left as `None`, + subclasses are detected using recursively the `__subclasses__` method of `cl` + and its descendents. + :param union_strategy: A callable of two arguments passed by position + (`subclass_union`, `converter`) that defines the union strategy to use to + disambiguate the subclasses union. If `None` (the default), the automatic unique + field disambiguation is used which means that every single subclass + participating in the union must have an attribute name that does not exist in + any other sibling class. + :param overrides: a mapping of `cl` attribute names to overrides (instantiated with + :func:`cattrs.gen.override`) to customize un/structuring. + + .. versionadded:: 23.1.0 + .. versionchanged:: 24.1.0 + When overrides are not provided, hooks for individual classes are retrieved from + the converter instead of generated with no overrides, using converter defaults. + """ + # Due to https://github.com/python-attrs/attrs/issues/1047 + collect() + if subclasses is not None: + parent_subclass_tree = (cl, *subclasses) + else: + parent_subclass_tree = tuple(_make_subclasses_tree(cl)) + + if union_strategy is None: + _include_subclasses_without_union_strategy( + cl, converter, parent_subclass_tree, overrides + ) + else: + _include_subclasses_with_union_strategy( + converter, parent_subclass_tree, union_strategy, overrides + ) + + +def _include_subclasses_without_union_strategy( + cl, + converter: BaseConverter, + parent_subclass_tree: tuple[type], + overrides: dict[str, AttributeOverride] | None, +): + # The iteration approach is required if subclasses are more than one level deep: + for cl in parent_subclass_tree: + # We re-create a reduced union type to handle the following case: + # + # converter.structure(d, as=Child) + # + # In the above, the `as=Child` argument will be transformed to a union type of + # itself and its subtypes, that way we guarantee that the returned object will + # not be the parent. + subclass_union = _get_union_type(cl, parent_subclass_tree) + + def cls_is_cl(cls, _cl=cl): + return cls is _cl + + if overrides is not None: + base_struct_hook = make_dict_structure_fn(cl, converter, **overrides) + base_unstruct_hook = make_dict_unstructure_fn(cl, converter, **overrides) + else: + base_struct_hook = converter.get_structure_hook(cl) + base_unstruct_hook = converter.get_unstructure_hook(cl) + + if subclass_union is None: + + def struct_hook(val: dict, _, _cl=cl, _base_hook=base_struct_hook) -> cl: + return _base_hook(val, _cl) + + else: + dis_fn = converter._get_dis_func(subclass_union, overrides=overrides) + + def struct_hook( + val: dict, + _, + _c=converter, + _cl=cl, + _base_hook=base_struct_hook, + _dis_fn=dis_fn, + ) -> cl: + """ + If val is disambiguated to the class `cl`, use its base hook. + + If val is disambiguated to a subclass, dispatch on its exact runtime + type. + """ + dis_cl = _dis_fn(val) + if dis_cl is _cl: + return _base_hook(val, _cl) + return _c.structure(val, dis_cl) + + def unstruct_hook( + val: parent_subclass_tree[0], + _c=converter, + _cl=cl, + _base_hook=base_unstruct_hook, + ) -> dict: + """ + If val is an instance of the class `cl`, use the hook. + + If val is an instance of a subclass, dispatch on its exact runtime type. + """ + if val.__class__ is _cl: + return _base_hook(val) + return _c.unstructure(val, unstructure_as=val.__class__) + + # This needs to use function dispatch, using singledispatch will again + # match A and all subclasses, which is not what we want. + converter.register_structure_hook_func(cls_is_cl, struct_hook) + converter.register_unstructure_hook_func(cls_is_cl, unstruct_hook) + + +def _include_subclasses_with_union_strategy( + converter: C, + union_classes: tuple[type, ...], + union_strategy: Callable[[Any, C], Any], + overrides: dict[str, AttributeOverride] | None, +): + """ + This function is tricky because we're dealing with what is essentially a circular + reference. + + We need to generate a structure hook for a class that is both: + * specific for that particular class and its own fields + * but should handle specific functions for all its descendants too + + Hence the dance with registering below. + """ + + parent_classes = [cl for cl in union_classes if _has_subclasses(cl, union_classes)] + if not parent_classes: + return + + original_unstruct_hooks = {} + original_struct_hooks = {} + for cl in union_classes: + # In the first pass, every class gets its own unstructure function according to + # the overrides. + # We just generate the hooks, and do not register them. This allows us to + # manipulate the _already_generating set to force runtime dispatch. + already_generating.working_set = set(union_classes) - {cl} + try: + if overrides is not None: + unstruct_hook = make_dict_unstructure_fn(cl, converter, **overrides) + struct_hook = make_dict_structure_fn(cl, converter, **overrides) + else: + unstruct_hook = converter.get_unstructure_hook(cl, cache_result=False) + struct_hook = converter.get_structure_hook(cl, cache_result=False) + finally: + already_generating.working_set = set() + original_unstruct_hooks[cl] = unstruct_hook + original_struct_hooks[cl] = struct_hook + + # Now that's done, we can register all the hooks and generate the + # union handler. The union handler needs them. + final_union = Union[union_classes] # type: ignore + + for cl, hook in original_unstruct_hooks.items(): + + def cls_is_cl(cls, _cl=cl): + return cls is _cl + + converter.register_unstructure_hook_func(cls_is_cl, hook) + + for cl, hook in original_struct_hooks.items(): + + def cls_is_cl(cls, _cl=cl): + return cls is _cl + + converter.register_structure_hook_func(cls_is_cl, hook) + + union_strategy(final_union, converter) + unstruct_hook = converter.get_unstructure_hook(final_union) + struct_hook = converter.get_structure_hook(final_union) + + for cl in union_classes: + # In the second pass, we overwrite the hooks with the union hook. + + def cls_is_cl(cls, _cl=cl): + return cls is _cl + + converter.register_unstructure_hook_func(cls_is_cl, unstruct_hook) + subclasses = tuple([c for c in union_classes if issubclass(c, cl)]) + if len(subclasses) > 1: + u = Union[subclasses] # type: ignore + union_strategy(u, converter) + struct_hook = converter.get_structure_hook(u) + + def sh(payload: dict, _, _u=u, _s=struct_hook) -> cl: + return _s(payload, _u) + + converter.register_structure_hook_func(cls_is_cl, sh) diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/strategies/_unions.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/strategies/_unions.py new file mode 100644 index 0000000..f0d270d --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/strategies/_unions.py @@ -0,0 +1,258 @@ +from collections import defaultdict +from typing import Any, Callable, Dict, Literal, Type, Union + +from attrs import NOTHING + +from cattrs import BaseConverter +from cattrs._compat import get_newtype_base, is_literal, is_subclass, is_union_type + +__all__ = [ + "default_tag_generator", + "configure_tagged_union", + "configure_union_passthrough", +] + + +def default_tag_generator(typ: Type) -> str: + """Return the class name.""" + return typ.__name__ + + +def configure_tagged_union( + union: Any, + converter: BaseConverter, + tag_generator: Callable[[Type], str] = default_tag_generator, + tag_name: str = "_type", + default: Union[Type, Literal[NOTHING]] = NOTHING, +) -> None: + """ + Configure the converter so that `union` (which should be a union) is + un/structured with the help of an additional piece of data in the + unstructured payload, the tag. + + :param converter: The converter to apply the strategy to. + :param tag_generator: A `tag_generator` function is used to map each + member of the union to a tag, which is then included in the + unstructured payload. The default tag generator returns the name of + the class. + :param tag_name: The key under which the tag will be set in the + unstructured payload. By default, `'_type'`. + :param default: An optional class to be used if the tag information + is not present when structuring. + + The tagged union strategy currently only works with the dict + un/structuring base strategy. + + .. versionadded:: 23.1.0 + """ + args = union.__args__ + tag_to_hook = {} + exact_cl_unstruct_hooks = {} + for cl in args: + tag = tag_generator(cl) + struct_handler = converter.get_structure_hook(cl) + unstruct_handler = converter.get_unstructure_hook(cl) + + def structure_union_member(val: dict, _cl=cl, _h=struct_handler) -> cl: + return _h(val, _cl) + + def unstructure_union_member(val: union, _h=unstruct_handler) -> dict: + return _h(val) + + tag_to_hook[tag] = structure_union_member + exact_cl_unstruct_hooks[cl] = unstructure_union_member + + cl_to_tag = {cl: tag_generator(cl) for cl in args} + + if default is not NOTHING: + default_handler = converter.get_structure_hook(default) + + def structure_default(val: dict, _cl=default, _h=default_handler): + return _h(val, _cl) + + tag_to_hook = defaultdict(lambda: structure_default, tag_to_hook) + cl_to_tag = defaultdict(lambda: default, cl_to_tag) + + def unstructure_tagged_union( + val: union, + _exact_cl_unstruct_hooks=exact_cl_unstruct_hooks, + _cl_to_tag=cl_to_tag, + _tag_name=tag_name, + ) -> Dict: + res = _exact_cl_unstruct_hooks[val.__class__](val) + res[_tag_name] = _cl_to_tag[val.__class__] + return res + + if default is NOTHING: + if getattr(converter, "forbid_extra_keys", False): + + def structure_tagged_union( + val: dict, _, _tag_to_cl=tag_to_hook, _tag_name=tag_name + ) -> union: + val = val.copy() + return _tag_to_cl[val.pop(_tag_name)](val) + + else: + + def structure_tagged_union( + val: dict, _, _tag_to_cl=tag_to_hook, _tag_name=tag_name + ) -> union: + return _tag_to_cl[val[_tag_name]](val) + + else: + if getattr(converter, "forbid_extra_keys", False): + + def structure_tagged_union( + val: dict, + _, + _tag_to_hook=tag_to_hook, + _tag_name=tag_name, + _dh=default_handler, + _default=default, + ) -> union: + if _tag_name in val: + val = val.copy() + return _tag_to_hook[val.pop(_tag_name)](val) + return _dh(val, _default) + + else: + + def structure_tagged_union( + val: dict, + _, + _tag_to_hook=tag_to_hook, + _tag_name=tag_name, + _dh=default_handler, + _default=default, + ) -> union: + if _tag_name in val: + return _tag_to_hook[val[_tag_name]](val) + return _dh(val, _default) + + converter.register_unstructure_hook(union, unstructure_tagged_union) + converter.register_structure_hook(union, structure_tagged_union) + + +def configure_union_passthrough(union: Any, converter: BaseConverter) -> None: + """ + Configure the converter to support validating and passing through unions of the + provided types and their subsets. + + For example, all mature JSON libraries natively support producing unions of ints, + floats, Nones, and strings. Using this strategy, a converter can be configured + to efficiently validate and pass through unions containing these types. + + The most important point is that another library (in this example the JSON + library) handles producing the union, and the converter is configured to just + validate it. + + Literals of provided types are also supported, and are checked by value. + + NewTypes of provided types are also supported. + + The strategy is designed to be O(1) in execution time, and independent of the + ordering of types in the union. + + If the union contains a class and one or more of its subclasses, the subclasses + will also be included when validating the superclass. + + .. versionadded:: 23.2.0 + """ + args = set(union.__args__) + + def make_structure_native_union(exact_type: Any) -> Callable: + # `exact_type` is likely to be a subset of the entire configured union (`args`). + literal_values = { + v for t in exact_type.__args__ if is_literal(t) for v in t.__args__ + } + + # We have no idea what the actual type of `val` will be, so we can't + # use it blindly with an `in` check since it might not be hashable. + # So we do an additional check when handling literals. + # Note: do no use `literal_values` here, since {0, False} gets reduced to {0} + literal_classes = { + v.__class__ + for t in exact_type.__args__ + if is_literal(t) + for v in t.__args__ + } + + non_literal_classes = { + get_newtype_base(t) or t + for t in exact_type.__args__ + if not is_literal(t) and ((get_newtype_base(t) or t) in args) + } + + # We augment the set of allowed classes with any configured subclasses of + # the exact subclasses. + non_literal_classes |= { + a for a in args if any(is_subclass(a, c) for c in non_literal_classes) + } + + # We check for spillover - union types not handled by the strategy. + # If spillover exists and we fail to validate our types, we call + # further into the converter with the rest. + spillover = { + a + for a in exact_type.__args__ + if (get_newtype_base(a) or a) not in non_literal_classes + and not is_literal(a) + } + + if spillover: + spillover_type = ( + Union[tuple(spillover)] if len(spillover) > 1 else next(iter(spillover)) + ) + + def structure_native_union( + val: Any, + _: Any, + classes=non_literal_classes, + vals=literal_values, + converter=converter, + spillover=spillover_type, + ) -> exact_type: + if val.__class__ in literal_classes and val in vals: + return val + if val.__class__ in classes: + return val + return converter.structure(val, spillover) + + else: + + def structure_native_union( + val: Any, _: Any, classes=non_literal_classes, vals=literal_values + ) -> exact_type: + if val.__class__ in literal_classes and val in vals: + return val + if val.__class__ in classes: + return val + raise TypeError(f"{val} ({val.__class__}) not part of {_}") + + return structure_native_union + + def contains_native_union(exact_type: Any) -> bool: + """Can we handle this type?""" + if is_union_type(exact_type): + type_args = set(exact_type.__args__) + # We special case optionals, since they are very common + # and are handled a little more efficiently by default. + if len(type_args) == 2 and type(None) in type_args: + return False + + literal_classes = { + lit_arg.__class__ + for t in type_args + if is_literal(t) + for lit_arg in t.__args__ + } + non_literal_types = { + get_newtype_base(t) or t for t in type_args if not is_literal(t) + } + + return (literal_classes | non_literal_types) & args + return False + + converter.register_structure_hook_factory( + contains_native_union, make_structure_native_union + ) diff --git a/lambdas/aws-dd-forwarder-3.127.0/cattrs/v.py b/lambdas/aws-dd-forwarder-3.127.0/cattrs/v.py new file mode 100644 index 0000000..c3ab18c --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/cattrs/v.py @@ -0,0 +1,112 @@ +"""Cattrs validation.""" + +from typing import Callable, List, Union + +from .errors import ( + ClassValidationError, + ForbiddenExtraKeysError, + IterableValidationError, +) + +__all__ = ["format_exception", "transform_error"] + + +def format_exception(exc: BaseException, type: Union[type, None]) -> str: + """The default exception formatter, handling the most common exceptions. + + The following exceptions are handled specially: + + * `KeyErrors` (`required field missing`) + * `ValueErrors` (`invalid value for type, expected ` or just `invalid value`) + * `TypeErrors` (`invalid value for type, expected ` and a couple special + cases for iterables) + * `cattrs.ForbiddenExtraKeysError` + * some `AttributeErrors` (special cased for structing mappings) + """ + if isinstance(exc, KeyError): + res = "required field missing" + elif isinstance(exc, ValueError): + if type is not None: + tn = type.__name__ if hasattr(type, "__name__") else repr(type) + res = f"invalid value for type, expected {tn}" + else: + res = "invalid value" + elif isinstance(exc, TypeError): + if type is None: + if exc.args[0].endswith("object is not iterable"): + res = "invalid value for type, expected an iterable" + else: + res = f"invalid type ({exc})" + else: + tn = type.__name__ if hasattr(type, "__name__") else repr(type) + res = f"invalid value for type, expected {tn}" + elif isinstance(exc, ForbiddenExtraKeysError): + res = f"extra fields found ({', '.join(exc.extra_fields)})" + elif isinstance(exc, AttributeError) and exc.args[0].endswith( + "object has no attribute 'items'" + ): + # This was supposed to be a mapping (and have .items()) but it something else. + res = "expected a mapping" + elif isinstance(exc, AttributeError) and exc.args[0].endswith( + "object has no attribute 'copy'" + ): + # This was supposed to be a mapping (and have .copy()) but it something else. + # Used for TypedDicts. + res = "expected a mapping" + else: + res = f"unknown error ({exc})" + + return res + + +def transform_error( + exc: Union[ClassValidationError, IterableValidationError, BaseException], + path: str = "$", + format_exception: Callable[ + [BaseException, Union[type, None]], str + ] = format_exception, +) -> List[str]: + """Transform an exception into a list of error messages. + + To get detailed error messages, the exception should be produced by a converter + with `detailed_validation` set. + + By default, the error messages are in the form of `{description} @ {path}`. + + While traversing the exception and subexceptions, the path is formed: + + * by appending `.{field_name}` for fields in classes + * by appending `[{int}]` for indices in iterables, like lists + * by appending `[{str}]` for keys in mappings, like dictionaries + + :param exc: The exception to transform into error messages. + :param path: The root path to use. + :param format_exception: A callable to use to transform `Exceptions` into + string descriptions of errors. + + .. versionadded:: 23.1.0 + """ + errors = [] + if isinstance(exc, IterableValidationError): + with_notes, without = exc.group_exceptions() + for exc, note in with_notes: + p = f"{path}[{note.index!r}]" + if isinstance(exc, (ClassValidationError, IterableValidationError)): + errors.extend(transform_error(exc, p, format_exception)) + else: + errors.append(f"{format_exception(exc, note.type)} @ {p}") + for exc in without: + errors.append(f"{format_exception(exc, None)} @ {path}") + elif isinstance(exc, ClassValidationError): + with_notes, without = exc.group_exceptions() + for exc, note in with_notes: + p = f"{path}.{note.name}" + if isinstance(exc, (ClassValidationError, IterableValidationError)): + errors.extend(transform_error(exc, p, format_exception)) + else: + errors.append(f"{format_exception(exc, note.type)} @ {p}") + for exc in without: + errors.append(f"{format_exception(exc, None)} @ {path}") + else: + errors.append(f"{format_exception(exc, None)} @ {path}") + return errors diff --git a/lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/INSTALLER b/lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/LICENSE b/lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/LICENSE new file mode 100644 index 0000000..62b076c --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/LICENSE @@ -0,0 +1,20 @@ +This package contains a modified version of ca-bundle.crt: + +ca-bundle.crt -- Bundle of CA Root Certificates + +This is a bundle of X.509 certificates of public Certificate Authorities +(CA). These were automatically extracted from Mozilla's root certificates +file (certdata.txt). This file can be found in the mozilla source tree: +https://hg.mozilla.org/mozilla-central/file/tip/security/nss/lib/ckfw/builtins/certdata.txt +It contains the certificates in PEM format and therefore +can be directly used with curl / libcurl / php_curl, or with +an Apache+mod_ssl webserver for SSL client authentication. +Just configure this file as the SSLCACertificateFile.# + +***** BEGIN LICENSE BLOCK ***** +This Source Code Form is subject to the terms of the Mozilla Public License, +v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain +one at http://mozilla.org/MPL/2.0/. + +***** END LICENSE BLOCK ***** +@(#) $RCSfile: certdata.txt,v $ $Revision: 1.80 $ $Date: 2011/11/03 15:11:58 $ diff --git a/lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/METADATA b/lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/METADATA new file mode 100644 index 0000000..0a3a772 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/METADATA @@ -0,0 +1,67 @@ +Metadata-Version: 2.1 +Name: certifi +Version: 2024.8.30 +Summary: Python package for providing Mozilla's CA Bundle. +Home-page: https://github.com/certifi/python-certifi +Author: Kenneth Reitz +Author-email: me@kennethreitz.com +License: MPL-2.0 +Project-URL: Source, https://github.com/certifi/python-certifi +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) +Classifier: Natural Language :: English +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Requires-Python: >=3.6 +License-File: LICENSE + +Certifi: Python SSL Certificates +================================ + +Certifi provides Mozilla's carefully curated collection of Root Certificates for +validating the trustworthiness of SSL certificates while verifying the identity +of TLS hosts. It has been extracted from the `Requests`_ project. + +Installation +------------ + +``certifi`` is available on PyPI. Simply install it with ``pip``:: + + $ pip install certifi + +Usage +----- + +To reference the installed certificate authority (CA) bundle, you can use the +built-in function:: + + >>> import certifi + + >>> certifi.where() + '/usr/local/lib/python3.7/site-packages/certifi/cacert.pem' + +Or from the command line:: + + $ python -m certifi + /usr/local/lib/python3.7/site-packages/certifi/cacert.pem + +Enjoy! + +.. _`Requests`: https://requests.readthedocs.io/en/master/ + +Addition/Removal of Certificates +-------------------------------- + +Certifi does not support any addition/removal or other modification of the +CA trust store content. This project is intended to provide a reliable and +highly portable root of trust to python deployments. Look to upstream projects +for methods to use alternate trust. diff --git a/lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/RECORD b/lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/RECORD new file mode 100644 index 0000000..7393811 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/RECORD @@ -0,0 +1,15 @@ +certifi-2024.8.30.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +certifi-2024.8.30.dist-info/LICENSE,sha256=6TcW2mucDVpKHfYP5pWzcPBpVgPSH2-D8FPkLPwQyvc,989 +certifi-2024.8.30.dist-info/METADATA,sha256=GhBHRVUN6a4ZdUgE_N5wmukJfyuoE-QyIl8Y3ifNQBM,2222 +certifi-2024.8.30.dist-info/RECORD,, +certifi-2024.8.30.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +certifi-2024.8.30.dist-info/WHEEL,sha256=UvcQYKBHoFqaQd6LKyqHw9fxEolWLQnlzP0h_LgJAfI,91 +certifi-2024.8.30.dist-info/top_level.txt,sha256=KMu4vUCfsjLrkPbSNdgdekS-pVJzBAJFO__nI8NF6-U,8 +certifi/__init__.py,sha256=p_GYZrjUwPBUhpLlCZoGb0miKBKSqDAyZC5DvIuqbHQ,94 +certifi/__main__.py,sha256=xBBoj905TUWBLRGANOcf7oi6e-3dMP4cEoG9OyMs11g,243 +certifi/__pycache__/__init__.cpython-311.pyc,, +certifi/__pycache__/__main__.cpython-311.pyc,, +certifi/__pycache__/core.cpython-311.pyc,, +certifi/cacert.pem,sha256=lO3rZukXdPyuk6BWUJFOKQliWaXH6HGh9l1GGrUgG0c,299427 +certifi/core.py,sha256=qRDDFyXVJwTB_EmoGppaXU_R9qCZvhl-EzxPMuV3nTA,4426 +certifi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/REQUESTED b/lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/WHEEL b/lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/WHEEL new file mode 100644 index 0000000..57e56b7 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (74.0.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/top_level.txt b/lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/top_level.txt new file mode 100644 index 0000000..963eac5 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/certifi-2024.8.30.dist-info/top_level.txt @@ -0,0 +1 @@ +certifi diff --git a/lambdas/aws-dd-forwarder-3.127.0/certifi/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/certifi/__init__.py new file mode 100644 index 0000000..f61d77f --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/certifi/__init__.py @@ -0,0 +1,4 @@ +from .core import contents, where + +__all__ = ["contents", "where"] +__version__ = "2024.08.30" diff --git a/lambdas/aws-dd-forwarder-3.127.0/certifi/__main__.py b/lambdas/aws-dd-forwarder-3.127.0/certifi/__main__.py new file mode 100644 index 0000000..8945b5d --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/certifi/__main__.py @@ -0,0 +1,12 @@ +import argparse + +from certifi import contents, where + +parser = argparse.ArgumentParser() +parser.add_argument("-c", "--contents", action="store_true") +args = parser.parse_args() + +if args.contents: + print(contents()) +else: + print(where()) diff --git a/lambdas/aws-dd-forwarder-3.127.0/certifi/cacert.pem b/lambdas/aws-dd-forwarder-3.127.0/certifi/cacert.pem new file mode 100644 index 0000000..3c165a1 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/certifi/cacert.pem @@ -0,0 +1,4929 @@ + +# Issuer: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA +# Subject: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA +# Label: "GlobalSign Root CA" +# Serial: 4835703278459707669005204 +# MD5 Fingerprint: 3e:45:52:15:09:51:92:e1:b7:5d:37:9f:b1:87:29:8a +# SHA1 Fingerprint: b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c +# SHA256 Fingerprint: eb:d4:10:40:e4:bb:3e:c7:42:c9:e3:81:d3:1e:f2:a4:1a:48:b6:68:5c:96:e7:ce:f3:c1:df:6c:d4:33:1c:99 +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG +A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv +b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw +MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i +YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT +aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ +jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp +xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp +1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG +snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ +U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 +9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B +AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz +yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE +38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP +AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad +DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME +HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE----- + +# Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited +# Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited +# Label: "Entrust.net Premium 2048 Secure Server CA" +# Serial: 946069240 +# MD5 Fingerprint: ee:29:31:bc:32:7e:9a:e6:e8:b5:f7:51:b4:34:71:90 +# SHA1 Fingerprint: 50:30:06:09:1d:97:d4:f5:ae:39:f7:cb:e7:92:7d:7d:65:2d:34:31 +# SHA256 Fingerprint: 6d:c4:71:72:e0:1c:bc:b0:bf:62:58:0d:89:5f:e2:b8:ac:9a:d4:f8:73:80:1e:0c:10:b9:c8:37:d2:1e:b1:77 +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML +RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp +bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5 +IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3 +MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3 +LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp +YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG +A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq +K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe +sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX +MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT +XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/ +HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH +4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub +j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo +U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf +zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b +u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+ +bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er +fF6adulZkMV8gzURZVE= +-----END CERTIFICATE----- + +# Issuer: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust +# Subject: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust +# Label: "Baltimore CyberTrust Root" +# Serial: 33554617 +# MD5 Fingerprint: ac:b6:94:a5:9c:17:e0:d7:91:52:9b:b1:97:06:a6:e4 +# SHA1 Fingerprint: d4:de:20:d0:5e:66:fc:53:fe:1a:50:88:2c:78:db:28:52:ca:e4:74 +# SHA256 Fingerprint: 16:af:57:a9:f6:76:b0:ab:12:60:95:aa:5e:ba:de:f2:2a:b3:11:19:d6:44:ac:95:cd:4b:93:db:f3:f2:6a:eb +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ +RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD +VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX +DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y +ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy +VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr +mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr +IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK +mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu +XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy +dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye +jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 +BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 +DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 +9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx +jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 +Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz +ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS +R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. +# Subject: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. +# Label: "Entrust Root Certification Authority" +# Serial: 1164660820 +# MD5 Fingerprint: d6:a5:c3:ed:5d:dd:3e:00:c1:3d:87:92:1f:1d:3f:e4 +# SHA1 Fingerprint: b3:1e:b1:b7:40:e3:6c:84:02:da:dc:37:d4:4d:f5:d4:67:49:52:f9 +# SHA256 Fingerprint: 73:c1:76:43:4f:1b:c6:d5:ad:f4:5b:0e:76:e7:27:28:7c:8d:e5:76:16:c1:e6:e6:14:1a:2b:2c:bc:7d:8e:4c +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0 +Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW +KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw +NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw +NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy +ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV +BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo +Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4 +4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9 +KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI +rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi +94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB +sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi +gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo +kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE +vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t +O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua +AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP +9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/ +eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m +0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- + +# Issuer: CN=AAA Certificate Services O=Comodo CA Limited +# Subject: CN=AAA Certificate Services O=Comodo CA Limited +# Label: "Comodo AAA Services root" +# Serial: 1 +# MD5 Fingerprint: 49:79:04:b0:eb:87:19:ac:47:b0:bc:11:51:9b:74:d0 +# SHA1 Fingerprint: d1:eb:23:a4:6d:17:d6:8f:d9:25:64:c2:f1:f1:60:17:64:d8:e3:49 +# SHA256 Fingerprint: d7:a7:a0:fb:5d:7e:27:31:d7:71:e9:48:4e:bc:de:f7:1d:5f:0c:3e:0a:29:48:78:2b:c8:3e:e0:ea:69:9e:f4 +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb +MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow +GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj +YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM +GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua +BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe +3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 +YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR +rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm +ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU +oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v +QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t +b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF +AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q +GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 +G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi +l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 +smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 2 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 2 O=QuoVadis Limited +# Label: "QuoVadis Root CA 2" +# Serial: 1289 +# MD5 Fingerprint: 5e:39:7b:dd:f8:ba:ec:82:e9:ac:62:ba:0c:54:00:2b +# SHA1 Fingerprint: ca:3a:fb:cf:12:40:36:4b:44:b2:16:20:88:80:48:39:19:93:7c:f7 +# SHA256 Fingerprint: 85:a0:dd:7d:d7:20:ad:b7:ff:05:f8:3d:54:2b:20:9d:c7:ff:45:28:f7:d6:77:b1:83:89:fe:a5:e5:c4:9e:86 +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa +GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg +Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J +WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB +rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp ++ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1 +ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i +Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz +PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og +/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH +oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI +yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud +EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2 +A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL +MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f +BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn +g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl +fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K +WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha +B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc +hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR +TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD +mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z +ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y +4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza +8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 3" +# Serial: 1478 +# MD5 Fingerprint: 31:85:3c:62:94:97:63:b9:aa:fd:89:4e:af:6f:e0:cf +# SHA1 Fingerprint: 1f:49:14:f7:d8:74:95:1d:dd:ae:02:c0:be:fd:3a:2d:82:75:51:85 +# SHA256 Fingerprint: 18:f1:fc:7f:20:5d:f8:ad:dd:eb:7f:e0:07:dd:57:e3:af:37:5a:9c:4d:8d:73:54:6b:f4:f1:fe:d1:e1:8d:35 +-----BEGIN CERTIFICATE----- +MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM +V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB +4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr +H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd +8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv +vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT +mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe +btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc +T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt +WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ +c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A +4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD +VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG +CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0 +aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 +aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu +dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw +czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G +A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC +TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg +Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0 +7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem +d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd ++LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B +4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN +t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x +DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57 +k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s +zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j +Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT +mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK +4SVhM7JZG+Ju1zdXtg2pEto= +-----END CERTIFICATE----- + +# Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com +# Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com +# Label: "XRamp Global CA Root" +# Serial: 107108908803651509692980124233745014957 +# MD5 Fingerprint: a1:0b:44:b3:ca:10:d8:00:6e:9d:0f:d8:0f:92:0a:d1 +# SHA1 Fingerprint: b8:01:86:d1:eb:9c:86:a5:41:04:cf:30:54:f3:4c:52:b7:e5:58:c6 +# SHA256 Fingerprint: ce:cd:dc:90:50:99:d8:da:df:c5:b1:d2:09:b7:37:cb:e2:c1:8c:fb:2c:10:c0:ff:0b:cf:0d:32:86:fc:1a:a2 +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB +gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk +MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY +UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx +NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3 +dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy +dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6 +38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP +KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q +DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4 +qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa +JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi +PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P +BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs +jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0 +eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD +ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR +vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt +qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa +IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy +i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ +O+7ETPTsJ3xCwnR8gooJybQDJbw= +-----END CERTIFICATE----- + +# Issuer: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority +# Subject: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority +# Label: "Go Daddy Class 2 CA" +# Serial: 0 +# MD5 Fingerprint: 91:de:06:25:ab:da:fd:32:17:0c:bb:25:17:2a:84:67 +# SHA1 Fingerprint: 27:96:ba:e6:3f:18:01:e2:77:26:1b:a0:d7:77:70:02:8f:20:ee:e4 +# SHA256 Fingerprint: c3:84:6b:f2:4b:9e:93:ca:64:27:4c:0e:c6:7c:1e:cc:5e:02:4f:fc:ac:d2:d7:40:19:35:0e:81:fe:54:6a:e4 +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh +MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE +YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 +MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo +ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg +MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN +ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA +PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w +wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi +EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY +avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+ +YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE +sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h +/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5 +IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy +OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P +TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER +dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf +ReYNnyicsbkqWletNw+vHX/bvZ8= +-----END CERTIFICATE----- + +# Issuer: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority +# Subject: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority +# Label: "Starfield Class 2 CA" +# Serial: 0 +# MD5 Fingerprint: 32:4a:4b:bb:c8:63:69:9b:be:74:9a:c6:dd:1d:46:24 +# SHA1 Fingerprint: ad:7e:1c:28:b0:64:ef:8f:60:03:40:20:14:c3:d0:e3:37:0e:b5:8a +# SHA256 Fingerprint: 14:65:fa:20:53:97:b8:76:fa:a6:f0:a9:95:8e:55:90:e4:0f:cc:7f:aa:4f:b7:c2:c8:67:75:21:fb:5f:b6:58 +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl +MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp +U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw +NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE +ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp +ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3 +DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf +8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN ++lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0 +X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa +K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA +1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G +A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR +zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0 +YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD +bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w +DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3 +L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D +eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp +VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY +WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root CA" +# Serial: 17154717934120587862167794914071425081 +# MD5 Fingerprint: 87:ce:0b:7b:2a:0e:49:00:e1:58:71:9b:37:a8:93:72 +# SHA1 Fingerprint: 05:63:b8:63:0d:62:d7:5a:bb:c8:ab:1e:4b:df:b5:a8:99:b2:4d:43 +# SHA256 Fingerprint: 3e:90:99:b5:01:5e:8f:48:6c:00:bc:ea:9d:11:1e:e7:21:fa:ba:35:5a:89:bc:f1:df:69:56:1e:3d:c6:32:5c +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c +JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP +mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ +wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 +VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ +AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB +AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun +pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC +dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf +fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm +NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx +H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root CA" +# Serial: 10944719598952040374951832963794454346 +# MD5 Fingerprint: 79:e4:a9:84:0d:7d:3a:96:d7:c0:4f:e2:43:4c:89:2e +# SHA1 Fingerprint: a8:98:5d:3a:65:e5:e5:c4:b2:d7:d6:6d:40:c6:dd:2f:b1:9c:54:36 +# SHA256 Fingerprint: 43:48:a0:e9:44:4c:78:cb:26:5e:05:8d:5e:89:44:b4:d8:4f:96:62:bd:26:db:25:7f:89:34:a4:43:c7:01:61 +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB +CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 +nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt +43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P +T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 +gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR +TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw +DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr +hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg +06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF +PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls +YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert High Assurance EV Root CA" +# Serial: 3553400076410547919724730734378100087 +# MD5 Fingerprint: d4:74:de:57:5c:39:b2:d3:9c:85:83:c5:c0:65:49:8a +# SHA1 Fingerprint: 5f:b7:ee:06:33:e2:59:db:ad:0c:4c:9a:e6:d3:8f:1a:61:c7:dc:25 +# SHA256 Fingerprint: 74:31:e5:f4:c3:c1:ce:46:90:77:4f:0b:61:e0:54:40:88:3b:a9:a0:1e:d0:0b:a6:ab:d7:80:6e:d3:b1:18:cf +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm ++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW +PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM +xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB +Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 +hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg +EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA +FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec +nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z +eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF +hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 +Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep ++OkuE6N36B9K +-----END CERTIFICATE----- + +# Issuer: CN=SwissSign Gold CA - G2 O=SwissSign AG +# Subject: CN=SwissSign Gold CA - G2 O=SwissSign AG +# Label: "SwissSign Gold CA - G2" +# Serial: 13492815561806991280 +# MD5 Fingerprint: 24:77:d9:a8:91:d1:3b:fa:88:2d:c2:ff:f8:cd:33:93 +# SHA1 Fingerprint: d8:c5:38:8a:b7:30:1b:1b:6e:d4:7a:e6:45:25:3a:6f:9f:1a:27:61 +# SHA256 Fingerprint: 62:dd:0b:e9:b9:f5:0a:16:3e:a0:f8:e7:5c:05:3b:1e:ca:57:ea:55:c8:68:8f:64:7c:68:81:f2:c8:35:7b:95 +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +# Issuer: CN=SwissSign Silver CA - G2 O=SwissSign AG +# Subject: CN=SwissSign Silver CA - G2 O=SwissSign AG +# Label: "SwissSign Silver CA - G2" +# Serial: 5700383053117599563 +# MD5 Fingerprint: e0:06:a1:c9:7d:cf:c9:fc:0d:c0:56:75:96:d8:62:13 +# SHA1 Fingerprint: 9b:aa:e5:9f:56:ee:21:cb:43:5a:be:25:93:df:a7:f0:40:d1:1d:cb +# SHA256 Fingerprint: be:6c:4d:a2:bb:b9:ba:59:b6:f3:93:97:68:37:42:46:c3:c0:05:99:3f:a9:8f:02:0d:1d:ed:be:d4:8a:81:d5 +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +# Issuer: CN=SecureTrust CA O=SecureTrust Corporation +# Subject: CN=SecureTrust CA O=SecureTrust Corporation +# Label: "SecureTrust CA" +# Serial: 17199774589125277788362757014266862032 +# MD5 Fingerprint: dc:32:c3:a7:6d:25:57:c7:68:09:9d:ea:2d:a9:a2:d1 +# SHA1 Fingerprint: 87:82:c6:c3:04:35:3b:cf:d2:96:92:d2:59:3e:7d:44:d9:34:ff:11 +# SHA256 Fingerprint: f1:c1:b5:0a:e5:a2:0d:d8:03:0e:c9:f6:bc:24:82:3d:d3:67:b5:25:57:59:b4:e7:1b:61:fc:e9:f7:37:5d:73 +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x +FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz +MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv +cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz +Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO +0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao +wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj +7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS +8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT +BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg +JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3 +6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/ +3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm +D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS +CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- + +# Issuer: CN=Secure Global CA O=SecureTrust Corporation +# Subject: CN=Secure Global CA O=SecureTrust Corporation +# Label: "Secure Global CA" +# Serial: 9751836167731051554232119481456978597 +# MD5 Fingerprint: cf:f4:27:0d:d4:ed:dc:65:16:49:6d:3d:da:bf:6e:de +# SHA1 Fingerprint: 3a:44:73:5a:e5:81:90:1f:24:86:61:46:1e:3b:9c:c4:5f:f5:3a:1b +# SHA256 Fingerprint: 42:00:f5:04:3a:c8:59:0e:bb:52:7d:20:9e:d1:50:30:29:fb:cb:d4:1c:a1:b5:06:ec:27:f1:5a:de:7d:ac:69 +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x +GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx +MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg +Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ +iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa +/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ +jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI +HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7 +sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w +gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw +KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG +AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L +URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO +H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm +I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY +iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE----- + +# Issuer: CN=COMODO Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO Certification Authority O=COMODO CA Limited +# Label: "COMODO Certification Authority" +# Serial: 104350513648249232941998508985834464573 +# MD5 Fingerprint: 5c:48:dc:f7:42:72:ec:56:94:6d:1c:cc:71:35:80:75 +# SHA1 Fingerprint: 66:31:bf:9e:f7:4f:9e:b6:c9:d5:a6:0c:ba:6a:be:d1:f7:bd:ef:7b +# SHA256 Fingerprint: 0c:2c:d6:3d:f7:80:6f:a3:99:ed:e8:09:11:6b:57:5b:f8:79:89:f0:65:18:f9:80:8c:86:05:03:17:8b:af:66 +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB +gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV +BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw +MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl +YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P +RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3 +UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI +2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8 +Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp ++2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+ +DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O +nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW +/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g +PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u +QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY +SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv +IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4 +zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd +BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB +ZQ== +-----END CERTIFICATE----- + +# Issuer: CN=COMODO ECC Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO ECC Certification Authority O=COMODO CA Limited +# Label: "COMODO ECC Certification Authority" +# Serial: 41578283867086692638256921589707938090 +# MD5 Fingerprint: 7c:62:ff:74:9d:31:53:5e:68:4a:d5:78:aa:1e:bf:23 +# SHA1 Fingerprint: 9f:74:4e:9f:2b:4d:ba:ec:0f:31:2c:50:b6:56:3b:8e:2d:93:c3:11 +# SHA256 Fingerprint: 17:93:92:7a:06:14:54:97:89:ad:ce:2f:8f:34:f7:f0:b6:6d:0f:3a:e3:a3:b8:4d:21:ec:15:db:ba:4f:ad:c7 +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT +IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw +MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy +ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N +T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR +FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J +cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW +BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm +fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv +GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +# Issuer: CN=Certigna O=Dhimyotis +# Subject: CN=Certigna O=Dhimyotis +# Label: "Certigna" +# Serial: 18364802974209362175 +# MD5 Fingerprint: ab:57:a6:5b:7d:42:82:19:b5:d8:58:26:28:5e:fd:ff +# SHA1 Fingerprint: b1:2e:13:63:45:86:a4:6f:1a:b2:60:68:37:58:2d:c4:ac:fd:94:97 +# SHA256 Fingerprint: e3:b6:a2:db:2e:d7:ce:48:84:2f:7a:c5:32:41:c7:b7:1d:54:14:4b:fb:40:c1:1f:3f:1d:0b:42:f5:ee:a1:2d +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV +BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X +DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ +BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4 +QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny +gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw +zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q +130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2 +JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw +ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT +AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj +AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG +9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h +bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc +fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu +HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w +t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE----- + +# Issuer: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority +# Subject: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority +# Label: "ePKI Root Certification Authority" +# Serial: 28956088682735189655030529057352760477 +# MD5 Fingerprint: 1b:2e:00:ca:26:06:90:3d:ad:fe:6f:15:68:d3:6b:b3 +# SHA1 Fingerprint: 67:65:0d:f1:7e:8e:7e:5b:82:40:a4:f4:56:4b:cf:e2:3d:69:c6:f0 +# SHA256 Fingerprint: c0:a6:f4:dc:63:a2:4b:fd:cf:54:ef:2a:6a:08:2a:0a:72:de:35:80:3e:2f:f5:ff:52:7a:e5:d8:72:06:df:d5 +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe +MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 +ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw +IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL +SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH +SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh +ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X +DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1 +TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ +fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA +sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU +WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS +nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH +dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip +NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC +AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF +MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB +uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl +PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP +JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/ +gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2 +j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6 +5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB +o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS +/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z +Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE +W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D +hNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- + +# Issuer: O=certSIGN OU=certSIGN ROOT CA +# Subject: O=certSIGN OU=certSIGN ROOT CA +# Label: "certSIGN ROOT CA" +# Serial: 35210227249154 +# MD5 Fingerprint: 18:98:c0:d6:e9:3a:fc:f9:b0:f5:0c:f7:4b:01:44:17 +# SHA1 Fingerprint: fa:b7:ee:36:97:26:62:fb:2d:b0:2a:f6:bf:03:fd:e8:7c:4b:2f:9b +# SHA256 Fingerprint: ea:a9:62:c4:fa:4a:6b:af:eb:e4:15:19:6d:35:1c:cd:88:8d:4f:53:f3:fa:8a:e6:d7:c4:66:a9:4e:60:42:bb +-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT +AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD +QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP +MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do +0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ +UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d +RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ +OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv +JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C +AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O +BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ +LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY +MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ +44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I +Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw +i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN +9u6wWk5JRFRYX0KD +-----END CERTIFICATE----- + +# Issuer: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) +# Subject: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) +# Label: "NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny" +# Serial: 80544274841616 +# MD5 Fingerprint: c5:a1:b7:ff:73:dd:d6:d7:34:32:18:df:fc:3c:ad:88 +# SHA1 Fingerprint: 06:08:3f:59:3f:15:a1:04:a0:69:a4:6b:a9:03:d0:06:b7:97:09:91 +# SHA256 Fingerprint: 6c:61:da:c3:a2:de:f0:31:50:6b:e0:36:d2:a6:fe:40:19:94:fb:d1:3d:f9:c8:d4:66:59:92:74:c4:46:ec:98 +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG +EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3 +MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl +cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR +dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB +pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM +b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm +aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz +IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT +lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz +AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5 +VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG +ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2 +BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG +AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M +U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh +bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C ++C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F +uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 +XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +# Issuer: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. +# Subject: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. +# Label: "SecureSign RootCA11" +# Serial: 1 +# MD5 Fingerprint: b7:52:74:e2:92:b4:80:93:f2:75:e4:cc:d7:f2:ea:26 +# SHA1 Fingerprint: 3b:c4:9f:48:f8:f3:73:a0:9c:1e:bd:f8:5b:b1:c3:65:c7:d8:11:b3 +# SHA256 Fingerprint: bf:0f:ee:fb:9e:3a:58:1a:d5:f9:e9:db:75:89:98:57:43:d2:61:08:5c:4d:31:4f:6f:5d:72:59:aa:42:16:12 +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDEr +MCkGA1UEChMiSmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoG +A1UEAxMTU2VjdXJlU2lnbiBSb290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0 +MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSswKQYDVQQKEyJKYXBhbiBDZXJ0aWZp +Y2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1cmVTaWduIFJvb3RD +QTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvLTJsz +i1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8 +h9uuywGOwvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOV +MdrAG/LuYpmGYz+/3ZMqg6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9 +UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rPO7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni +8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitAbpSACW22s293bzUIUPsC +h8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZXt94wDgYD +VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB +AKChOBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xm +KbabfSVSSUOrTC4rbnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQ +X5Ucv+2rIrVls4W6ng+4reV6G4pQOh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWr +QbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01y8hSyn+B/tlr0/cR7SXf+Of5 +pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061lgeLKBObjBmN +QSdJQO7e5iNEOdyhIta6A/I= +-----END CERTIFICATE----- + +# Issuer: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. +# Subject: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. +# Label: "Microsec e-Szigno Root CA 2009" +# Serial: 14014712776195784473 +# MD5 Fingerprint: f8:49:f4:03:bc:44:2d:83:be:48:69:7d:29:64:fc:b1 +# SHA1 Fingerprint: 89:df:74:fe:5c:f4:0f:4a:80:f9:e3:37:7d:54:da:91:e1:01:31:8e +# SHA256 Fingerprint: 3c:5f:81:fe:a5:fa:b8:2c:64:bf:a2:ea:ec:af:cd:e8:e0:77:fc:86:20:a7:ca:e5:37:16:3d:f3:6e:db:f3:78 +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD +VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 +ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G +CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y +OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx +FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp +Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP +kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc +cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U +fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7 +N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC +xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1 ++rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM +Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG +SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h +mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk +ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c +2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t +HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 +# Label: "GlobalSign Root CA - R3" +# Serial: 4835703278459759426209954 +# MD5 Fingerprint: c5:df:b8:49:ca:05:13:55:ee:2d:ba:1a:c3:3e:b0:28 +# SHA1 Fingerprint: d6:9b:56:11:48:f0:1c:77:c5:45:78:c1:09:26:df:5b:85:69:76:ad +# SHA256 Fingerprint: cb:b5:22:d7:b7:f1:27:ad:6a:01:13:86:5b:df:1c:d4:10:2e:7d:07:59:af:63:5a:7c:f4:72:0d:c9:63:c5:3b +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 +MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 +RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT +gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm +KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd +QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ +XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o +LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU +RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp +jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK +6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX +mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs +Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH +WD9f +-----END CERTIFICATE----- + +# Issuer: CN=Izenpe.com O=IZENPE S.A. +# Subject: CN=Izenpe.com O=IZENPE S.A. +# Label: "Izenpe.com" +# Serial: 917563065490389241595536686991402621 +# MD5 Fingerprint: a6:b0:cd:85:80:da:5c:50:34:a3:39:90:2f:55:67:73 +# SHA1 Fingerprint: 2f:78:3d:25:52:18:a7:4a:65:39:71:b5:2c:a2:9c:45:15:6f:e9:19 +# SHA256 Fingerprint: 25:30:cc:8e:98:32:15:02:ba:d9:6f:9b:1f:ba:1b:09:9e:2d:29:9e:0f:45:48:bb:91:4f:36:3b:c0:d4:53:1f +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +# Issuer: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. +# Subject: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. +# Label: "Go Daddy Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: 80:3a:bc:22:c1:e6:fb:8d:9b:3b:27:4a:32:1b:9a:01 +# SHA1 Fingerprint: 47:be:ab:c9:22:ea:e8:0e:78:78:34:62:a7:9f:45:c2:54:fd:e6:8b +# SHA256 Fingerprint: 45:14:0b:32:47:eb:9c:c8:c5:b4:f0:d7:b5:30:91:f7:32:92:08:9e:6e:5a:63:e2:74:9d:d3:ac:a9:19:8e:da +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT +EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp +ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz +NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH +EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE +AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD +E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH +/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy +DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh +GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR +tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA +AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX +WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu +9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr +gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo +2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI +4uJEvlz36hz1 +-----END CERTIFICATE----- + +# Issuer: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Subject: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Label: "Starfield Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: d6:39:81:c6:52:7e:96:69:fc:fc:ca:66:ed:05:f2:96 +# SHA1 Fingerprint: b5:1c:06:7c:ee:2b:0c:3d:f8:55:ab:2d:92:f4:fe:39:d4:e7:0f:0e +# SHA256 Fingerprint: 2c:e1:cb:0b:f9:d2:f9:e1:02:99:3f:be:21:51:52:c3:b2:dd:0c:ab:de:1c:68:e5:31:9b:83:91:54:db:b7:f5 +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs +ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw +MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj +aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp +Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg +nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 +HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N +Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN +dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 +HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G +CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU +sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 +4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg +8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 +mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +# Issuer: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Subject: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Label: "Starfield Services Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: 17:35:74:af:7b:61:1c:eb:f4:f9:3c:e2:ee:40:f9:a2 +# SHA1 Fingerprint: 92:5a:8f:8d:2c:6d:04:e0:66:5f:59:6a:ff:22:d8:63:e8:25:6f:3f +# SHA256 Fingerprint: 56:8d:69:05:a2:c8:87:08:a4:b3:02:51:90:ed:cf:ed:b1:97:4a:60:6a:13:c6:e5:29:0f:cb:2a:e6:3e:da:b5 +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs +ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD +VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy +ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy +dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p +OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2 +8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K +Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe +hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk +6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q +AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI +bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB +ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z +qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn +0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN +sSi6 +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Commercial O=AffirmTrust +# Subject: CN=AffirmTrust Commercial O=AffirmTrust +# Label: "AffirmTrust Commercial" +# Serial: 8608355977964138876 +# MD5 Fingerprint: 82:92:ba:5b:ef:cd:8a:6f:a6:3d:55:f9:84:f6:d6:b7 +# SHA1 Fingerprint: f9:b5:b6:32:45:5f:9c:be:ec:57:5f:80:dc:e9:6e:2c:c7:b2:78:b7 +# SHA256 Fingerprint: 03:76:ab:1d:54:c5:f9:80:3c:e4:b2:e2:01:a0:ee:7e:ef:7b:57:b6:36:e8:a9:3c:9b:8d:48:60:c9:6f:5f:a7 +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz +dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL +MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp +cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP +Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr +ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL +MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1 +yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr +VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/ +nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ +KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG +XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj +vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt +Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g +N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC +nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Networking O=AffirmTrust +# Subject: CN=AffirmTrust Networking O=AffirmTrust +# Label: "AffirmTrust Networking" +# Serial: 8957382827206547757 +# MD5 Fingerprint: 42:65:ca:be:01:9a:9a:4c:a9:8c:41:49:cd:c0:d5:7f +# SHA1 Fingerprint: 29:36:21:02:8b:20:ed:02:f5:66:c5:32:d1:d6:ed:90:9f:45:00:2f +# SHA256 Fingerprint: 0a:81:ec:5a:92:97:77:f1:45:90:4a:f3:8d:5d:50:9f:66:b5:e2:c5:8f:cd:b5:31:05:8b:0e:17:f3:f0:b4:1b +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz +dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL +MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp +cm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y +YJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua +kCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL +QESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp +6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG +yH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i +QLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ +KoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO +tDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu +QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ +Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u +olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48 +x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Premium O=AffirmTrust +# Subject: CN=AffirmTrust Premium O=AffirmTrust +# Label: "AffirmTrust Premium" +# Serial: 7893706540734352110 +# MD5 Fingerprint: c4:5d:0e:48:b6:ac:28:30:4e:0a:bc:f9:38:16:87:57 +# SHA1 Fingerprint: d8:a6:33:2c:e0:03:6f:b1:85:f6:63:4f:7d:6a:06:65:26:32:28:27 +# SHA256 Fingerprint: 70:a7:3f:7f:37:6b:60:07:42:48:90:45:34:b1:14:82:d5:bf:0e:69:8e:cc:49:8d:f5:25:77:eb:f2:e9:3b:9a +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz +dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG +A1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U +cnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf +qV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ +JG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ ++jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS +s8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5 +HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7 +70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG +V+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S +qHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S +5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia +C1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX +OwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE +FJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2 +KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg +Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B +8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ +MKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc +0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ +u4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF +u+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH +YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8 +GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO +RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e +KeC2uAloGRwYQw== +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Premium ECC O=AffirmTrust +# Subject: CN=AffirmTrust Premium ECC O=AffirmTrust +# Label: "AffirmTrust Premium ECC" +# Serial: 8401224907861490260 +# MD5 Fingerprint: 64:b0:09:55:cf:b1:d5:99:e2:be:13:ab:a6:5d:ea:4d +# SHA1 Fingerprint: b8:23:6b:00:2f:1d:16:86:53:01:55:6c:11:a4:37:ca:eb:ff:c3:bb +# SHA256 Fingerprint: bd:71:fd:f6:da:97:e4:cf:62:d1:64:7a:dd:25:81:b0:7d:79:ad:f8:39:7e:b4:ec:ba:9c:5e:84:88:82:14:23 +-----BEGIN CERTIFICATE----- +MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC +VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ +cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ +BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt +VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D +0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9 +ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G +A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs +aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I +flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ== +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Network CA" +# Serial: 279744 +# MD5 Fingerprint: d5:e9:81:40:c5:18:69:fc:46:2c:89:75:62:0f:aa:78 +# SHA1 Fingerprint: 07:e0:32:e0:20:b7:2c:3f:19:2f:06:28:a2:59:3a:19:a7:0f:06:9e +# SHA256 Fingerprint: 5c:58:46:8d:55:f5:8e:49:7e:74:39:82:d2:b5:00:10:b6:d1:65:37:4a:cf:83:a7:d4:a3:2d:b7:68:c4:40:8e +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM +MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D +ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU +cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3 +WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg +Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw +IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH +UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM +TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU +BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM +kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x +AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV +HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y +sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL +I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8 +J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY +VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +# Issuer: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA +# Label: "TWCA Root Certification Authority" +# Serial: 1 +# MD5 Fingerprint: aa:08:8f:f6:f9:7b:b7:f2:b1:a7:1e:9b:ea:ea:bd:79 +# SHA1 Fingerprint: cf:9e:87:6d:d3:eb:fc:42:26:97:a3:b5:a3:7a:a0:76:a9:06:23:48 +# SHA256 Fingerprint: bf:d8:8f:e1:10:1c:41:ae:3e:80:1b:f8:be:56:35:0e:e9:ba:d1:a6:b9:bd:51:5e:dc:5c:6d:5b:87:11:ac:44 +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES +MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU +V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz +WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO +LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE +AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH +K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX +RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z +rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx +3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq +hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC +MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls +XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D +lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn +aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ +YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +# Issuer: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 +# Subject: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 +# Label: "Security Communication RootCA2" +# Serial: 0 +# MD5 Fingerprint: 6c:39:7d:a4:0e:55:59:b2:3f:d6:41:b1:12:50:de:43 +# SHA1 Fingerprint: 5f:3b:8c:f2:f8:10:b3:7d:78:b4:ce:ec:19:19:c3:73:34:b9:c7:74 +# SHA256 Fingerprint: 51:3b:2c:ec:b8:10:d4:cd:e5:dd:85:39:1a:df:c6:c2:dd:60:d8:7b:b7:36:d2:b5:21:48:4a:a4:7a:0e:be:f6 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl +MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe +U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX +DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy +dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj +YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV +OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr +zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM +VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ +hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO +ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw +awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs +OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF +coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc +okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8 +t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy +1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/ +SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- + +# Issuer: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 +# Subject: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 +# Label: "Actalis Authentication Root CA" +# Serial: 6271844772424770508 +# MD5 Fingerprint: 69:c1:0d:4f:07:a3:1b:c3:fe:56:3d:04:bc:11:f6:a6 +# SHA1 Fingerprint: f3:73:b3:87:06:5a:28:84:8a:f2:f3:4a:ce:19:2b:dd:c7:8e:9c:ac +# SHA256 Fingerprint: 55:92:60:84:ec:96:3a:64:b9:6e:2a:be:01:ce:0b:a8:6a:64:fb:fe:bc:c7:aa:b5:af:c1:55:b3:7f:d7:60:66 +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE +BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w +MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC +SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1 +ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv +UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX +4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9 +KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/ +gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb +rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ +51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F +be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe +KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F +v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn +fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7 +jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz +ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL +e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70 +jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz +WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V +SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j +pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX +X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok +fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R +K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU +ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU +LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT +LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +# Issuer: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 +# Subject: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 +# Label: "Buypass Class 2 Root CA" +# Serial: 2 +# MD5 Fingerprint: 46:a7:d2:fe:45:fb:64:5a:a8:59:90:9b:78:44:9b:29 +# SHA1 Fingerprint: 49:0a:75:74:de:87:0a:47:fe:58:ee:f6:c7:6b:eb:c6:0b:12:40:99 +# SHA256 Fingerprint: 9a:11:40:25:19:7c:5b:b9:5d:94:e6:3d:55:cd:43:79:08:47:b6:46:b2:3c:df:11:ad:a4:a0:0e:ff:15:fb:48 +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr +6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV +L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91 +1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx +MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ +QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB +arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr +Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi +FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS +P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN +9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz +uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h +9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t +OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo ++fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7 +KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2 +DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us +H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ +I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7 +5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h +3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz +Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA= +-----END CERTIFICATE----- + +# Issuer: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 +# Subject: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 +# Label: "Buypass Class 3 Root CA" +# Serial: 2 +# MD5 Fingerprint: 3d:3b:18:9e:2c:64:5a:e8:d5:88:ce:0e:f9:37:c2:ec +# SHA1 Fingerprint: da:fa:f7:fa:66:84:ec:06:8f:14:50:bd:c7:c2:81:a5:bc:a9:64:57 +# SHA256 Fingerprint: ed:f7:eb:bc:a2:7a:2a:38:4d:38:7b:7d:40:10:c6:66:e2:ed:b4:84:3e:4c:29:b4:ae:1d:5b:93:32:e6:b2:4d +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y +ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E +N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9 +tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX +0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c +/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X +KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY +zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS +O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D +34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP +K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3 +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv +Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj +QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS +IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2 +HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa +O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv +033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u +dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE +kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41 +3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD +u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq +4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc= +-----END CERTIFICATE----- + +# Issuer: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Subject: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Label: "T-TeleSec GlobalRoot Class 3" +# Serial: 1 +# MD5 Fingerprint: ca:fb:40:a8:4e:39:92:8a:1d:fe:8e:2f:c4:27:ea:ef +# SHA1 Fingerprint: 55:a6:72:3e:cb:f2:ec:cd:c3:23:74:70:19:9d:2a:be:11:e3:81:d1 +# SHA256 Fingerprint: fd:73:da:d3:1c:64:4f:f1:b4:3b:ef:0c:cd:da:96:71:0b:9c:d9:87:5e:ca:7e:31:70:7a:f3:e9:6d:52:2b:bd +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN +8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/ +RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4 +hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5 +ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM +EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1 +A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy +WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ +1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30 +6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT +91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p +TpPDpFQUWw== +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH +# Subject: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH +# Label: "D-TRUST Root Class 3 CA 2 2009" +# Serial: 623603 +# MD5 Fingerprint: cd:e0:25:69:8d:47:ac:9c:89:35:90:f7:fd:51:3d:2f +# SHA1 Fingerprint: 58:e8:ab:b0:36:15:33:fb:80:f7:9b:1b:6d:29:d3:ff:8d:5f:00:f0 +# SHA256 Fingerprint: 49:e7:a4:42:ac:f0:ea:62:87:05:00:54:b5:25:64:b6:50:e4:f4:9e:42:e3:48:d6:aa:38:e0:39:e9:57:b1:c1 +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha +ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM +HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03 +UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42 +tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R +ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM +lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp +/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G +A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy +MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl +cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js +L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL +BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni +acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K +zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8 +PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y +Johw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH +# Subject: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH +# Label: "D-TRUST Root Class 3 CA 2 EV 2009" +# Serial: 623604 +# MD5 Fingerprint: aa:c6:43:2c:5e:2d:cd:c4:34:c0:50:4f:11:02:4f:b6 +# SHA1 Fingerprint: 96:c9:1b:0b:95:b4:10:98:42:fa:d0:d8:22:79:fe:60:fa:b9:16:83 +# SHA256 Fingerprint: ee:c5:49:6b:98:8c:e9:86:25:b9:34:09:2e:ec:29:08:be:d0:b0:f3:16:c2:d4:73:0c:84:ea:f1:f3:d3:48:81 +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw +NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV +BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn +ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0 +3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z +qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR +p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8 +HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw +ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea +HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw +Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh +c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E +RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt +dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku +Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp +3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF +CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na +xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX +KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +# Issuer: CN=CA Disig Root R2 O=Disig a.s. +# Subject: CN=CA Disig Root R2 O=Disig a.s. +# Label: "CA Disig Root R2" +# Serial: 10572350602393338211 +# MD5 Fingerprint: 26:01:fb:d8:27:a7:17:9a:45:54:38:1a:43:01:3b:03 +# SHA1 Fingerprint: b5:61:eb:ea:a4:de:e4:25:4b:69:1a:98:a5:57:47:c2:34:c7:d9:71 +# SHA256 Fingerprint: e2:3d:4a:03:6d:7b:70:e9:f5:95:b1:42:20:79:d2:b9:1e:df:bb:1f:b6:51:a0:63:3e:aa:8a:9d:c5:f8:07:03 +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV +BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu +MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy +MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx +EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe +NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH +PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I +x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe +QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR +yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO +QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912 +H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ +QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD +i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs +nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1 +rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI +hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf +GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb +lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka ++elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal +TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i +nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3 +gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr +G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os +zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x +L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +# Issuer: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV +# Subject: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV +# Label: "ACCVRAIZ1" +# Serial: 6828503384748696800 +# MD5 Fingerprint: d0:a0:5a:ee:05:b6:09:94:21:a1:7d:f1:b2:29:82:02 +# SHA1 Fingerprint: 93:05:7a:88:15:c6:4f:ce:88:2f:fa:91:16:52:28:78:bc:53:64:17 +# SHA256 Fingerprint: 9a:6e:c0:12:e1:a7:da:9d:be:34:19:4d:47:8a:d7:c0:db:18:22:fb:07:1d:f1:29:81:49:6e:d1:04:38:41:13 +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE +AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw +CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ +BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND +VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb +qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY +HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo +G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA +lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr +IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/ +0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH +k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47 +4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO +m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa +cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl +uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI +KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls +ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG +AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT +VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG +CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA +cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA +QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA +7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA +cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA +QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA +czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu +aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt +aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud +DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF +BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp +D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU +JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m +AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD +vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms +tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH +7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA +h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF +d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H +pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +# Issuer: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA +# Label: "TWCA Global Root CA" +# Serial: 3262 +# MD5 Fingerprint: f9:03:7e:cf:e6:9e:3c:73:7a:2a:90:07:69:ff:2b:96 +# SHA1 Fingerprint: 9c:bb:48:53:f6:a4:f6:d3:52:a4:e8:32:52:55:60:13:f5:ad:af:65 +# SHA256 Fingerprint: 59:76:90:07:f7:68:5d:0f:cd:50:87:2f:9f:95:d5:75:5a:5b:2b:45:7d:81:f3:69:2b:61:0a:98:67:2f:0e:1b +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx +EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT +VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5 +NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT +B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF +10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz +0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh +MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH +zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc +46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2 +yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi +laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP +oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA +BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE +qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm +4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL +1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF +H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo +RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+ +nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh +15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW +6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW +nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j +wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz +aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy +KwbQBM0= +-----END CERTIFICATE----- + +# Issuer: CN=TeliaSonera Root CA v1 O=TeliaSonera +# Subject: CN=TeliaSonera Root CA v1 O=TeliaSonera +# Label: "TeliaSonera Root CA v1" +# Serial: 199041966741090107964904287217786801558 +# MD5 Fingerprint: 37:41:49:1b:18:56:9a:26:f5:ad:c2:66:fb:40:a5:4c +# SHA1 Fingerprint: 43:13:bb:96:f1:d5:86:9b:c1:4e:6a:92:f6:cf:f6:34:69:87:82:37 +# SHA256 Fingerprint: dd:69:36:fe:21:f8:f0:77:c1:23:a1:a5:21:c1:22:24:f7:22:55:b7:3e:03:a7:26:06:93:e8:a2:4b:0f:a3:89 +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw +NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv +b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD +VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2 +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F +VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1 +7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X +Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+ +/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs +81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm +dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe +Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu +sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4 +pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs +slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ +arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD +VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG +9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl +dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj +TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed +Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7 +Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI +OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7 +vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW +t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn +HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx +SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +# Issuer: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Subject: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Label: "T-TeleSec GlobalRoot Class 2" +# Serial: 1 +# MD5 Fingerprint: 2b:9b:9e:e4:7b:6c:1f:00:72:1a:cc:c1:77:79:df:6a +# SHA1 Fingerprint: 59:0d:2d:7d:88:4f:40:2e:61:7e:a5:62:32:17:65:cf:17:d8:94:e9 +# SHA256 Fingerprint: 91:e2:f5:78:8d:58:10:eb:a7:ba:58:73:7d:e1:54:8a:8e:ca:cd:01:45:98:bc:0b:14:3e:04:1b:17:05:25:52 +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd +AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC +FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi +1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq +jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ +wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/ +WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy +NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC +uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw +IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 +g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP +BSeOE6Fuwg== +-----END CERTIFICATE----- + +# Issuer: CN=Atos TrustedRoot 2011 O=Atos +# Subject: CN=Atos TrustedRoot 2011 O=Atos +# Label: "Atos TrustedRoot 2011" +# Serial: 6643877497813316402 +# MD5 Fingerprint: ae:b9:c4:32:4b:ac:7f:5d:66:cc:77:94:bb:2a:77:56 +# SHA1 Fingerprint: 2b:b1:f5:3e:55:0c:1d:c5:f1:d4:e6:b7:6a:46:4b:55:06:02:ac:21 +# SHA256 Fingerprint: f3:56:be:a2:44:b7:a9:1e:b3:5d:53:ca:9a:d7:86:4a:ce:01:8e:2d:35:d5:f8:f9:6d:df:68:a6:f4:1a:a4:74 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE +AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG +EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM +FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC +REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp +Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM +VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ +SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ +4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L +cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi +eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG +A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 +DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j +vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP +DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc +maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D +lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv +KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 1 G3" +# Serial: 687049649626669250736271037606554624078720034195 +# MD5 Fingerprint: a4:bc:5b:3f:fe:37:9a:fa:64:f0:e2:fa:05:3d:0b:ab +# SHA1 Fingerprint: 1b:8e:ea:57:96:29:1a:c9:39:ea:b8:0a:81:1a:73:73:c0:93:79:67 +# SHA256 Fingerprint: 8a:86:6f:d1:b2:76:b5:7e:57:8e:92:1c:65:82:8a:2b:ed:58:e9:f2:f2:88:05:41:34:b7:f1:f4:bf:c9:cc:74 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00 +MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV +wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe +rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341 +68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh +4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp +UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o +abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc +3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G +KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt +hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO +Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt +zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD +ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2 +cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN +qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5 +YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv +b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2 +8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k +NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj +ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp +q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt +nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 2 G3" +# Serial: 390156079458959257446133169266079962026824725800 +# MD5 Fingerprint: af:0c:86:6e:bf:40:2d:7f:0b:3e:12:50:ba:12:3d:06 +# SHA1 Fingerprint: 09:3c:61:f3:8b:8b:dc:7d:55:df:75:38:02:05:00:e1:25:f5:c8:36 +# SHA256 Fingerprint: 8f:e4:fb:0a:f9:3a:4d:0d:67:db:0b:eb:b2:3e:37:c7:1b:f3:25:dc:bc:dd:24:0e:a0:4d:af:58:b4:7e:18:40 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 +MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW +n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ +O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j +IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz +8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l +7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga +W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE ++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd +dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM +HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 3 G3" +# Serial: 268090761170461462463995952157327242137089239581 +# MD5 Fingerprint: df:7d:b9:ad:54:6f:68:a1:df:89:57:03:97:43:b0:d7 +# SHA1 Fingerprint: 48:12:bd:92:3c:a8:c4:39:06:e7:30:6d:27:96:e6:a4:cf:22:2e:7d +# SHA256 Fingerprint: 88:ef:81:de:20:2e:b0:18:45:2e:43:f8:64:72:5c:ea:5f:bd:1f:c2:d9:d2:05:73:07:09:c5:d8:b8:69:0f:46 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 +MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR +/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu +FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR +U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c +ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR +FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k +A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw +eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl +sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp +VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q +A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ +ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD +ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI +FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv +oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg +u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP +0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf +3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl +8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ +DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN +PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ +ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G2" +# Serial: 15385348160840213938643033620894905419 +# MD5 Fingerprint: 92:38:b9:f8:63:24:82:65:2c:57:33:e6:fe:81:8f:9d +# SHA1 Fingerprint: a1:4b:48:d9:43:ee:0a:0e:40:90:4f:3c:e0:a4:c0:91:93:51:5d:3f +# SHA256 Fingerprint: 7d:05:eb:b6:82:33:9f:8c:94:51:ee:09:4e:eb:fe:fa:79:53:a1:14:ed:b2:f4:49:49:45:2f:ab:7d:2f:c1:85 +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA +n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc +biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp +EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA +bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu +YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW +BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI +QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I +0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni +lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 +B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv +ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G3" +# Serial: 15459312981008553731928384953135426796 +# MD5 Fingerprint: 7c:7f:65:31:0c:81:df:8d:ba:3e:99:e2:5c:ad:6e:fb +# SHA1 Fingerprint: f5:17:a2:4f:9a:48:c6:c9:f8:a2:00:26:9f:dc:0f:48:2c:ab:30:89 +# SHA256 Fingerprint: 7e:37:cb:8b:4c:47:09:0c:ab:36:55:1b:a6:f4:5d:b8:40:68:0f:ba:16:6a:95:2d:b1:00:71:7f:43:05:3f:c2 +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg +RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf +Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q +RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD +AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY +JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv +6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G2" +# Serial: 4293743540046975378534879503202253541 +# MD5 Fingerprint: e4:a6:8a:c8:54:ac:52:42:46:0a:fd:72:48:1b:2a:44 +# SHA1 Fingerprint: df:3c:24:f9:bf:d6:66:76:1b:26:80:73:fe:06:d1:cc:8d:4f:82:a4 +# SHA256 Fingerprint: cb:3c:cb:b7:60:31:e5:e0:13:8f:8d:d3:9a:23:f9:de:47:ff:c3:5e:43:c1:14:4c:ea:27:d4:6a:5a:b1:cb:5f +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G3" +# Serial: 7089244469030293291760083333884364146 +# MD5 Fingerprint: f5:5d:a4:50:a5:fb:28:7e:1e:0f:0d:cc:96:57:56:ca +# SHA1 Fingerprint: 7e:04:de:89:6a:3e:66:6d:00:e6:87:d3:3f:fa:d9:3b:e8:3d:34:9e +# SHA256 Fingerprint: 31:ad:66:48:f8:10:41:38:c7:38:f3:9e:a4:32:01:33:39:3e:3a:18:cc:02:29:6e:f9:7c:2a:c9:ef:67:31:d0 +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe +Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw +EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x +IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG +fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO +Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx +AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ +oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 +sycX +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Trusted Root G4" +# Serial: 7451500558977370777930084869016614236 +# MD5 Fingerprint: 78:f2:fc:aa:60:1f:2f:b4:eb:c9:37:ba:53:2e:75:49 +# SHA1 Fingerprint: dd:fb:16:cd:49:31:c9:73:a2:03:7d:3f:c8:3a:4d:7d:77:5d:05:e4 +# SHA256 Fingerprint: 55:2f:7b:dc:f1:a7:af:9e:6c:e6:72:01:7f:4f:12:ab:f7:72:40:c7:8e:76:1a:c2:03:d1:d9:d2:0a:c8:99:88 +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg +RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y +ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If +xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV +ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO +DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ +jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ +CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi +EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM +fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY +uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK +chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t +9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 +SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd ++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc +fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa +sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N +cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N +0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie +4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI +r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 +/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm +gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ +-----END CERTIFICATE----- + +# Issuer: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Label: "COMODO RSA Certification Authority" +# Serial: 101909084537582093308941363524873193117 +# MD5 Fingerprint: 1b:31:b0:71:40:36:cc:14:36:91:ad:c4:3e:fd:ec:18 +# SHA1 Fingerprint: af:e5:d2:44:a8:d1:19:42:30:ff:47:9f:e2:f8:97:bb:cd:7a:8c:b4 +# SHA256 Fingerprint: 52:f0:e1:c4:e5:8e:c6:29:29:1b:60:31:7f:07:46:71:b8:5d:7e:a8:0d:5b:07:27:34:63:53:4b:32:b4:02:34 +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB +hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV +BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT +EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR +6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X +pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC +9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV +/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf +Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z ++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w +qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah +SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC +u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf +Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq +crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl +wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM +4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV +2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna +FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ +CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK +boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke +jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL +S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb +QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl +0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB +NVOFBkpdn627G190 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Label: "USERTrust RSA Certification Authority" +# Serial: 2645093764781058787591871645665788717 +# MD5 Fingerprint: 1b:fe:69:d1:91:b7:19:33:a3:72:a8:0f:e1:55:e5:b5 +# SHA1 Fingerprint: 2b:8f:1b:57:33:0d:bb:a2:d0:7a:6c:51:f7:0e:e9:0d:da:b9:ad:8e +# SHA256 Fingerprint: e7:93:c9:b0:2f:d8:aa:13:e2:1c:31:22:8a:cc:b0:81:19:64:3b:74:9c:89:89:64:b1:74:6d:46:c3:d4:cb:d2 +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Label: "USERTrust ECC Certification Authority" +# Serial: 123013823720199481456569720443997572134 +# MD5 Fingerprint: fa:68:bc:d9:b5:7f:ad:fd:c9:1d:06:83:28:cc:24:c1 +# SHA1 Fingerprint: d1:cb:ca:5d:b2:d5:2a:7f:69:3b:67:4d:e5:f0:5a:1d:0c:95:7d:f0 +# SHA256 Fingerprint: 4f:f4:60:d5:4b:9c:86:da:bf:bc:fc:57:12:e0:40:0d:2b:ed:3f:bc:4d:4f:bd:aa:86:e0:6a:dc:d2:a9:ad:7a +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl +eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT +JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg +VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo +I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng +o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G +A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB +zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW +RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 +# Label: "GlobalSign ECC Root CA - R5" +# Serial: 32785792099990507226680698011560947931244 +# MD5 Fingerprint: 9f:ad:3b:1c:02:1e:8a:ba:17:74:38:81:0c:a2:bc:08 +# SHA1 Fingerprint: 1f:24:c6:30:cd:a4:18:ef:20:69:ff:ad:4f:dd:5f:46:3a:1b:69:aa +# SHA256 Fingerprint: 17:9f:bc:14:8a:3d:d0:0f:d2:4e:a1:34:58:cc:43:bf:a7:f5:9c:81:82:d7:83:a5:13:f6:eb:ec:10:0c:89:24 +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc +8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke +hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI +KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg +515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO +xwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +# Issuer: CN=IdenTrust Commercial Root CA 1 O=IdenTrust +# Subject: CN=IdenTrust Commercial Root CA 1 O=IdenTrust +# Label: "IdenTrust Commercial Root CA 1" +# Serial: 13298821034946342390520003877796839426 +# MD5 Fingerprint: b3:3e:77:73:75:ee:a0:d3:e3:7e:49:63:49:59:bb:c7 +# SHA1 Fingerprint: df:71:7e:aa:4a:d9:4e:c9:55:84:99:60:2d:48:de:5f:bc:f0:3a:25 +# SHA256 Fingerprint: 5d:56:49:9b:e4:d2:e0:8b:cf:ca:d0:8a:3e:38:72:3d:50:50:3b:de:70:69:48:e4:2f:55:60:30:19:e5:28:ae +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu +VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw +MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw +JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT +3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU ++ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp +S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1 +bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi +T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL +vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK +Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK +dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT +c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv +l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N +iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD +ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt +LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93 +nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3 ++wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK +W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT +AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq +l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG +4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ +mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A +7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +# Issuer: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Subject: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Label: "IdenTrust Public Sector Root CA 1" +# Serial: 13298821034946342390521976156843933698 +# MD5 Fingerprint: 37:06:a5:b0:fc:89:9d:ba:f4:6b:8c:1a:64:cd:d5:ba +# SHA1 Fingerprint: ba:29:41:60:77:98:3f:f4:f3:ef:f2:31:05:3b:2e:ea:6d:4d:45:fd +# SHA256 Fingerprint: 30:d0:89:5a:9a:44:8a:26:20:91:63:55:22:d1:f5:20:10:b5:86:7a:ca:e1:2c:78:ef:95:8f:d4:f4:38:9f:2f +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu +VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN +MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0 +MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7 +ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy +RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS +bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF +/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R +3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw +EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy +9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V +GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ +2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV +WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD +W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN +AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV +DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9 +TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G +lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW +mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df +WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5 ++bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ +tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA +GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv +8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - G2" +# Serial: 1246989352 +# MD5 Fingerprint: 4b:e2:c9:91:96:65:0c:f4:0e:5a:93:92:a0:0a:fe:b2 +# SHA1 Fingerprint: 8c:f4:27:fd:79:0c:3a:d1:66:06:8d:e8:1e:57:ef:bb:93:22:72:d4 +# SHA256 Fingerprint: 43:df:57:74:b0:3e:7f:ef:5f:e4:0d:93:1a:7b:ed:f1:bb:2e:6b:42:73:8c:4e:6d:38:41:10:3d:3a:a7:f3:39 +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50 +cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs +IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz +dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy +NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu +dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt +dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0 +aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T +RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN +cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW +wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1 +U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0 +jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN +BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/ +jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v +1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R +nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH +VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g== +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - EC1" +# Serial: 51543124481930649114116133369 +# MD5 Fingerprint: b6:7e:1d:f0:58:c5:49:6c:24:3b:3d:ed:98:18:ed:bc +# SHA1 Fingerprint: 20:d8:06:40:df:9b:25:f5:12:25:3a:11:ea:f7:59:8a:eb:14:b5:47 +# SHA256 Fingerprint: 02:ed:0e:b2:8c:14:da:45:16:5c:56:67:91:70:0d:64:51:d7:fb:56:f0:b2:ab:1d:3b:8e:b0:70:e5:6e:df:f5 +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG +A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3 +d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu +dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq +RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy +MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD +VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 +L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g +Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi +A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt +ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH +Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC +R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX +hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- + +# Issuer: CN=CFCA EV ROOT O=China Financial Certification Authority +# Subject: CN=CFCA EV ROOT O=China Financial Certification Authority +# Label: "CFCA EV ROOT" +# Serial: 407555286 +# MD5 Fingerprint: 74:e1:b6:ed:26:7a:7a:44:30:33:94:ab:7b:27:81:30 +# SHA1 Fingerprint: e2:b8:29:4b:55:84:ab:6b:58:c2:90:46:6c:ac:3f:b8:39:8f:84:83 +# SHA256 Fingerprint: 5c:c3:d7:8e:4e:1d:5e:45:54:7a:04:e6:87:3e:64:f9:0c:f9:53:6d:1c:cc:2e:f8:00:f3:55:c4:c5:fd:70:fd +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD +TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y +aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx +MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j +aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP +T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03 +sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL +TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5 +/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp +7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz +EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt +hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP +a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot +aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg +TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV +PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv +cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL +tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd +BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT +ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL +jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS +ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy +P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19 +xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d +Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN +5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe +/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z +AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ +5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GB CA" +# Serial: 157768595616588414422159278966750757568 +# MD5 Fingerprint: a4:eb:b9:61:28:2e:b7:2f:98:b0:35:26:90:99:51:1d +# SHA1 Fingerprint: 0f:f9:40:76:18:d3:d7:6a:4b:98:f0:a8:35:9e:0c:fd:27:ac:cc:ed +# SHA256 Fingerprint: 6b:9c:08:e8:6e:b0:f7:67:cf:ad:65:cd:98:b6:21:49:e5:49:4a:67:f5:84:5e:7b:d1:ed:01:9f:27:b8:6b:d6 +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt +MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg +Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i +YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x +CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG +b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3 +HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx +WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX +1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk +u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P +99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r +M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB +BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh +cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5 +gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO +ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf +aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- + +# Issuer: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. +# Subject: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. +# Label: "SZAFIR ROOT CA2" +# Serial: 357043034767186914217277344587386743377558296292 +# MD5 Fingerprint: 11:64:c1:89:b0:24:b1:8c:b1:07:7e:89:9e:51:9e:99 +# SHA1 Fingerprint: e2:52:fa:95:3f:ed:db:24:60:bd:6e:28:f3:9c:cc:cf:5e:b3:3f:de +# SHA256 Fingerprint: a1:33:9d:33:28:1a:0b:56:e5:57:d3:d3:2b:1c:e7:f9:36:7e:b0:94:bd:5f:a7:2a:7e:50:04:c8:de:d7:ca:fe +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6 +ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw +NzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L +cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg +Uk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN +QLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT +3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw +3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6 +3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5 +BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN +XGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF +AAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw +8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG +nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP +oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy +d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg +LvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Network CA 2" +# Serial: 44979900017204383099463764357512596969 +# MD5 Fingerprint: 6d:46:9e:d9:25:6d:08:23:5b:5e:74:7d:1e:27:db:f2 +# SHA1 Fingerprint: d3:dd:48:3e:2b:bf:4c:05:e8:af:10:f5:fa:76:26:cf:d3:dc:30:92 +# SHA256 Fingerprint: b6:76:f2:ed:da:e8:77:5c:d3:6c:b0:f6:3c:d1:d4:60:39:61:f4:9e:62:65:ba:01:3a:2f:03:07:b6:d0:b8:04 +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB +gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu +QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG +A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz +OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ +VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3 +b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA +DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn +0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB +OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE +fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E +Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m +o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i +sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW +OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez +Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS +adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n +3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ +F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf +CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29 +XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm +djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/ +WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb +AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq +P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko +b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj +XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P +5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi +DrW5viSP +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions RootCA 2015" +# Serial: 0 +# MD5 Fingerprint: ca:ff:e2:db:03:d9:cb:4b:e9:0f:ad:84:fd:7b:18:ce +# SHA1 Fingerprint: 01:0c:06:95:a6:98:19:14:ff:bf:5f:c6:b0:b6:95:ea:29:e9:12:a6 +# SHA256 Fingerprint: a0:40:92:9a:02:ce:53:b4:ac:f4:f2:ff:c6:98:1c:e4:49:6f:75:5e:6d:45:fe:0b:2a:69:2b:cd:52:52:3f:36 +-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix +DzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k +IFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMT +N0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9v +dENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAxMTIxWjCBpjELMAkG +A1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNh +ZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkx +QDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 +dGlvbnMgUm9vdENBIDIwMTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQDC+Kk/G4n8PDwEXT2QNrCROnk8ZlrvbTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA +4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+ehiGsxr/CL0BgzuNtFajT0 +AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+6PAQZe10 +4S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06C +ojXdFPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV +9Cz82XBST3i4vTwri5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrD +gfgXy5I2XdGj2HUb4Ysn6npIQf1FGQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6 +Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2fu/Z8VFRfS0myGlZYeCsargq +NhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9muiNX6hME6wGko +LfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVd +ctA4GGqd83EkVAswDQYJKoZIhvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0I +XtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+D1hYc2Ryx+hFjtyp8iY/xnmMsVMI +M4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrMd/K4kPFox/la/vot +9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+yd+2V +Z5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/ea +j8GsGsVn82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnh +X9izjFk0WaSrT2y7HxjbdavYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQ +l033DlZdwJVqwjbDG2jJ9SrcR5q+ss7FJej6A7na+RZukYT1HCjI/CbM1xyQVqdf +bzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4 +pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK +e7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0 +vm9qp/UsQu0yrbYhnr68 +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions ECC RootCA 2015" +# Serial: 0 +# MD5 Fingerprint: 81:e5:b4:17:eb:c2:f5:e1:4b:0d:41:7b:49:92:fe:ef +# SHA1 Fingerprint: 9f:f1:71:8d:92:d5:9a:f3:7d:74:97:b4:bc:6f:84:68:0b:ba:b6:66 +# SHA256 Fingerprint: 44:b5:45:aa:8a:25:e6:5a:73:ca:15:dc:27:fc:36:d2:4c:1c:b9:95:3a:06:65:39:b1:15:82:dc:48:7b:48:33 +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN +BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl +bGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJv +b3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEwMzcxMlowgaoxCzAJ +BgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmljIEFj +YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5 +MUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0 +dXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg +QehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJa +jq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi +C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep +lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof +TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- + +# Issuer: CN=ISRG Root X1 O=Internet Security Research Group +# Subject: CN=ISRG Root X1 O=Internet Security Research Group +# Label: "ISRG Root X1" +# Serial: 172886928669790476064670243504169061120 +# MD5 Fingerprint: 0c:d2:f9:e0:da:17:73:e9:ed:86:4d:a5:e3:70:e7:4e +# SHA1 Fingerprint: ca:bd:2a:79:a1:07:6a:31:f2:1d:25:36:35:cb:03:9d:43:29:a5:e8 +# SHA256 Fingerprint: 96:bc:ec:06:26:49:76:f3:74:60:77:9a:cf:28:c5:a7:cf:e8:a3:c0:aa:e1:1a:8f:fc:ee:05:c0:bd:df:08:c6 +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- + +# Issuer: O=FNMT-RCM OU=AC RAIZ FNMT-RCM +# Subject: O=FNMT-RCM OU=AC RAIZ FNMT-RCM +# Label: "AC RAIZ FNMT-RCM" +# Serial: 485876308206448804701554682760554759 +# MD5 Fingerprint: e2:09:04:b4:d3:bd:d1:a0:14:fd:1a:d2:47:c4:57:1d +# SHA1 Fingerprint: ec:50:35:07:b2:15:c4:95:62:19:e2:a8:9a:5b:42:99:2c:4c:2c:20 +# SHA256 Fingerprint: eb:c5:57:0c:29:01:8c:4d:67:b1:aa:12:7b:af:12:f7:03:b4:61:1e:bc:17:b7:da:b5:57:38:94:17:9b:93:fa +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx +CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ +WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ +BgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG +Tk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/ +yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf +BBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz +WHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF +tBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z +374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC +IfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL +mbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7 +wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS +MKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2 +ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet +UqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H +YJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3 +LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1 +RXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM +LVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf +77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N +JpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm +fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp +6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp +1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B +9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok +RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv +uu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 1 O=Amazon +# Subject: CN=Amazon Root CA 1 O=Amazon +# Label: "Amazon Root CA 1" +# Serial: 143266978916655856878034712317230054538369994 +# MD5 Fingerprint: 43:c6:bf:ae:ec:fe:ad:2f:18:c6:88:68:30:fc:c8:e6 +# SHA1 Fingerprint: 8d:a7:f9:65:ec:5e:fc:37:91:0f:1c:6e:59:fd:c1:cc:6a:6e:de:16 +# SHA256 Fingerprint: 8e:cd:e6:88:4f:3d:87:b1:12:5b:a3:1a:c3:fc:b1:3d:70:16:de:7f:57:cc:90:4f:e1:cb:97:c6:ae:98:19:6e +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj +ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM +9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw +IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 +VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L +93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm +jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA +A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI +U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs +N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv +o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU +5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy +rqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 2 O=Amazon +# Subject: CN=Amazon Root CA 2 O=Amazon +# Label: "Amazon Root CA 2" +# Serial: 143266982885963551818349160658925006970653239 +# MD5 Fingerprint: c8:e5:8d:ce:a8:42:e2:7a:c0:2a:5c:7c:9e:26:bf:66 +# SHA1 Fingerprint: 5a:8c:ef:45:d7:a6:98:59:76:7a:8c:8b:44:96:b5:78:cf:47:4b:1a +# SHA256 Fingerprint: 1b:a5:b2:aa:8c:65:40:1a:82:96:01:18:f8:0b:ec:4f:62:30:4d:83:ce:c4:71:3a:19:c3:9c:01:1e:a4:6d:b4 +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK +gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ +W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg +1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K +8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r +2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me +z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR +8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj +mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz +7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6 ++XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI +0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm +UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2 +LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS +k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl +7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm +btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl +urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+ +fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63 +n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE +76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H +9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT +4PsJYGw= +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 3 O=Amazon +# Subject: CN=Amazon Root CA 3 O=Amazon +# Label: "Amazon Root CA 3" +# Serial: 143266986699090766294700635381230934788665930 +# MD5 Fingerprint: a0:d4:ef:0b:f7:b5:d8:49:95:2a:ec:f5:c4:fc:81:87 +# SHA1 Fingerprint: 0d:44:dd:8c:3c:8c:1a:1a:58:75:64:81:e9:0f:2e:2a:ff:b3:d2:6e +# SHA256 Fingerprint: 18:ce:6c:fe:7b:f1:4e:60:b2:e3:47:b8:df:e8:68:cb:31:d0:2e:bb:3a:da:27:15:69:f5:03:43:b4:6d:b3:a4 +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl +ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr +ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr +BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM +YyRIHN8wfdVoOw== +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 4 O=Amazon +# Subject: CN=Amazon Root CA 4 O=Amazon +# Label: "Amazon Root CA 4" +# Serial: 143266989758080763974105200630763877849284878 +# MD5 Fingerprint: 89:bc:27:d5:eb:17:8d:06:6a:69:d5:fd:89:47:b4:cd +# SHA1 Fingerprint: f6:10:84:07:d6:f8:bb:67:98:0c:c2:e2:44:c2:eb:ae:1c:ef:63:be +# SHA256 Fingerprint: e3:5d:28:41:9e:d0:20:25:cf:a6:90:38:cd:62:39:62:45:8d:a5:c6:95:fb:de:a3:c2:2b:0b:fb:25:89:70:92 +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi +9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk +M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB +MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw +CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW +1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- + +# Issuer: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM +# Subject: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM +# Label: "TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1" +# Serial: 1 +# MD5 Fingerprint: dc:00:81:dc:69:2f:3e:2f:b0:3b:f6:3d:5a:91:8e:49 +# SHA1 Fingerprint: 31:43:64:9b:ec:ce:27:ec:ed:3a:3f:0b:8f:0d:e4:e8:91:dd:ee:ca +# SHA256 Fingerprint: 46:ed:c3:68:90:46:d5:3a:45:3f:b3:10:4a:b8:0d:ca:ec:65:8b:26:60:ea:16:29:dd:7e:86:79:90:64:87:16 +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx +GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp +bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w +KwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0 +BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy +dW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG +EwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll +IEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU +QUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT +TTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg +LSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7 +a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr +LqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr +N3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X +YacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/ +iSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f +AJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH +V8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf +IPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4 +lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c +8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf +lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- + +# Issuer: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. +# Subject: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. +# Label: "GDCA TrustAUTH R5 ROOT" +# Serial: 9009899650740120186 +# MD5 Fingerprint: 63:cc:d9:3d:34:35:5c:6f:53:a3:e2:08:70:48:1f:b4 +# SHA1 Fingerprint: 0f:36:38:5b:81:1a:25:c3:9b:31:4e:83:ca:e9:34:66:70:cc:74:b4 +# SHA256 Fingerprint: bf:ff:8f:d0:44:33:48:7d:6a:8a:a6:0c:1a:29:76:7a:9f:c2:bb:b0:5e:42:0f:71:3a:13:b9:92:89:1d:38:93 +-----BEGIN CERTIFICATE----- +MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UE +BhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ +IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0 +MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVowYjELMAkGA1UEBhMCQ04xMjAwBgNV +BAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8w +HQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJj +Dp6L3TQsAlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBj +TnnEt1u9ol2x8kECK62pOqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+u +KU49tm7srsHwJ5uu4/Ts765/94Y9cnrrpftZTqfrlYwiOXnhLQiPzLyRuEH3FMEj +qcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ9Cy5WmYqsBebnh52nUpm +MUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQxXABZG12 +ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloP +zgsMR6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3Gk +L30SgLdTMEZeS1SZD2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeC +jGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4oR24qoAATILnsn8JuLwwoC8N9VKejveSswoA +HQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx9hoh49pwBiFYFIeFd3mqgnkC +AwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlRMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg +p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZm +DRd9FBUb1Ov9H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5 +COmSdI31R9KrO9b7eGZONn356ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ry +L3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd+PwyvzeG5LuOmCd+uh8W4XAR8gPf +JWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQHtZa37dG/OaG+svg +IHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBDF8Io +2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV +09tL7ECQ8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQ +XR4EzzffHqhmsYzmIGrv/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrq +T8p+ck0LcIymSLumoRT2+1hEmRSuqguTaaApJUqlyyvdimYHFngVV3Eb7PVHhPOe +MTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com Root Certification Authority RSA O=SSL Corporation +# Subject: CN=SSL.com Root Certification Authority RSA O=SSL Corporation +# Label: "SSL.com Root Certification Authority RSA" +# Serial: 8875640296558310041 +# MD5 Fingerprint: 86:69:12:c0:70:f1:ec:ac:ac:c2:d5:bc:a5:5b:a1:29 +# SHA1 Fingerprint: b7:ab:33:08:d1:ea:44:77:ba:14:80:12:5a:6f:bd:a9:36:49:0c:bb +# SHA256 Fingerprint: 85:66:6a:56:2e:e0:be:5c:e9:25:c1:d8:89:0a:6f:76:a8:7e:c1:6d:4d:7d:5f:29:ea:74:19:cf:20:12:3b:69 +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE +BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK +DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz +OTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R +xFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX +qhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC +C52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3 +6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh +/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF +YD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E +JNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc +US4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8 +ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm ++Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi +M+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G +A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV +cpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc +Hadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs +PgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/ +q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0 +cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr +a6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I +H37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y +K9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu +nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf +oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY +Ic2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com Root Certification Authority ECC O=SSL Corporation +# Subject: CN=SSL.com Root Certification Authority ECC O=SSL Corporation +# Label: "SSL.com Root Certification Authority ECC" +# Serial: 8495723813297216424 +# MD5 Fingerprint: 2e:da:e4:39:7f:9c:8f:37:d1:70:9f:26:17:51:3a:8e +# SHA1 Fingerprint: c3:19:7c:39:24:e6:54:af:1b:c4:ab:20:95:7a:e2:c3:0e:13:02:6a +# SHA256 Fingerprint: 34:17:bb:06:cc:60:07:da:1b:96:1c:92:0b:8a:b4:ce:3f:ad:82:0e:4a:a3:0b:9a:cb:c4:a7:4e:bd:ce:bc:65 +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz +WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0 +b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS +b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI +7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg +CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud +EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD +VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T +kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+ +gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation +# Subject: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation +# Label: "SSL.com EV Root Certification Authority RSA R2" +# Serial: 6248227494352943350 +# MD5 Fingerprint: e1:1e:31:58:1a:ae:54:53:02:f6:17:6a:11:7b:4d:95 +# SHA1 Fingerprint: 74:3a:f0:52:9b:d0:32:a0:f4:4a:83:cd:d4:ba:a9:7b:7c:2e:c4:9a +# SHA256 Fingerprint: 2e:7b:f1:6c:c2:24:85:a7:bb:e2:aa:86:96:75:07:61:b0:ae:39:be:3b:2f:e9:d0:cc:6d:4e:f7:34:91:42:5c +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV +BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE +CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy +MDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G +A1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD +DC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq +M0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf +OePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa +4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9 +HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR +aZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA +b9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ +Gp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV +PWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO +pgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu +UDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY +MBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4 +9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW +s47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5 +Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg +cLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM +79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz +/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt +ll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm +Kf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK +QbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ +w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi +S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07 +mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation +# Subject: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation +# Label: "SSL.com EV Root Certification Authority ECC" +# Serial: 3182246526754555285 +# MD5 Fingerprint: 59:53:22:65:83:42:01:54:c0:ce:42:b9:5a:7c:f2:90 +# SHA1 Fingerprint: 4c:dd:51:a3:d1:f5:20:32:14:b0:c6:c5:32:23:03:91:c7:46:42:6d +# SHA256 Fingerprint: 22:a2:c1:f7:bd:ed:70:4c:c1:e7:01:b5:f4:08:c3:10:88:0f:e9:56:b5:de:2a:4a:44:f9:9c:87:3a:25:a7:c8 +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx +NTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv +bSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA +VIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku +WnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP +MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX +5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ +ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg +h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 +# Label: "GlobalSign Root CA - R6" +# Serial: 1417766617973444989252670301619537 +# MD5 Fingerprint: 4f:dd:07:e4:d4:22:64:39:1e:0c:37:42:ea:d1:c6:ae +# SHA1 Fingerprint: 80:94:64:0e:b5:a7:a1:ca:11:9c:1f:dd:d5:9f:81:02:63:a7:fb:d1 +# SHA256 Fingerprint: 2c:ab:ea:fe:37:d0:6c:a2:2a:ba:73:91:c0:03:3d:25:98:29:52:c4:53:64:73:49:76:3a:3a:b5:ad:6c:cf:69 +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg +MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh +bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx +MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET +MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI +xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k +ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD +aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw +LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw +1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX +k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2 +SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h +bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n +WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY +rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce +MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu +bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt +Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61 +55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj +vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf +cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz +oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp +nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs +pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v +JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R +8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4 +5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GC CA" +# Serial: 44084345621038548146064804565436152554 +# MD5 Fingerprint: a9:d6:b9:2d:2f:93:64:f8:a5:69:ca:91:e9:68:07:23 +# SHA1 Fingerprint: e0:11:84:5e:34:de:be:88:81:b9:9c:f6:16:26:d1:96:1f:c3:b9:31 +# SHA256 Fingerprint: 85:60:f9:1c:36:24:da:ba:95:70:b5:fe:a0:db:e3:6f:f1:1a:83:23:be:94:86:85:4f:b3:f3:4a:55:71:19:8d +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw +CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91 +bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg +Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ +BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu +ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS +b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni +eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W +p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T +rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV +57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg +Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 +-----END CERTIFICATE----- + +# Issuer: CN=UCA Global G2 Root O=UniTrust +# Subject: CN=UCA Global G2 Root O=UniTrust +# Label: "UCA Global G2 Root" +# Serial: 124779693093741543919145257850076631279 +# MD5 Fingerprint: 80:fe:f0:c4:4a:f0:5c:62:32:9f:1c:ba:78:a9:50:f8 +# SHA1 Fingerprint: 28:f9:78:16:19:7a:ff:18:25:18:aa:44:fe:c1:a0:ce:5c:b6:4c:8a +# SHA256 Fingerprint: 9b:ea:11:c9:76:fe:01:47:64:c1:be:56:a6:f9:14:b5:a5:60:31:7a:bd:99:88:39:33:82:e5:16:1a:a0:49:3c +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9 +MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBH +bG9iYWwgRzIgUm9vdDAeFw0xNjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0x +CzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEds +b2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxeYr +b3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmToni9 +kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzm +VHqUwCoV8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/R +VogvGjqNO7uCEeBHANBSh6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDc +C/Vkw85DvG1xudLeJ1uK6NjGruFZfc8oLTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIj +tm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/R+zvWr9LesGtOxdQXGLY +D0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBeKW4bHAyv +j5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6Dl +NaBa4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6 +iIis7nCs+dwp4wwcOxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznP +O6Q0ibd5Ei9Hxeepl2n8pndntd978XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/ +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIHEjMz15DD/pQwIX4wV +ZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo5sOASD0Ee/oj +L3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 +1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl +1qnN3e92mI0ADs0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oU +b3n09tDh05S60FdRvScFDcH9yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LV +PtateJLbXDzz2K36uGt/xDYotgIVilQsnLAXc47QN6MUPJiVAAwpBVueSUmxX8fj +y88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHojhJi6IjMtX9Gl8Cb +EGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZkbxqg +DMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI ++Vg7RE+xygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGy +YiGqhkCyLmTTX8jjfhFnRR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bX +UB+K+wb1whnw0A== +-----END CERTIFICATE----- + +# Issuer: CN=UCA Extended Validation Root O=UniTrust +# Subject: CN=UCA Extended Validation Root O=UniTrust +# Label: "UCA Extended Validation Root" +# Serial: 106100277556486529736699587978573607008 +# MD5 Fingerprint: a1:f3:5f:43:c6:34:9b:da:bf:8c:7e:05:53:ad:96:e2 +# SHA1 Fingerprint: a3:a1:b0:6f:24:61:23:4a:e3:36:a5:c2:37:fc:a6:ff:dd:f0:d7:3a +# SHA256 Fingerprint: d4:3a:f9:b3:54:73:75:5c:96:84:fc:06:d7:d8:cb:70:ee:5c:28:e7:73:fb:29:4e:b4:1e:e7:17:22:92:4d:24 +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBH +MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBF +eHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMx +MDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNV +BAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrsiWog +D4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvS +sPGP2KxFRv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aop +O2z6+I9tTcg1367r3CTueUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dk +sHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR59mzLC52LqGj3n5qiAno8geK+LLNEOfi +c0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH0mK1lTnj8/FtDw5lhIpj +VMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KRel7sFsLz +KuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/ +TuDvB0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41G +sx2VYVdWf6/wFlthWG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs +1+lvK9JKBZP8nm9rZ/+I8U6laUpSNwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQD +fwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS3H5aBZ8eNJr34RQwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBADaN +l8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR +ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQ +VBcZEhrxH9cMaVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5 +c6sq1WnIeJEmMX3ixzDx/BR4dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp +4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb+7lsq+KePRXBOy5nAliRn+/4Qh8s +t2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOWF3sGPjLtx7dCvHaj +2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwiGpWO +vpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2C +xR9GUeOcGMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmx +cmtpzyKEC2IPrNkZAJSidjzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbM +fjKaiJUINlK73nZfdklJrX+9ZSCyycErdhh2n1ax +-----END CERTIFICATE----- + +# Issuer: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036 +# Subject: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036 +# Label: "Certigna Root CA" +# Serial: 269714418870597844693661054334862075617 +# MD5 Fingerprint: 0e:5c:30:62:27:eb:5b:bc:d7:ae:62:ba:e9:d5:df:77 +# SHA1 Fingerprint: 2d:0d:52:14:ff:9e:ad:99:24:01:74:20:47:6e:6c:85:27:27:f5:43 +# SHA256 Fingerprint: d4:8d:3d:23:ee:db:50:a4:59:e5:51:97:60:1c:27:77:4b:9d:7b:18:c9:4d:5a:05:95:11:a1:02:50:b9:31:68 +-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw +WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw +MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x +MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD +VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX +BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO +ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M +CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu +I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm +TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh +C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf +ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz +IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT +Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k +JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5 +hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB +GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of +1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov +L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo +dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr +aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq +hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L +6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG +HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6 +0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB +lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi +o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1 +gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v +faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63 +Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh +jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw +3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= +-----END CERTIFICATE----- + +# Issuer: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI +# Subject: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI +# Label: "emSign Root CA - G1" +# Serial: 235931866688319308814040 +# MD5 Fingerprint: 9c:42:84:57:dd:cb:0b:a7:2e:95:ad:b6:f3:da:bc:ac +# SHA1 Fingerprint: 8a:c7:ad:8f:73:ac:4e:c1:b5:75:4d:a5:40:f4:fc:cf:7c:b5:8e:8c +# SHA256 Fingerprint: 40:f6:af:03:46:a9:9a:a1:cd:1d:55:5a:4e:9c:ce:62:c7:f9:63:46:03:ee:40:66:15:83:3d:c8:c8:d0:03:67 +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD +VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU +ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH +MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO +MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv +Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz +f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO +8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq +d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM +tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt +Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB +o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD +AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x +PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM +wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d +GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH +6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby +RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx +iN66zB+Afko= +-----END CERTIFICATE----- + +# Issuer: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI +# Subject: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI +# Label: "emSign ECC Root CA - G3" +# Serial: 287880440101571086945156 +# MD5 Fingerprint: ce:0b:72:d1:9f:88:8e:d0:50:03:e8:e3:b8:8b:67:40 +# SHA1 Fingerprint: 30:43:fa:4f:f2:57:dc:a0:c3:80:ee:2e:58:ea:78:b2:3f:e6:bb:c1 +# SHA256 Fingerprint: 86:a1:ec:ba:08:9c:4a:8d:3b:be:27:34:c6:12:ba:34:1d:81:3e:04:3c:f9:e8:a8:62:cd:5c:57:a3:6b:be:6b +-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG +EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo +bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ +TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s +b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0 +WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS +fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB +zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq +hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB +CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD ++JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE----- + +# Issuer: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI +# Subject: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI +# Label: "emSign Root CA - C1" +# Serial: 825510296613316004955058 +# MD5 Fingerprint: d8:e3:5d:01:21:fa:78:5a:b0:df:ba:d2:ee:2a:5f:68 +# SHA1 Fingerprint: e7:2e:f1:df:fc:b2:09:28:cf:5d:d4:d5:67:37:b1:51:cb:86:4f:01 +# SHA256 Fingerprint: 12:56:09:aa:30:1d:a0:a2:49:b9:7a:82:39:cb:6a:34:21:6f:44:dc:ac:9f:39:54:b1:42:92:f2:e8:c8:60:8f +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG +A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg +SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw +MFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln +biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v +dCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ +BczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ +HdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH +3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH +GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c +xSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1 +aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq +TbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87 +/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4 +kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG +YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT ++xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo +WXzhriKi4gp6D/piq1JM4fHfyr6DDUI= +-----END CERTIFICATE----- + +# Issuer: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI +# Subject: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI +# Label: "emSign ECC Root CA - C3" +# Serial: 582948710642506000014504 +# MD5 Fingerprint: 3e:53:b3:a3:81:ee:d7:10:f8:d3:b0:1d:17:92:f5:d5 +# SHA1 Fingerprint: b6:af:43:c2:9b:81:53:7d:f6:ef:6b:c3:1f:1f:60:15:0c:ee:48:66 +# SHA256 Fingerprint: bc:4d:80:9b:15:18:9d:78:db:3e:1d:8c:f4:f9:72:6a:79:5d:a1:64:3c:a5:f1:35:8e:1d:db:0e:dc:0d:7e:b3 +-----BEGIN CERTIFICATE----- +MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG +EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx +IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw +MFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln +biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND +IFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci +MK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti +sIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O +BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB +Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c +3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J +0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== +-----END CERTIFICATE----- + +# Issuer: CN=Hongkong Post Root CA 3 O=Hongkong Post +# Subject: CN=Hongkong Post Root CA 3 O=Hongkong Post +# Label: "Hongkong Post Root CA 3" +# Serial: 46170865288971385588281144162979347873371282084 +# MD5 Fingerprint: 11:fc:9f:bd:73:30:02:8a:fd:3f:f3:58:b9:cb:20:f0 +# SHA1 Fingerprint: 58:a2:d0:ec:20:52:81:5b:c1:f3:f8:64:02:24:4e:c2:8e:02:4b:02 +# SHA256 Fingerprint: 5a:2f:c0:3f:0c:83:b0:90:bb:fa:40:60:4b:09:88:44:6c:76:36:18:3d:f9:84:6e:17:10:1a:44:7f:b8:ef:d6 +-----BEGIN CERTIFICATE----- +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 +mpv0 +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - G4 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2015 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - G4 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2015 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - G4" +# Serial: 289383649854506086828220374796556676440 +# MD5 Fingerprint: 89:53:f1:83:23:b7:7c:8e:05:f1:8c:71:38:4e:1f:88 +# SHA1 Fingerprint: 14:88:4e:86:26:37:b0:26:af:59:62:5c:40:77:ec:35:29:ba:96:01 +# SHA256 Fingerprint: db:35:17:d1:f6:73:2a:2d:5a:b9:7c:53:3e:c7:07:79:ee:32:70:a6:2f:b4:ac:42:38:37:24:60:e6:f0:1e:88 +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAw +gb4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQL +Ex9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykg +MjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAw +BgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0 +MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYTAlVT +MRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1 +c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJ +bmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3Qg +Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3DumSXbcr3DbVZwbPLqGgZ +2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV3imz/f3E +T+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j +5pds8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAM +C1rlLAHGVK/XqsEQe9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73T +DtTUXm6Hnmo9RR3RXRv06QqsYJn7ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNX +wbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5XxNMhIWNlUpEbsZmOeX7m640A +2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV7rtNOzK+mndm +nqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8 +dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwl +N4y6mACXi0mWHv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNj +c0kCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9nMA0GCSqGSIb3DQEBCwUAA4ICAQAS +5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4QjbRaZIxowLByQzTS +Gwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht7LGr +hFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/ +B7NTeLUKYvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uI +AeV8KEsD+UmDfLJ/fOPtjqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbw +H5Lk6rWS02FREAutp9lfx1/cH6NcjKF+m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+ +b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKWRGhXxNUzzxkvFMSUHHuk +2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjAJOgc47Ol +IQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk +5F6G+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuY +n/PIjhs4ViFqUZPTkcpG2om3PVODLAgfi49T3f+sHw== +-----END CERTIFICATE----- + +# Issuer: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation +# Subject: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation +# Label: "Microsoft ECC Root Certificate Authority 2017" +# Serial: 136839042543790627607696632466672567020 +# MD5 Fingerprint: dd:a1:03:e6:4a:93:10:d1:bf:f0:19:42:cb:fe:ed:67 +# SHA1 Fingerprint: 99:9a:64:c3:7f:f4:7d:9f:ab:95:f1:47:69:89:14:60:ee:c4:c3:c5 +# SHA256 Fingerprint: 35:8d:f3:9d:76:4a:f9:e1:b7:66:e9:c9:72:df:35:2e:e1:5c:fa:c2:27:af:6a:d1:d7:0e:8e:4a:6e:dc:ba:02 +-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD +VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw +MTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4MjMxNjA0WjBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNy +b3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZR +ogPZnZH6thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYb +hGBKia/teQ87zvH2RPUBeMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTIy5lycFIM+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3 +FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlfXu5gKcs68tvWMoQZP3zV +L8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaReNtUjGUB +iudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE----- + +# Issuer: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation +# Subject: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation +# Label: "Microsoft RSA Root Certificate Authority 2017" +# Serial: 40975477897264996090493496164228220339 +# MD5 Fingerprint: 10:ff:00:ff:cf:c9:f8:c7:7a:c0:ee:35:8e:c9:0f:47 +# SHA1 Fingerprint: 73:a5:e6:4a:3b:ff:83:16:ff:0e:dc:cc:61:8a:90:6e:4e:ae:4d:74 +# SHA256 Fingerprint: c7:41:f7:0f:4b:2a:8d:88:bf:2e:71:c1:41:22:ef:53:ef:10:eb:a0:cf:a5:e6:4c:fa:20:f4:18:85:30:73:e0 +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl +MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1N +aWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZ +Nt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0 +ZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1 +HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztm +gGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XFWvLJ +jEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUc +aDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaG +YaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6 +W6IYZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4K +UGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH ++FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+Q +W5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZC +LgLNFgVZJ8og6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OC +gMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6 +tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nh +SnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2 +TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3 +pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGR +xpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiApp +GWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9 +dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN +AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB +RA+GsCyRxj3qrg+E +-----END CERTIFICATE----- + +# Issuer: CN=e-Szigno Root CA 2017 O=Microsec Ltd. +# Subject: CN=e-Szigno Root CA 2017 O=Microsec Ltd. +# Label: "e-Szigno Root CA 2017" +# Serial: 411379200276854331539784714 +# MD5 Fingerprint: de:1f:f6:9e:84:ae:a7:b4:21:ce:1e:58:7d:d1:84:98 +# SHA1 Fingerprint: 89:d4:83:03:4f:9e:9a:48:80:5f:72:37:d4:a9:a6:ef:cb:7c:1f:d1 +# SHA256 Fingerprint: be:b0:0b:30:83:9b:9b:c3:2c:32:e4:44:79:05:95:06:41:f2:64:21:b1:5e:d0:89:19:8b:51:8a:e2:ea:1b:99 +-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV +BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk +LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv +b3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZaFw00MjA4MjIxMjA3MDZaMHExCzAJ +BgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMg +THRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25v +IFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtv +xie+RJCxs1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+H +Wyx7xf58etqjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB +eAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo +jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ ++efcMQ== +-----END CERTIFICATE----- + +# Issuer: O=CERTSIGN SA OU=certSIGN ROOT CA G2 +# Subject: O=CERTSIGN SA OU=certSIGN ROOT CA G2 +# Label: "certSIGN Root CA G2" +# Serial: 313609486401300475190 +# MD5 Fingerprint: 8c:f1:75:8a:c6:19:cf:94:b7:f7:65:20:87:c3:97:c7 +# SHA1 Fingerprint: 26:f9:93:b4:ed:3d:28:27:b0:b9:4b:a7:e9:15:1d:a3:8d:92:e5:32 +# SHA256 Fingerprint: 65:7c:fe:2f:a7:3f:aa:38:46:25:71:f3:32:a2:36:3a:46:fc:e7:02:09:51:71:07:02:cd:fb:b6:ee:da:33:05 +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV +BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g +Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ +BgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJ +R04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDF +dRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05N0Iw +vlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZ +uIt4ImfkabBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhp +n+Sc8CnTXPnGFiWeI8MgwT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKs +cpc/I1mbySKEwQdPzH/iV8oScLumZfNpdWO9lfsbl83kqK/20U6o2YpxJM02PbyW +xPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91QqhngLjYl/rNUssuHLoPj1P +rCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732jcZZroiF +DsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fx +DTvf95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgy +LcsUDFDYg2WD7rlcz8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6C +eWRgKRM+o/1Pcmqr4tTluCRVLERLiohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSCIS1mxteg4BXrzkwJ +d8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOBywaK8SJJ6ejq +kX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQl +qiCA2ClV9+BB/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0 +OJD7uNGzcgbJceaBxXntC6Z58hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+c +NywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5BiKDUyUM/FHE5r7iOZULJK2v0ZXk +ltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklWatKcsWMy5WHgUyIO +pwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tUSxfj +03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZk +PuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE +1LlSVHJ7liXMvGnjSG4N0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MX +QRBdJ3NghVdJIgc= +-----END CERTIFICATE----- + +# Issuer: CN=Trustwave Global Certification Authority O=Trustwave Holdings, Inc. +# Subject: CN=Trustwave Global Certification Authority O=Trustwave Holdings, Inc. +# Label: "Trustwave Global Certification Authority" +# Serial: 1846098327275375458322922162 +# MD5 Fingerprint: f8:1c:18:2d:2f:ba:5f:6d:a1:6c:bc:c7:ab:91:c7:0e +# SHA1 Fingerprint: 2f:8f:36:4f:e1:58:97:44:21:59:87:a5:2a:9a:d0:69:95:26:7f:b5 +# SHA256 Fingerprint: 97:55:20:15:f5:dd:fc:3c:87:88:c0:06:94:45:55:40:88:94:45:00:84:f1:00:86:70:86:bc:1a:2b:b5:8d:c8 +-----BEGIN CERTIFICATE----- +MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQsw +CQYDVQQGEwJVUzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28x +ITAfBgNVBAoMGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1 +c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMx +OTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJVUzERMA8GA1UECAwI +SWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +ALldUShLPDeS0YLOvR29zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0Xzn +swuvCAAJWX/NKSqIk4cXGIDtiLK0thAfLdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu +7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4BqstTnoApTAbqOl5F2brz8 +1Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9oWN0EACyW +80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotP +JqX+OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1l +RtzuzWniTY+HKE40Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfw +hI0Vcnyh78zyiGG69Gm7DIwLdVcEuE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10 +coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm+9jaJXLE9gCxInm943xZYkqc +BW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqjifLJS3tBEW1n +twiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1Ud +DwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W +0OhUKDtkLSGm+J1WE2pIPU/HPinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfe +uyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0HZJDmHvUqoai7PF35owgLEQzxPy0Q +lG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla4gt5kNdXElE1GYhB +aCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5RvbbE +sLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPT +MaCm/zjdzyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qe +qu5AvzSxnI9O4fKSTx+O856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxh +VicGaeVyQYHTtgGJoC86cnn+OjC/QezHYj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8 +h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu3R3y4G5OBVixwJAWKqQ9 +EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP29FpHOTK +yeC2nOnOcXHebD8WpHk= +-----END CERTIFICATE----- + +# Issuer: CN=Trustwave Global ECC P256 Certification Authority O=Trustwave Holdings, Inc. +# Subject: CN=Trustwave Global ECC P256 Certification Authority O=Trustwave Holdings, Inc. +# Label: "Trustwave Global ECC P256 Certification Authority" +# Serial: 4151900041497450638097112925 +# MD5 Fingerprint: 5b:44:e3:8d:5d:36:86:26:e8:0d:05:d2:59:a7:83:54 +# SHA1 Fingerprint: b4:90:82:dd:45:0c:be:8b:5b:b1:66:d3:e2:a4:08:26:cd:ed:42:cf +# SHA256 Fingerprint: 94:5b:bc:82:5e:a5:54:f4:89:d1:fd:51:a7:3d:df:2e:a6:24:ac:70:19:a0:52:05:22:5c:22:a7:8c:cf:a8:b4 +-----BEGIN CERTIFICATE----- +MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf +BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 +YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x +NzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYDVQQGEwJVUzERMA8G +A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 +d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF +Q0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqG +SM49AwEHA0IABH77bOYj43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoN +FWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqmP62jQzBBMA8GA1UdEwEB/wQFMAMBAf8w +DwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt0UrrdaVKEJmzsaGLSvcw +CgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjzRM4q3wgh +DDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 +-----END CERTIFICATE----- + +# Issuer: CN=Trustwave Global ECC P384 Certification Authority O=Trustwave Holdings, Inc. +# Subject: CN=Trustwave Global ECC P384 Certification Authority O=Trustwave Holdings, Inc. +# Label: "Trustwave Global ECC P384 Certification Authority" +# Serial: 2704997926503831671788816187 +# MD5 Fingerprint: ea:cf:60:c4:3b:b9:15:29:40:a1:97:ed:78:27:93:d6 +# SHA1 Fingerprint: e7:f3:a3:c8:cf:6f:c3:04:2e:6d:0e:67:32:c5:9e:68:95:0d:5e:d2 +# SHA256 Fingerprint: 55:90:38:59:c8:c0:c3:eb:b8:75:9e:ce:4e:25:57:22:5f:f5:75:8b:bd:38:eb:d4:82:76:60:1e:1b:d5:80:97 +-----BEGIN CERTIFICATE----- +MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf +BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 +YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x +NzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYDVQQGEwJVUzERMA8G +A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 +d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF +Q0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuB +BAAiA2IABGvaDXU1CDFHBa5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJ +j9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr/TklZvFe/oyujUF5nQlgziip04pt89ZF +1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwYAMB0G +A1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNnADBkAjA3 +AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsC +MGclCrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVu +Sw== +-----END CERTIFICATE----- + +# Issuer: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp. +# Subject: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp. +# Label: "NAVER Global Root Certification Authority" +# Serial: 9013692873798656336226253319739695165984492813 +# MD5 Fingerprint: c8:7e:41:f6:25:3b:f5:09:b3:17:e8:46:3d:bf:d0:9b +# SHA1 Fingerprint: 8f:6b:f2:a9:27:4a:da:14:a0:c4:f4:8e:61:27:f9:c0:1e:78:5d:d1 +# SHA256 Fingerprint: 88:f4:38:dc:f8:ff:d1:fa:8f:42:91:15:ff:e5:f8:2a:e1:e0:6e:0c:70:c3:75:fa:ad:71:7b:34:a4:9e:72:65 +-----BEGIN CERTIFICATE----- +MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM +BQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG +T1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4NDJaFw0zNzA4MTgyMzU5NTlaMGkx +CzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVTUyBQTEFURk9STSBD +b3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVA +iQqrDZBbUGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH +38dq6SZeWYp34+hInDEW+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lE +HoSTGEq0n+USZGnQJoViAbbJAh2+g1G7XNr4rRVqmfeSVPc0W+m/6imBEtRTkZaz +kVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2aacp+yPOiNgSnABIqKYP +szuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4Yb8Obtoq +vC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHf +nZ3zVHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaG +YQ5fG8Ir4ozVu53BA0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo +0es+nPxdGoMuK8u180SdOqcXYZaicdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3a +CJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejyYhbLgGvtPe31HzClrkvJE+2K +AQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNVHQ4EFgQU0p+I +36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoN +qo0hV4/GPnrK21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatj +cu3cvuzHV+YwIHHW1xDBE1UBjCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm ++LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bxhYTeodoS76TiEJd6eN4MUZeoIUCL +hr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTgE34h5prCy8VCZLQe +lHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTHD8z7 +p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8 +piKCk5XQA76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLR +LBT/DShycpWbXgnbiUSYqqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX +5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oGI/hGoiLtk/bdmuYqh7GYVPEi92tF4+KO +dh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmgkpzNNIaRkPpkUZ3+/uul +9XXeifdy +-----END CERTIFICATE----- + +# Issuer: CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS O=FNMT-RCM OU=Ceres +# Subject: CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS O=FNMT-RCM OU=Ceres +# Label: "AC RAIZ FNMT-RCM SERVIDORES SEGUROS" +# Serial: 131542671362353147877283741781055151509 +# MD5 Fingerprint: 19:36:9c:52:03:2f:d2:d1:bb:23:cc:dd:1e:12:55:bb +# SHA1 Fingerprint: 62:ff:d9:9e:c0:65:0d:03:ce:75:93:d2:ed:3f:2d:32:c9:e3:e5:4a +# SHA256 Fingerprint: 55:41:53:b1:3d:2c:f9:dd:b7:53:bf:be:1a:4e:0a:e0:8d:0a:a4:18:70:58:fe:60:a2:b8:62:b2:e4:b8:7b:cb +-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw +CQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw +FgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S +Q00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4MTIyMDA5MzczM1oXDTQzMTIyMDA5 +MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQtUkNNMQ4wDAYDVQQL +DAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNBQyBS +QUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LH +sbI6GA60XYyzZl2hNPk2LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oK +Um8BA06Oi6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqGSM49BAMDA2kAMGYCMQCu +SuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoDzBOQn5IC +MQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJy +v+c= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign Root R46 O=GlobalSign nv-sa +# Subject: CN=GlobalSign Root R46 O=GlobalSign nv-sa +# Label: "GlobalSign Root R46" +# Serial: 1552617688466950547958867513931858518042577 +# MD5 Fingerprint: c4:14:30:e4:fa:66:43:94:2a:6a:1b:24:5f:19:d0:ef +# SHA1 Fingerprint: 53:a2:b0:4b:ca:6b:d6:45:e6:39:8a:8e:c4:0d:d2:bf:77:c3:a2:90 +# SHA256 Fingerprint: 4f:a3:12:6d:8d:3a:11:d1:c4:85:5a:4f:80:7c:ba:d6:cf:91:9d:3a:5a:88:b0:3b:ea:2c:63:72:d9:3c:40:c9 +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA +MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD +VQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy +MDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYt +c2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08EsCVeJ +OaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQG +vGIFAha/r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud +316HCkD7rRlr+/fKYIje2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo +0q3v84RLHIf8E6M6cqJaESvWJ3En7YEtbWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSE +y132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvjK8Cd+RTyG/FWaha/LIWF +zXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD412lPFzYE ++cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCN +I/onccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzs +x2sZy/N78CsHpdlseVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqa +ByFrgY/bxFn63iLABJzjqls2k+g9vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC +4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEMBQADggIBAHx4 +7PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti +2kM3S+LGteWygxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIk +pnnpHs6i58FZFZ8d4kuaPp92CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRF +FRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZmOUdkLG5NrmJ7v2B0GbhWrJKsFjLt +rWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qqJZ4d16GLuc1CLgSk +ZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwyeqiv5 +u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP +4vkYxboznxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6 +N3ec592kD3ZDZopD8p/7DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3 +vouXsXgxT7PntgMTzlSdriVZzH81Xwj3QEUxeCp6 +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign Root E46 O=GlobalSign nv-sa +# Subject: CN=GlobalSign Root E46 O=GlobalSign nv-sa +# Label: "GlobalSign Root E46" +# Serial: 1552617690338932563915843282459653771421763 +# MD5 Fingerprint: b5:b8:66:ed:de:08:83:e3:c9:e2:01:34:06:ac:51:6f +# SHA1 Fingerprint: 39:b4:6c:d5:fe:80:06:eb:e2:2f:4a:bb:08:33:a0:af:db:b9:dd:84 +# SHA256 Fingerprint: cb:b9:c4:4d:84:b8:04:3e:10:50:ea:31:a6:9f:51:49:55:d7:bf:d2:e2:c6:b4:93:01:01:9a:d6:1d:9f:50:58 +-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx +CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD +ExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw +MDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2Ex +HDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkBjtjq +R+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGdd +yXqBPCCjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ +7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZkvLtoURMMA/cVi4RguYv/Uo7njLwcAjA8 ++RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+CAezNIm8BZ/3Hobui3A= +-----END CERTIFICATE----- + +# Issuer: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz +# Subject: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz +# Label: "ANF Secure Server Root CA" +# Serial: 996390341000653745 +# MD5 Fingerprint: 26:a6:44:5a:d9:af:4e:2f:b2:1d:b6:65:b0:4e:e8:96 +# SHA1 Fingerprint: 5b:6e:68:d0:cc:15:b6:a0:5f:1e:c1:5f:ae:02:fc:6b:2f:5d:6f:74 +# SHA256 Fingerprint: fb:8f:ec:75:91:69:b9:10:6b:1e:51:16:44:c6:18:c5:13:04:37:3f:6c:06:43:08:8d:8b:ef:fd:1b:99:75:99 +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +# Issuer: CN=Certum EC-384 CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Subject: CN=Certum EC-384 CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Label: "Certum EC-384 CA" +# Serial: 160250656287871593594747141429395092468 +# MD5 Fingerprint: b6:65:b3:96:60:97:12:a1:ec:4e:e1:3d:a3:c6:c9:f1 +# SHA1 Fingerprint: f3:3e:78:3c:ac:df:f4:a2:cc:ac:67:55:69:56:d7:e5:16:3c:e1:ed +# SHA256 Fingerprint: 6b:32:80:85:62:53:18:aa:50:d1:73:c9:8d:8b:da:09:d5:7e:27:41:3d:11:4c:f7:87:a0:f5:d0:6c:03:0c:f6 +-----BEGIN CERTIFICATE----- +MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw +CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw +JQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT +EENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2MDcyNDU0WhcNNDMwMzI2MDcyNDU0 +WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBT +LkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAX +BgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATE +KI6rGFtqvm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7Tm +Fy8as10CW4kjPMIRBSqniBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68Kj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI0GZnQkdjrzife81r1HfS+8 +EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjADVS2m5hjEfO/J +UG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0QoSZ/6vn +nvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Root CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Root CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Root CA" +# Serial: 40870380103424195783807378461123655149 +# MD5 Fingerprint: 51:e1:c2:e7:fe:4c:84:af:59:0e:2f:f4:54:6f:ea:29 +# SHA1 Fingerprint: c8:83:44:c0:18:ae:9f:cc:f1:87:b7:8f:22:d1:c5:d7:45:84:ba:e5 +# SHA256 Fingerprint: fe:76:96:57:38:55:77:3e:37:a9:5e:7a:d4:d9:cc:96:c3:01:57:c1:5d:31:76:5b:a9:b1:57:04:e1:ae:78:fd +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +# Issuer: CN=TunTrust Root CA O=Agence Nationale de Certification Electronique +# Subject: CN=TunTrust Root CA O=Agence Nationale de Certification Electronique +# Label: "TunTrust Root CA" +# Serial: 108534058042236574382096126452369648152337120275 +# MD5 Fingerprint: 85:13:b9:90:5b:36:5c:b6:5e:b8:5a:f8:e0:31:57:b4 +# SHA1 Fingerprint: cf:e9:70:84:0f:e0:73:0f:9d:f6:0c:7f:2c:4b:ee:20:46:34:9c:bb +# SHA256 Fingerprint: 2e:44:10:2a:b5:8c:b8:54:19:45:1c:8e:19:d9:ac:f3:66:2c:af:bc:61:4b:6a:53:96:0a:30:f7:d0:e2:eb:41 +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQEL +BQAwYTELMAkGA1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUg +Q2VydGlmaWNhdGlvbiBFbGVjdHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJv +b3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQwNDI2MDg1NzU2WjBhMQswCQYDVQQG +EwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBDZXJ0aWZpY2F0aW9u +IEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZ +n56eY+hz2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd +2JQDoOw05TDENX37Jk0bbjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgF +VwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZ +GoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAdgjH8KcwAWJeRTIAAHDOF +li/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViWVSHbhlnU +r8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2 +eY8fTpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIb +MlEsPvLfe/ZdeikZjuXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISg +jwBUFfyRbVinljvrS5YnzWuioYasDXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB +7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwSVXAkPcvCFDVDXSdOvsC9qnyW +5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI04Y+oXNZtPdE +ITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0 +90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+z +xiD2BkewhpMl0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYu +QEkHDVneixCwSQXi/5E/S7fdAo74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4 +FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRYYdZ2vyJ/0Adqp2RT8JeNnYA/u8EH +22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJpadbGNjHh/PqAulxP +xOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65xxBzn +dFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5 +Xc0yGYuPjCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7b +nV2UqL1g52KAdoGDDIzMMEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQ +CvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9zZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZH +u/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3rAZ3r2OvEhJn7wAzMMujj +d9qDRIueVSjAi1jTkD5OGwDxFa2DK5o= +-----END CERTIFICATE----- + +# Issuer: CN=HARICA TLS RSA Root CA 2021 O=Hellenic Academic and Research Institutions CA +# Subject: CN=HARICA TLS RSA Root CA 2021 O=Hellenic Academic and Research Institutions CA +# Label: "HARICA TLS RSA Root CA 2021" +# Serial: 76817823531813593706434026085292783742 +# MD5 Fingerprint: 65:47:9b:58:86:dd:2c:f0:fc:a2:84:1f:1e:96:c4:91 +# SHA1 Fingerprint: 02:2d:05:82:fa:88:ce:14:0c:06:79:de:7f:14:10:e9:45:d7:a5:6d +# SHA256 Fingerprint: d9:5d:0e:8e:da:79:52:5b:f9:be:b1:1b:14:d2:10:0d:32:94:98:5f:0c:62:d9:fa:bd:9c:d9:99:ec:cb:7b:1d +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBs +MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0Eg +Um9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUzOFoXDTQ1MDIxMzEwNTUzN1owbDEL +MAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl +YXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNBIFJv +b3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569l +mwVnlskNJLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE +4VGC/6zStGndLuwRo0Xua2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uv +a9of08WRiFukiZLRgeaMOVig1mlDqa2YUlhu2wr7a89o+uOkXjpFc5gH6l8Cct4M +pbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K5FrZx40d/JiZ+yykgmvw +Kh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEvdmn8kN3b +LW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcY +AuUR0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqB +AGMUuTNe3QvboEUHGjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYq +E613TBoYm5EPWNgGVMWX+Ko/IIqmhaZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHr +W2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQCPxrvrNQKlr9qEgYRtaQQJKQ +CoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAU +X15QvWiWkKQUEapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3 +f5Z2EMVGpdAgS1D0NTsY9FVqQRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxaja +H6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxDQpSbIPDRzbLrLFPCU3hKTwSUQZqP +JzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcRj88YxeMn/ibvBZ3P +zzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5vZSt +jBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0 +/L5H9MG0qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pT +BGIBnfHAT+7hOtSLIBD6Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79 +aPib8qXPMThcFarmlwDB31qlpzmq6YR/PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YW +xw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnnkf3/W9b3raYvAwtt41dU +63ZTGI0RmLo= +-----END CERTIFICATE----- + +# Issuer: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA +# Subject: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA +# Label: "HARICA TLS ECC Root CA 2021" +# Serial: 137515985548005187474074462014555733966 +# MD5 Fingerprint: ae:f7:4c:e5:66:35:d1:b7:9b:8c:22:93:74:d3:4b:b0 +# SHA1 Fingerprint: bc:b0:c1:9d:e9:98:92:70:19:38:57:e9:8d:a7:b4:5d:6e:ee:01:48 +# SHA256 Fingerprint: 3f:99:cc:47:4a:cf:ce:4d:fe:d5:87:94:66:5e:47:8d:15:47:73:9f:2e:78:0f:1b:b4:ca:9b:13:30:97:d4:01 +-----BEGIN CERTIFICATE----- +MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw +CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh +cmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9v +dCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoXDTQ1MDIxMzExMDEwOVowbDELMAkG +A1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj +aCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJvb3Qg +Q0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7 +KKrxcm1lAEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9Y +STHMmE5gEYd103KUkE+bECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQD +AgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAircJRQO9gcS3ujwLEXQNw +SaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/QwCZ61IygN +nxS2PFOiTAZpffpskcYqSUXm7LcT4Tps +-----END CERTIFICATE----- + +# Issuer: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 +# Subject: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 +# Label: "Autoridad de Certificacion Firmaprofesional CIF A62634068" +# Serial: 1977337328857672817 +# MD5 Fingerprint: 4e:6e:9b:54:4c:ca:b7:fa:48:e4:90:b1:15:4b:1c:a3 +# SHA1 Fingerprint: 0b:be:c2:27:22:49:cb:39:aa:db:35:5c:53:e3:8c:ae:78:ff:b6:fe +# SHA256 Fingerprint: 57:de:05:83:ef:d2:b2:6e:03:61:da:99:da:9d:f4:64:8d:ef:7e:e8:44:1c:3b:72:8a:fa:9b:cd:e0:f9:b2:6a +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UE +BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h +cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1 +MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg +Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9 +thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM +cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG +L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i +NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h +X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b +m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy +Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja +EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T +KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF +6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh +OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1UdDgQWBBRlzeurNR4APn7VdMAc +tHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4wgZswgZgGBFUd +IAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j +b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABC +AG8AbgBhAG4AbwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAw +ADEANzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9m +iWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL4QjbEwj4KKE1soCzC1HA01aajTNF +Sa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDbLIpgD7dvlAceHabJ +hfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1ilI45P +Vf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZE +EAEeiGaPcjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV +1aUsIC+nmCjuRfzxuIgALI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2t +CsvMo2ebKHTEm9caPARYpoKdrcd7b/+Alun4jWq9GJAd/0kakFI3ky88Al2CdgtR +5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH9IBk9W6VULgRfhVwOEqw +f9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpfNIbnYrX9 +ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNK +GbqEZycPvEJdvSRUDewdcAZfpLz6IHxV +-----END CERTIFICATE----- + +# Issuer: CN=vTrus ECC Root CA O=iTrusChina Co.,Ltd. +# Subject: CN=vTrus ECC Root CA O=iTrusChina Co.,Ltd. +# Label: "vTrus ECC Root CA" +# Serial: 630369271402956006249506845124680065938238527194 +# MD5 Fingerprint: de:4b:c1:f5:52:8c:9b:43:e1:3e:8f:55:54:17:8d:85 +# SHA1 Fingerprint: f6:9c:db:b0:fc:f6:02:13:b6:52:32:a6:a3:91:3f:16:70:da:c3:e1 +# SHA256 Fingerprint: 30:fb:ba:2c:32:23:8e:2a:98:54:7a:f9:79:31:e5:50:42:8b:9b:3f:1c:8e:eb:66:33:dc:fa:86:c5:b2:7d:d3 +-----BEGIN CERTIFICATE----- +MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMw +RzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAY +BgNVBAMTEXZUcnVzIEVDQyBSb290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDcz +MTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28u +LEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+cToL0 +v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUd +e4BdS49nTPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYD +VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIw +V53dVvHH4+m4SVBrm2nDb+zDfSXkV5UTQJtS0zvzQBm8JsctBp61ezaf9SXUY2sA +AjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQLYgmRWAD5Tfs0aNoJrSEG +GJTO +-----END CERTIFICATE----- + +# Issuer: CN=vTrus Root CA O=iTrusChina Co.,Ltd. +# Subject: CN=vTrus Root CA O=iTrusChina Co.,Ltd. +# Label: "vTrus Root CA" +# Serial: 387574501246983434957692974888460947164905180485 +# MD5 Fingerprint: b8:c9:37:df:fa:6b:31:84:64:c5:ea:11:6a:1b:75:fc +# SHA1 Fingerprint: 84:1a:69:fb:f5:cd:1a:25:34:13:3d:e3:f8:fc:b8:99:d0:c9:14:b7 +# SHA256 Fingerprint: 8a:71:de:65:59:33:6f:42:6c:26:e5:38:80:d0:0d:88:a1:8d:a4:c6:a9:1f:0d:cb:61:94:e2:06:c5:c9:63:87 +-----BEGIN CERTIFICATE----- +MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQEL +BQAwQzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4x +FjAUBgNVBAMTDXZUcnVzIFJvb3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMx +MDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoGA1UEChMTaVRydXNDaGluYSBDby4s +THRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZotsSKYc +IrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykU +AyyNJJrIZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+ +GrPSbcKvdmaVayqwlHeFXgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z9 +8Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KAYPxMvDVTAWqXcoKv8R1w6Jz1717CbMdH +flqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70kLJrxLT5ZOrpGgrIDajt +J8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2AXPKBlim +0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZN +pGvu/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQ +UqqzApVg+QxMaPnu1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHW +OXSuTEGC2/KmSNGzm/MzqvOmwMVO9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMB +AAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYgscasGrz2iTAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAKbqSSaet +8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd +nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1j +bhd47F18iMjrjld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvM +Kar5CKXiNxTKsbhm7xqC5PD48acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIiv +TDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJnxDHO2zTlJQNgJXtxmOTAGytfdELS +S8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554WgicEFOwE30z9J4nfr +I8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4sEb9 +b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNB +UvupLnKWnyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1P +Ti07NEPhmg4NpGaXutIcSkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929ven +sBxXVsFy6K2ir40zSbofitzmdHxghm+Hl3s= +-----END CERTIFICATE----- + +# Issuer: CN=ISRG Root X2 O=Internet Security Research Group +# Subject: CN=ISRG Root X2 O=Internet Security Research Group +# Label: "ISRG Root X2" +# Serial: 87493402998870891108772069816698636114 +# MD5 Fingerprint: d3:9e:c4:1e:23:3c:a6:df:cf:a3:7e:6d:e0:14:e6:e5 +# SHA1 Fingerprint: bd:b1:b9:3c:d5:97:8d:45:c6:26:14:55:f8:db:95:c7:5a:d1:53:af +# SHA256 Fingerprint: 69:72:9b:8e:15:a8:6e:fc:17:7a:57:af:b7:17:1d:fc:64:ad:d2:8c:2f:ca:8c:f1:50:7e:34:45:3c:cb:14:70 +-----BEGIN CERTIFICATE----- +MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw +CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg +R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00 +MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT +ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW ++1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9 +ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI +zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW +tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1 +/q4AaOeMSQ+2b1tbFfLn +-----END CERTIFICATE----- + +# Issuer: CN=HiPKI Root CA - G1 O=Chunghwa Telecom Co., Ltd. +# Subject: CN=HiPKI Root CA - G1 O=Chunghwa Telecom Co., Ltd. +# Label: "HiPKI Root CA - G1" +# Serial: 60966262342023497858655262305426234976 +# MD5 Fingerprint: 69:45:df:16:65:4b:e8:68:9a:8f:76:5f:ff:80:9e:d3 +# SHA1 Fingerprint: 6a:92:e4:a8:ee:1b:ec:96:45:37:e3:29:57:49:cd:96:e3:e5:d2:60 +# SHA256 Fingerprint: f0:15:ce:3c:c2:39:bf:ef:06:4b:e9:f1:d2:c4:17:e1:a0:26:4a:0a:94:be:1f:0c:8d:12:18:64:eb:69:49:cc +-----BEGIN CERTIFICATE----- +MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBP +MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 +ZC4xGzAZBgNVBAMMEkhpUEtJIFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRa +Fw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3 +YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kgUm9vdCBDQSAtIEcx +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0o9Qw +qNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twv +Vcg3Px+kwJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6 +lZgRZq2XNdZ1AYDgr/SEYYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnz +Qs7ZngyzsHeXZJzA9KMuH5UHsBffMNsAGJZMoYFL3QRtU6M9/Aes1MU3guvklQgZ +KILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfdhSi8MEyr48KxRURHH+CK +FgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj1jOXTyFj +HluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDr +y+K49a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ +/W3c1pzAtH2lsN0/Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgM +a/aOEmem8rJY5AIJEzypuxC00jBF8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6 +fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQDAgGGMA0GCSqG +SIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi +7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqc +SE5XCV0vrPSltJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6Fza +ZsT0pPBWGTMpWmWSBUdGSquEwx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9Tc +XzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07QJNBAsNB1CI69aO4I1258EHBGG3zg +iLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv5wiZqAxeJoBF1Pho +L5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+GpzjLrF +Ne85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wr +kkVbbiVghUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+ +vhV4nYWBSipX3tUZQ9rbyltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQU +YDksswBVLuT1sw5XxJFBAJw/6KXf6vb/yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ== +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 +# Label: "GlobalSign ECC Root CA - R4" +# Serial: 159662223612894884239637590694 +# MD5 Fingerprint: 26:29:f8:6d:e1:88:bf:a2:65:7f:aa:c4:cd:0f:7f:fc +# SHA1 Fingerprint: 6b:a0:b0:98:e1:71:ef:5a:ad:fe:48:15:80:77:10:f4:bd:6f:0b:28 +# SHA256 Fingerprint: b0:85:d7:0b:96:4f:19:1a:73:e4:af:0d:54:ae:7a:0e:07:aa:fd:af:9b:71:dd:08:62:13:8a:b7:32:5a:24:a2 +-----BEGIN CERTIFICATE----- +MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD +VQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh +bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw +MTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0g +UjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wWTAT +BgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkWymOx +uYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNV +HQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/ ++wpu+74zyTyjhNUwCgYIKoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147 +bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm +-----END CERTIFICATE----- + +# Issuer: CN=GTS Root R1 O=Google Trust Services LLC +# Subject: CN=GTS Root R1 O=Google Trust Services LLC +# Label: "GTS Root R1" +# Serial: 159662320309726417404178440727 +# MD5 Fingerprint: 05:fe:d0:bf:71:a8:a3:76:63:da:01:e0:d8:52:dc:40 +# SHA1 Fingerprint: e5:8c:1c:c4:91:3b:38:63:4b:e9:10:6e:e3:ad:8e:6b:9d:d9:81:4a +# SHA256 Fingerprint: d9:47:43:2a:bd:e7:b7:fa:90:fc:2e:6b:59:10:1b:12:80:e0:e1:c7:e4:e4:0f:a3:c6:88:7f:ff:57:a7:f4:cf +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw +CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU +MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw +MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp +Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo +27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w +Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw +TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl +qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH +szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8 +Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk +MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92 +wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p +aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN +VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID +AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb +C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe +QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy +h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4 +7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J +ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef +MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/ +Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT +6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ +0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm +2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb +bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c +-----END CERTIFICATE----- + +# Issuer: CN=GTS Root R2 O=Google Trust Services LLC +# Subject: CN=GTS Root R2 O=Google Trust Services LLC +# Label: "GTS Root R2" +# Serial: 159662449406622349769042896298 +# MD5 Fingerprint: 1e:39:c0:53:e6:1e:29:82:0b:ca:52:55:36:5d:57:dc +# SHA1 Fingerprint: 9a:44:49:76:32:db:de:fa:d0:bc:fb:5a:7b:17:bd:9e:56:09:24:94 +# SHA256 Fingerprint: 8d:25:cd:97:22:9d:bf:70:35:6b:da:4e:b3:cc:73:40:31:e2:4c:f0:0f:af:cf:d3:2d:c7:6e:b5:84:1c:7e:a8 +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQsw +CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU +MBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw +MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp +Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3LvCvpt +nfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY +6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAu +MC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7k +RXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWg +f9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1mKPV ++3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K8Yzo +dDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW +Ir9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKa +G73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCq +gc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwID +AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBAB/Kzt3H +vqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8 +0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyC +B19m3H0Q/gxhswWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2u +NmSRXbBoGOqKYcl3qJfEycel/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMg +yALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVnjWQye+mew4K6Ki3pHrTgSAai/Gev +HyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y59PYjJbigapordwj6 +xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M7YNR +TOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924Sg +JPFI/2R80L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV +7LXTWtiBmelDGDfrs7vRWGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl +6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjWHYbL +-----END CERTIFICATE----- + +# Issuer: CN=GTS Root R3 O=Google Trust Services LLC +# Subject: CN=GTS Root R3 O=Google Trust Services LLC +# Label: "GTS Root R3" +# Serial: 159662495401136852707857743206 +# MD5 Fingerprint: 3e:e7:9d:58:02:94:46:51:94:e5:e0:22:4a:8b:e7:73 +# SHA1 Fingerprint: ed:e5:71:80:2b:c8:92:b9:5b:83:3c:d2:32:68:3f:09:cd:a0:1e:46 +# SHA256 Fingerprint: 34:d8:a7:3e:e2:08:d9:bc:db:0d:95:65:20:93:4b:4e:40:e6:94:82:59:6e:8b:6f:73:c8:42:6b:01:0a:6f:48 +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD +VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG +A1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw +WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz +IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout736G +jOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL2 +4CejQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7 +VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azTL818+FsuVbu/3ZL3pAzcMeGiAjEA/Jdm +ZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X +-----END CERTIFICATE----- + +# Issuer: CN=GTS Root R4 O=Google Trust Services LLC +# Subject: CN=GTS Root R4 O=Google Trust Services LLC +# Label: "GTS Root R4" +# Serial: 159662532700760215368942768210 +# MD5 Fingerprint: 43:96:83:77:19:4d:76:b3:9d:65:52:e4:1d:22:a5:e8 +# SHA1 Fingerprint: 77:d3:03:67:b5:e0:0c:15:f6:0c:38:61:df:7c:e1:3b:92:46:4d:47 +# SHA256 Fingerprint: 34:9d:fa:40:58:c5:e2:63:12:3b:39:8a:e7:95:57:3c:4e:13:13:c8:3f:e6:8f:93:55:6c:d5:e8:03:1b:3c:7d +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD +VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG +A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw +WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz +IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi +QHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR +HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D +9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8 +p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD +-----END CERTIFICATE----- + +# Issuer: CN=Telia Root CA v2 O=Telia Finland Oyj +# Subject: CN=Telia Root CA v2 O=Telia Finland Oyj +# Label: "Telia Root CA v2" +# Serial: 7288924052977061235122729490515358 +# MD5 Fingerprint: 0e:8f:ac:aa:82:df:85:b1:f4:dc:10:1c:fc:99:d9:48 +# SHA1 Fingerprint: b9:99:cd:d1:73:50:8a:c4:47:05:08:9c:8c:88:fb:be:a0:2b:40:cd +# SHA256 Fingerprint: 24:2b:69:74:2f:cb:1e:5b:2a:bf:98:89:8b:94:57:21:87:54:4e:5b:4d:99:11:78:65:73:62:1f:6a:74:b8:2c +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQx +CzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UE +AwwQVGVsaWEgUm9vdCBDQSB2MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1 +NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZ +MBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ76zBq +AMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9 +vVYiQJ3q9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9 +lRdU2HhE8Qx3FZLgmEKnpNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTOD +n3WhUidhOPFZPY5Q4L15POdslv5e2QJltI5c0BE0312/UqeBAMN/mUWZFdUXyApT +7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW5olWK8jjfN7j/4nlNW4o +6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNrRBH0pUPC +TEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6 +WT0EBXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63R +DolUK5X6wK0dmBR4M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZI +pEYslOqodmJHixBTB0hXbOKSTbauBcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGj +YzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7Wxy+G2CQ5MB0GA1UdDgQWBBRy +rOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ +8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi +0f6X+J8wfBj5tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMM +A8iZGok1GTzTyVR8qPAs5m4HeW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBS +SRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+Cy748fdHif64W1lZYudogsYMVoe+K +TTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygCQMez2P2ccGrGKMOF +6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15h2Er +3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMt +Ty3EHD70sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pT +VmBds9hCG1xLEooc6+t9xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAW +ysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQraVplI/owd8k+BsHMYeB2F326CjYSlKA +rBPuUBQemMc= +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST BR Root CA 1 2020 O=D-Trust GmbH +# Subject: CN=D-TRUST BR Root CA 1 2020 O=D-Trust GmbH +# Label: "D-TRUST BR Root CA 1 2020" +# Serial: 165870826978392376648679885835942448534 +# MD5 Fingerprint: b5:aa:4b:d5:ed:f7:e3:55:2e:8f:72:0a:f3:75:b8:ed +# SHA1 Fingerprint: 1f:5b:98:f0:e3:b5:f7:74:3c:ed:e6:b0:36:7d:32:cd:f4:09:41:67 +# SHA256 Fingerprint: e5:9a:aa:81:60:09:c2:2b:ff:5b:25:ba:d3:7d:f3:06:f0:49:79:7c:1f:81:d8:5a:b0:89:e6:57:bd:8f:00:44 +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQsw +CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS +VVNUIEJSIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5 +NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG +A1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB +BAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7dPYS +zuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0 +QVK5buXuQqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/ +VbNafAkl1bK6CKBrqx9tMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g +PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2JyX3Jvb3Rf +Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l +dC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1 +c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO +PQQDAwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFW +wKrY7RjEsK70PvomAjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHV +dWNbFJWcHwHP2NVypw87 +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST EV Root CA 1 2020 O=D-Trust GmbH +# Subject: CN=D-TRUST EV Root CA 1 2020 O=D-Trust GmbH +# Label: "D-TRUST EV Root CA 1 2020" +# Serial: 126288379621884218666039612629459926992 +# MD5 Fingerprint: 8c:2d:9d:70:9f:48:99:11:06:11:fb:e9:cb:30:c0:6e +# SHA1 Fingerprint: 61:db:8c:21:59:69:03:90:d8:7c:9c:12:86:54:cf:9d:3d:f4:dd:07 +# SHA256 Fingerprint: 08:17:0d:1a:a3:64:53:90:1a:2f:95:92:45:e3:47:db:0c:8d:37:ab:aa:bc:56:b8:1a:a1:00:dc:95:89:70:db +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQsw +CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS +VVNUIEVWIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5 +NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG +A1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB +BAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8ZRCC +/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rD +wpdhQntJraOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3 +OqQo5FD4pPfsazK2/umLMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g +PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2V2X3Jvb3Rf +Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l +dC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1 +c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO +PQQDAwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CA +y/m0sRtW9XLS/BnRAjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJb +gfM0agPnIjhQW+0ZT0MW +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert TLS ECC P384 Root G5 O=DigiCert, Inc. +# Subject: CN=DigiCert TLS ECC P384 Root G5 O=DigiCert, Inc. +# Label: "DigiCert TLS ECC P384 Root G5" +# Serial: 13129116028163249804115411775095713523 +# MD5 Fingerprint: d3:71:04:6a:43:1c:db:a6:59:e1:a8:a3:aa:c5:71:ed +# SHA1 Fingerprint: 17:f3:de:5e:9f:0f:19:e9:8e:f6:1f:32:26:6e:20:c4:07:ae:30:ee +# SHA256 Fingerprint: 01:8e:13:f0:77:25:32:cf:80:9b:d1:b1:72:81:86:72:83:fc:48:c6:e1:3b:e9:c6:98:12:85:4a:49:0c:1b:05 +-----BEGIN CERTIFICATE----- +MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp +Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2 +MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ +bmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS +7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp +0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS +B4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49 +BAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ +LgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4 +DXZDjC5Ty3zfDBeWUA== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert TLS RSA4096 Root G5 O=DigiCert, Inc. +# Subject: CN=DigiCert TLS RSA4096 Root G5 O=DigiCert, Inc. +# Label: "DigiCert TLS RSA4096 Root G5" +# Serial: 11930366277458970227240571539258396554 +# MD5 Fingerprint: ac:fe:f7:34:96:a9:f2:b3:b4:12:4b:e4:27:41:6f:e1 +# SHA1 Fingerprint: a7:88:49:dc:5d:7c:75:8c:8c:de:39:98:56:b3:aa:d0:b2:a5:71:35 +# SHA256 Fingerprint: 37:1a:00:dc:05:33:b3:72:1a:7e:eb:40:e8:41:9e:70:79:9d:2b:0a:0f:2c:1d:80:69:31:65:f7:ce:c4:ad:75 +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN +MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT +HERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN +NDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs +IEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS87IE+ +ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG0 +2C+JFvuUAT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgp +wgscONyfMXdcvyej/Cestyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZM +pG2T6T867jp8nVid9E6P/DsjyG244gXazOvswzH016cpVIDPRFtMbzCe88zdH5RD +nU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnVDdXifBBiqmvwPXbzP6Po +sMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9qTXeXAaDx +Zre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cd +Lvvyz6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvX +KyY//SovcfXWJL5/MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNe +XoVPzthwiHvOAbWWl9fNff2C+MIkwcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPL +tgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4EFgQUUTMc7TZArxfTJc1paPKv +TiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN +AQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw +GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7H +PNtQOa27PShNlnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLF +O4uJ+DQtpBflF+aZfTCIITfNMBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQ +REtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/u4cnYiWB39yhL/btp/96j1EuMPik +AdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9GOUrYU9DzLjtxpdRv +/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh47a+ +p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilw +MUc/dNAUFvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WF +qUITVuwhd4GTWgzqltlJyqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCK +ovfepEWFJqgejF0pW8hL2JpqA15w8oVPbEtoL8pU9ozaMv7Da4M/OMZ+ +-----END CERTIFICATE----- + +# Issuer: CN=Certainly Root R1 O=Certainly +# Subject: CN=Certainly Root R1 O=Certainly +# Label: "Certainly Root R1" +# Serial: 188833316161142517227353805653483829216 +# MD5 Fingerprint: 07:70:d4:3e:82:87:a0:fa:33:36:13:f4:fa:33:e7:12 +# SHA1 Fingerprint: a0:50:ee:0f:28:71:f4:27:b2:12:6d:6f:50:96:25:ba:cc:86:42:af +# SHA256 Fingerprint: 77:b8:2c:d8:64:4c:43:05:f7:ac:c5:cb:15:6b:45:67:50:04:03:3d:51:c6:0c:62:02:a8:e0:c3:34:67:d3:a0 +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAw +PTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2Vy +dGFpbmx5IFJvb3QgUjEwHhcNMjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9 +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0 +YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANA2 +1B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O5MQT +vqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbed +aFySpvXl8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b0 +1C7jcvk2xusVtyWMOvwlDbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5 +r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGIXsXwClTNSaa/ApzSRKft43jvRl5tcdF5 +cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkNKPl6I7ENPT2a/Z2B7yyQ +wHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQAjeZjOVJ +6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA +2CnbrlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyH +Wyf5QBGenDPBt+U1VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMR +eiFPCyEQtkA6qyI6BJyLm4SGcprSp6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB +/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTgqj8ljZ9EXME66C6u +d0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAszHQNTVfSVcOQr +PbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d +8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi +1wrykXprOQ4vMMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrd +rRT90+7iIgXr0PK3aBLXWopBGsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9di +taY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+gjwN/KUD+nsa2UUeYNrEjvn8K8l7 +lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgHJBu6haEaBQmAupVj +yTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7fpYn +Kx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLy +yCwzk5Iwx06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5n +wXARPbv0+Em34yaXOp/SX3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6 +OV+KmalBWQewLK8= +-----END CERTIFICATE----- + +# Issuer: CN=Certainly Root E1 O=Certainly +# Subject: CN=Certainly Root E1 O=Certainly +# Label: "Certainly Root E1" +# Serial: 8168531406727139161245376702891150584 +# MD5 Fingerprint: 0a:9e:ca:cd:3e:52:50:c6:36:f3:4b:a3:ed:a7:53:e9 +# SHA1 Fingerprint: f9:e1:6d:dc:01:89:cf:d5:82:45:63:3e:c5:37:7d:c2:eb:93:6f:2b +# SHA256 Fingerprint: b4:58:5f:22:e4:ac:75:6a:4e:86:12:a1:36:1c:5d:9d:03:1a:93:fd:84:fe:bb:77:8f:a3:06:8b:0f:c4:2d:c2 +-----BEGIN CERTIFICATE----- +MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQsw +CQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlu +bHkgUm9vdCBFMTAeFw0yMTA0MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJ +BgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlubHkxGjAYBgNVBAMTEUNlcnRhaW5s +eSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4fxzf7flHh4axpMCK ++IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9YBk2 +QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4 +hevIIgcwCgYIKoZIzj0EAwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozm +ut6Dacpps6kFtZaSF4fC0urQe87YQVt8rgIwRt7qy12a7DLCZRawTDBcMPPaTnOG +BtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR +-----END CERTIFICATE----- + +# Issuer: CN=Security Communication RootCA3 O=SECOM Trust Systems CO.,LTD. +# Subject: CN=Security Communication RootCA3 O=SECOM Trust Systems CO.,LTD. +# Label: "Security Communication RootCA3" +# Serial: 16247922307909811815 +# MD5 Fingerprint: 1c:9a:16:ff:9e:5c:e0:4d:8a:14:01:f4:35:5d:29:26 +# SHA1 Fingerprint: c3:03:c8:22:74:92:e5:61:a2:9c:5f:79:91:2b:1e:44:13:91:30:3a +# SHA256 Fingerprint: 24:a5:5c:2a:b0:51:44:2d:06:17:76:65:41:23:9a:4a:d0:32:d7:c5:51:75:aa:34:ff:de:2f:bc:4f:5c:52:94 +-----BEGIN CERTIFICATE----- +MIIFfzCCA2egAwIBAgIJAOF8N0D9G/5nMA0GCSqGSIb3DQEBDAUAMF0xCzAJBgNV +BAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScw +JQYDVQQDEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTMwHhcNMTYwNjE2 +MDYxNzE2WhcNMzgwMTE4MDYxNzE2WjBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc +U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UEAxMeU2VjdXJpdHkg +Q29tbXVuaWNhdGlvbiBSb290Q0EzMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEA48lySfcw3gl8qUCBWNO0Ot26YQ+TUG5pPDXC7ltzkBtnTCHsXzW7OT4r +CmDvu20rhvtxosis5FaU+cmvsXLUIKx00rgVrVH+hXShuRD+BYD5UpOzQD11EKzA +lrenfna84xtSGc4RHwsENPXY9Wk8d/Nk9A2qhd7gCVAEF5aEt8iKvE1y/By7z/MG +TfmfZPd+pmaGNXHIEYBMwXFAWB6+oHP2/D5Q4eAvJj1+XCO1eXDe+uDRpdYMQXF7 +9+qMHIjH7Iv10S9VlkZ8WjtYO/u62C21Jdp6Ts9EriGmnpjKIG58u4iFW/vAEGK7 +8vknR+/RiTlDxN/e4UG/VHMgly1s2vPUB6PmudhvrvyMGS7TZ2crldtYXLVqAvO4 +g160a75BflcJdURQVc1aEWEhCmHCqYj9E7wtiS/NYeCVvsq1e+F7NGcLH7YMx3we +GVPKp7FKFSBWFHA9K4IsD50VHUeAR/94mQ4xr28+j+2GaR57GIgUssL8gjMunEst ++3A7caoreyYn8xrC3PsXuKHqy6C0rtOUfnrQq8PsOC0RLoi/1D+tEjtCrI8Cbn3M +0V9hvqG8OmpI6iZVIhZdXw3/JzOfGAN0iltSIEdrRU0id4xVJ/CvHozJgyJUt5rQ +T9nO/NkuHJYosQLTA70lUhw0Zk8jq/R3gpYd0VcwCBEF/VfR2ccCAwEAAaNCMEAw +HQYDVR0OBBYEFGQUfPxYchamCik0FW8qy7z8r6irMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDAUAA4ICAQDcAiMI4u8hOscNtybS +YpOnpSNyByCCYN8Y11StaSWSntkUz5m5UoHPrmyKO1o5yGwBQ8IibQLwYs1OY0PA +FNr0Y/Dq9HHuTofjcan0yVflLl8cebsjqodEV+m9NU1Bu0soo5iyG9kLFwfl9+qd +9XbXv8S2gVj/yP9kaWJ5rW4OH3/uHWnlt3Jxs/6lATWUVCvAUm2PVcTJ0rjLyjQI +UYWg9by0F1jqClx6vWPGOi//lkkZhOpn2ASxYfQAW0q3nHE3GYV5v4GwxxMOdnE+ +OoAGrgYWp421wsTL/0ClXI2lyTrtcoHKXJg80jQDdwj98ClZXSEIx2C/pHF7uNke +gr4Jr2VvKKu/S7XuPghHJ6APbw+LP6yVGPO5DtxnVW5inkYO0QR4ynKudtml+LLf +iAlhi+8kTtFZP1rUPcmTPCtk9YENFpb3ksP+MW/oKjJ0DvRMmEoYDjBU1cXrvMUV +nuiZIesnKwkK2/HmcBhWuwzkvvnoEKQTkrgc4NtnHVMDpCKn3F2SEDzq//wbEBrD +2NCcnWXL0CsnMQMeNuE9dnUM/0Umud1RvCPHX9jYhxBAEg09ODfnRDwYwFMJZI// +1ZqmfHAuc1Uh6N//g7kdPjIe1qZ9LPFm6Vwdp6POXiUyK+OVrCoHzrQoeIY8Laad +TdJ0MN1kURXbg4NR16/9M51NZg== +-----END CERTIFICATE----- + +# Issuer: CN=Security Communication ECC RootCA1 O=SECOM Trust Systems CO.,LTD. +# Subject: CN=Security Communication ECC RootCA1 O=SECOM Trust Systems CO.,LTD. +# Label: "Security Communication ECC RootCA1" +# Serial: 15446673492073852651 +# MD5 Fingerprint: 7e:43:b0:92:68:ec:05:43:4c:98:ab:5d:35:2e:7e:86 +# SHA1 Fingerprint: b8:0e:26:a9:bf:d2:b2:3b:c0:ef:46:c9:ba:c7:bb:f6:1d:0d:41:41 +# SHA256 Fingerprint: e7:4f:bd:a5:5b:d5:64:c4:73:a3:6b:44:1a:a7:99:c8:a6:8e:07:74:40:e8:28:8b:9f:a1:e5:0e:4b:ba:ca:11 +-----BEGIN CERTIFICATE----- +MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYT +AkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYD +VQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYx +NjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTELMAkGA1UEBhMCSlAxJTAjBgNVBAoT +HFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNVBAMTIlNlY3VyaXR5 +IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+Cnnfdl +dB9sELLo5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpK +ULGjQjBAMB0GA1UdDgQWBBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu +9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3LsnNdo4gIxwwCMQDAqy0O +be0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70eN9k= +-----END CERTIFICATE----- + +# Issuer: CN=BJCA Global Root CA1 O=BEIJING CERTIFICATE AUTHORITY +# Subject: CN=BJCA Global Root CA1 O=BEIJING CERTIFICATE AUTHORITY +# Label: "BJCA Global Root CA1" +# Serial: 113562791157148395269083148143378328608 +# MD5 Fingerprint: 42:32:99:76:43:33:36:24:35:07:82:9b:28:f9:d0:90 +# SHA1 Fingerprint: d5:ec:8d:7b:4c:ba:79:f4:e7:e8:cb:9d:6b:ae:77:83:10:03:21:6a +# SHA256 Fingerprint: f3:89:6f:88:fe:7c:0a:88:27:66:a7:fa:6a:d2:74:9f:b5:7a:7f:3e:98:fb:76:9c:1f:a7:b0:9c:2c:44:d5:ae +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIQVW9l47TZkGobCdFsPsBsIDANBgkqhkiG9w0BAQsFADBU +MQswCQYDVQQGEwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRI +T1JJVFkxHTAbBgNVBAMMFEJKQ0EgR2xvYmFsIFJvb3QgQ0ExMB4XDTE5MTIxOTAz +MTYxN1oXDTQ0MTIxMjAzMTYxN1owVDELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHUJF +SUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRCSkNBIEdsb2Jh +bCBSb290IENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPFmCL3Z +xRVhy4QEQaVpN3cdwbB7+sN3SJATcmTRuHyQNZ0YeYjjlwE8R4HyDqKYDZ4/N+AZ +spDyRhySsTphzvq3Rp4Dhtczbu33RYx2N95ulpH3134rhxfVizXuhJFyV9xgw8O5 +58dnJCNPYwpj9mZ9S1WnP3hkSWkSl+BMDdMJoDIwOvqfwPKcxRIqLhy1BDPapDgR +at7GGPZHOiJBhyL8xIkoVNiMpTAK+BcWyqw3/XmnkRd4OJmtWO2y3syJfQOcs4ll +5+M7sSKGjwZteAf9kRJ/sGsciQ35uMt0WwfCyPQ10WRjeulumijWML3mG90Vr4Tq +nMfK9Q7q8l0ph49pczm+LiRvRSGsxdRpJQaDrXpIhRMsDQa4bHlW/KNnMoH1V6XK +V0Jp6VwkYe/iMBhORJhVb3rCk9gZtt58R4oRTklH2yiUAguUSiz5EtBP6DF+bHq/ +pj+bOT0CFqMYs2esWz8sgytnOYFcuX6U1WTdno9uruh8W7TXakdI136z1C2OVnZO +z2nxbkRs1CTqjSShGL+9V/6pmTW12xB3uD1IutbB5/EjPtffhZ0nPNRAvQoMvfXn +jSXWgXSHRtQpdaJCbPdzied9v3pKH9MiyRVVz99vfFXQpIsHETdfg6YmV6YBW37+ +WGgHqel62bno/1Afq8K0wM7o6v0PvY1NuLxxAgMBAAGjQjBAMB0GA1UdDgQWBBTF +7+3M2I0hxkjk49cULqcWk+WYATAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE +AwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAUoKsITQfI/Ki2Pm4rzc2IInRNwPWaZ+4 +YRC6ojGYWUfo0Q0lHhVBDOAqVdVXUsv45Mdpox1NcQJeXyFFYEhcCY5JEMEE3Kli +awLwQ8hOnThJdMkycFRtwUf8jrQ2ntScvd0g1lPJGKm1Vrl2i5VnZu69mP6u775u ++2D2/VnGKhs/I0qUJDAnyIm860Qkmss9vk/Ves6OF8tiwdneHg56/0OGNFK8YT88 +X7vZdrRTvJez/opMEi4r89fO4aL/3Xtw+zuhTaRjAv04l5U/BXCga99igUOLtFkN +SoxUnMW7gZ/NfaXvCyUeOiDbHPwfmGcCCtRzRBPbUYQaVQNW4AB+dAb/OMRyHdOo +P2gxXdMJxy6MW2Pg6Nwe0uxhHvLe5e/2mXZgLR6UcnHGCyoyx5JO1UbXHfmpGQrI ++pXObSOYqgs4rZpWDW+N8TEAiMEXnM0ZNjX+VVOg4DwzX5Ze4jLp3zO7Bkqp2IRz +znfSxqxx4VyjHQy7Ct9f4qNx2No3WqB4K/TUfet27fJhcKVlmtOJNBir+3I+17Q9 +eVzYH6Eze9mCUAyTF6ps3MKCuwJXNq+YJyo5UOGwifUll35HaBC07HPKs5fRJNz2 +YqAo07WjuGS3iGJCz51TzZm+ZGiPTx4SSPfSKcOYKMryMguTjClPPGAyzQWWYezy +r/6zcCwupvI= +-----END CERTIFICATE----- + +# Issuer: CN=BJCA Global Root CA2 O=BEIJING CERTIFICATE AUTHORITY +# Subject: CN=BJCA Global Root CA2 O=BEIJING CERTIFICATE AUTHORITY +# Label: "BJCA Global Root CA2" +# Serial: 58605626836079930195615843123109055211 +# MD5 Fingerprint: 5e:0a:f6:47:5f:a6:14:e8:11:01:95:3f:4d:01:eb:3c +# SHA1 Fingerprint: f4:27:86:eb:6e:b8:6d:88:31:67:02:fb:ba:66:a4:53:00:aa:7a:a6 +# SHA256 Fingerprint: 57:4d:f6:93:1e:27:80:39:66:7b:72:0a:fd:c1:60:0f:c2:7e:b6:6d:d3:09:29:79:fb:73:85:64:87:21:28:82 +-----BEGIN CERTIFICATE----- +MIICJTCCAaugAwIBAgIQLBcIfWQqwP6FGFkGz7RK6zAKBggqhkjOPQQDAzBUMQsw +CQYDVQQGEwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJ +VFkxHTAbBgNVBAMMFEJKQ0EgR2xvYmFsIFJvb3QgQ0EyMB4XDTE5MTIxOTAzMTgy +MVoXDTQ0MTIxMjAzMTgyMVowVDELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHUJFSUpJ +TkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRCSkNBIEdsb2JhbCBS +b290IENBMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABJ3LgJGNU2e1uVCxA/jlSR9B +IgmwUVJY1is0j8USRhTFiy8shP8sbqjV8QnjAyEUxEM9fMEsxEtqSs3ph+B99iK+ ++kpRuDCK/eHeGBIK9ke35xe/J4rUQUyWPGCWwf0VHKNCMEAwHQYDVR0OBBYEFNJK +sVF/BvDRgh9Obl+rg/xI1LCRMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMAoGCCqGSM49BAMDA2gAMGUCMBq8W9f+qdJUDkpd0m2xQNz0Q9XSSpkZElaA +94M04TVOSG0ED1cxMDAtsaqdAzjbBgIxAMvMh1PLet8gUXOQwKhbYdDFUDn9hf7B +43j4ptZLvZuHjw/l1lOWqzzIQNph91Oj9w== +-----END CERTIFICATE----- + +# Issuer: CN=Sectigo Public Server Authentication Root E46 O=Sectigo Limited +# Subject: CN=Sectigo Public Server Authentication Root E46 O=Sectigo Limited +# Label: "Sectigo Public Server Authentication Root E46" +# Serial: 88989738453351742415770396670917916916 +# MD5 Fingerprint: 28:23:f8:b2:98:5c:37:16:3b:3e:46:13:4e:b0:b3:01 +# SHA1 Fingerprint: ec:8a:39:6c:40:f0:2e:bc:42:75:d4:9f:ab:1c:1a:5b:67:be:d2:9a +# SHA256 Fingerprint: c9:0f:26:f0:fb:1b:40:18:b2:22:27:51:9b:5c:a2:b5:3e:2c:a5:b3:be:5c:f1:8e:fe:1b:ef:47:38:0c:53:83 +-----BEGIN CERTIFICATE----- +MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQsw +CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T +ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcN +MjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYG +A1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT +ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccC +WvkEN/U0NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+ +6xnOQ6OjQjBAMB0GA1UdDgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8B +Af8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNnADBkAjAn7qRa +qCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RHlAFWovgzJQxC36oCMB3q +4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21USAGKcw== +-----END CERTIFICATE----- + +# Issuer: CN=Sectigo Public Server Authentication Root R46 O=Sectigo Limited +# Subject: CN=Sectigo Public Server Authentication Root R46 O=Sectigo Limited +# Label: "Sectigo Public Server Authentication Root R46" +# Serial: 156256931880233212765902055439220583700 +# MD5 Fingerprint: 32:10:09:52:00:d5:7e:6c:43:df:15:c0:b1:16:93:e5 +# SHA1 Fingerprint: ad:98:f9:f3:e4:7d:75:3b:65:d4:82:b3:a4:52:17:bb:6e:f5:e4:38 +# SHA256 Fingerprint: 7b:b6:47:a6:2a:ee:ac:88:bf:25:7a:a5:22:d0:1f:fe:a3:95:e0:ab:45:c7:3f:93:f6:56:54:ec:38:f2:5a:06 +-----BEGIN CERTIFICATE----- +MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBf +MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD +Ey1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw +HhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEY +MBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1Ymxp +YyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDa +ef0rty2k1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnz +SDBh+oF8HqcIStw+KxwfGExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xf +iOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMPFF1bFOdLvt30yNoDN9HWOaEhUTCDsG3X +ME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vuZDCQOc2TZYEhMbUjUDM3 +IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5QazYw6A3OAS +VYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgE +SJ/AwSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu ++Zd4KKTIRJLpfSYFplhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt +8uaZFURww3y8nDnAtOFr94MlI1fZEoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+L +HaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW6aWWrL3DkJiy4Pmi1KZHQ3xt +zwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWIIUkwDgYDVR0P +AQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c +mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQ +YKlJfp/imTYpE0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52 +gDY9hAaLMyZlbcp+nv4fjFg4exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZA +Fv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M0ejf5lG5Nkc/kLnHvALcWxxPDkjB +JYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI84HxZmduTILA7rpX +DhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9mpFui +TdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5 +dHn5HrwdVw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65 +LvKRRFHQV80MNNVIIb/bE/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp +0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmmJ1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAY +QqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com TLS RSA Root CA 2022 O=SSL Corporation +# Subject: CN=SSL.com TLS RSA Root CA 2022 O=SSL Corporation +# Label: "SSL.com TLS RSA Root CA 2022" +# Serial: 148535279242832292258835760425842727825 +# MD5 Fingerprint: d8:4e:c6:59:30:d8:fe:a0:d6:7a:5a:2c:2c:69:78:da +# SHA1 Fingerprint: ec:2c:83:40:72:af:26:95:10:ff:0e:f2:03:ee:31:70:f6:78:9d:ca +# SHA256 Fingerprint: 8f:af:7d:2e:2c:b4:70:9b:b8:e0:b3:36:66:bf:75:a5:dd:45:b5:de:48:0f:8e:a8:d4:bf:e6:be:bc:17:f2:ed +-----BEGIN CERTIFICATE----- +MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBO +MQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQD +DBxTU0wuY29tIFRMUyBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloX +DTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jw +b3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJvb3QgQ0EgMjAyMjCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u9nTP +L3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OY +t6/wNr/y7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0ins +S657Lb85/bRi3pZ7QcacoOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3 +PnxEX4MN8/HdIGkWCVDi1FW24IBydm5MR7d1VVm0U3TZlMZBrViKMWYPHqIbKUBO +L9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDGD6C1vBdOSHtRwvzpXGk3 +R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEWTO6Af77w +dr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS ++YCk8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYS +d66UNHsef8JmAOSqg+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoG +AtUjHBPW6dvbxrB6y3snm/vg1UYk7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2f +gTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j +BBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsuN+7jhHonLs0Z +NbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt +hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsM +QtfhWsSWTVTNj8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvf +R4iyrT7gJ4eLSYwfqUdYe5byiB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJ +DPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjUo3KUQyxi4U5cMj29TH0ZR6LDSeeW +P4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqoENjwuSfr98t67wVy +lrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7EgkaibMOlq +bLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2w +AgDHbICivRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3q +r5nsLFR+jM4uElZI7xc7P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sji +Mho6/4UIyYOf8kpIEFR3N+2ivEC+5BB09+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU +98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA= +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com TLS ECC Root CA 2022 O=SSL Corporation +# Subject: CN=SSL.com TLS ECC Root CA 2022 O=SSL Corporation +# Label: "SSL.com TLS ECC Root CA 2022" +# Serial: 26605119622390491762507526719404364228 +# MD5 Fingerprint: 99:d7:5c:f1:51:36:cc:e9:ce:d9:19:2e:77:71:56:c5 +# SHA1 Fingerprint: 9f:5f:d9:1a:54:6d:f5:0c:71:f0:ee:7a:bd:17:49:98:84:73:e2:39 +# SHA256 Fingerprint: c3:2f:fd:9f:46:f9:36:d1:6c:36:73:99:09:59:43:4b:9a:d6:0a:af:bb:9e:7c:f3:36:54:f1:44:cc:1b:a1:43 +-----BEGIN CERTIFICATE----- +MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQsw +CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxT +U0wuY29tIFRMUyBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2 +MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3Jh +dGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3QgQ0EgMjAyMjB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWyJGYm +acCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFN +SeR7T5v15wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME +GDAWgBSJjy+j6CugFFR781a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NW +uCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp +15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w7deedWo1dlJF4AIxAMeN +b0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5Zn6g6g== +-----END CERTIFICATE----- + +# Issuer: CN=Atos TrustedRoot Root CA ECC TLS 2021 O=Atos +# Subject: CN=Atos TrustedRoot Root CA ECC TLS 2021 O=Atos +# Label: "Atos TrustedRoot Root CA ECC TLS 2021" +# Serial: 81873346711060652204712539181482831616 +# MD5 Fingerprint: 16:9f:ad:f1:70:ad:79:d6:ed:29:b4:d1:c5:79:70:a8 +# SHA1 Fingerprint: 9e:bc:75:10:42:b3:02:f3:81:f4:f7:30:62:d4:8f:c3:a7:51:b2:dd +# SHA256 Fingerprint: b2:fa:e5:3e:14:cc:d7:ab:92:12:06:47:01:ae:27:9c:1d:89:88:fa:cb:77:5f:a8:a0:08:91:4e:66:39:88:a8 +-----BEGIN CERTIFICATE----- +MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4w +LAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0w +CwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0 +MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBF +Q0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMHYwEAYHKoZI +zj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6KDP/X +tXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4 +AjJn8ZQSb+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2 +KCXWfeBmmnoJsmo7jjPXNtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMD +aAAwZQIwW5kp85wxtolrbNa9d+F851F+uDrNozZffPc8dz7kUK2o59JZDCaOMDtu +CCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGYa3cpetskz2VAv9LcjBHo +9H1/IISpQuQo +-----END CERTIFICATE----- + +# Issuer: CN=Atos TrustedRoot Root CA RSA TLS 2021 O=Atos +# Subject: CN=Atos TrustedRoot Root CA RSA TLS 2021 O=Atos +# Label: "Atos TrustedRoot Root CA RSA TLS 2021" +# Serial: 111436099570196163832749341232207667876 +# MD5 Fingerprint: d4:d3:46:b8:9a:c0:9c:76:5d:9e:3a:c3:b9:99:31:d2 +# SHA1 Fingerprint: 18:52:3b:0d:06:37:e4:d6:3a:df:23:e4:98:fb:5b:16:fb:86:74:48 +# SHA256 Fingerprint: 81:a9:08:8e:a5:9f:b3:64:c5:48:a6:f8:55:59:09:9b:6f:04:05:ef:bf:18:e5:32:4e:c9:f4:57:ba:00:11:2f +-----BEGIN CERTIFICATE----- +MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBM +MS4wLAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIx +MQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00 +MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBD +QSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BBl01Z +4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYv +Ye+W/CBGvevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZ +kmGbzSoXfduP9LVq6hdKZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDs +GY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt0xU6kGpn8bRrZtkh68rZYnxGEFzedUln +nkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVKPNe0OwANwI8f4UDErmwh +3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMYsluMWuPD +0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzy +geBYBr3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8 +ANSbhqRAvNncTFd+rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezB +c6eUWsuSZIKmAMFwoW4sKeFYV+xafJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lI +pw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +dEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +DAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS +4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPs +o0UvFJ/1TCplQ3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJ +qM7F78PRreBrAwA0JrRUITWXAdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuyw +xfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9GslA9hGCZcbUztVdF5kJHdWoOsAgM +rr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2VktafcxBPTy+av5EzH4 +AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9qTFsR +0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuY +o7Ey7Nmj1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5 +dDTedk+SKlOxJTnbPP/lPqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcE +oji2jbDwN/zIIX8/syQbPYtuzE2wFg2WHYMfRsCbvUOZ58SWLs5fyQ== +-----END CERTIFICATE----- + +# Issuer: CN=TrustAsia Global Root CA G3 O=TrustAsia Technologies, Inc. +# Subject: CN=TrustAsia Global Root CA G3 O=TrustAsia Technologies, Inc. +# Label: "TrustAsia Global Root CA G3" +# Serial: 576386314500428537169965010905813481816650257167 +# MD5 Fingerprint: 30:42:1b:b7:bb:81:75:35:e4:16:4f:53:d2:94:de:04 +# SHA1 Fingerprint: 63:cf:b6:c1:27:2b:56:e4:88:8e:1c:23:9a:b6:2e:81:47:24:c3:c7 +# SHA256 Fingerprint: e0:d3:22:6a:eb:11:63:c2:e4:8f:f9:be:3b:50:b4:c6:43:1b:e7:bb:1e:ac:c5:c3:6b:5d:5e:c5:09:03:9a:08 +-----BEGIN CERTIFICATE----- +MIIFpTCCA42gAwIBAgIUZPYOZXdhaqs7tOqFhLuxibhxkw8wDQYJKoZIhvcNAQEM +BQAwWjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dp +ZXMsIEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHMzAe +Fw0yMTA1MjAwMjEwMTlaFw00NjA1MTkwMjEwMTlaMFoxCzAJBgNVBAYTAkNOMSUw +IwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtU +cnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDAMYJhkuSUGwoqZdC+BqmHO1ES6nBBruL7dOoKjbmzTNyPtxNS +T1QY4SxzlZHFZjtqz6xjbYdT8PfxObegQ2OwxANdV6nnRM7EoYNl9lA+sX4WuDqK +AtCWHwDNBSHvBm3dIZwZQ0WhxeiAysKtQGIXBsaqvPPW5vxQfmZCHzyLpnl5hkA1 +nyDvP+uLRx+PjsXUjrYsyUQE49RDdT/VP68czH5GX6zfZBCK70bwkPAPLfSIC7Ep +qq+FqklYqL9joDiR5rPmd2jE+SoZhLsO4fWvieylL1AgdB4SQXMeJNnKziyhWTXA +yB1GJ2Faj/lN03J5Zh6fFZAhLf3ti1ZwA0pJPn9pMRJpxx5cynoTi+jm9WAPzJMs +hH/x/Gr8m0ed262IPfN2dTPXS6TIi/n1Q1hPy8gDVI+lhXgEGvNz8teHHUGf59gX +zhqcD0r83ERoVGjiQTz+LISGNzzNPy+i2+f3VANfWdP3kXjHi3dqFuVJhZBFcnAv +kV34PmVACxmZySYgWmjBNb9Pp1Hx2BErW+Canig7CjoKH8GB5S7wprlppYiU5msT +f9FkPz2ccEblooV7WIQn3MSAPmeamseaMQ4w7OYXQJXZRe0Blqq/DPNL0WP3E1jA +uPP6Z92bfW1K/zJMtSU7/xxnD4UiWQWRkUF3gdCFTIcQcf+eQxuulXUtgQIDAQAB +o2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEDk5PIj7zjKsK5Xf/Ih +MBY027ySMB0GA1UdDgQWBBRA5OTyI+84yrCuV3/yITAWNNu8kjAOBgNVHQ8BAf8E +BAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACY7UeFNOPMyGLS0XuFlXsSUT9SnYaP4 +wM8zAQLpw6o1D/GUE3d3NZ4tVlFEbuHGLige/9rsR82XRBf34EzC4Xx8MnpmyFq2 +XFNFV1pF1AWZLy4jVe5jaN/TG3inEpQGAHUNcoTpLrxaatXeL1nHo+zSh2bbt1S1 +JKv0Q3jbSwTEb93mPmY+KfJLaHEih6D4sTNjduMNhXJEIlU/HHzp/LgV6FL6qj6j +ITk1dImmasI5+njPtqzn59ZW/yOSLlALqbUHM/Q4X6RJpstlcHboCoWASzY9M/eV +VHUl2qzEc4Jl6VL1XP04lQJqaTDFHApXB64ipCz5xUG3uOyfT0gA+QEEVcys+TIx +xHWVBqB/0Y0n3bOppHKH/lmLmnp0Ft0WpWIp6zqW3IunaFnT63eROfjXy9mPX1on +AX1daBli2MjN9LdyR75bl87yraKZk62Uy5P2EgmVtqvXO9A/EcswFi55gORngS1d +7XB4tmBZrOFdRWOPyN9yaFvqHbgB8X7754qz41SgOAngPN5C8sLtLpvzHzW2Ntjj +gKGLzZlkD8Kqq7HK9W+eQ42EVJmzbsASZthwEPEGNTNDqJwuuhQxzhB/HIbjj9LV ++Hfsm6vxL2PZQl/gZ4FkkfGXL/xuJvYz+NO1+MRiqzFRJQJ6+N1rZdVtTTDIZbpo +FGWsJwt0ivKH +-----END CERTIFICATE----- + +# Issuer: CN=TrustAsia Global Root CA G4 O=TrustAsia Technologies, Inc. +# Subject: CN=TrustAsia Global Root CA G4 O=TrustAsia Technologies, Inc. +# Label: "TrustAsia Global Root CA G4" +# Serial: 451799571007117016466790293371524403291602933463 +# MD5 Fingerprint: 54:dd:b2:d7:5f:d8:3e:ed:7c:e0:0b:2e:cc:ed:eb:eb +# SHA1 Fingerprint: 57:73:a5:61:5d:80:b2:e6:ac:38:82:fc:68:07:31:ac:9f:b5:92:5a +# SHA256 Fingerprint: be:4b:56:cb:50:56:c0:13:6a:52:6d:f4:44:50:8d:aa:36:a0:b5:4f:42:e4:ac:38:f7:2a:f4:70:e4:79:65:4c +-----BEGIN CERTIFICATE----- +MIICVTCCAdygAwIBAgIUTyNkuI6XY57GU4HBdk7LKnQV1tcwCgYIKoZIzj0EAwMw +WjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs +IEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHNDAeFw0y +MTA1MjAwMjEwMjJaFw00NjA1MTkwMjEwMjJaMFoxCzAJBgNVBAYTAkNOMSUwIwYD +VQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtUcnVz +dEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATx +s8045CVD5d4ZCbuBeaIVXxVjAd7Cq92zphtnS4CDr5nLrBfbK5bKfFJV4hrhPVbw +LxYI+hW8m7tH5j/uqOFMjPXTNvk4XatwmkcN4oFBButJ+bAp3TPsUKV/eSm4IJij +YzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUpbtKl86zK3+kMd6Xg1mD +pm9xy94wHQYDVR0OBBYEFKW7SpfOsyt/pDHel4NZg6ZvccveMA4GA1UdDwEB/wQE +AwIBBjAKBggqhkjOPQQDAwNnADBkAjBe8usGzEkxn0AAbbd+NvBNEU/zy4k6LHiR +UKNbwMp1JvK/kF0LgoxgKJ/GcJpo5PECMFxYDlZ2z1jD1xCMuo6u47xkdUfFVZDj +/bpV6wfEU6s3qe4hsiFbYI89MvHVI5TWWA== +-----END CERTIFICATE----- + +# Issuer: CN=CommScope Public Trust ECC Root-01 O=CommScope +# Subject: CN=CommScope Public Trust ECC Root-01 O=CommScope +# Label: "CommScope Public Trust ECC Root-01" +# Serial: 385011430473757362783587124273108818652468453534 +# MD5 Fingerprint: 3a:40:a7:fc:03:8c:9c:38:79:2f:3a:a2:6c:b6:0a:16 +# SHA1 Fingerprint: 07:86:c0:d8:dd:8e:c0:80:98:06:98:d0:58:7a:ef:de:a6:cc:a2:5d +# SHA256 Fingerprint: 11:43:7c:da:7b:b4:5e:41:36:5f:45:b3:9a:38:98:6b:0d:e0:0d:ef:34:8e:0c:7b:b0:87:36:33:80:0b:c3:8b +-----BEGIN CERTIFICATE----- +MIICHTCCAaOgAwIBAgIUQ3CCd89NXTTxyq4yLzf39H91oJ4wCgYIKoZIzj0EAwMw +TjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29t +bVNjb3BlIFB1YmxpYyBUcnVzdCBFQ0MgUm9vdC0wMTAeFw0yMTA0MjgxNzM1NDNa +Fw00NjA0MjgxNzM1NDJaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2Nv +cGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgRUNDIFJvb3QtMDEw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAARLNumuV16ocNfQj3Rid8NeeqrltqLxeP0C +flfdkXmcbLlSiFS8LwS+uM32ENEp7LXQoMPwiXAZu1FlxUOcw5tjnSCDPgYLpkJE +hRGnSjot6dZoL0hOUysHP029uax3OVejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD +VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSOB2LAUN3GGQYARnQE9/OufXVNMDAKBggq +hkjOPQQDAwNoADBlAjEAnDPfQeMjqEI2Jpc1XHvr20v4qotzVRVcrHgpD7oh2MSg +2NED3W3ROT3Ek2DS43KyAjB8xX6I01D1HiXo+k515liWpDVfG2XqYZpwI7UNo5uS +Um9poIyNStDuiw7LR47QjRE= +-----END CERTIFICATE----- + +# Issuer: CN=CommScope Public Trust ECC Root-02 O=CommScope +# Subject: CN=CommScope Public Trust ECC Root-02 O=CommScope +# Label: "CommScope Public Trust ECC Root-02" +# Serial: 234015080301808452132356021271193974922492992893 +# MD5 Fingerprint: 59:b0:44:d5:65:4d:b8:5c:55:19:92:02:b6:d1:94:b2 +# SHA1 Fingerprint: 3c:3f:ef:57:0f:fe:65:93:86:9e:a0:fe:b0:f6:ed:8e:d1:13:c7:e5 +# SHA256 Fingerprint: 2f:fb:7f:81:3b:bb:b3:c8:9a:b4:e8:16:2d:0f:16:d7:15:09:a8:30:cc:9d:73:c2:62:e5:14:08:75:d1:ad:4a +-----BEGIN CERTIFICATE----- +MIICHDCCAaOgAwIBAgIUKP2ZYEFHpgE6yhR7H+/5aAiDXX0wCgYIKoZIzj0EAwMw +TjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29t +bVNjb3BlIFB1YmxpYyBUcnVzdCBFQ0MgUm9vdC0wMjAeFw0yMTA0MjgxNzQ0NTRa +Fw00NjA0MjgxNzQ0NTNaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2Nv +cGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgRUNDIFJvb3QtMDIw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAR4MIHoYx7l63FRD/cHB8o5mXxO1Q/MMDAL +j2aTPs+9xYa9+bG3tD60B8jzljHz7aRP+KNOjSkVWLjVb3/ubCK1sK9IRQq9qEmU +v4RDsNuESgMjGWdqb8FuvAY5N9GIIvejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD +VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTmGHX/72DehKT1RsfeSlXjMjZ59TAKBggq +hkjOPQQDAwNnADBkAjAmc0l6tqvmSfR9Uj/UQQSugEODZXW5hYA4O9Zv5JOGq4/n +ich/m35rChJVYaoR4HkCMHfoMXGsPHED1oQmHhS48zs73u1Z/GtMMH9ZzkXpc2AV +mkzw5l4lIhVtwodZ0LKOag== +-----END CERTIFICATE----- + +# Issuer: CN=CommScope Public Trust RSA Root-01 O=CommScope +# Subject: CN=CommScope Public Trust RSA Root-01 O=CommScope +# Label: "CommScope Public Trust RSA Root-01" +# Serial: 354030733275608256394402989253558293562031411421 +# MD5 Fingerprint: 0e:b4:15:bc:87:63:5d:5d:02:73:d4:26:38:68:73:d8 +# SHA1 Fingerprint: 6d:0a:5f:f7:b4:23:06:b4:85:b3:b7:97:64:fc:ac:75:f5:33:f2:93 +# SHA256 Fingerprint: 02:bd:f9:6e:2a:45:dd:9b:f1:8f:c7:e1:db:df:21:a0:37:9b:a3:c9:c2:61:03:44:cf:d8:d6:06:fe:c1:ed:81 +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIUPgNJgXUWdDGOTKvVxZAplsU5EN0wDQYJKoZIhvcNAQEL +BQAwTjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwi +Q29tbVNjb3BlIFB1YmxpYyBUcnVzdCBSU0EgUm9vdC0wMTAeFw0yMTA0MjgxNjQ1 +NTRaFw00NjA0MjgxNjQ1NTNaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21t +U2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgUlNBIFJvb3Qt +MDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwSGWjDR1C45FtnYSk +YZYSwu3D2iM0GXb26v1VWvZVAVMP8syMl0+5UMuzAURWlv2bKOx7dAvnQmtVzslh +suitQDy6uUEKBU8bJoWPQ7VAtYXR1HHcg0Hz9kXHgKKEUJdGzqAMxGBWBB0HW0al +DrJLpA6lfO741GIDuZNqihS4cPgugkY4Iw50x2tBt9Apo52AsH53k2NC+zSDO3Oj +WiE260f6GBfZumbCk6SP/F2krfxQapWsvCQz0b2If4b19bJzKo98rwjyGpg/qYFl +P8GMicWWMJoKz/TUyDTtnS+8jTiGU+6Xn6myY5QXjQ/cZip8UlF1y5mO6D1cv547 +KI2DAg+pn3LiLCuz3GaXAEDQpFSOm117RTYm1nJD68/A6g3czhLmfTifBSeolz7p +UcZsBSjBAg/pGG3svZwG1KdJ9FQFa2ww8esD1eo9anbCyxooSU1/ZOD6K9pzg4H/ +kQO9lLvkuI6cMmPNn7togbGEW682v3fuHX/3SZtS7NJ3Wn2RnU3COS3kuoL4b/JO +Hg9O5j9ZpSPcPYeoKFgo0fEbNttPxP/hjFtyjMcmAyejOQoBqsCyMWCDIqFPEgkB +Ea801M/XrmLTBQe0MXXgDW1XT2mH+VepuhX2yFJtocucH+X8eKg1mp9BFM6ltM6U +CBwJrVbl2rZJmkrqYxhTnCwuwwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUN12mmnQywsL5x6YVEFm45P3luG0wDQYJ +KoZIhvcNAQELBQADggIBAK+nz97/4L1CjU3lIpbfaOp9TSp90K09FlxD533Ahuh6 +NWPxzIHIxgvoLlI1pKZJkGNRrDSsBTtXAOnTYtPZKdVUvhwQkZyybf5Z/Xn36lbQ +nmhUQo8mUuJM3y+Xpi/SB5io82BdS5pYV4jvguX6r2yBS5KPQJqTRlnLX3gWsWc+ +QgvfKNmwrZggvkN80V4aCRckjXtdlemrwWCrWxhkgPut4AZ9HcpZuPN4KWfGVh2v +trV0KnahP/t1MJ+UXjulYPPLXAziDslg+MkfFoom3ecnf+slpoq9uC02EJqxWE2a +aE9gVOX2RhOOiKy8IUISrcZKiX2bwdgt6ZYD9KJ0DLwAHb/WNyVntHKLr4W96ioD +j8z7PEQkguIBpQtZtjSNMgsSDesnwv1B10A8ckYpwIzqug/xBpMu95yo9GA+o/E4 +Xo4TwbM6l4c/ksp4qRyv0LAbJh6+cOx69TOY6lz/KwsETkPdY34Op054A5U+1C0w +lREQKC6/oAI+/15Z0wUOlV9TRe9rh9VIzRamloPh37MG88EU26fsHItdkJANclHn +YfkUyq+Dj7+vsQpZXdxc1+SWrVtgHdqul7I52Qb1dgAT+GhMIbA1xNxVssnBQVoc +icCMb3SgazNNtQEo/a2tiRc7ppqEvOuM6sRxJKi6KfkIsidWNTJf6jn7MZrVGczw +-----END CERTIFICATE----- + +# Issuer: CN=CommScope Public Trust RSA Root-02 O=CommScope +# Subject: CN=CommScope Public Trust RSA Root-02 O=CommScope +# Label: "CommScope Public Trust RSA Root-02" +# Serial: 480062499834624527752716769107743131258796508494 +# MD5 Fingerprint: e1:29:f9:62:7b:76:e2:96:6d:f3:d4:d7:0f:ae:1f:aa +# SHA1 Fingerprint: ea:b0:e2:52:1b:89:93:4c:11:68:f2:d8:9a:ac:22:4c:a3:8a:57:ae +# SHA256 Fingerprint: ff:e9:43:d7:93:42:4b:4f:7c:44:0c:1c:3d:64:8d:53:63:f3:4b:82:dc:87:aa:7a:9f:11:8f:c5:de:e1:01:f1 +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIUVBa/O345lXGN0aoApYYNK496BU4wDQYJKoZIhvcNAQEL +BQAwTjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwi +Q29tbVNjb3BlIFB1YmxpYyBUcnVzdCBSU0EgUm9vdC0wMjAeFw0yMTA0MjgxNzE2 +NDNaFw00NjA0MjgxNzE2NDJaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21t +U2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgUlNBIFJvb3Qt +MDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDh+g77aAASyE3VrCLE +NQE7xVTlWXZjpX/rwcRqmL0yjReA61260WI9JSMZNRTpf4mnG2I81lDnNJUDMrG0 +kyI9p+Kx7eZ7Ti6Hmw0zdQreqjXnfuU2mKKuJZ6VszKWpCtYHu8//mI0SFHRtI1C +rWDaSWqVcN3SAOLMV2MCe5bdSZdbkk6V0/nLKR8YSvgBKtJjCW4k6YnS5cciTNxz +hkcAqg2Ijq6FfUrpuzNPDlJwnZXjfG2WWy09X6GDRl224yW4fKcZgBzqZUPckXk2 +LHR88mcGyYnJ27/aaL8j7dxrrSiDeS/sOKUNNwFnJ5rpM9kzXzehxfCrPfp4sOcs +n/Y+n2Dg70jpkEUeBVF4GiwSLFworA2iI540jwXmojPOEXcT1A6kHkIfhs1w/tku +FT0du7jyU1fbzMZ0KZwYszZ1OC4PVKH4kh+Jlk+71O6d6Ts2QrUKOyrUZHk2EOH5 +kQMreyBUzQ0ZGshBMjTRsJnhkB4BQDa1t/qp5Xd1pCKBXbCL5CcSD1SIxtuFdOa3 +wNemKfrb3vOTlycEVS8KbzfFPROvCgCpLIscgSjX74Yxqa7ybrjKaixUR9gqiC6v +wQcQeKwRoi9C8DfF8rhW3Q5iLc4tVn5V8qdE9isy9COoR+jUKgF4z2rDN6ieZdIs +5fq6M8EGRPbmz6UNp2YINIos8wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUR9DnsSL/nSz12Vdgs7GxcJXvYXowDQYJ +KoZIhvcNAQELBQADggIBAIZpsU0v6Z9PIpNojuQhmaPORVMbc0RTAIFhzTHjCLqB +KCh6krm2qMhDnscTJk3C2OVVnJJdUNjCK9v+5qiXz1I6JMNlZFxHMaNlNRPDk7n3 ++VGXu6TwYofF1gbTl4MgqX67tiHCpQ2EAOHyJxCDut0DgdXdaMNmEMjRdrSzbyme +APnCKfWxkxlSaRosTKCL4BWaMS/TiJVZbuXEs1DIFAhKm4sTg7GkcrI7djNB3Nyq +pgdvHSQSn8h2vS/ZjvQs7rfSOBAkNlEv41xdgSGn2rtO/+YHqP65DSdsu3BaVXoT +6fEqSWnHX4dXTEN5bTpl6TBcQe7rd6VzEojov32u5cSoHw2OHG1QAk8mGEPej1WF +sQs3BWDJVTkSBKEqz3EWnzZRSb9wO55nnPt7eck5HHisd5FUmrh1CoFSl+NmYWvt +PjgelmFV4ZFUjO2MJB+ByRCac5krFk5yAD9UG/iNuovnFNa2RU9g7Jauwy8CTl2d +lklyALKrdVwPaFsdZcJfMw8eD/A7hvWwTruc9+olBdytoptLFwG+Qt81IR2tq670 +v64fG9PiO/yzcnMcmyiQiRM9HcEARwmWmjgb3bHPDcK0RPOWlc4yOo80nOAXx17O +rg3bhzjlP1v9mxnhMUF6cKojawHhRUzNlM47ni3niAIi9G7oyOzWPPO5std3eqx7 +-----END CERTIFICATE----- + +# Issuer: CN=Telekom Security TLS ECC Root 2020 O=Deutsche Telekom Security GmbH +# Subject: CN=Telekom Security TLS ECC Root 2020 O=Deutsche Telekom Security GmbH +# Label: "Telekom Security TLS ECC Root 2020" +# Serial: 72082518505882327255703894282316633856 +# MD5 Fingerprint: c1:ab:fe:6a:10:2c:03:8d:bc:1c:22:32:c0:85:a7:fd +# SHA1 Fingerprint: c0:f8:96:c5:a9:3b:01:06:21:07:da:18:42:48:bc:e9:9d:88:d5:ec +# SHA256 Fingerprint: 57:8a:f4:de:d0:85:3f:4e:59:98:db:4a:ea:f9:cb:ea:8d:94:5f:60:b6:20:a3:8d:1a:3c:13:b2:bc:7b:a8:e1 +-----BEGIN CERTIFICATE----- +MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQsw +CQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBH +bWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIw +MB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIzNTk1OVowYzELMAkGA1UEBhMCREUx +JzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkGA1UE +AwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/O +tdKPD/M12kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDP +f8iAC8GXs7s1J8nCG6NCMEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6f +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cA +MGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZMo7k+5Dck2TOrbRBR2Di +z6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdUga/sf+Rn +27iQ7t0l +-----END CERTIFICATE----- + +# Issuer: CN=Telekom Security TLS RSA Root 2023 O=Deutsche Telekom Security GmbH +# Subject: CN=Telekom Security TLS RSA Root 2023 O=Deutsche Telekom Security GmbH +# Label: "Telekom Security TLS RSA Root 2023" +# Serial: 44676229530606711399881795178081572759 +# MD5 Fingerprint: bf:5b:eb:54:40:cd:48:71:c4:20:8d:7d:de:0a:42:f2 +# SHA1 Fingerprint: 54:d3:ac:b3:bd:57:56:f6:85:9d:ce:e5:c3:21:e2:d4:ad:83:d0:93 +# SHA256 Fingerprint: ef:c6:5c:ad:bb:59:ad:b6:ef:e8:4d:a2:23:11:b3:56:24:b7:1b:3b:1e:a0:da:8b:66:55:17:4e:c8:97:86:46 +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBj +MQswCQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0 +eSBHbWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAy +MDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMyNzIzNTk1OVowYzELMAkGA1UEBhMC +REUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkG +A1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9 +cUD/h3VCKSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHV +cp6R+SPWcHu79ZvB7JPPGeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMA +U6DksquDOFczJZSfvkgdmOGjup5czQRxUX11eKvzWarE4GC+j4NSuHUaQTXtvPM6 +Y+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWol8hHD/BeEIvnHRz+sTug +BTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9FIS3R/qy +8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73J +co4vzLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg +8qKrBC7m8kwOFjQgrIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8 +rFEz0ciD0cmfHdRHNCk+y7AO+oMLKFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12 +mAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7SWWO/gLCMk3PLNaaZlSJhZQNg ++y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtqeX +gj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2 +p5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQ +pGv7qHBFfLp+sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm +9S3ul0A8Yute1hTWjOKWi0FpkzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErw +M807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy/SKE8YXJN3nptT+/XOR0so8RYgDd +GGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4mZqTuXNnQkYRIer+ +CqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtzaL1t +xKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+ +w6jv/naaoqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aK +L4x35bcF7DvB7L6Gs4a8wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+lj +X273CXE2whJdV/LItM3z7gLfEdxquVeEHVlNjM7IDiPCtyaaEBRx/pOyiriA8A4Q +ntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0o82bNSQ3+pCTE4FCxpgm +dTdmQRCsu/WU48IxK63nI1bMNSWSs1A= +-----END CERTIFICATE----- + +# Issuer: CN=FIRMAPROFESIONAL CA ROOT-A WEB O=Firmaprofesional SA +# Subject: CN=FIRMAPROFESIONAL CA ROOT-A WEB O=Firmaprofesional SA +# Label: "FIRMAPROFESIONAL CA ROOT-A WEB" +# Serial: 65916896770016886708751106294915943533 +# MD5 Fingerprint: 82:b2:ad:45:00:82:b0:66:63:f8:5f:c3:67:4e:ce:a3 +# SHA1 Fingerprint: a8:31:11:74:a6:14:15:0d:ca:77:dd:0e:e4:0c:5d:58:fc:a0:72:a5 +# SHA256 Fingerprint: be:f2:56:da:f2:6e:9c:69:bd:ec:16:02:35:97:98:f3:ca:f7:18:21:a0:3e:01:82:57:c5:3c:65:61:7f:3d:4a +-----BEGIN CERTIFICATE----- +MIICejCCAgCgAwIBAgIQMZch7a+JQn81QYehZ1ZMbTAKBggqhkjOPQQDAzBuMQsw +CQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UE +YQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENB +IFJPT1QtQSBXRUIwHhcNMjIwNDA2MDkwMTM2WhcNNDcwMzMxMDkwMTM2WjBuMQsw +CQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UE +YQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENB +IFJPT1QtQSBXRUIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARHU+osEaR3xyrq89Zf +e9MEkVz6iMYiuYMQYneEMy3pA4jU4DP37XcsSmDq5G+tbbT4TIqk5B/K6k84Si6C +cyvHZpsKjECcfIr28jlgst7L7Ljkb+qbXbdTkBgyVcUgt5SjYzBhMA8GA1UdEwEB +/wQFMAMBAf8wHwYDVR0jBBgwFoAUk+FDY1w8ndYn81LsF7Kpryz3dvgwHQYDVR0O +BBYEFJPhQ2NcPJ3WJ/NS7Beyqa8s93b4MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjO +PQQDAwNoADBlAjAdfKR7w4l1M+E7qUW/Runpod3JIha3RxEL2Jq68cgLcFBTApFw +hVmpHqTm6iMxoAACMQD94vizrxa5HnPEluPBMBnYfubDl94cT7iJLzPrSA8Z94dG +XSaQpYXFuXqUPoeovQA= +-----END CERTIFICATE----- + +# Issuer: CN=TWCA CYBER Root CA O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA CYBER Root CA O=TAIWAN-CA OU=Root CA +# Label: "TWCA CYBER Root CA" +# Serial: 85076849864375384482682434040119489222 +# MD5 Fingerprint: 0b:33:a0:97:52:95:d4:a9:fd:bb:db:6e:a3:55:5b:51 +# SHA1 Fingerprint: f6:b1:1c:1a:83:38:e9:7b:db:b3:a8:c8:33:24:e0:2d:9c:7f:26:66 +# SHA256 Fingerprint: 3f:63:bb:28:14:be:17:4e:c8:b6:43:9c:f0:8d:6d:56:f0:b7:c4:05:88:3a:56:48:a3:34:42:4d:6b:3e:c5:58 +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQ +MQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290 +IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5 +WhcNNDcxMTIyMTU1OTU5WjBQMQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FO +LUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG+Moe2Qkgfh1sTs6P +40czRJzHyWmqOlt47nDSkvgEs1JSHWdyKKHfi12VCv7qze33Kc7wb3+szT3vsxxF +avcokPFhV8UMxKNQXd7UtcsZyoC5dc4pztKFIuwCY8xEMCDa6pFbVuYdHNWdZsc/ +34bKS1PE2Y2yHer43CdTo0fhYcx9tbD47nORxc5zb87uEB8aBs/pJ2DFTxnk684i +JkXXYJndzk834H/nY62wuFm40AZoNWDTNq5xQwTxaWV4fPMf88oon1oglWa0zbfu +j3ikRRjpJi+NmykosaS3Om251Bw4ckVYsV7r8Cibt4LK/c/WMw+f+5eesRycnupf +Xtuq3VTpMCEobY5583WSjCb+3MX2w7DfRFlDo7YDKPYIMKoNM+HvnKkHIuNZW0CP +2oi3aQiotyMuRAlZN1vH4xfyIutuOVLF3lSnmMlLIJXcRolftBL5hSmO68gnFSDA +S9TMfAxsNAwmmyYxpjyn9tnQS6Jk/zuZQXLB4HCX8SS7K8R0IrGsayIyJNN4KsDA +oS/xUgXJP+92ZuJF2A09rZXIx4kmyA+upwMu+8Ff+iDhcK2wZSA3M2Cw1a/XDBzC +kHDXShi8fgGwsOsVHkQGzaRP6AzRwyAQ4VRlnrZR0Bp2a0JaWHY06rc3Ga4udfmW +5cFZ95RXKSWNOkyrTZpB0F8mAwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSdhWEUfMFib5do5E83QOGt4A1WNzAd +BgNVHQ4EFgQUnYVhFHzBYm+XaORPN0DhreANVjcwDQYJKoZIhvcNAQEMBQADggIB +AGSPesRiDrWIzLjHhg6hShbNcAu3p4ULs3a2D6f/CIsLJc+o1IN1KriWiLb73y0t +tGlTITVX1olNc79pj3CjYcya2x6a4CD4bLubIp1dhDGaLIrdaqHXKGnK/nZVekZn +68xDiBaiA9a5F/gZbG0jAn/xX9AKKSM70aoK7akXJlQKTcKlTfjF/biBzysseKNn +TKkHmvPfXvt89YnNdJdhEGoHK4Fa0o635yDRIG4kqIQnoVesqlVYL9zZyvpoBJ7t +RCT5dEA7IzOrg1oYJkK2bVS1FmAwbLGg+LhBoF1JSdJlBTrq/p1hvIbZv97Tujqx +f36SNI7JAG7cmL3c7IAFrQI932XtCwP39xaEBDG6k5TY8hL4iuO/Qq+n1M0RFxbI +Qh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz +8ppy6rBePm6pUlvscG21h483XjyMnM7k8M4MZ0HMzvaAq07MTFb1wWFZk7Q+ptq4 +NxKfKjLji7gh7MMrZQzvIt6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzX +xeSDwWrruoBa3lwtcHb4yOWHh8qgnaHlIhInD0Q9HWzq1MKLL295q39QpsQZp6F6 +t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X +-----END CERTIFICATE----- + +# Issuer: CN=SecureSign Root CA12 O=Cybertrust Japan Co., Ltd. +# Subject: CN=SecureSign Root CA12 O=Cybertrust Japan Co., Ltd. +# Label: "SecureSign Root CA12" +# Serial: 587887345431707215246142177076162061960426065942 +# MD5 Fingerprint: c6:89:ca:64:42:9b:62:08:49:0b:1e:7f:e9:07:3d:e8 +# SHA1 Fingerprint: 7a:22:1e:3d:de:1b:06:ac:9e:c8:47:70:16:8e:3c:e5:f7:6b:06:f4 +# SHA256 Fingerprint: 3f:03:4b:b5:70:4d:44:b2:d0:85:45:a0:20:57:de:93:eb:f3:90:5f:ce:72:1a:cb:c7:30:c0:6d:da:ee:90:4e +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUZvnHwa/swlG07VOX5uaCwysckBYwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u +LCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExMjAeFw0yMDA0MDgw +NTM2NDZaFw00MDA0MDgwNTM2NDZaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpD +eWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBS +b290IENBMTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6OcE3emhF +KxS06+QT61d1I02PJC0W6K6OyX2kVzsqdiUzg2zqMoqUm048luT9Ub+ZyZN+v/mt +p7JIKwccJ/VMvHASd6SFVLX9kHrko+RRWAPNEHl57muTH2SOa2SroxPjcf59q5zd +J1M3s6oYwlkm7Fsf0uZlfO+TvdhYXAvA42VvPMfKWeP+bl+sg779XSVOKik71gur +FzJ4pOE+lEa+Ym6b3kaosRbnhW70CEBFEaCeVESE99g2zvVQR9wsMJvuwPWW0v4J +hscGWa5Pro4RmHvzC1KqYiaqId+OJTN5lxZJjfU+1UefNzFJM3IFTQy2VYzxV4+K +h9GtxRESOaCtAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBRXNPN0zwRL1SXm8UC2LEzZLemgrTANBgkqhkiG9w0BAQsF +AAOCAQEAPrvbFxbS8hQBICw4g0utvsqFepq2m2um4fylOqyttCg6r9cBg0krY6Ld +mmQOmFxv3Y67ilQiLUoT865AQ9tPkbeGGuwAtEGBpE/6aouIs3YIcipJQMPTw4WJ +mBClnW8Zt7vPemVV2zfrPIpyMpcemik+rY3moxtt9XUa5rBouVui7mlHJzWhhpmA +8zNL4WukJsPvdFlseqJkth5Ew1DgDzk9qTPxpfPSvWKErI4cqc1avTc7bgoitPQV +55FYxTpE05Uo2cBl6XLK0A+9H7MV2anjpEcJnuDLN/v9vZfVvhgaaaI5gdka9at/ +yOPiZwud9AzqVN/Ssq+xIvEg37xEHA== +-----END CERTIFICATE----- + +# Issuer: CN=SecureSign Root CA14 O=Cybertrust Japan Co., Ltd. +# Subject: CN=SecureSign Root CA14 O=Cybertrust Japan Co., Ltd. +# Label: "SecureSign Root CA14" +# Serial: 575790784512929437950770173562378038616896959179 +# MD5 Fingerprint: 71:0d:72:fa:92:19:65:5e:89:04:ac:16:33:f0:bc:d5 +# SHA1 Fingerprint: dd:50:c0:f7:79:b3:64:2e:74:a2:b8:9d:9f:d3:40:dd:bb:f0:f2:4f +# SHA256 Fingerprint: 4b:00:9c:10:34:49:4f:9a:b5:6b:ba:3b:a1:d6:27:31:fc:4d:20:d8:95:5a:dc:ec:10:a9:25:60:72:61:e3:38 +-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIUZNtaDCBO6Ncpd8hQJ6JaJ90t8sswDQYJKoZIhvcNAQEM +BQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u +LCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNDAeFw0yMDA0MDgw +NzA2MTlaFw00NTA0MDgwNzA2MTlaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpD +eWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBS +b290IENBMTQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDF0nqh1oq/ +FjHQmNE6lPxauG4iwWL3pwon71D2LrGeaBLwbCRjOfHw3xDG3rdSINVSW0KZnvOg +vlIfX8xnbacuUKLBl422+JX1sLrcneC+y9/3OPJH9aaakpUqYllQC6KxNedlsmGy +6pJxaeQp8E+BgQQ8sqVb1MWoWWd7VRxJq3qdwudzTe/NCcLEVxLbAQ4jeQkHO6Lo +/IrPj8BGJJw4J+CDnRugv3gVEOuGTgpa/d/aLIJ+7sr2KeH6caH3iGicnPCNvg9J +kdjqOvn90Ghx2+m1K06Ckm9mH+Dw3EzsytHqunQG+bOEkJTRX45zGRBdAuVwpcAQ +0BB8b8VYSbSwbprafZX1zNoCr7gsfXmPvkPx+SgojQlD+Ajda8iLLCSxjVIHvXib +y8posqTdDEx5YMaZ0ZPxMBoH064iwurO8YQJzOAUbn8/ftKChazcqRZOhaBgy/ac +18izju3Gm5h1DVXoX+WViwKkrkMpKBGk5hIwAUt1ax5mnXkvpXYvHUC0bcl9eQjs +0Wq2XSqypWa9a4X0dFbD9ed1Uigspf9mR6XU/v6eVL9lfgHWMI+lNpyiUBzuOIAB +SMbHdPTGrMNASRZhdCyvjG817XsYAFs2PJxQDcqSMxDxJklt33UkN4Ii1+iW/RVL +ApY+B3KVfqs9TC7XyvDf4Fg/LS8EmjijAQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUBpOjCl4oaTeqYR3r6/wtbyPk +86AwDQYJKoZIhvcNAQEMBQADggIBAJaAcgkGfpzMkwQWu6A6jZJOtxEaCnFxEM0E +rX+lRVAQZk5KQaID2RFPeje5S+LGjzJmdSX7684/AykmjbgWHfYfM25I5uj4V7Ib +ed87hwriZLoAymzvftAj63iP/2SbNDefNWWipAA9EiOWWF3KY4fGoweITedpdopT +zfFP7ELyk+OZpDc8h7hi2/DsHzc/N19DzFGdtfCXwreFamgLRB7lUe6TzktuhsHS +DCRZNhqfLJGP4xjblJUK7ZGqDpncllPjYYPGFrojutzdfhrGe0K22VoF3Jpf1d+4 +2kd92jjbrDnVHmtsKheMYc2xbXIBw8MgAGJoFjHVdqqGuw6qnsb58Nn4DSEC5MUo +FlkRudlpcyqSeLiSV5sI8jrlL5WwWLdrIBRtFO8KvH7YVdiI2i/6GaX7i+B/OfVy +K4XELKzvGUWSTLNhB9xNH27SgRNcmvMSZ4PPmz+Ln52kuaiWA3rF7iDeM9ovnhp6 +dB7h7sxaOgTdsxoEqBRjrLdHEoOabPXm6RUVkRqEGQ6UROcSjiVbgGcZ3GOTEAtl +Lor6CZpO2oYofaphNdgOpygau1LgePhsumywbrmHXumZNTfxPWQrqaA0k89jL9WB +365jJ6UeTo3cKXhZ+PmhIIynJkBugnLNeLLIjzwec+fBH7/PzqUqm9tEZDKgu39c +JRNItX+S +-----END CERTIFICATE----- + +# Issuer: CN=SecureSign Root CA15 O=Cybertrust Japan Co., Ltd. +# Subject: CN=SecureSign Root CA15 O=Cybertrust Japan Co., Ltd. +# Label: "SecureSign Root CA15" +# Serial: 126083514594751269499665114766174399806381178503 +# MD5 Fingerprint: 13:30:fc:c4:62:a6:a9:de:b5:c1:68:af:b5:d2:31:47 +# SHA1 Fingerprint: cb:ba:83:c8:c1:5a:5d:f1:f9:73:6f:ca:d7:ef:28:13:06:4a:07:7d +# SHA256 Fingerprint: e7:78:f0:f0:95:fe:84:37:29:cd:1a:00:82:17:9e:53:14:a9:c2:91:44:28:05:e1:fb:1d:8f:b6:b8:88:6c:3a +-----BEGIN CERTIFICATE----- +MIICIzCCAamgAwIBAgIUFhXHw9hJp75pDIqI7fBw+d23PocwCgYIKoZIzj0EAwMw +UTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBM +dGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNTAeFw0yMDA0MDgwODMy +NTZaFw00NTA0MDgwODMyNTZaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpDeWJl +cnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBSb290 +IENBMTUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQLUHSNZDKZmbPSYAi4Io5GdCx4 +wCtELW1fHcmuS1Iggz24FG1Th2CeX2yF2wYUleDHKP+dX+Sq8bOLbe1PL0vJSpSR +ZHX+AezB2Ot6lHhWGENfa4HL9rzatAy2KZMIaY+jQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTrQciu/NWeUUj1vYv0hyCTQSvT +9DAKBggqhkjOPQQDAwNoADBlAjEA2S6Jfl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp +4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJSwdLZrWeqrqgHkHZAXQ6 +bkU6iYAZezKYVWOr62Nuk22rGwlgMU4= +-----END CERTIFICATE----- diff --git a/lambdas/aws-dd-forwarder-3.127.0/certifi/core.py b/lambdas/aws-dd-forwarder-3.127.0/certifi/core.py new file mode 100644 index 0000000..91f538b --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/certifi/core.py @@ -0,0 +1,114 @@ +""" +certifi.py +~~~~~~~~~~ + +This module returns the installation location of cacert.pem or its contents. +""" +import sys +import atexit + +def exit_cacert_ctx() -> None: + _CACERT_CTX.__exit__(None, None, None) # type: ignore[union-attr] + + +if sys.version_info >= (3, 11): + + from importlib.resources import as_file, files + + _CACERT_CTX = None + _CACERT_PATH = None + + def where() -> str: + # This is slightly terrible, but we want to delay extracting the file + # in cases where we're inside of a zipimport situation until someone + # actually calls where(), but we don't want to re-extract the file + # on every call of where(), so we'll do it once then store it in a + # global variable. + global _CACERT_CTX + global _CACERT_PATH + if _CACERT_PATH is None: + # This is slightly janky, the importlib.resources API wants you to + # manage the cleanup of this file, so it doesn't actually return a + # path, it returns a context manager that will give you the path + # when you enter it and will do any cleanup when you leave it. In + # the common case of not needing a temporary file, it will just + # return the file system location and the __exit__() is a no-op. + # + # We also have to hold onto the actual context manager, because + # it will do the cleanup whenever it gets garbage collected, so + # we will also store that at the global level as well. + _CACERT_CTX = as_file(files("certifi").joinpath("cacert.pem")) + _CACERT_PATH = str(_CACERT_CTX.__enter__()) + atexit.register(exit_cacert_ctx) + + return _CACERT_PATH + + def contents() -> str: + return files("certifi").joinpath("cacert.pem").read_text(encoding="ascii") + +elif sys.version_info >= (3, 7): + + from importlib.resources import path as get_path, read_text + + _CACERT_CTX = None + _CACERT_PATH = None + + def where() -> str: + # This is slightly terrible, but we want to delay extracting the + # file in cases where we're inside of a zipimport situation until + # someone actually calls where(), but we don't want to re-extract + # the file on every call of where(), so we'll do it once then store + # it in a global variable. + global _CACERT_CTX + global _CACERT_PATH + if _CACERT_PATH is None: + # This is slightly janky, the importlib.resources API wants you + # to manage the cleanup of this file, so it doesn't actually + # return a path, it returns a context manager that will give + # you the path when you enter it and will do any cleanup when + # you leave it. In the common case of not needing a temporary + # file, it will just return the file system location and the + # __exit__() is a no-op. + # + # We also have to hold onto the actual context manager, because + # it will do the cleanup whenever it gets garbage collected, so + # we will also store that at the global level as well. + _CACERT_CTX = get_path("certifi", "cacert.pem") + _CACERT_PATH = str(_CACERT_CTX.__enter__()) + atexit.register(exit_cacert_ctx) + + return _CACERT_PATH + + def contents() -> str: + return read_text("certifi", "cacert.pem", encoding="ascii") + +else: + import os + import types + from typing import Union + + Package = Union[types.ModuleType, str] + Resource = Union[str, "os.PathLike"] + + # This fallback will work for Python versions prior to 3.7 that lack the + # importlib.resources module but relies on the existing `where` function + # so won't address issues with environments like PyOxidizer that don't set + # __file__ on modules. + def read_text( + package: Package, + resource: Resource, + encoding: str = 'utf-8', + errors: str = 'strict' + ) -> str: + with open(where(), encoding=encoding) as data: + return data.read() + + # If we don't have importlib.resources, then we will just do the old logic + # of assuming we're on the filesystem and munge the path directly. + def where() -> str: + f = os.path.dirname(__file__) + + return os.path.join(f, "cacert.pem") + + def contents() -> str: + return read_text("certifi", "cacert.pem", encoding="ascii") diff --git a/lambdas/aws-dd-forwarder-3.127.0/certifi/py.typed b/lambdas/aws-dd-forwarder-3.127.0/certifi/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/INSTALLER b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/LICENSE b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/LICENSE new file mode 100644 index 0000000..ad82355 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 TAHRI Ahmed R. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/METADATA b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/METADATA new file mode 100644 index 0000000..b19096b --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/METADATA @@ -0,0 +1,695 @@ +Metadata-Version: 2.1 +Name: charset-normalizer +Version: 3.4.0 +Summary: The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet. +Home-page: https://github.com/Ousret/charset_normalizer +Author: Ahmed TAHRI +Author-email: tahri.ahmed@proton.me +License: MIT +Project-URL: Bug Reports, https://github.com/Ousret/charset_normalizer/issues +Project-URL: Documentation, https://charset-normalizer.readthedocs.io/en/latest +Keywords: encoding,charset,charset-detector,detector,normalization,unicode,chardet,detect +Classifier: Development Status :: 5 - Production/Stable +Classifier: License :: OSI Approved :: MIT License +Classifier: Intended Audience :: Developers +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Text Processing :: Linguistic +Classifier: Topic :: Utilities +Classifier: Typing :: Typed +Requires-Python: >=3.7.0 +Description-Content-Type: text/markdown +License-File: LICENSE +Provides-Extra: unicode_backport + +

Charset Detection, for Everyone 👋

+ +

+ The Real First Universal Charset Detector
+ + + + + Download Count Total + + + + +

+

+ Featured Packages
+ + Static Badge + + + Static Badge + +

+

+ In other language (unofficial port - by the community)
+ + Static Badge + +

+ +> A library that helps you read text from an unknown charset encoding.
Motivated by `chardet`, +> I'm trying to resolve the issue by taking a new approach. +> All IANA character set names for which the Python core library provides codecs are supported. + +

+ >>>>> 👉 Try Me Online Now, Then Adopt Me 👈 <<<<< +

+ +This project offers you an alternative to **Universal Charset Encoding Detector**, also known as **Chardet**. + +| Feature | [Chardet](https://github.com/chardet/chardet) | Charset Normalizer | [cChardet](https://github.com/PyYoshi/cChardet) | +|--------------------------------------------------|:---------------------------------------------:|:--------------------------------------------------------------------------------------------------:|:-----------------------------------------------:| +| `Fast` | ❌ | ✅ | ✅ | +| `Universal**` | ❌ | ✅ | ❌ | +| `Reliable` **without** distinguishable standards | ❌ | ✅ | ✅ | +| `Reliable` **with** distinguishable standards | ✅ | ✅ | ✅ | +| `License` | LGPL-2.1
_restrictive_ | MIT | MPL-1.1
_restrictive_ | +| `Native Python` | ✅ | ✅ | ❌ | +| `Detect spoken language` | ❌ | ✅ | N/A | +| `UnicodeDecodeError Safety` | ❌ | ✅ | ❌ | +| `Whl Size (min)` | 193.6 kB | 42 kB | ~200 kB | +| `Supported Encoding` | 33 | 🎉 [99](https://charset-normalizer.readthedocs.io/en/latest/user/support.html#supported-encodings) | 40 | + +

+Reading Normalized TextCat Reading Text +

+ +*\*\* : They are clearly using specific code for a specific encoding even if covering most of used one*
+Did you got there because of the logs? See [https://charset-normalizer.readthedocs.io/en/latest/user/miscellaneous.html](https://charset-normalizer.readthedocs.io/en/latest/user/miscellaneous.html) + +## ⚡ Performance + +This package offer better performance than its counterpart Chardet. Here are some numbers. + +| Package | Accuracy | Mean per file (ms) | File per sec (est) | +|-----------------------------------------------|:--------:|:------------------:|:------------------:| +| [chardet](https://github.com/chardet/chardet) | 86 % | 200 ms | 5 file/sec | +| charset-normalizer | **98 %** | **10 ms** | 100 file/sec | + +| Package | 99th percentile | 95th percentile | 50th percentile | +|-----------------------------------------------|:---------------:|:---------------:|:---------------:| +| [chardet](https://github.com/chardet/chardet) | 1200 ms | 287 ms | 23 ms | +| charset-normalizer | 100 ms | 50 ms | 5 ms | + +Chardet's performance on larger file (1MB+) are very poor. Expect huge difference on large payload. + +> Stats are generated using 400+ files using default parameters. More details on used files, see GHA workflows. +> And yes, these results might change at any time. The dataset can be updated to include more files. +> The actual delays heavily depends on your CPU capabilities. The factors should remain the same. +> Keep in mind that the stats are generous and that Chardet accuracy vs our is measured using Chardet initial capability +> (eg. Supported Encoding) Challenge-them if you want. + +## ✨ Installation + +Using pip: + +```sh +pip install charset-normalizer -U +``` + +## 🚀 Basic Usage + +### CLI +This package comes with a CLI. + +``` +usage: normalizer [-h] [-v] [-a] [-n] [-m] [-r] [-f] [-t THRESHOLD] + file [file ...] + +The Real First Universal Charset Detector. Discover originating encoding used +on text file. Normalize text to unicode. + +positional arguments: + files File(s) to be analysed + +optional arguments: + -h, --help show this help message and exit + -v, --verbose Display complementary information about file if any. + Stdout will contain logs about the detection process. + -a, --with-alternative + Output complementary possibilities if any. Top-level + JSON WILL be a list. + -n, --normalize Permit to normalize input file. If not set, program + does not write anything. + -m, --minimal Only output the charset detected to STDOUT. Disabling + JSON output. + -r, --replace Replace file when trying to normalize it instead of + creating a new one. + -f, --force Replace file without asking if you are sure, use this + flag with caution. + -t THRESHOLD, --threshold THRESHOLD + Define a custom maximum amount of chaos allowed in + decoded content. 0. <= chaos <= 1. + --version Show version information and exit. +``` + +```bash +normalizer ./data/sample.1.fr.srt +``` + +or + +```bash +python -m charset_normalizer ./data/sample.1.fr.srt +``` + +🎉 Since version 1.4.0 the CLI produce easily usable stdout result in JSON format. + +```json +{ + "path": "/home/default/projects/charset_normalizer/data/sample.1.fr.srt", + "encoding": "cp1252", + "encoding_aliases": [ + "1252", + "windows_1252" + ], + "alternative_encodings": [ + "cp1254", + "cp1256", + "cp1258", + "iso8859_14", + "iso8859_15", + "iso8859_16", + "iso8859_3", + "iso8859_9", + "latin_1", + "mbcs" + ], + "language": "French", + "alphabets": [ + "Basic Latin", + "Latin-1 Supplement" + ], + "has_sig_or_bom": false, + "chaos": 0.149, + "coherence": 97.152, + "unicode_path": null, + "is_preferred": true +} +``` + +### Python +*Just print out normalized text* +```python +from charset_normalizer import from_path + +results = from_path('./my_subtitle.srt') + +print(str(results.best())) +``` + +*Upgrade your code without effort* +```python +from charset_normalizer import detect +``` + +The above code will behave the same as **chardet**. We ensure that we offer the best (reasonable) BC result possible. + +See the docs for advanced usage : [readthedocs.io](https://charset-normalizer.readthedocs.io/en/latest/) + +## 😇 Why + +When I started using Chardet, I noticed that it was not suited to my expectations, and I wanted to propose a +reliable alternative using a completely different method. Also! I never back down on a good challenge! + +I **don't care** about the **originating charset** encoding, because **two different tables** can +produce **two identical rendered string.** +What I want is to get readable text, the best I can. + +In a way, **I'm brute forcing text decoding.** How cool is that ? 😎 + +Don't confuse package **ftfy** with charset-normalizer or chardet. ftfy goal is to repair unicode string whereas charset-normalizer to convert raw file in unknown encoding to unicode. + +## 🍰 How + + - Discard all charset encoding table that could not fit the binary content. + - Measure noise, or the mess once opened (by chunks) with a corresponding charset encoding. + - Extract matches with the lowest mess detected. + - Additionally, we measure coherence / probe for a language. + +**Wait a minute**, what is noise/mess and coherence according to **YOU ?** + +*Noise :* I opened hundred of text files, **written by humans**, with the wrong encoding table. **I observed**, then +**I established** some ground rules about **what is obvious** when **it seems like** a mess. + I know that my interpretation of what is noise is probably incomplete, feel free to contribute in order to + improve or rewrite it. + +*Coherence :* For each language there is on earth, we have computed ranked letter appearance occurrences (the best we can). So I thought +that intel is worth something here. So I use those records against decoded text to check if I can detect intelligent design. + +## ⚡ Known limitations + + - Language detection is unreliable when text contains two or more languages sharing identical letters. (eg. HTML (english tags) + Turkish content (Sharing Latin characters)) + - Every charset detector heavily depends on sufficient content. In common cases, do not bother run detection on very tiny content. + +## ⚠️ About Python EOLs + +**If you are running:** + +- Python >=2.7,<3.5: Unsupported +- Python 3.5: charset-normalizer < 2.1 +- Python 3.6: charset-normalizer < 3.1 +- Python 3.7: charset-normalizer < 4.0 + +Upgrade your Python interpreter as soon as possible. + +## 👤 Contributing + +Contributions, issues and feature requests are very much welcome.
+Feel free to check [issues page](https://github.com/ousret/charset_normalizer/issues) if you want to contribute. + +## 📝 License + +Copyright © [Ahmed TAHRI @Ousret](https://github.com/Ousret).
+This project is [MIT](https://github.com/Ousret/charset_normalizer/blob/master/LICENSE) licensed. + +Characters frequencies used in this project © 2012 [Denny Vrandečić](http://simia.net/letters/) + +## 💼 For Enterprise + +Professional support for charset-normalizer is available as part of the [Tidelift +Subscription][1]. Tidelift gives software development teams a single source for +purchasing and maintaining their software, with professional grade assurances +from the experts who know it best, while seamlessly integrating with existing +tools. + +[1]: https://tidelift.com/subscription/pkg/pypi-charset-normalizer?utm_source=pypi-charset-normalizer&utm_medium=readme + +# Changelog +All notable changes to charset-normalizer will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +## [3.4.0](https://github.com/Ousret/charset_normalizer/compare/3.3.2...3.4.0) (2024-10-08) + +### Added +- Argument `--no-preemptive` in the CLI to prevent the detector to search for hints. +- Support for Python 3.13 (#512) + +### Fixed +- Relax the TypeError exception thrown when trying to compare a CharsetMatch with anything else than a CharsetMatch. +- Improved the general reliability of the detector based on user feedbacks. (#520) (#509) (#498) (#407) (#537) +- Declared charset in content (preemptive detection) not changed when converting to utf-8 bytes. (#381) + +## [3.3.2](https://github.com/Ousret/charset_normalizer/compare/3.3.1...3.3.2) (2023-10-31) + +### Fixed +- Unintentional memory usage regression when using large payload that match several encoding (#376) +- Regression on some detection case showcased in the documentation (#371) + +### Added +- Noise (md) probe that identify malformed arabic representation due to the presence of letters in isolated form (credit to my wife) + +## [3.3.1](https://github.com/Ousret/charset_normalizer/compare/3.3.0...3.3.1) (2023-10-22) + +### Changed +- Optional mypyc compilation upgraded to version 1.6.1 for Python >= 3.8 +- Improved the general detection reliability based on reports from the community + +## [3.3.0](https://github.com/Ousret/charset_normalizer/compare/3.2.0...3.3.0) (2023-09-30) + +### Added +- Allow to execute the CLI (e.g. normalizer) through `python -m charset_normalizer.cli` or `python -m charset_normalizer` +- Support for 9 forgotten encoding that are supported by Python but unlisted in `encoding.aliases` as they have no alias (#323) + +### Removed +- (internal) Redundant utils.is_ascii function and unused function is_private_use_only +- (internal) charset_normalizer.assets is moved inside charset_normalizer.constant + +### Changed +- (internal) Unicode code blocks in constants are updated using the latest v15.0.0 definition to improve detection +- Optional mypyc compilation upgraded to version 1.5.1 for Python >= 3.8 + +### Fixed +- Unable to properly sort CharsetMatch when both chaos/noise and coherence were close due to an unreachable condition in \_\_lt\_\_ (#350) + +## [3.2.0](https://github.com/Ousret/charset_normalizer/compare/3.1.0...3.2.0) (2023-06-07) + +### Changed +- Typehint for function `from_path` no longer enforce `PathLike` as its first argument +- Minor improvement over the global detection reliability + +### Added +- Introduce function `is_binary` that relies on main capabilities, and optimized to detect binaries +- Propagate `enable_fallback` argument throughout `from_bytes`, `from_path`, and `from_fp` that allow a deeper control over the detection (default True) +- Explicit support for Python 3.12 + +### Fixed +- Edge case detection failure where a file would contain 'very-long' camel cased word (Issue #289) + +## [3.1.0](https://github.com/Ousret/charset_normalizer/compare/3.0.1...3.1.0) (2023-03-06) + +### Added +- Argument `should_rename_legacy` for legacy function `detect` and disregard any new arguments without errors (PR #262) + +### Removed +- Support for Python 3.6 (PR #260) + +### Changed +- Optional speedup provided by mypy/c 1.0.1 + +## [3.0.1](https://github.com/Ousret/charset_normalizer/compare/3.0.0...3.0.1) (2022-11-18) + +### Fixed +- Multi-bytes cutter/chunk generator did not always cut correctly (PR #233) + +### Changed +- Speedup provided by mypy/c 0.990 on Python >= 3.7 + +## [3.0.0](https://github.com/Ousret/charset_normalizer/compare/2.1.1...3.0.0) (2022-10-20) + +### Added +- Extend the capability of explain=True when cp_isolation contains at most two entries (min one), will log in details of the Mess-detector results +- Support for alternative language frequency set in charset_normalizer.assets.FREQUENCIES +- Add parameter `language_threshold` in `from_bytes`, `from_path` and `from_fp` to adjust the minimum expected coherence ratio +- `normalizer --version` now specify if current version provide extra speedup (meaning mypyc compilation whl) + +### Changed +- Build with static metadata using 'build' frontend +- Make the language detection stricter +- Optional: Module `md.py` can be compiled using Mypyc to provide an extra speedup up to 4x faster than v2.1 + +### Fixed +- CLI with opt --normalize fail when using full path for files +- TooManyAccentuatedPlugin induce false positive on the mess detection when too few alpha character have been fed to it +- Sphinx warnings when generating the documentation + +### Removed +- Coherence detector no longer return 'Simple English' instead return 'English' +- Coherence detector no longer return 'Classical Chinese' instead return 'Chinese' +- Breaking: Method `first()` and `best()` from CharsetMatch +- UTF-7 will no longer appear as "detected" without a recognized SIG/mark (is unreliable/conflict with ASCII) +- Breaking: Class aliases CharsetDetector, CharsetDoctor, CharsetNormalizerMatch and CharsetNormalizerMatches +- Breaking: Top-level function `normalize` +- Breaking: Properties `chaos_secondary_pass`, `coherence_non_latin` and `w_counter` from CharsetMatch +- Support for the backport `unicodedata2` + +## [3.0.0rc1](https://github.com/Ousret/charset_normalizer/compare/3.0.0b2...3.0.0rc1) (2022-10-18) + +### Added +- Extend the capability of explain=True when cp_isolation contains at most two entries (min one), will log in details of the Mess-detector results +- Support for alternative language frequency set in charset_normalizer.assets.FREQUENCIES +- Add parameter `language_threshold` in `from_bytes`, `from_path` and `from_fp` to adjust the minimum expected coherence ratio + +### Changed +- Build with static metadata using 'build' frontend +- Make the language detection stricter + +### Fixed +- CLI with opt --normalize fail when using full path for files +- TooManyAccentuatedPlugin induce false positive on the mess detection when too few alpha character have been fed to it + +### Removed +- Coherence detector no longer return 'Simple English' instead return 'English' +- Coherence detector no longer return 'Classical Chinese' instead return 'Chinese' + +## [3.0.0b2](https://github.com/Ousret/charset_normalizer/compare/3.0.0b1...3.0.0b2) (2022-08-21) + +### Added +- `normalizer --version` now specify if current version provide extra speedup (meaning mypyc compilation whl) + +### Removed +- Breaking: Method `first()` and `best()` from CharsetMatch +- UTF-7 will no longer appear as "detected" without a recognized SIG/mark (is unreliable/conflict with ASCII) + +### Fixed +- Sphinx warnings when generating the documentation + +## [3.0.0b1](https://github.com/Ousret/charset_normalizer/compare/2.1.0...3.0.0b1) (2022-08-15) + +### Changed +- Optional: Module `md.py` can be compiled using Mypyc to provide an extra speedup up to 4x faster than v2.1 + +### Removed +- Breaking: Class aliases CharsetDetector, CharsetDoctor, CharsetNormalizerMatch and CharsetNormalizerMatches +- Breaking: Top-level function `normalize` +- Breaking: Properties `chaos_secondary_pass`, `coherence_non_latin` and `w_counter` from CharsetMatch +- Support for the backport `unicodedata2` + +## [2.1.1](https://github.com/Ousret/charset_normalizer/compare/2.1.0...2.1.1) (2022-08-19) + +### Deprecated +- Function `normalize` scheduled for removal in 3.0 + +### Changed +- Removed useless call to decode in fn is_unprintable (#206) + +### Fixed +- Third-party library (i18n xgettext) crashing not recognizing utf_8 (PEP 263) with underscore from [@aleksandernovikov](https://github.com/aleksandernovikov) (#204) + +## [2.1.0](https://github.com/Ousret/charset_normalizer/compare/2.0.12...2.1.0) (2022-06-19) + +### Added +- Output the Unicode table version when running the CLI with `--version` (PR #194) + +### Changed +- Re-use decoded buffer for single byte character sets from [@nijel](https://github.com/nijel) (PR #175) +- Fixing some performance bottlenecks from [@deedy5](https://github.com/deedy5) (PR #183) + +### Fixed +- Workaround potential bug in cpython with Zero Width No-Break Space located in Arabic Presentation Forms-B, Unicode 1.1 not acknowledged as space (PR #175) +- CLI default threshold aligned with the API threshold from [@oleksandr-kuzmenko](https://github.com/oleksandr-kuzmenko) (PR #181) + +### Removed +- Support for Python 3.5 (PR #192) + +### Deprecated +- Use of backport unicodedata from `unicodedata2` as Python is quickly catching up, scheduled for removal in 3.0 (PR #194) + +## [2.0.12](https://github.com/Ousret/charset_normalizer/compare/2.0.11...2.0.12) (2022-02-12) + +### Fixed +- ASCII miss-detection on rare cases (PR #170) + +## [2.0.11](https://github.com/Ousret/charset_normalizer/compare/2.0.10...2.0.11) (2022-01-30) + +### Added +- Explicit support for Python 3.11 (PR #164) + +### Changed +- The logging behavior have been completely reviewed, now using only TRACE and DEBUG levels (PR #163 #165) + +## [2.0.10](https://github.com/Ousret/charset_normalizer/compare/2.0.9...2.0.10) (2022-01-04) + +### Fixed +- Fallback match entries might lead to UnicodeDecodeError for large bytes sequence (PR #154) + +### Changed +- Skipping the language-detection (CD) on ASCII (PR #155) + +## [2.0.9](https://github.com/Ousret/charset_normalizer/compare/2.0.8...2.0.9) (2021-12-03) + +### Changed +- Moderating the logging impact (since 2.0.8) for specific environments (PR #147) + +### Fixed +- Wrong logging level applied when setting kwarg `explain` to True (PR #146) + +## [2.0.8](https://github.com/Ousret/charset_normalizer/compare/2.0.7...2.0.8) (2021-11-24) +### Changed +- Improvement over Vietnamese detection (PR #126) +- MD improvement on trailing data and long foreign (non-pure latin) data (PR #124) +- Efficiency improvements in cd/alphabet_languages from [@adbar](https://github.com/adbar) (PR #122) +- call sum() without an intermediary list following PEP 289 recommendations from [@adbar](https://github.com/adbar) (PR #129) +- Code style as refactored by Sourcery-AI (PR #131) +- Minor adjustment on the MD around european words (PR #133) +- Remove and replace SRTs from assets / tests (PR #139) +- Initialize the library logger with a `NullHandler` by default from [@nmaynes](https://github.com/nmaynes) (PR #135) +- Setting kwarg `explain` to True will add provisionally (bounded to function lifespan) a specific stream handler (PR #135) + +### Fixed +- Fix large (misleading) sequence giving UnicodeDecodeError (PR #137) +- Avoid using too insignificant chunk (PR #137) + +### Added +- Add and expose function `set_logging_handler` to configure a specific StreamHandler from [@nmaynes](https://github.com/nmaynes) (PR #135) +- Add `CHANGELOG.md` entries, format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) (PR #141) + +## [2.0.7](https://github.com/Ousret/charset_normalizer/compare/2.0.6...2.0.7) (2021-10-11) +### Added +- Add support for Kazakh (Cyrillic) language detection (PR #109) + +### Changed +- Further, improve inferring the language from a given single-byte code page (PR #112) +- Vainly trying to leverage PEP263 when PEP3120 is not supported (PR #116) +- Refactoring for potential performance improvements in loops from [@adbar](https://github.com/adbar) (PR #113) +- Various detection improvement (MD+CD) (PR #117) + +### Removed +- Remove redundant logging entry about detected language(s) (PR #115) + +### Fixed +- Fix a minor inconsistency between Python 3.5 and other versions regarding language detection (PR #117 #102) + +## [2.0.6](https://github.com/Ousret/charset_normalizer/compare/2.0.5...2.0.6) (2021-09-18) +### Fixed +- Unforeseen regression with the loss of the backward-compatibility with some older minor of Python 3.5.x (PR #100) +- Fix CLI crash when using --minimal output in certain cases (PR #103) + +### Changed +- Minor improvement to the detection efficiency (less than 1%) (PR #106 #101) + +## [2.0.5](https://github.com/Ousret/charset_normalizer/compare/2.0.4...2.0.5) (2021-09-14) +### Changed +- The project now comply with: flake8, mypy, isort and black to ensure a better overall quality (PR #81) +- The BC-support with v1.x was improved, the old staticmethods are restored (PR #82) +- The Unicode detection is slightly improved (PR #93) +- Add syntax sugar \_\_bool\_\_ for results CharsetMatches list-container (PR #91) + +### Removed +- The project no longer raise warning on tiny content given for detection, will be simply logged as warning instead (PR #92) + +### Fixed +- In some rare case, the chunks extractor could cut in the middle of a multi-byte character and could mislead the mess detection (PR #95) +- Some rare 'space' characters could trip up the UnprintablePlugin/Mess detection (PR #96) +- The MANIFEST.in was not exhaustive (PR #78) + +## [2.0.4](https://github.com/Ousret/charset_normalizer/compare/2.0.3...2.0.4) (2021-07-30) +### Fixed +- The CLI no longer raise an unexpected exception when no encoding has been found (PR #70) +- Fix accessing the 'alphabets' property when the payload contains surrogate characters (PR #68) +- The logger could mislead (explain=True) on detected languages and the impact of one MBCS match (PR #72) +- Submatch factoring could be wrong in rare edge cases (PR #72) +- Multiple files given to the CLI were ignored when publishing results to STDOUT. (After the first path) (PR #72) +- Fix line endings from CRLF to LF for certain project files (PR #67) + +### Changed +- Adjust the MD to lower the sensitivity, thus improving the global detection reliability (PR #69 #76) +- Allow fallback on specified encoding if any (PR #71) + +## [2.0.3](https://github.com/Ousret/charset_normalizer/compare/2.0.2...2.0.3) (2021-07-16) +### Changed +- Part of the detection mechanism has been improved to be less sensitive, resulting in more accurate detection results. Especially ASCII. (PR #63) +- According to the community wishes, the detection will fall back on ASCII or UTF-8 in a last-resort case. (PR #64) + +## [2.0.2](https://github.com/Ousret/charset_normalizer/compare/2.0.1...2.0.2) (2021-07-15) +### Fixed +- Empty/Too small JSON payload miss-detection fixed. Report from [@tseaver](https://github.com/tseaver) (PR #59) + +### Changed +- Don't inject unicodedata2 into sys.modules from [@akx](https://github.com/akx) (PR #57) + +## [2.0.1](https://github.com/Ousret/charset_normalizer/compare/2.0.0...2.0.1) (2021-07-13) +### Fixed +- Make it work where there isn't a filesystem available, dropping assets frequencies.json. Report from [@sethmlarson](https://github.com/sethmlarson). (PR #55) +- Using explain=False permanently disable the verbose output in the current runtime (PR #47) +- One log entry (language target preemptive) was not show in logs when using explain=True (PR #47) +- Fix undesired exception (ValueError) on getitem of instance CharsetMatches (PR #52) + +### Changed +- Public function normalize default args values were not aligned with from_bytes (PR #53) + +### Added +- You may now use charset aliases in cp_isolation and cp_exclusion arguments (PR #47) + +## [2.0.0](https://github.com/Ousret/charset_normalizer/compare/1.4.1...2.0.0) (2021-07-02) +### Changed +- 4x to 5 times faster than the previous 1.4.0 release. At least 2x faster than Chardet. +- Accent has been made on UTF-8 detection, should perform rather instantaneous. +- The backward compatibility with Chardet has been greatly improved. The legacy detect function returns an identical charset name whenever possible. +- The detection mechanism has been slightly improved, now Turkish content is detected correctly (most of the time) +- The program has been rewritten to ease the readability and maintainability. (+Using static typing)+ +- utf_7 detection has been reinstated. + +### Removed +- This package no longer require anything when used with Python 3.5 (Dropped cached_property) +- Removed support for these languages: Catalan, Esperanto, Kazakh, Baque, Volapük, Azeri, Galician, Nynorsk, Macedonian, and Serbocroatian. +- The exception hook on UnicodeDecodeError has been removed. + +### Deprecated +- Methods coherence_non_latin, w_counter, chaos_secondary_pass of the class CharsetMatch are now deprecated and scheduled for removal in v3.0 + +### Fixed +- The CLI output used the relative path of the file(s). Should be absolute. + +## [1.4.1](https://github.com/Ousret/charset_normalizer/compare/1.4.0...1.4.1) (2021-05-28) +### Fixed +- Logger configuration/usage no longer conflict with others (PR #44) + +## [1.4.0](https://github.com/Ousret/charset_normalizer/compare/1.3.9...1.4.0) (2021-05-21) +### Removed +- Using standard logging instead of using the package loguru. +- Dropping nose test framework in favor of the maintained pytest. +- Choose to not use dragonmapper package to help with gibberish Chinese/CJK text. +- Require cached_property only for Python 3.5 due to constraint. Dropping for every other interpreter version. +- Stop support for UTF-7 that does not contain a SIG. +- Dropping PrettyTable, replaced with pure JSON output in CLI. + +### Fixed +- BOM marker in a CharsetNormalizerMatch instance could be False in rare cases even if obviously present. Due to the sub-match factoring process. +- Not searching properly for the BOM when trying utf32/16 parent codec. + +### Changed +- Improving the package final size by compressing frequencies.json. +- Huge improvement over the larges payload. + +### Added +- CLI now produces JSON consumable output. +- Return ASCII if given sequences fit. Given reasonable confidence. + +## [1.3.9](https://github.com/Ousret/charset_normalizer/compare/1.3.8...1.3.9) (2021-05-13) + +### Fixed +- In some very rare cases, you may end up getting encode/decode errors due to a bad bytes payload (PR #40) + +## [1.3.8](https://github.com/Ousret/charset_normalizer/compare/1.3.7...1.3.8) (2021-05-12) + +### Fixed +- Empty given payload for detection may cause an exception if trying to access the `alphabets` property. (PR #39) + +## [1.3.7](https://github.com/Ousret/charset_normalizer/compare/1.3.6...1.3.7) (2021-05-12) + +### Fixed +- The legacy detect function should return UTF-8-SIG if sig is present in the payload. (PR #38) + +## [1.3.6](https://github.com/Ousret/charset_normalizer/compare/1.3.5...1.3.6) (2021-02-09) + +### Changed +- Amend the previous release to allow prettytable 2.0 (PR #35) + +## [1.3.5](https://github.com/Ousret/charset_normalizer/compare/1.3.4...1.3.5) (2021-02-08) + +### Fixed +- Fix error while using the package with a python pre-release interpreter (PR #33) + +### Changed +- Dependencies refactoring, constraints revised. + +### Added +- Add python 3.9 and 3.10 to the supported interpreters + +MIT License + +Copyright (c) 2019 TAHRI Ahmed R. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/RECORD b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/RECORD new file mode 100644 index 0000000..9b31b27 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/RECORD @@ -0,0 +1,36 @@ +../../bin/normalizer,sha256=d64Y2GlBYzj4fRL5WK1WS-VHgezegWBH89IcZpevMig,242 +charset_normalizer-3.4.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +charset_normalizer-3.4.0.dist-info/LICENSE,sha256=6zGgxaT7Cbik4yBV0lweX5w1iidS_vPNcgIT0cz-4kE,1070 +charset_normalizer-3.4.0.dist-info/METADATA,sha256=WGbEW9ehh2spNJxo1M6sEGGZWmsQ-oj2DsMjV29zoms,34159 +charset_normalizer-3.4.0.dist-info/RECORD,, +charset_normalizer-3.4.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +charset_normalizer-3.4.0.dist-info/WHEEL,sha256=XihS4yPLFu_eB7R4sl7jUHiEAA7zQ3q0-_CuIzkpFkk,151 +charset_normalizer-3.4.0.dist-info/entry_points.txt,sha256=ADSTKrkXZ3hhdOVFi6DcUEHQRS0xfxDIE_pEz4wLIXA,65 +charset_normalizer-3.4.0.dist-info/top_level.txt,sha256=7ASyzePr8_xuZWJsnqJjIBtyV8vhEo0wBCv1MPRRi3Q,19 +charset_normalizer/__init__.py,sha256=UzI3xC8PhmcLRMzSgPb6minTmRq0kWznnCBJ8ZCc2XI,1577 +charset_normalizer/__main__.py,sha256=JxY8bleaENOFlLRb9HfoeZCzAMnn2A1oGR5Xm2eyqg0,73 +charset_normalizer/__pycache__/__init__.cpython-311.pyc,, +charset_normalizer/__pycache__/__main__.cpython-311.pyc,, +charset_normalizer/__pycache__/api.cpython-311.pyc,, +charset_normalizer/__pycache__/cd.cpython-311.pyc,, +charset_normalizer/__pycache__/constant.cpython-311.pyc,, +charset_normalizer/__pycache__/legacy.cpython-311.pyc,, +charset_normalizer/__pycache__/md.cpython-311.pyc,, +charset_normalizer/__pycache__/models.cpython-311.pyc,, +charset_normalizer/__pycache__/utils.cpython-311.pyc,, +charset_normalizer/__pycache__/version.cpython-311.pyc,, +charset_normalizer/api.py,sha256=kMyNUqrfBZU22PP0pYKrSldtYUGA24wsGlXGLAKra7c,22559 +charset_normalizer/cd.py,sha256=xwZliZcTQFA3jU0c00PRiu9MNxXTFxQkFLWmMW24ZzI,12560 +charset_normalizer/cli/__init__.py,sha256=D5ERp8P62llm2FuoMzydZ7d9rs8cvvLXqE-1_6oViPc,100 +charset_normalizer/cli/__main__.py,sha256=zX9sV_ApU1d96Wb0cS04vulstdB4F0Eh7kLn-gevfw4,10411 +charset_normalizer/cli/__pycache__/__init__.cpython-311.pyc,, +charset_normalizer/cli/__pycache__/__main__.cpython-311.pyc,, +charset_normalizer/constant.py,sha256=uwoW87NicWZDTLviX7le0wdoYBbhBQDA4n1JtJo77ts,40499 +charset_normalizer/legacy.py,sha256=XJjkT0hejMH8qfAKz1ts8OUiBT18t2FJP3tJgLwUWwc,2327 +charset_normalizer/md.cpython-311-x86_64-linux-gnu.so,sha256=Y7QSLD5QLoSFAWys0-tL7R6QB7oi5864zM6zr7RWek4,16064 +charset_normalizer/md.py,sha256=SIIZcENrslI7h3v4GigbFN61fRyE_wiCN1z9Ii3fBRo,20138 +charset_normalizer/md__mypyc.cpython-311-x86_64-linux-gnu.so,sha256=xDjCrj9MzdH8kW7d-HbtvIaOcrX6SFiV7SrBv4QgGEI,272696 +charset_normalizer/models.py,sha256=oAMAcBSEY7CngbUXJp34Wc4Rl9NKJJjGmUwW3EPtk6g,12425 +charset_normalizer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +charset_normalizer/utils.py,sha256=teiosMqzKjXyAHXnGdjSBOgnBZwx-SkBbCLrx0UXy8M,11894 +charset_normalizer/version.py,sha256=AX66S4ytQFdd6F5jbVU2OPMqYwFS5M3BkMvyX-3BKF8,79 diff --git a/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/REQUESTED b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/WHEEL b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/WHEEL new file mode 100644 index 0000000..d9c3682 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: setuptools (75.1.0) +Root-Is-Purelib: false +Tag: cp311-cp311-manylinux_2_17_x86_64 +Tag: cp311-cp311-manylinux2014_x86_64 + diff --git a/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/entry_points.txt b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/entry_points.txt new file mode 100644 index 0000000..65619e7 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +normalizer = charset_normalizer.cli:cli_detect diff --git a/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/top_level.txt b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/top_level.txt new file mode 100644 index 0000000..66958f0 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer-3.4.0.dist-info/top_level.txt @@ -0,0 +1 @@ +charset_normalizer diff --git a/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/.DS_Store b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..7e52a6c1f31dfebafd29d3ff8a2840c29e06eb70 GIT binary patch literal 6148 zcmeHKy-veG47S@2MIGqKcq?Py;HbhAbVUj@MJ-aIWMN>tM`Pl-_oju8e*;Q@ReM@`7vuTTBU$=XBV!w2HfB3kY-nVHFzwqu(qb%mgv!<6} zez(^+(JW{A94(*TIynE#Iy^68Qx|8z8E^(Jjsetck>yI!XJ^0}a0Yq?>!|6!0nIRnnXMKQpOVpnYON>*D(FDJD&U_4@oh+k6d jL$JuD7`{@9Z!oezA7lZHhDi|`i2n#=8hmmF{*-}Fxz9*q literal 0 HcmV?d00001 diff --git a/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/__init__.py new file mode 100644 index 0000000..55991fc --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/__init__.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +""" +Charset-Normalizer +~~~~~~~~~~~~~~ +The Real First Universal Charset Detector. +A library that helps you read text from an unknown charset encoding. +Motivated by chardet, This package is trying to resolve the issue by taking a new approach. +All IANA character set names for which the Python core library provides codecs are supported. + +Basic usage: + >>> from charset_normalizer import from_bytes + >>> results = from_bytes('Bсеки човек има право на образование. Oбразованието!'.encode('utf_8')) + >>> best_guess = results.best() + >>> str(best_guess) + 'Bсеки човек има право на образование. Oбразованието!' + +Others methods and usages are available - see the full documentation +at . +:copyright: (c) 2021 by Ahmed TAHRI +:license: MIT, see LICENSE for more details. +""" +import logging + +from .api import from_bytes, from_fp, from_path, is_binary +from .legacy import detect +from .models import CharsetMatch, CharsetMatches +from .utils import set_logging_handler +from .version import VERSION, __version__ + +__all__ = ( + "from_fp", + "from_path", + "from_bytes", + "is_binary", + "detect", + "CharsetMatch", + "CharsetMatches", + "__version__", + "VERSION", + "set_logging_handler", +) + +# Attach a NullHandler to the top level logger by default +# https://docs.python.org/3.3/howto/logging.html#configuring-logging-for-a-library + +logging.getLogger("charset_normalizer").addHandler(logging.NullHandler()) diff --git a/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/__main__.py b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/__main__.py new file mode 100644 index 0000000..beae2ef --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/__main__.py @@ -0,0 +1,4 @@ +from .cli import cli_detect + +if __name__ == "__main__": + cli_detect() diff --git a/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/api.py b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/api.py new file mode 100644 index 0000000..e3f2283 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/api.py @@ -0,0 +1,668 @@ +import logging +from os import PathLike +from typing import BinaryIO, List, Optional, Set, Union + +from .cd import ( + coherence_ratio, + encoding_languages, + mb_encoding_languages, + merge_coherence_ratios, +) +from .constant import IANA_SUPPORTED, TOO_BIG_SEQUENCE, TOO_SMALL_SEQUENCE, TRACE +from .md import mess_ratio +from .models import CharsetMatch, CharsetMatches +from .utils import ( + any_specified_encoding, + cut_sequence_chunks, + iana_name, + identify_sig_or_bom, + is_cp_similar, + is_multi_byte_encoding, + should_strip_sig_or_bom, +) + +# Will most likely be controversial +# logging.addLevelName(TRACE, "TRACE") +logger = logging.getLogger("charset_normalizer") +explain_handler = logging.StreamHandler() +explain_handler.setFormatter( + logging.Formatter("%(asctime)s | %(levelname)s | %(message)s") +) + + +def from_bytes( + sequences: Union[bytes, bytearray], + steps: int = 5, + chunk_size: int = 512, + threshold: float = 0.2, + cp_isolation: Optional[List[str]] = None, + cp_exclusion: Optional[List[str]] = None, + preemptive_behaviour: bool = True, + explain: bool = False, + language_threshold: float = 0.1, + enable_fallback: bool = True, +) -> CharsetMatches: + """ + Given a raw bytes sequence, return the best possibles charset usable to render str objects. + If there is no results, it is a strong indicator that the source is binary/not text. + By default, the process will extract 5 blocks of 512o each to assess the mess and coherence of a given sequence. + And will give up a particular code page after 20% of measured mess. Those criteria are customizable at will. + + The preemptive behavior DOES NOT replace the traditional detection workflow, it prioritize a particular code page + but never take it for granted. Can improve the performance. + + You may want to focus your attention to some code page or/and not others, use cp_isolation and cp_exclusion for that + purpose. + + This function will strip the SIG in the payload/sequence every time except on UTF-16, UTF-32. + By default the library does not setup any handler other than the NullHandler, if you choose to set the 'explain' + toggle to True it will alter the logger configuration to add a StreamHandler that is suitable for debugging. + Custom logging format and handler can be set manually. + """ + + if not isinstance(sequences, (bytearray, bytes)): + raise TypeError( + "Expected object of type bytes or bytearray, got: {0}".format( + type(sequences) + ) + ) + + if explain: + previous_logger_level: int = logger.level + logger.addHandler(explain_handler) + logger.setLevel(TRACE) + + length: int = len(sequences) + + if length == 0: + logger.debug("Encoding detection on empty bytes, assuming utf_8 intention.") + if explain: + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level or logging.WARNING) + return CharsetMatches([CharsetMatch(sequences, "utf_8", 0.0, False, [], "")]) + + if cp_isolation is not None: + logger.log( + TRACE, + "cp_isolation is set. use this flag for debugging purpose. " + "limited list of encoding allowed : %s.", + ", ".join(cp_isolation), + ) + cp_isolation = [iana_name(cp, False) for cp in cp_isolation] + else: + cp_isolation = [] + + if cp_exclusion is not None: + logger.log( + TRACE, + "cp_exclusion is set. use this flag for debugging purpose. " + "limited list of encoding excluded : %s.", + ", ".join(cp_exclusion), + ) + cp_exclusion = [iana_name(cp, False) for cp in cp_exclusion] + else: + cp_exclusion = [] + + if length <= (chunk_size * steps): + logger.log( + TRACE, + "override steps (%i) and chunk_size (%i) as content does not fit (%i byte(s) given) parameters.", + steps, + chunk_size, + length, + ) + steps = 1 + chunk_size = length + + if steps > 1 and length / steps < chunk_size: + chunk_size = int(length / steps) + + is_too_small_sequence: bool = len(sequences) < TOO_SMALL_SEQUENCE + is_too_large_sequence: bool = len(sequences) >= TOO_BIG_SEQUENCE + + if is_too_small_sequence: + logger.log( + TRACE, + "Trying to detect encoding from a tiny portion of ({}) byte(s).".format( + length + ), + ) + elif is_too_large_sequence: + logger.log( + TRACE, + "Using lazy str decoding because the payload is quite large, ({}) byte(s).".format( + length + ), + ) + + prioritized_encodings: List[str] = [] + + specified_encoding: Optional[str] = ( + any_specified_encoding(sequences) if preemptive_behaviour else None + ) + + if specified_encoding is not None: + prioritized_encodings.append(specified_encoding) + logger.log( + TRACE, + "Detected declarative mark in sequence. Priority +1 given for %s.", + specified_encoding, + ) + + tested: Set[str] = set() + tested_but_hard_failure: List[str] = [] + tested_but_soft_failure: List[str] = [] + + fallback_ascii: Optional[CharsetMatch] = None + fallback_u8: Optional[CharsetMatch] = None + fallback_specified: Optional[CharsetMatch] = None + + results: CharsetMatches = CharsetMatches() + + early_stop_results: CharsetMatches = CharsetMatches() + + sig_encoding, sig_payload = identify_sig_or_bom(sequences) + + if sig_encoding is not None: + prioritized_encodings.append(sig_encoding) + logger.log( + TRACE, + "Detected a SIG or BOM mark on first %i byte(s). Priority +1 given for %s.", + len(sig_payload), + sig_encoding, + ) + + prioritized_encodings.append("ascii") + + if "utf_8" not in prioritized_encodings: + prioritized_encodings.append("utf_8") + + for encoding_iana in prioritized_encodings + IANA_SUPPORTED: + if cp_isolation and encoding_iana not in cp_isolation: + continue + + if cp_exclusion and encoding_iana in cp_exclusion: + continue + + if encoding_iana in tested: + continue + + tested.add(encoding_iana) + + decoded_payload: Optional[str] = None + bom_or_sig_available: bool = sig_encoding == encoding_iana + strip_sig_or_bom: bool = bom_or_sig_available and should_strip_sig_or_bom( + encoding_iana + ) + + if encoding_iana in {"utf_16", "utf_32"} and not bom_or_sig_available: + logger.log( + TRACE, + "Encoding %s won't be tested as-is because it require a BOM. Will try some sub-encoder LE/BE.", + encoding_iana, + ) + continue + if encoding_iana in {"utf_7"} and not bom_or_sig_available: + logger.log( + TRACE, + "Encoding %s won't be tested as-is because detection is unreliable without BOM/SIG.", + encoding_iana, + ) + continue + + try: + is_multi_byte_decoder: bool = is_multi_byte_encoding(encoding_iana) + except (ModuleNotFoundError, ImportError): + logger.log( + TRACE, + "Encoding %s does not provide an IncrementalDecoder", + encoding_iana, + ) + continue + + try: + if is_too_large_sequence and is_multi_byte_decoder is False: + str( + ( + sequences[: int(50e4)] + if strip_sig_or_bom is False + else sequences[len(sig_payload) : int(50e4)] + ), + encoding=encoding_iana, + ) + else: + decoded_payload = str( + ( + sequences + if strip_sig_or_bom is False + else sequences[len(sig_payload) :] + ), + encoding=encoding_iana, + ) + except (UnicodeDecodeError, LookupError) as e: + if not isinstance(e, LookupError): + logger.log( + TRACE, + "Code page %s does not fit given bytes sequence at ALL. %s", + encoding_iana, + str(e), + ) + tested_but_hard_failure.append(encoding_iana) + continue + + similar_soft_failure_test: bool = False + + for encoding_soft_failed in tested_but_soft_failure: + if is_cp_similar(encoding_iana, encoding_soft_failed): + similar_soft_failure_test = True + break + + if similar_soft_failure_test: + logger.log( + TRACE, + "%s is deemed too similar to code page %s and was consider unsuited already. Continuing!", + encoding_iana, + encoding_soft_failed, + ) + continue + + r_ = range( + 0 if not bom_or_sig_available else len(sig_payload), + length, + int(length / steps), + ) + + multi_byte_bonus: bool = ( + is_multi_byte_decoder + and decoded_payload is not None + and len(decoded_payload) < length + ) + + if multi_byte_bonus: + logger.log( + TRACE, + "Code page %s is a multi byte encoding table and it appear that at least one character " + "was encoded using n-bytes.", + encoding_iana, + ) + + max_chunk_gave_up: int = int(len(r_) / 4) + + max_chunk_gave_up = max(max_chunk_gave_up, 2) + early_stop_count: int = 0 + lazy_str_hard_failure = False + + md_chunks: List[str] = [] + md_ratios = [] + + try: + for chunk in cut_sequence_chunks( + sequences, + encoding_iana, + r_, + chunk_size, + bom_or_sig_available, + strip_sig_or_bom, + sig_payload, + is_multi_byte_decoder, + decoded_payload, + ): + md_chunks.append(chunk) + + md_ratios.append( + mess_ratio( + chunk, + threshold, + explain is True and 1 <= len(cp_isolation) <= 2, + ) + ) + + if md_ratios[-1] >= threshold: + early_stop_count += 1 + + if (early_stop_count >= max_chunk_gave_up) or ( + bom_or_sig_available and strip_sig_or_bom is False + ): + break + except ( + UnicodeDecodeError + ) as e: # Lazy str loading may have missed something there + logger.log( + TRACE, + "LazyStr Loading: After MD chunk decode, code page %s does not fit given bytes sequence at ALL. %s", + encoding_iana, + str(e), + ) + early_stop_count = max_chunk_gave_up + lazy_str_hard_failure = True + + # We might want to check the sequence again with the whole content + # Only if initial MD tests passes + if ( + not lazy_str_hard_failure + and is_too_large_sequence + and not is_multi_byte_decoder + ): + try: + sequences[int(50e3) :].decode(encoding_iana, errors="strict") + except UnicodeDecodeError as e: + logger.log( + TRACE, + "LazyStr Loading: After final lookup, code page %s does not fit given bytes sequence at ALL. %s", + encoding_iana, + str(e), + ) + tested_but_hard_failure.append(encoding_iana) + continue + + mean_mess_ratio: float = sum(md_ratios) / len(md_ratios) if md_ratios else 0.0 + if mean_mess_ratio >= threshold or early_stop_count >= max_chunk_gave_up: + tested_but_soft_failure.append(encoding_iana) + logger.log( + TRACE, + "%s was excluded because of initial chaos probing. Gave up %i time(s). " + "Computed mean chaos is %f %%.", + encoding_iana, + early_stop_count, + round(mean_mess_ratio * 100, ndigits=3), + ) + # Preparing those fallbacks in case we got nothing. + if ( + enable_fallback + and encoding_iana in ["ascii", "utf_8", specified_encoding] + and not lazy_str_hard_failure + ): + fallback_entry = CharsetMatch( + sequences, + encoding_iana, + threshold, + False, + [], + decoded_payload, + preemptive_declaration=specified_encoding, + ) + if encoding_iana == specified_encoding: + fallback_specified = fallback_entry + elif encoding_iana == "ascii": + fallback_ascii = fallback_entry + else: + fallback_u8 = fallback_entry + continue + + logger.log( + TRACE, + "%s passed initial chaos probing. Mean measured chaos is %f %%", + encoding_iana, + round(mean_mess_ratio * 100, ndigits=3), + ) + + if not is_multi_byte_decoder: + target_languages: List[str] = encoding_languages(encoding_iana) + else: + target_languages = mb_encoding_languages(encoding_iana) + + if target_languages: + logger.log( + TRACE, + "{} should target any language(s) of {}".format( + encoding_iana, str(target_languages) + ), + ) + + cd_ratios = [] + + # We shall skip the CD when its about ASCII + # Most of the time its not relevant to run "language-detection" on it. + if encoding_iana != "ascii": + for chunk in md_chunks: + chunk_languages = coherence_ratio( + chunk, + language_threshold, + ",".join(target_languages) if target_languages else None, + ) + + cd_ratios.append(chunk_languages) + + cd_ratios_merged = merge_coherence_ratios(cd_ratios) + + if cd_ratios_merged: + logger.log( + TRACE, + "We detected language {} using {}".format( + cd_ratios_merged, encoding_iana + ), + ) + + current_match = CharsetMatch( + sequences, + encoding_iana, + mean_mess_ratio, + bom_or_sig_available, + cd_ratios_merged, + ( + decoded_payload + if ( + is_too_large_sequence is False + or encoding_iana in [specified_encoding, "ascii", "utf_8"] + ) + else None + ), + preemptive_declaration=specified_encoding, + ) + + results.append(current_match) + + if ( + encoding_iana in [specified_encoding, "ascii", "utf_8"] + and mean_mess_ratio < 0.1 + ): + # If md says nothing to worry about, then... stop immediately! + if mean_mess_ratio == 0.0: + logger.debug( + "Encoding detection: %s is most likely the one.", + current_match.encoding, + ) + if explain: + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level) + return CharsetMatches([current_match]) + + early_stop_results.append(current_match) + + if ( + len(early_stop_results) + and (specified_encoding is None or specified_encoding in tested) + and "ascii" in tested + and "utf_8" in tested + ): + probable_result: CharsetMatch = early_stop_results.best() # type: ignore[assignment] + logger.debug( + "Encoding detection: %s is most likely the one.", + probable_result.encoding, + ) + if explain: + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level) + + return CharsetMatches([probable_result]) + + if encoding_iana == sig_encoding: + logger.debug( + "Encoding detection: %s is most likely the one as we detected a BOM or SIG within " + "the beginning of the sequence.", + encoding_iana, + ) + if explain: + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level) + return CharsetMatches([results[encoding_iana]]) + + if len(results) == 0: + if fallback_u8 or fallback_ascii or fallback_specified: + logger.log( + TRACE, + "Nothing got out of the detection process. Using ASCII/UTF-8/Specified fallback.", + ) + + if fallback_specified: + logger.debug( + "Encoding detection: %s will be used as a fallback match", + fallback_specified.encoding, + ) + results.append(fallback_specified) + elif ( + (fallback_u8 and fallback_ascii is None) + or ( + fallback_u8 + and fallback_ascii + and fallback_u8.fingerprint != fallback_ascii.fingerprint + ) + or (fallback_u8 is not None) + ): + logger.debug("Encoding detection: utf_8 will be used as a fallback match") + results.append(fallback_u8) + elif fallback_ascii: + logger.debug("Encoding detection: ascii will be used as a fallback match") + results.append(fallback_ascii) + + if results: + logger.debug( + "Encoding detection: Found %s as plausible (best-candidate) for content. With %i alternatives.", + results.best().encoding, # type: ignore + len(results) - 1, + ) + else: + logger.debug("Encoding detection: Unable to determine any suitable charset.") + + if explain: + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level) + + return results + + +def from_fp( + fp: BinaryIO, + steps: int = 5, + chunk_size: int = 512, + threshold: float = 0.20, + cp_isolation: Optional[List[str]] = None, + cp_exclusion: Optional[List[str]] = None, + preemptive_behaviour: bool = True, + explain: bool = False, + language_threshold: float = 0.1, + enable_fallback: bool = True, +) -> CharsetMatches: + """ + Same thing than the function from_bytes but using a file pointer that is already ready. + Will not close the file pointer. + """ + return from_bytes( + fp.read(), + steps, + chunk_size, + threshold, + cp_isolation, + cp_exclusion, + preemptive_behaviour, + explain, + language_threshold, + enable_fallback, + ) + + +def from_path( + path: Union[str, bytes, PathLike], # type: ignore[type-arg] + steps: int = 5, + chunk_size: int = 512, + threshold: float = 0.20, + cp_isolation: Optional[List[str]] = None, + cp_exclusion: Optional[List[str]] = None, + preemptive_behaviour: bool = True, + explain: bool = False, + language_threshold: float = 0.1, + enable_fallback: bool = True, +) -> CharsetMatches: + """ + Same thing than the function from_bytes but with one extra step. Opening and reading given file path in binary mode. + Can raise IOError. + """ + with open(path, "rb") as fp: + return from_fp( + fp, + steps, + chunk_size, + threshold, + cp_isolation, + cp_exclusion, + preemptive_behaviour, + explain, + language_threshold, + enable_fallback, + ) + + +def is_binary( + fp_or_path_or_payload: Union[PathLike, str, BinaryIO, bytes], # type: ignore[type-arg] + steps: int = 5, + chunk_size: int = 512, + threshold: float = 0.20, + cp_isolation: Optional[List[str]] = None, + cp_exclusion: Optional[List[str]] = None, + preemptive_behaviour: bool = True, + explain: bool = False, + language_threshold: float = 0.1, + enable_fallback: bool = False, +) -> bool: + """ + Detect if the given input (file, bytes, or path) points to a binary file. aka. not a string. + Based on the same main heuristic algorithms and default kwargs at the sole exception that fallbacks match + are disabled to be stricter around ASCII-compatible but unlikely to be a string. + """ + if isinstance(fp_or_path_or_payload, (str, PathLike)): + guesses = from_path( + fp_or_path_or_payload, + steps=steps, + chunk_size=chunk_size, + threshold=threshold, + cp_isolation=cp_isolation, + cp_exclusion=cp_exclusion, + preemptive_behaviour=preemptive_behaviour, + explain=explain, + language_threshold=language_threshold, + enable_fallback=enable_fallback, + ) + elif isinstance( + fp_or_path_or_payload, + ( + bytes, + bytearray, + ), + ): + guesses = from_bytes( + fp_or_path_or_payload, + steps=steps, + chunk_size=chunk_size, + threshold=threshold, + cp_isolation=cp_isolation, + cp_exclusion=cp_exclusion, + preemptive_behaviour=preemptive_behaviour, + explain=explain, + language_threshold=language_threshold, + enable_fallback=enable_fallback, + ) + else: + guesses = from_fp( + fp_or_path_or_payload, + steps=steps, + chunk_size=chunk_size, + threshold=threshold, + cp_isolation=cp_isolation, + cp_exclusion=cp_exclusion, + preemptive_behaviour=preemptive_behaviour, + explain=explain, + language_threshold=language_threshold, + enable_fallback=enable_fallback, + ) + + return not guesses diff --git a/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/cd.py b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/cd.py new file mode 100644 index 0000000..4ea6760 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/cd.py @@ -0,0 +1,395 @@ +import importlib +from codecs import IncrementalDecoder +from collections import Counter +from functools import lru_cache +from typing import Counter as TypeCounter, Dict, List, Optional, Tuple + +from .constant import ( + FREQUENCIES, + KO_NAMES, + LANGUAGE_SUPPORTED_COUNT, + TOO_SMALL_SEQUENCE, + ZH_NAMES, +) +from .md import is_suspiciously_successive_range +from .models import CoherenceMatches +from .utils import ( + is_accentuated, + is_latin, + is_multi_byte_encoding, + is_unicode_range_secondary, + unicode_range, +) + + +def encoding_unicode_range(iana_name: str) -> List[str]: + """ + Return associated unicode ranges in a single byte code page. + """ + if is_multi_byte_encoding(iana_name): + raise IOError("Function not supported on multi-byte code page") + + decoder = importlib.import_module( + "encodings.{}".format(iana_name) + ).IncrementalDecoder + + p: IncrementalDecoder = decoder(errors="ignore") + seen_ranges: Dict[str, int] = {} + character_count: int = 0 + + for i in range(0x40, 0xFF): + chunk: str = p.decode(bytes([i])) + + if chunk: + character_range: Optional[str] = unicode_range(chunk) + + if character_range is None: + continue + + if is_unicode_range_secondary(character_range) is False: + if character_range not in seen_ranges: + seen_ranges[character_range] = 0 + seen_ranges[character_range] += 1 + character_count += 1 + + return sorted( + [ + character_range + for character_range in seen_ranges + if seen_ranges[character_range] / character_count >= 0.15 + ] + ) + + +def unicode_range_languages(primary_range: str) -> List[str]: + """ + Return inferred languages used with a unicode range. + """ + languages: List[str] = [] + + for language, characters in FREQUENCIES.items(): + for character in characters: + if unicode_range(character) == primary_range: + languages.append(language) + break + + return languages + + +@lru_cache() +def encoding_languages(iana_name: str) -> List[str]: + """ + Single-byte encoding language association. Some code page are heavily linked to particular language(s). + This function does the correspondence. + """ + unicode_ranges: List[str] = encoding_unicode_range(iana_name) + primary_range: Optional[str] = None + + for specified_range in unicode_ranges: + if "Latin" not in specified_range: + primary_range = specified_range + break + + if primary_range is None: + return ["Latin Based"] + + return unicode_range_languages(primary_range) + + +@lru_cache() +def mb_encoding_languages(iana_name: str) -> List[str]: + """ + Multi-byte encoding language association. Some code page are heavily linked to particular language(s). + This function does the correspondence. + """ + if ( + iana_name.startswith("shift_") + or iana_name.startswith("iso2022_jp") + or iana_name.startswith("euc_j") + or iana_name == "cp932" + ): + return ["Japanese"] + if iana_name.startswith("gb") or iana_name in ZH_NAMES: + return ["Chinese"] + if iana_name.startswith("iso2022_kr") or iana_name in KO_NAMES: + return ["Korean"] + + return [] + + +@lru_cache(maxsize=LANGUAGE_SUPPORTED_COUNT) +def get_target_features(language: str) -> Tuple[bool, bool]: + """ + Determine main aspects from a supported language if it contains accents and if is pure Latin. + """ + target_have_accents: bool = False + target_pure_latin: bool = True + + for character in FREQUENCIES[language]: + if not target_have_accents and is_accentuated(character): + target_have_accents = True + if target_pure_latin and is_latin(character) is False: + target_pure_latin = False + + return target_have_accents, target_pure_latin + + +def alphabet_languages( + characters: List[str], ignore_non_latin: bool = False +) -> List[str]: + """ + Return associated languages associated to given characters. + """ + languages: List[Tuple[str, float]] = [] + + source_have_accents = any(is_accentuated(character) for character in characters) + + for language, language_characters in FREQUENCIES.items(): + target_have_accents, target_pure_latin = get_target_features(language) + + if ignore_non_latin and target_pure_latin is False: + continue + + if target_have_accents is False and source_have_accents: + continue + + character_count: int = len(language_characters) + + character_match_count: int = len( + [c for c in language_characters if c in characters] + ) + + ratio: float = character_match_count / character_count + + if ratio >= 0.2: + languages.append((language, ratio)) + + languages = sorted(languages, key=lambda x: x[1], reverse=True) + + return [compatible_language[0] for compatible_language in languages] + + +def characters_popularity_compare( + language: str, ordered_characters: List[str] +) -> float: + """ + Determine if a ordered characters list (by occurrence from most appearance to rarest) match a particular language. + The result is a ratio between 0. (absolutely no correspondence) and 1. (near perfect fit). + Beware that is function is not strict on the match in order to ease the detection. (Meaning close match is 1.) + """ + if language not in FREQUENCIES: + raise ValueError("{} not available".format(language)) + + character_approved_count: int = 0 + FREQUENCIES_language_set = set(FREQUENCIES[language]) + + ordered_characters_count: int = len(ordered_characters) + target_language_characters_count: int = len(FREQUENCIES[language]) + + large_alphabet: bool = target_language_characters_count > 26 + + for character, character_rank in zip( + ordered_characters, range(0, ordered_characters_count) + ): + if character not in FREQUENCIES_language_set: + continue + + character_rank_in_language: int = FREQUENCIES[language].index(character) + expected_projection_ratio: float = ( + target_language_characters_count / ordered_characters_count + ) + character_rank_projection: int = int(character_rank * expected_projection_ratio) + + if ( + large_alphabet is False + and abs(character_rank_projection - character_rank_in_language) > 4 + ): + continue + + if ( + large_alphabet is True + and abs(character_rank_projection - character_rank_in_language) + < target_language_characters_count / 3 + ): + character_approved_count += 1 + continue + + characters_before_source: List[str] = FREQUENCIES[language][ + 0:character_rank_in_language + ] + characters_after_source: List[str] = FREQUENCIES[language][ + character_rank_in_language: + ] + characters_before: List[str] = ordered_characters[0:character_rank] + characters_after: List[str] = ordered_characters[character_rank:] + + before_match_count: int = len( + set(characters_before) & set(characters_before_source) + ) + + after_match_count: int = len( + set(characters_after) & set(characters_after_source) + ) + + if len(characters_before_source) == 0 and before_match_count <= 4: + character_approved_count += 1 + continue + + if len(characters_after_source) == 0 and after_match_count <= 4: + character_approved_count += 1 + continue + + if ( + before_match_count / len(characters_before_source) >= 0.4 + or after_match_count / len(characters_after_source) >= 0.4 + ): + character_approved_count += 1 + continue + + return character_approved_count / len(ordered_characters) + + +def alpha_unicode_split(decoded_sequence: str) -> List[str]: + """ + Given a decoded text sequence, return a list of str. Unicode range / alphabet separation. + Ex. a text containing English/Latin with a bit a Hebrew will return two items in the resulting list; + One containing the latin letters and the other hebrew. + """ + layers: Dict[str, str] = {} + + for character in decoded_sequence: + if character.isalpha() is False: + continue + + character_range: Optional[str] = unicode_range(character) + + if character_range is None: + continue + + layer_target_range: Optional[str] = None + + for discovered_range in layers: + if ( + is_suspiciously_successive_range(discovered_range, character_range) + is False + ): + layer_target_range = discovered_range + break + + if layer_target_range is None: + layer_target_range = character_range + + if layer_target_range not in layers: + layers[layer_target_range] = character.lower() + continue + + layers[layer_target_range] += character.lower() + + return list(layers.values()) + + +def merge_coherence_ratios(results: List[CoherenceMatches]) -> CoherenceMatches: + """ + This function merge results previously given by the function coherence_ratio. + The return type is the same as coherence_ratio. + """ + per_language_ratios: Dict[str, List[float]] = {} + for result in results: + for sub_result in result: + language, ratio = sub_result + if language not in per_language_ratios: + per_language_ratios[language] = [ratio] + continue + per_language_ratios[language].append(ratio) + + merge = [ + ( + language, + round( + sum(per_language_ratios[language]) / len(per_language_ratios[language]), + 4, + ), + ) + for language in per_language_ratios + ] + + return sorted(merge, key=lambda x: x[1], reverse=True) + + +def filter_alt_coherence_matches(results: CoherenceMatches) -> CoherenceMatches: + """ + We shall NOT return "English—" in CoherenceMatches because it is an alternative + of "English". This function only keeps the best match and remove the em-dash in it. + """ + index_results: Dict[str, List[float]] = dict() + + for result in results: + language, ratio = result + no_em_name: str = language.replace("—", "") + + if no_em_name not in index_results: + index_results[no_em_name] = [] + + index_results[no_em_name].append(ratio) + + if any(len(index_results[e]) > 1 for e in index_results): + filtered_results: CoherenceMatches = [] + + for language in index_results: + filtered_results.append((language, max(index_results[language]))) + + return filtered_results + + return results + + +@lru_cache(maxsize=2048) +def coherence_ratio( + decoded_sequence: str, threshold: float = 0.1, lg_inclusion: Optional[str] = None +) -> CoherenceMatches: + """ + Detect ANY language that can be identified in given sequence. The sequence will be analysed by layers. + A layer = Character extraction by alphabets/ranges. + """ + + results: List[Tuple[str, float]] = [] + ignore_non_latin: bool = False + + sufficient_match_count: int = 0 + + lg_inclusion_list = lg_inclusion.split(",") if lg_inclusion is not None else [] + if "Latin Based" in lg_inclusion_list: + ignore_non_latin = True + lg_inclusion_list.remove("Latin Based") + + for layer in alpha_unicode_split(decoded_sequence): + sequence_frequencies: TypeCounter[str] = Counter(layer) + most_common = sequence_frequencies.most_common() + + character_count: int = sum(o for c, o in most_common) + + if character_count <= TOO_SMALL_SEQUENCE: + continue + + popular_character_ordered: List[str] = [c for c, o in most_common] + + for language in lg_inclusion_list or alphabet_languages( + popular_character_ordered, ignore_non_latin + ): + ratio: float = characters_popularity_compare( + language, popular_character_ordered + ) + + if ratio < threshold: + continue + elif ratio >= 0.8: + sufficient_match_count += 1 + + results.append((language, round(ratio, 4))) + + if sufficient_match_count >= 3: + break + + return sorted( + filter_alt_coherence_matches(results), key=lambda x: x[1], reverse=True + ) diff --git a/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/cli/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/cli/__init__.py new file mode 100644 index 0000000..d95fedf --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/cli/__init__.py @@ -0,0 +1,6 @@ +from .__main__ import cli_detect, query_yes_no + +__all__ = ( + "cli_detect", + "query_yes_no", +) diff --git a/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/cli/__main__.py b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/cli/__main__.py new file mode 100644 index 0000000..e7edd0f --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/cli/__main__.py @@ -0,0 +1,320 @@ +import argparse +import sys +from json import dumps +from os.path import abspath, basename, dirname, join, realpath +from platform import python_version +from typing import List, Optional +from unicodedata import unidata_version + +import charset_normalizer.md as md_module +from charset_normalizer import from_fp +from charset_normalizer.models import CliDetectionResult +from charset_normalizer.version import __version__ + + +def query_yes_no(question: str, default: str = "yes") -> bool: + """Ask a yes/no question via input() and return their answer. + + "question" is a string that is presented to the user. + "default" is the presumed answer if the user just hits . + It must be "yes" (the default), "no" or None (meaning + an answer is required of the user). + + The "answer" return value is True for "yes" or False for "no". + + Credit goes to (c) https://stackoverflow.com/questions/3041986/apt-command-line-interface-like-yes-no-input + """ + valid = {"yes": True, "y": True, "ye": True, "no": False, "n": False} + if default is None: + prompt = " [y/n] " + elif default == "yes": + prompt = " [Y/n] " + elif default == "no": + prompt = " [y/N] " + else: + raise ValueError("invalid default answer: '%s'" % default) + + while True: + sys.stdout.write(question + prompt) + choice = input().lower() + if default is not None and choice == "": + return valid[default] + elif choice in valid: + return valid[choice] + else: + sys.stdout.write("Please respond with 'yes' or 'no' " "(or 'y' or 'n').\n") + + +def cli_detect(argv: Optional[List[str]] = None) -> int: + """ + CLI assistant using ARGV and ArgumentParser + :param argv: + :return: 0 if everything is fine, anything else equal trouble + """ + parser = argparse.ArgumentParser( + description="The Real First Universal Charset Detector. " + "Discover originating encoding used on text file. " + "Normalize text to unicode." + ) + + parser.add_argument( + "files", type=argparse.FileType("rb"), nargs="+", help="File(s) to be analysed" + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + default=False, + dest="verbose", + help="Display complementary information about file if any. " + "Stdout will contain logs about the detection process.", + ) + parser.add_argument( + "-a", + "--with-alternative", + action="store_true", + default=False, + dest="alternatives", + help="Output complementary possibilities if any. Top-level JSON WILL be a list.", + ) + parser.add_argument( + "-n", + "--normalize", + action="store_true", + default=False, + dest="normalize", + help="Permit to normalize input file. If not set, program does not write anything.", + ) + parser.add_argument( + "-m", + "--minimal", + action="store_true", + default=False, + dest="minimal", + help="Only output the charset detected to STDOUT. Disabling JSON output.", + ) + parser.add_argument( + "-r", + "--replace", + action="store_true", + default=False, + dest="replace", + help="Replace file when trying to normalize it instead of creating a new one.", + ) + parser.add_argument( + "-f", + "--force", + action="store_true", + default=False, + dest="force", + help="Replace file without asking if you are sure, use this flag with caution.", + ) + parser.add_argument( + "-i", + "--no-preemptive", + action="store_true", + default=False, + dest="no_preemptive", + help="Disable looking at a charset declaration to hint the detector.", + ) + parser.add_argument( + "-t", + "--threshold", + action="store", + default=0.2, + type=float, + dest="threshold", + help="Define a custom maximum amount of chaos allowed in decoded content. 0. <= chaos <= 1.", + ) + parser.add_argument( + "--version", + action="version", + version="Charset-Normalizer {} - Python {} - Unicode {} - SpeedUp {}".format( + __version__, + python_version(), + unidata_version, + "OFF" if md_module.__file__.lower().endswith(".py") else "ON", + ), + help="Show version information and exit.", + ) + + args = parser.parse_args(argv) + + if args.replace is True and args.normalize is False: + if args.files: + for my_file in args.files: + my_file.close() + print("Use --replace in addition of --normalize only.", file=sys.stderr) + return 1 + + if args.force is True and args.replace is False: + if args.files: + for my_file in args.files: + my_file.close() + print("Use --force in addition of --replace only.", file=sys.stderr) + return 1 + + if args.threshold < 0.0 or args.threshold > 1.0: + if args.files: + for my_file in args.files: + my_file.close() + print("--threshold VALUE should be between 0. AND 1.", file=sys.stderr) + return 1 + + x_ = [] + + for my_file in args.files: + matches = from_fp( + my_file, + threshold=args.threshold, + explain=args.verbose, + preemptive_behaviour=args.no_preemptive is False, + ) + + best_guess = matches.best() + + if best_guess is None: + print( + 'Unable to identify originating encoding for "{}". {}'.format( + my_file.name, + ( + "Maybe try increasing maximum amount of chaos." + if args.threshold < 1.0 + else "" + ), + ), + file=sys.stderr, + ) + x_.append( + CliDetectionResult( + abspath(my_file.name), + None, + [], + [], + "Unknown", + [], + False, + 1.0, + 0.0, + None, + True, + ) + ) + else: + x_.append( + CliDetectionResult( + abspath(my_file.name), + best_guess.encoding, + best_guess.encoding_aliases, + [ + cp + for cp in best_guess.could_be_from_charset + if cp != best_guess.encoding + ], + best_guess.language, + best_guess.alphabets, + best_guess.bom, + best_guess.percent_chaos, + best_guess.percent_coherence, + None, + True, + ) + ) + + if len(matches) > 1 and args.alternatives: + for el in matches: + if el != best_guess: + x_.append( + CliDetectionResult( + abspath(my_file.name), + el.encoding, + el.encoding_aliases, + [ + cp + for cp in el.could_be_from_charset + if cp != el.encoding + ], + el.language, + el.alphabets, + el.bom, + el.percent_chaos, + el.percent_coherence, + None, + False, + ) + ) + + if args.normalize is True: + if best_guess.encoding.startswith("utf") is True: + print( + '"{}" file does not need to be normalized, as it already came from unicode.'.format( + my_file.name + ), + file=sys.stderr, + ) + if my_file.closed is False: + my_file.close() + continue + + dir_path = dirname(realpath(my_file.name)) + file_name = basename(realpath(my_file.name)) + + o_: List[str] = file_name.split(".") + + if args.replace is False: + o_.insert(-1, best_guess.encoding) + if my_file.closed is False: + my_file.close() + elif ( + args.force is False + and query_yes_no( + 'Are you sure to normalize "{}" by replacing it ?'.format( + my_file.name + ), + "no", + ) + is False + ): + if my_file.closed is False: + my_file.close() + continue + + try: + x_[0].unicode_path = join(dir_path, ".".join(o_)) + + with open(x_[0].unicode_path, "wb") as fp: + fp.write(best_guess.output()) + except IOError as e: + print(str(e), file=sys.stderr) + if my_file.closed is False: + my_file.close() + return 2 + + if my_file.closed is False: + my_file.close() + + if args.minimal is False: + print( + dumps( + [el.__dict__ for el in x_] if len(x_) > 1 else x_[0].__dict__, + ensure_ascii=True, + indent=4, + ) + ) + else: + for my_file in args.files: + print( + ", ".join( + [ + el.encoding or "undefined" + for el in x_ + if el.path == abspath(my_file.name) + ] + ) + ) + + return 0 + + +if __name__ == "__main__": + cli_detect() diff --git a/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/constant.py b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/constant.py new file mode 100644 index 0000000..f8f2a81 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/constant.py @@ -0,0 +1,1997 @@ +# -*- coding: utf-8 -*- +from codecs import BOM_UTF8, BOM_UTF16_BE, BOM_UTF16_LE, BOM_UTF32_BE, BOM_UTF32_LE +from encodings.aliases import aliases +from re import IGNORECASE, compile as re_compile +from typing import Dict, List, Set, Union + +# Contain for each eligible encoding a list of/item bytes SIG/BOM +ENCODING_MARKS: Dict[str, Union[bytes, List[bytes]]] = { + "utf_8": BOM_UTF8, + "utf_7": [ + b"\x2b\x2f\x76\x38", + b"\x2b\x2f\x76\x39", + b"\x2b\x2f\x76\x2b", + b"\x2b\x2f\x76\x2f", + b"\x2b\x2f\x76\x38\x2d", + ], + "gb18030": b"\x84\x31\x95\x33", + "utf_32": [BOM_UTF32_BE, BOM_UTF32_LE], + "utf_16": [BOM_UTF16_BE, BOM_UTF16_LE], +} + +TOO_SMALL_SEQUENCE: int = 32 +TOO_BIG_SEQUENCE: int = int(10e6) + +UTF8_MAXIMAL_ALLOCATION: int = 1_112_064 + +# Up-to-date Unicode ucd/15.0.0 +UNICODE_RANGES_COMBINED: Dict[str, range] = { + "Control character": range(32), + "Basic Latin": range(32, 128), + "Latin-1 Supplement": range(128, 256), + "Latin Extended-A": range(256, 384), + "Latin Extended-B": range(384, 592), + "IPA Extensions": range(592, 688), + "Spacing Modifier Letters": range(688, 768), + "Combining Diacritical Marks": range(768, 880), + "Greek and Coptic": range(880, 1024), + "Cyrillic": range(1024, 1280), + "Cyrillic Supplement": range(1280, 1328), + "Armenian": range(1328, 1424), + "Hebrew": range(1424, 1536), + "Arabic": range(1536, 1792), + "Syriac": range(1792, 1872), + "Arabic Supplement": range(1872, 1920), + "Thaana": range(1920, 1984), + "NKo": range(1984, 2048), + "Samaritan": range(2048, 2112), + "Mandaic": range(2112, 2144), + "Syriac Supplement": range(2144, 2160), + "Arabic Extended-B": range(2160, 2208), + "Arabic Extended-A": range(2208, 2304), + "Devanagari": range(2304, 2432), + "Bengali": range(2432, 2560), + "Gurmukhi": range(2560, 2688), + "Gujarati": range(2688, 2816), + "Oriya": range(2816, 2944), + "Tamil": range(2944, 3072), + "Telugu": range(3072, 3200), + "Kannada": range(3200, 3328), + "Malayalam": range(3328, 3456), + "Sinhala": range(3456, 3584), + "Thai": range(3584, 3712), + "Lao": range(3712, 3840), + "Tibetan": range(3840, 4096), + "Myanmar": range(4096, 4256), + "Georgian": range(4256, 4352), + "Hangul Jamo": range(4352, 4608), + "Ethiopic": range(4608, 4992), + "Ethiopic Supplement": range(4992, 5024), + "Cherokee": range(5024, 5120), + "Unified Canadian Aboriginal Syllabics": range(5120, 5760), + "Ogham": range(5760, 5792), + "Runic": range(5792, 5888), + "Tagalog": range(5888, 5920), + "Hanunoo": range(5920, 5952), + "Buhid": range(5952, 5984), + "Tagbanwa": range(5984, 6016), + "Khmer": range(6016, 6144), + "Mongolian": range(6144, 6320), + "Unified Canadian Aboriginal Syllabics Extended": range(6320, 6400), + "Limbu": range(6400, 6480), + "Tai Le": range(6480, 6528), + "New Tai Lue": range(6528, 6624), + "Khmer Symbols": range(6624, 6656), + "Buginese": range(6656, 6688), + "Tai Tham": range(6688, 6832), + "Combining Diacritical Marks Extended": range(6832, 6912), + "Balinese": range(6912, 7040), + "Sundanese": range(7040, 7104), + "Batak": range(7104, 7168), + "Lepcha": range(7168, 7248), + "Ol Chiki": range(7248, 7296), + "Cyrillic Extended-C": range(7296, 7312), + "Georgian Extended": range(7312, 7360), + "Sundanese Supplement": range(7360, 7376), + "Vedic Extensions": range(7376, 7424), + "Phonetic Extensions": range(7424, 7552), + "Phonetic Extensions Supplement": range(7552, 7616), + "Combining Diacritical Marks Supplement": range(7616, 7680), + "Latin Extended Additional": range(7680, 7936), + "Greek Extended": range(7936, 8192), + "General Punctuation": range(8192, 8304), + "Superscripts and Subscripts": range(8304, 8352), + "Currency Symbols": range(8352, 8400), + "Combining Diacritical Marks for Symbols": range(8400, 8448), + "Letterlike Symbols": range(8448, 8528), + "Number Forms": range(8528, 8592), + "Arrows": range(8592, 8704), + "Mathematical Operators": range(8704, 8960), + "Miscellaneous Technical": range(8960, 9216), + "Control Pictures": range(9216, 9280), + "Optical Character Recognition": range(9280, 9312), + "Enclosed Alphanumerics": range(9312, 9472), + "Box Drawing": range(9472, 9600), + "Block Elements": range(9600, 9632), + "Geometric Shapes": range(9632, 9728), + "Miscellaneous Symbols": range(9728, 9984), + "Dingbats": range(9984, 10176), + "Miscellaneous Mathematical Symbols-A": range(10176, 10224), + "Supplemental Arrows-A": range(10224, 10240), + "Braille Patterns": range(10240, 10496), + "Supplemental Arrows-B": range(10496, 10624), + "Miscellaneous Mathematical Symbols-B": range(10624, 10752), + "Supplemental Mathematical Operators": range(10752, 11008), + "Miscellaneous Symbols and Arrows": range(11008, 11264), + "Glagolitic": range(11264, 11360), + "Latin Extended-C": range(11360, 11392), + "Coptic": range(11392, 11520), + "Georgian Supplement": range(11520, 11568), + "Tifinagh": range(11568, 11648), + "Ethiopic Extended": range(11648, 11744), + "Cyrillic Extended-A": range(11744, 11776), + "Supplemental Punctuation": range(11776, 11904), + "CJK Radicals Supplement": range(11904, 12032), + "Kangxi Radicals": range(12032, 12256), + "Ideographic Description Characters": range(12272, 12288), + "CJK Symbols and Punctuation": range(12288, 12352), + "Hiragana": range(12352, 12448), + "Katakana": range(12448, 12544), + "Bopomofo": range(12544, 12592), + "Hangul Compatibility Jamo": range(12592, 12688), + "Kanbun": range(12688, 12704), + "Bopomofo Extended": range(12704, 12736), + "CJK Strokes": range(12736, 12784), + "Katakana Phonetic Extensions": range(12784, 12800), + "Enclosed CJK Letters and Months": range(12800, 13056), + "CJK Compatibility": range(13056, 13312), + "CJK Unified Ideographs Extension A": range(13312, 19904), + "Yijing Hexagram Symbols": range(19904, 19968), + "CJK Unified Ideographs": range(19968, 40960), + "Yi Syllables": range(40960, 42128), + "Yi Radicals": range(42128, 42192), + "Lisu": range(42192, 42240), + "Vai": range(42240, 42560), + "Cyrillic Extended-B": range(42560, 42656), + "Bamum": range(42656, 42752), + "Modifier Tone Letters": range(42752, 42784), + "Latin Extended-D": range(42784, 43008), + "Syloti Nagri": range(43008, 43056), + "Common Indic Number Forms": range(43056, 43072), + "Phags-pa": range(43072, 43136), + "Saurashtra": range(43136, 43232), + "Devanagari Extended": range(43232, 43264), + "Kayah Li": range(43264, 43312), + "Rejang": range(43312, 43360), + "Hangul Jamo Extended-A": range(43360, 43392), + "Javanese": range(43392, 43488), + "Myanmar Extended-B": range(43488, 43520), + "Cham": range(43520, 43616), + "Myanmar Extended-A": range(43616, 43648), + "Tai Viet": range(43648, 43744), + "Meetei Mayek Extensions": range(43744, 43776), + "Ethiopic Extended-A": range(43776, 43824), + "Latin Extended-E": range(43824, 43888), + "Cherokee Supplement": range(43888, 43968), + "Meetei Mayek": range(43968, 44032), + "Hangul Syllables": range(44032, 55216), + "Hangul Jamo Extended-B": range(55216, 55296), + "High Surrogates": range(55296, 56192), + "High Private Use Surrogates": range(56192, 56320), + "Low Surrogates": range(56320, 57344), + "Private Use Area": range(57344, 63744), + "CJK Compatibility Ideographs": range(63744, 64256), + "Alphabetic Presentation Forms": range(64256, 64336), + "Arabic Presentation Forms-A": range(64336, 65024), + "Variation Selectors": range(65024, 65040), + "Vertical Forms": range(65040, 65056), + "Combining Half Marks": range(65056, 65072), + "CJK Compatibility Forms": range(65072, 65104), + "Small Form Variants": range(65104, 65136), + "Arabic Presentation Forms-B": range(65136, 65280), + "Halfwidth and Fullwidth Forms": range(65280, 65520), + "Specials": range(65520, 65536), + "Linear B Syllabary": range(65536, 65664), + "Linear B Ideograms": range(65664, 65792), + "Aegean Numbers": range(65792, 65856), + "Ancient Greek Numbers": range(65856, 65936), + "Ancient Symbols": range(65936, 66000), + "Phaistos Disc": range(66000, 66048), + "Lycian": range(66176, 66208), + "Carian": range(66208, 66272), + "Coptic Epact Numbers": range(66272, 66304), + "Old Italic": range(66304, 66352), + "Gothic": range(66352, 66384), + "Old Permic": range(66384, 66432), + "Ugaritic": range(66432, 66464), + "Old Persian": range(66464, 66528), + "Deseret": range(66560, 66640), + "Shavian": range(66640, 66688), + "Osmanya": range(66688, 66736), + "Osage": range(66736, 66816), + "Elbasan": range(66816, 66864), + "Caucasian Albanian": range(66864, 66928), + "Vithkuqi": range(66928, 67008), + "Linear A": range(67072, 67456), + "Latin Extended-F": range(67456, 67520), + "Cypriot Syllabary": range(67584, 67648), + "Imperial Aramaic": range(67648, 67680), + "Palmyrene": range(67680, 67712), + "Nabataean": range(67712, 67760), + "Hatran": range(67808, 67840), + "Phoenician": range(67840, 67872), + "Lydian": range(67872, 67904), + "Meroitic Hieroglyphs": range(67968, 68000), + "Meroitic Cursive": range(68000, 68096), + "Kharoshthi": range(68096, 68192), + "Old South Arabian": range(68192, 68224), + "Old North Arabian": range(68224, 68256), + "Manichaean": range(68288, 68352), + "Avestan": range(68352, 68416), + "Inscriptional Parthian": range(68416, 68448), + "Inscriptional Pahlavi": range(68448, 68480), + "Psalter Pahlavi": range(68480, 68528), + "Old Turkic": range(68608, 68688), + "Old Hungarian": range(68736, 68864), + "Hanifi Rohingya": range(68864, 68928), + "Rumi Numeral Symbols": range(69216, 69248), + "Yezidi": range(69248, 69312), + "Arabic Extended-C": range(69312, 69376), + "Old Sogdian": range(69376, 69424), + "Sogdian": range(69424, 69488), + "Old Uyghur": range(69488, 69552), + "Chorasmian": range(69552, 69600), + "Elymaic": range(69600, 69632), + "Brahmi": range(69632, 69760), + "Kaithi": range(69760, 69840), + "Sora Sompeng": range(69840, 69888), + "Chakma": range(69888, 69968), + "Mahajani": range(69968, 70016), + "Sharada": range(70016, 70112), + "Sinhala Archaic Numbers": range(70112, 70144), + "Khojki": range(70144, 70224), + "Multani": range(70272, 70320), + "Khudawadi": range(70320, 70400), + "Grantha": range(70400, 70528), + "Newa": range(70656, 70784), + "Tirhuta": range(70784, 70880), + "Siddham": range(71040, 71168), + "Modi": range(71168, 71264), + "Mongolian Supplement": range(71264, 71296), + "Takri": range(71296, 71376), + "Ahom": range(71424, 71504), + "Dogra": range(71680, 71760), + "Warang Citi": range(71840, 71936), + "Dives Akuru": range(71936, 72032), + "Nandinagari": range(72096, 72192), + "Zanabazar Square": range(72192, 72272), + "Soyombo": range(72272, 72368), + "Unified Canadian Aboriginal Syllabics Extended-A": range(72368, 72384), + "Pau Cin Hau": range(72384, 72448), + "Devanagari Extended-A": range(72448, 72544), + "Bhaiksuki": range(72704, 72816), + "Marchen": range(72816, 72896), + "Masaram Gondi": range(72960, 73056), + "Gunjala Gondi": range(73056, 73136), + "Makasar": range(73440, 73472), + "Kawi": range(73472, 73568), + "Lisu Supplement": range(73648, 73664), + "Tamil Supplement": range(73664, 73728), + "Cuneiform": range(73728, 74752), + "Cuneiform Numbers and Punctuation": range(74752, 74880), + "Early Dynastic Cuneiform": range(74880, 75088), + "Cypro-Minoan": range(77712, 77824), + "Egyptian Hieroglyphs": range(77824, 78896), + "Egyptian Hieroglyph Format Controls": range(78896, 78944), + "Anatolian Hieroglyphs": range(82944, 83584), + "Bamum Supplement": range(92160, 92736), + "Mro": range(92736, 92784), + "Tangsa": range(92784, 92880), + "Bassa Vah": range(92880, 92928), + "Pahawh Hmong": range(92928, 93072), + "Medefaidrin": range(93760, 93856), + "Miao": range(93952, 94112), + "Ideographic Symbols and Punctuation": range(94176, 94208), + "Tangut": range(94208, 100352), + "Tangut Components": range(100352, 101120), + "Khitan Small Script": range(101120, 101632), + "Tangut Supplement": range(101632, 101760), + "Kana Extended-B": range(110576, 110592), + "Kana Supplement": range(110592, 110848), + "Kana Extended-A": range(110848, 110896), + "Small Kana Extension": range(110896, 110960), + "Nushu": range(110960, 111360), + "Duployan": range(113664, 113824), + "Shorthand Format Controls": range(113824, 113840), + "Znamenny Musical Notation": range(118528, 118736), + "Byzantine Musical Symbols": range(118784, 119040), + "Musical Symbols": range(119040, 119296), + "Ancient Greek Musical Notation": range(119296, 119376), + "Kaktovik Numerals": range(119488, 119520), + "Mayan Numerals": range(119520, 119552), + "Tai Xuan Jing Symbols": range(119552, 119648), + "Counting Rod Numerals": range(119648, 119680), + "Mathematical Alphanumeric Symbols": range(119808, 120832), + "Sutton SignWriting": range(120832, 121520), + "Latin Extended-G": range(122624, 122880), + "Glagolitic Supplement": range(122880, 122928), + "Cyrillic Extended-D": range(122928, 123024), + "Nyiakeng Puachue Hmong": range(123136, 123216), + "Toto": range(123536, 123584), + "Wancho": range(123584, 123648), + "Nag Mundari": range(124112, 124160), + "Ethiopic Extended-B": range(124896, 124928), + "Mende Kikakui": range(124928, 125152), + "Adlam": range(125184, 125280), + "Indic Siyaq Numbers": range(126064, 126144), + "Ottoman Siyaq Numbers": range(126208, 126288), + "Arabic Mathematical Alphabetic Symbols": range(126464, 126720), + "Mahjong Tiles": range(126976, 127024), + "Domino Tiles": range(127024, 127136), + "Playing Cards": range(127136, 127232), + "Enclosed Alphanumeric Supplement": range(127232, 127488), + "Enclosed Ideographic Supplement": range(127488, 127744), + "Miscellaneous Symbols and Pictographs": range(127744, 128512), + "Emoticons range(Emoji)": range(128512, 128592), + "Ornamental Dingbats": range(128592, 128640), + "Transport and Map Symbols": range(128640, 128768), + "Alchemical Symbols": range(128768, 128896), + "Geometric Shapes Extended": range(128896, 129024), + "Supplemental Arrows-C": range(129024, 129280), + "Supplemental Symbols and Pictographs": range(129280, 129536), + "Chess Symbols": range(129536, 129648), + "Symbols and Pictographs Extended-A": range(129648, 129792), + "Symbols for Legacy Computing": range(129792, 130048), + "CJK Unified Ideographs Extension B": range(131072, 173792), + "CJK Unified Ideographs Extension C": range(173824, 177984), + "CJK Unified Ideographs Extension D": range(177984, 178208), + "CJK Unified Ideographs Extension E": range(178208, 183984), + "CJK Unified Ideographs Extension F": range(183984, 191472), + "CJK Compatibility Ideographs Supplement": range(194560, 195104), + "CJK Unified Ideographs Extension G": range(196608, 201552), + "CJK Unified Ideographs Extension H": range(201552, 205744), + "Tags": range(917504, 917632), + "Variation Selectors Supplement": range(917760, 918000), + "Supplementary Private Use Area-A": range(983040, 1048576), + "Supplementary Private Use Area-B": range(1048576, 1114112), +} + + +UNICODE_SECONDARY_RANGE_KEYWORD: List[str] = [ + "Supplement", + "Extended", + "Extensions", + "Modifier", + "Marks", + "Punctuation", + "Symbols", + "Forms", + "Operators", + "Miscellaneous", + "Drawing", + "Block", + "Shapes", + "Supplemental", + "Tags", +] + +RE_POSSIBLE_ENCODING_INDICATION = re_compile( + r"(?:(?:encoding)|(?:charset)|(?:coding))(?:[\:= ]{1,10})(?:[\"\']?)([a-zA-Z0-9\-_]+)(?:[\"\']?)", + IGNORECASE, +) + +IANA_NO_ALIASES = [ + "cp720", + "cp737", + "cp856", + "cp874", + "cp875", + "cp1006", + "koi8_r", + "koi8_t", + "koi8_u", +] + +IANA_SUPPORTED: List[str] = sorted( + filter( + lambda x: x.endswith("_codec") is False + and x not in {"rot_13", "tactis", "mbcs"}, + list(set(aliases.values())) + IANA_NO_ALIASES, + ) +) + +IANA_SUPPORTED_COUNT: int = len(IANA_SUPPORTED) + +# pre-computed code page that are similar using the function cp_similarity. +IANA_SUPPORTED_SIMILAR: Dict[str, List[str]] = { + "cp037": ["cp1026", "cp1140", "cp273", "cp500"], + "cp1026": ["cp037", "cp1140", "cp273", "cp500"], + "cp1125": ["cp866"], + "cp1140": ["cp037", "cp1026", "cp273", "cp500"], + "cp1250": ["iso8859_2"], + "cp1251": ["kz1048", "ptcp154"], + "cp1252": ["iso8859_15", "iso8859_9", "latin_1"], + "cp1253": ["iso8859_7"], + "cp1254": ["iso8859_15", "iso8859_9", "latin_1"], + "cp1257": ["iso8859_13"], + "cp273": ["cp037", "cp1026", "cp1140", "cp500"], + "cp437": ["cp850", "cp858", "cp860", "cp861", "cp862", "cp863", "cp865"], + "cp500": ["cp037", "cp1026", "cp1140", "cp273"], + "cp850": ["cp437", "cp857", "cp858", "cp865"], + "cp857": ["cp850", "cp858", "cp865"], + "cp858": ["cp437", "cp850", "cp857", "cp865"], + "cp860": ["cp437", "cp861", "cp862", "cp863", "cp865"], + "cp861": ["cp437", "cp860", "cp862", "cp863", "cp865"], + "cp862": ["cp437", "cp860", "cp861", "cp863", "cp865"], + "cp863": ["cp437", "cp860", "cp861", "cp862", "cp865"], + "cp865": ["cp437", "cp850", "cp857", "cp858", "cp860", "cp861", "cp862", "cp863"], + "cp866": ["cp1125"], + "iso8859_10": ["iso8859_14", "iso8859_15", "iso8859_4", "iso8859_9", "latin_1"], + "iso8859_11": ["tis_620"], + "iso8859_13": ["cp1257"], + "iso8859_14": [ + "iso8859_10", + "iso8859_15", + "iso8859_16", + "iso8859_3", + "iso8859_9", + "latin_1", + ], + "iso8859_15": [ + "cp1252", + "cp1254", + "iso8859_10", + "iso8859_14", + "iso8859_16", + "iso8859_3", + "iso8859_9", + "latin_1", + ], + "iso8859_16": [ + "iso8859_14", + "iso8859_15", + "iso8859_2", + "iso8859_3", + "iso8859_9", + "latin_1", + ], + "iso8859_2": ["cp1250", "iso8859_16", "iso8859_4"], + "iso8859_3": ["iso8859_14", "iso8859_15", "iso8859_16", "iso8859_9", "latin_1"], + "iso8859_4": ["iso8859_10", "iso8859_2", "iso8859_9", "latin_1"], + "iso8859_7": ["cp1253"], + "iso8859_9": [ + "cp1252", + "cp1254", + "cp1258", + "iso8859_10", + "iso8859_14", + "iso8859_15", + "iso8859_16", + "iso8859_3", + "iso8859_4", + "latin_1", + ], + "kz1048": ["cp1251", "ptcp154"], + "latin_1": [ + "cp1252", + "cp1254", + "cp1258", + "iso8859_10", + "iso8859_14", + "iso8859_15", + "iso8859_16", + "iso8859_3", + "iso8859_4", + "iso8859_9", + ], + "mac_iceland": ["mac_roman", "mac_turkish"], + "mac_roman": ["mac_iceland", "mac_turkish"], + "mac_turkish": ["mac_iceland", "mac_roman"], + "ptcp154": ["cp1251", "kz1048"], + "tis_620": ["iso8859_11"], +} + + +CHARDET_CORRESPONDENCE: Dict[str, str] = { + "iso2022_kr": "ISO-2022-KR", + "iso2022_jp": "ISO-2022-JP", + "euc_kr": "EUC-KR", + "tis_620": "TIS-620", + "utf_32": "UTF-32", + "euc_jp": "EUC-JP", + "koi8_r": "KOI8-R", + "iso8859_1": "ISO-8859-1", + "iso8859_2": "ISO-8859-2", + "iso8859_5": "ISO-8859-5", + "iso8859_6": "ISO-8859-6", + "iso8859_7": "ISO-8859-7", + "iso8859_8": "ISO-8859-8", + "utf_16": "UTF-16", + "cp855": "IBM855", + "mac_cyrillic": "MacCyrillic", + "gb2312": "GB2312", + "gb18030": "GB18030", + "cp932": "CP932", + "cp866": "IBM866", + "utf_8": "utf-8", + "utf_8_sig": "UTF-8-SIG", + "shift_jis": "SHIFT_JIS", + "big5": "Big5", + "cp1250": "windows-1250", + "cp1251": "windows-1251", + "cp1252": "Windows-1252", + "cp1253": "windows-1253", + "cp1255": "windows-1255", + "cp1256": "windows-1256", + "cp1254": "Windows-1254", + "cp949": "CP949", +} + + +COMMON_SAFE_ASCII_CHARACTERS: Set[str] = { + "<", + ">", + "=", + ":", + "/", + "&", + ";", + "{", + "}", + "[", + "]", + ",", + "|", + '"', + "-", + "(", + ")", +} + + +KO_NAMES: Set[str] = {"johab", "cp949", "euc_kr"} +ZH_NAMES: Set[str] = {"big5", "cp950", "big5hkscs", "hz"} + +# Logging LEVEL below DEBUG +TRACE: int = 5 + + +# Language label that contain the em dash "—" +# character are to be considered alternative seq to origin +FREQUENCIES: Dict[str, List[str]] = { + "English": [ + "e", + "a", + "t", + "i", + "o", + "n", + "s", + "r", + "h", + "l", + "d", + "c", + "u", + "m", + "f", + "p", + "g", + "w", + "y", + "b", + "v", + "k", + "x", + "j", + "z", + "q", + ], + "English—": [ + "e", + "a", + "t", + "i", + "o", + "n", + "s", + "r", + "h", + "l", + "d", + "c", + "m", + "u", + "f", + "p", + "g", + "w", + "b", + "y", + "v", + "k", + "j", + "x", + "z", + "q", + ], + "German": [ + "e", + "n", + "i", + "r", + "s", + "t", + "a", + "d", + "h", + "u", + "l", + "g", + "o", + "c", + "m", + "b", + "f", + "k", + "w", + "z", + "p", + "v", + "ü", + "ä", + "ö", + "j", + ], + "French": [ + "e", + "a", + "s", + "n", + "i", + "t", + "r", + "l", + "u", + "o", + "d", + "c", + "p", + "m", + "é", + "v", + "g", + "f", + "b", + "h", + "q", + "à", + "x", + "è", + "y", + "j", + ], + "Dutch": [ + "e", + "n", + "a", + "i", + "r", + "t", + "o", + "d", + "s", + "l", + "g", + "h", + "v", + "m", + "u", + "k", + "c", + "p", + "b", + "w", + "j", + "z", + "f", + "y", + "x", + "ë", + ], + "Italian": [ + "e", + "i", + "a", + "o", + "n", + "l", + "t", + "r", + "s", + "c", + "d", + "u", + "p", + "m", + "g", + "v", + "f", + "b", + "z", + "h", + "q", + "è", + "à", + "k", + "y", + "ò", + ], + "Polish": [ + "a", + "i", + "o", + "e", + "n", + "r", + "z", + "w", + "s", + "c", + "t", + "k", + "y", + "d", + "p", + "m", + "u", + "l", + "j", + "ł", + "g", + "b", + "h", + "ą", + "ę", + "ó", + ], + "Spanish": [ + "e", + "a", + "o", + "n", + "s", + "r", + "i", + "l", + "d", + "t", + "c", + "u", + "m", + "p", + "b", + "g", + "v", + "f", + "y", + "ó", + "h", + "q", + "í", + "j", + "z", + "á", + ], + "Russian": [ + "о", + "а", + "е", + "и", + "н", + "с", + "т", + "р", + "в", + "л", + "к", + "м", + "д", + "п", + "у", + "г", + "я", + "ы", + "з", + "б", + "й", + "ь", + "ч", + "х", + "ж", + "ц", + ], + # Jap-Kanji + "Japanese": [ + "人", + "一", + "大", + "亅", + "丁", + "丨", + "竹", + "笑", + "口", + "日", + "今", + "二", + "彳", + "行", + "十", + "土", + "丶", + "寸", + "寺", + "時", + "乙", + "丿", + "乂", + "气", + "気", + "冂", + "巾", + "亠", + "市", + "目", + "儿", + "見", + "八", + "小", + "凵", + "県", + "月", + "彐", + "門", + "間", + "木", + "東", + "山", + "出", + "本", + "中", + "刀", + "分", + "耳", + "又", + "取", + "最", + "言", + "田", + "心", + "思", + "刂", + "前", + "京", + "尹", + "事", + "生", + "厶", + "云", + "会", + "未", + "来", + "白", + "冫", + "楽", + "灬", + "馬", + "尸", + "尺", + "駅", + "明", + "耂", + "者", + "了", + "阝", + "都", + "高", + "卜", + "占", + "厂", + "广", + "店", + "子", + "申", + "奄", + "亻", + "俺", + "上", + "方", + "冖", + "学", + "衣", + "艮", + "食", + "自", + ], + # Jap-Katakana + "Japanese—": [ + "ー", + "ン", + "ス", + "・", + "ル", + "ト", + "リ", + "イ", + "ア", + "ラ", + "ッ", + "ク", + "ド", + "シ", + "レ", + "ジ", + "タ", + "フ", + "ロ", + "カ", + "テ", + "マ", + "ィ", + "グ", + "バ", + "ム", + "プ", + "オ", + "コ", + "デ", + "ニ", + "ウ", + "メ", + "サ", + "ビ", + "ナ", + "ブ", + "ャ", + "エ", + "ュ", + "チ", + "キ", + "ズ", + "ダ", + "パ", + "ミ", + "ェ", + "ョ", + "ハ", + "セ", + "ベ", + "ガ", + "モ", + "ツ", + "ネ", + "ボ", + "ソ", + "ノ", + "ァ", + "ヴ", + "ワ", + "ポ", + "ペ", + "ピ", + "ケ", + "ゴ", + "ギ", + "ザ", + "ホ", + "ゲ", + "ォ", + "ヤ", + "ヒ", + "ユ", + "ヨ", + "ヘ", + "ゼ", + "ヌ", + "ゥ", + "ゾ", + "ヶ", + "ヂ", + "ヲ", + "ヅ", + "ヵ", + "ヱ", + "ヰ", + "ヮ", + "ヽ", + "゠", + "ヾ", + "ヷ", + "ヿ", + "ヸ", + "ヹ", + "ヺ", + ], + # Jap-Hiragana + "Japanese——": [ + "の", + "に", + "る", + "た", + "と", + "は", + "し", + "い", + "を", + "で", + "て", + "が", + "な", + "れ", + "か", + "ら", + "さ", + "っ", + "り", + "す", + "あ", + "も", + "こ", + "ま", + "う", + "く", + "よ", + "き", + "ん", + "め", + "お", + "け", + "そ", + "つ", + "だ", + "や", + "え", + "ど", + "わ", + "ち", + "み", + "せ", + "じ", + "ば", + "へ", + "び", + "ず", + "ろ", + "ほ", + "げ", + "む", + "べ", + "ひ", + "ょ", + "ゆ", + "ぶ", + "ご", + "ゃ", + "ね", + "ふ", + "ぐ", + "ぎ", + "ぼ", + "ゅ", + "づ", + "ざ", + "ぞ", + "ぬ", + "ぜ", + "ぱ", + "ぽ", + "ぷ", + "ぴ", + "ぃ", + "ぁ", + "ぇ", + "ぺ", + "ゞ", + "ぢ", + "ぉ", + "ぅ", + "ゐ", + "ゝ", + "ゑ", + "゛", + "゜", + "ゎ", + "ゔ", + "゚", + "ゟ", + "゙", + "ゕ", + "ゖ", + ], + "Portuguese": [ + "a", + "e", + "o", + "s", + "i", + "r", + "d", + "n", + "t", + "m", + "u", + "c", + "l", + "p", + "g", + "v", + "b", + "f", + "h", + "ã", + "q", + "é", + "ç", + "á", + "z", + "í", + ], + "Swedish": [ + "e", + "a", + "n", + "r", + "t", + "s", + "i", + "l", + "d", + "o", + "m", + "k", + "g", + "v", + "h", + "f", + "u", + "p", + "ä", + "c", + "b", + "ö", + "å", + "y", + "j", + "x", + ], + "Chinese": [ + "的", + "一", + "是", + "不", + "了", + "在", + "人", + "有", + "我", + "他", + "这", + "个", + "们", + "中", + "来", + "上", + "大", + "为", + "和", + "国", + "地", + "到", + "以", + "说", + "时", + "要", + "就", + "出", + "会", + "可", + "也", + "你", + "对", + "生", + "能", + "而", + "子", + "那", + "得", + "于", + "着", + "下", + "自", + "之", + "年", + "过", + "发", + "后", + "作", + "里", + "用", + "道", + "行", + "所", + "然", + "家", + "种", + "事", + "成", + "方", + "多", + "经", + "么", + "去", + "法", + "学", + "如", + "都", + "同", + "现", + "当", + "没", + "动", + "面", + "起", + "看", + "定", + "天", + "分", + "还", + "进", + "好", + "小", + "部", + "其", + "些", + "主", + "样", + "理", + "心", + "她", + "本", + "前", + "开", + "但", + "因", + "只", + "从", + "想", + "实", + ], + "Ukrainian": [ + "о", + "а", + "н", + "і", + "и", + "р", + "в", + "т", + "е", + "с", + "к", + "л", + "у", + "д", + "м", + "п", + "з", + "я", + "ь", + "б", + "г", + "й", + "ч", + "х", + "ц", + "ї", + ], + "Norwegian": [ + "e", + "r", + "n", + "t", + "a", + "s", + "i", + "o", + "l", + "d", + "g", + "k", + "m", + "v", + "f", + "p", + "u", + "b", + "h", + "å", + "y", + "j", + "ø", + "c", + "æ", + "w", + ], + "Finnish": [ + "a", + "i", + "n", + "t", + "e", + "s", + "l", + "o", + "u", + "k", + "ä", + "m", + "r", + "v", + "j", + "h", + "p", + "y", + "d", + "ö", + "g", + "c", + "b", + "f", + "w", + "z", + ], + "Vietnamese": [ + "n", + "h", + "t", + "i", + "c", + "g", + "a", + "o", + "u", + "m", + "l", + "r", + "à", + "đ", + "s", + "e", + "v", + "p", + "b", + "y", + "ư", + "d", + "á", + "k", + "ộ", + "ế", + ], + "Czech": [ + "o", + "e", + "a", + "n", + "t", + "s", + "i", + "l", + "v", + "r", + "k", + "d", + "u", + "m", + "p", + "í", + "c", + "h", + "z", + "á", + "y", + "j", + "b", + "ě", + "é", + "ř", + ], + "Hungarian": [ + "e", + "a", + "t", + "l", + "s", + "n", + "k", + "r", + "i", + "o", + "z", + "á", + "é", + "g", + "m", + "b", + "y", + "v", + "d", + "h", + "u", + "p", + "j", + "ö", + "f", + "c", + ], + "Korean": [ + "이", + "다", + "에", + "의", + "는", + "로", + "하", + "을", + "가", + "고", + "지", + "서", + "한", + "은", + "기", + "으", + "년", + "대", + "사", + "시", + "를", + "리", + "도", + "인", + "스", + "일", + ], + "Indonesian": [ + "a", + "n", + "e", + "i", + "r", + "t", + "u", + "s", + "d", + "k", + "m", + "l", + "g", + "p", + "b", + "o", + "h", + "y", + "j", + "c", + "w", + "f", + "v", + "z", + "x", + "q", + ], + "Turkish": [ + "a", + "e", + "i", + "n", + "r", + "l", + "ı", + "k", + "d", + "t", + "s", + "m", + "y", + "u", + "o", + "b", + "ü", + "ş", + "v", + "g", + "z", + "h", + "c", + "p", + "ç", + "ğ", + ], + "Romanian": [ + "e", + "i", + "a", + "r", + "n", + "t", + "u", + "l", + "o", + "c", + "s", + "d", + "p", + "m", + "ă", + "f", + "v", + "î", + "g", + "b", + "ș", + "ț", + "z", + "h", + "â", + "j", + ], + "Farsi": [ + "ا", + "ی", + "ر", + "د", + "ن", + "ه", + "و", + "م", + "ت", + "ب", + "س", + "ل", + "ک", + "ش", + "ز", + "ف", + "گ", + "ع", + "خ", + "ق", + "ج", + "آ", + "پ", + "ح", + "ط", + "ص", + ], + "Arabic": [ + "ا", + "ل", + "ي", + "م", + "و", + "ن", + "ر", + "ت", + "ب", + "ة", + "ع", + "د", + "س", + "ف", + "ه", + "ك", + "ق", + "أ", + "ح", + "ج", + "ش", + "ط", + "ص", + "ى", + "خ", + "إ", + ], + "Danish": [ + "e", + "r", + "n", + "t", + "a", + "i", + "s", + "d", + "l", + "o", + "g", + "m", + "k", + "f", + "v", + "u", + "b", + "h", + "p", + "å", + "y", + "ø", + "æ", + "c", + "j", + "w", + ], + "Serbian": [ + "а", + "и", + "о", + "е", + "н", + "р", + "с", + "у", + "т", + "к", + "ј", + "в", + "д", + "м", + "п", + "л", + "г", + "з", + "б", + "a", + "i", + "e", + "o", + "n", + "ц", + "ш", + ], + "Lithuanian": [ + "i", + "a", + "s", + "o", + "r", + "e", + "t", + "n", + "u", + "k", + "m", + "l", + "p", + "v", + "d", + "j", + "g", + "ė", + "b", + "y", + "ų", + "š", + "ž", + "c", + "ą", + "į", + ], + "Slovene": [ + "e", + "a", + "i", + "o", + "n", + "r", + "s", + "l", + "t", + "j", + "v", + "k", + "d", + "p", + "m", + "u", + "z", + "b", + "g", + "h", + "č", + "c", + "š", + "ž", + "f", + "y", + ], + "Slovak": [ + "o", + "a", + "e", + "n", + "i", + "r", + "v", + "t", + "s", + "l", + "k", + "d", + "m", + "p", + "u", + "c", + "h", + "j", + "b", + "z", + "á", + "y", + "ý", + "í", + "č", + "é", + ], + "Hebrew": [ + "י", + "ו", + "ה", + "ל", + "ר", + "ב", + "ת", + "מ", + "א", + "ש", + "נ", + "ע", + "ם", + "ד", + "ק", + "ח", + "פ", + "ס", + "כ", + "ג", + "ט", + "צ", + "ן", + "ז", + "ך", + ], + "Bulgarian": [ + "а", + "и", + "о", + "е", + "н", + "т", + "р", + "с", + "в", + "л", + "к", + "д", + "п", + "м", + "з", + "г", + "я", + "ъ", + "у", + "б", + "ч", + "ц", + "й", + "ж", + "щ", + "х", + ], + "Croatian": [ + "a", + "i", + "o", + "e", + "n", + "r", + "j", + "s", + "t", + "u", + "k", + "l", + "v", + "d", + "m", + "p", + "g", + "z", + "b", + "c", + "č", + "h", + "š", + "ž", + "ć", + "f", + ], + "Hindi": [ + "क", + "र", + "स", + "न", + "त", + "म", + "ह", + "प", + "य", + "ल", + "व", + "ज", + "द", + "ग", + "ब", + "श", + "ट", + "अ", + "ए", + "थ", + "भ", + "ड", + "च", + "ध", + "ष", + "इ", + ], + "Estonian": [ + "a", + "i", + "e", + "s", + "t", + "l", + "u", + "n", + "o", + "k", + "r", + "d", + "m", + "v", + "g", + "p", + "j", + "h", + "ä", + "b", + "õ", + "ü", + "f", + "c", + "ö", + "y", + ], + "Thai": [ + "า", + "น", + "ร", + "อ", + "ก", + "เ", + "ง", + "ม", + "ย", + "ล", + "ว", + "ด", + "ท", + "ส", + "ต", + "ะ", + "ป", + "บ", + "ค", + "ห", + "แ", + "จ", + "พ", + "ช", + "ข", + "ใ", + ], + "Greek": [ + "α", + "τ", + "ο", + "ι", + "ε", + "ν", + "ρ", + "σ", + "κ", + "η", + "π", + "ς", + "υ", + "μ", + "λ", + "ί", + "ό", + "ά", + "γ", + "έ", + "δ", + "ή", + "ω", + "χ", + "θ", + "ύ", + ], + "Tamil": [ + "க", + "த", + "ப", + "ட", + "ர", + "ம", + "ல", + "ன", + "வ", + "ற", + "ய", + "ள", + "ச", + "ந", + "இ", + "ண", + "அ", + "ஆ", + "ழ", + "ங", + "எ", + "உ", + "ஒ", + "ஸ", + ], + "Kazakh": [ + "а", + "ы", + "е", + "н", + "т", + "р", + "л", + "і", + "д", + "с", + "м", + "қ", + "к", + "о", + "б", + "и", + "у", + "ғ", + "ж", + "ң", + "з", + "ш", + "й", + "п", + "г", + "ө", + ], +} + +LANGUAGE_SUPPORTED_COUNT: int = len(FREQUENCIES) diff --git a/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/legacy.py b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/legacy.py new file mode 100644 index 0000000..3f6d490 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/legacy.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Optional +from warnings import warn + +from .api import from_bytes +from .constant import CHARDET_CORRESPONDENCE + +# TODO: remove this check when dropping Python 3.7 support +if TYPE_CHECKING: + from typing_extensions import TypedDict + + class ResultDict(TypedDict): + encoding: Optional[str] + language: str + confidence: Optional[float] + + +def detect( + byte_str: bytes, should_rename_legacy: bool = False, **kwargs: Any +) -> ResultDict: + """ + chardet legacy method + Detect the encoding of the given byte string. It should be mostly backward-compatible. + Encoding name will match Chardet own writing whenever possible. (Not on encoding name unsupported by it) + This function is deprecated and should be used to migrate your project easily, consult the documentation for + further information. Not planned for removal. + + :param byte_str: The byte sequence to examine. + :param should_rename_legacy: Should we rename legacy encodings + to their more modern equivalents? + """ + if len(kwargs): + warn( + f"charset-normalizer disregard arguments '{','.join(list(kwargs.keys()))}' in legacy function detect()" + ) + + if not isinstance(byte_str, (bytearray, bytes)): + raise TypeError( # pragma: nocover + "Expected object of type bytes or bytearray, got: " + "{0}".format(type(byte_str)) + ) + + if isinstance(byte_str, bytearray): + byte_str = bytes(byte_str) + + r = from_bytes(byte_str).best() + + encoding = r.encoding if r is not None else None + language = r.language if r is not None and r.language != "Unknown" else "" + confidence = 1.0 - r.chaos if r is not None else None + + # Note: CharsetNormalizer does not return 'UTF-8-SIG' as the sig get stripped in the detection/normalization process + # but chardet does return 'utf-8-sig' and it is a valid codec name. + if r is not None and encoding == "utf_8" and r.bom: + encoding += "_sig" + + if should_rename_legacy is False and encoding in CHARDET_CORRESPONDENCE: + encoding = CHARDET_CORRESPONDENCE[encoding] + + return { + "encoding": encoding, + "language": language, + "confidence": confidence, + } diff --git a/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/md.cpython-311-x86_64-linux-gnu.so b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/md.cpython-311-x86_64-linux-gnu.so new file mode 100755 index 0000000000000000000000000000000000000000..3824a428ffd621958e1f1f22dfd105c58417ffd0 GIT binary patch literal 16064 zcmeHOZ)_Y_5r3D9n`<3x6Ph-Sk-Uu(DM937yCkFpH|O}@8tl{?%Bt)gEUl64ds70bGAW+gqCE^P$Ab~1XvVc@}i%3F4Wxn{Bd2fE} z-Ss&jA@QaAEbq+xX6C)w*?oI+x9@$uZ>WD$BB9Wd>ORHNLW@B%P#gxMR7M?Ex6-jy z?bNoL*Hr7dTLc1?etC=rR*4*?7bPT6=o!}IS?o;B(np`JhzJ_=;}BN%8-tauFdf-s z;Rw->O~~ zdofqfXuMIqAh@10taO}{#d681dTzyY9OX=vraa|L5K+$H*szn!SMn#Ps$RY_Hr!Jv zm-1uocp-0e^)*g**5wY|!ql0(8krlU9-U&2x#D!0Mloc#oSUJJM&^3l=_+voD`#ZR z>CL-^LOBa(M@L1`6{g0gy~#@6&84g5bUVvg<_@WWp}}K4PG`C^eGsQCbQ9e(=8pm9 zenk}5W@hIUC2^CwEY_IMn131MQRXXfC3q@=LtZ>;I^=tm?-8`60FF7wx@!SEIR91x z_!e|RbVtSrjLFo}fEB=b&Y@0O~{Z&>D%cKHP@zrr$?vdhnE`R6Qi3A?;N zvVFemMFQ;e4bL;Vcg5RI;T>Y0!n?J$*xWWxw(3=(WdE49KX``8zE%6&HT&YDC^6hh1ysgyjJ0DkS>0t_K zX_5}FHQYsZiH9*R=<-)dIgxH|dz_^PW>GSBm<@UnO14IZknIdM?X8H`Ay2oujkf z!Xf`0`6gPEM=YZJ<^R&vTJ6(BZ(OU@ST}aPR^xBD{0^C0;>@T@%(f(2w{A%-(60%* z3FCLY^W-81Z~Qos5gYj0D_qqjAsKHK;} z6TkoB_k}#(Wzq-VlMxiRI0JD8;ta$Yh%*ppAkILXfj9$k2I35SATr>ukBqE`#G1z+ zOBNvW+DmR*4e=LZen@1jTfIwUtjBB>8EX-D!e7>3^7l7x(EoF_R%ZDTeuGR6-`QT~ zFOk%+#`3$O=k>$f{$L2LxHWBuP;sLLkOmMpwIF(Y|HPWeI|ZMXfq`G%Y!ry|IkBTf zsJdZ1dONG*e@^V_6|k0(NBSD7<)=jdFOmH?rN93d!Fu-Sc!y-6ZNJb5giZ)OE%X~g z2YPxwW$mSv;QOqOc3REbVRg25cC>fg({Ak@&F3uJ^+d71qhnumw6L6{mA6oApUr?QTiF!uE~+ zev2@^em%J9hi07>v^0EDHTnAng4(2NR`Yf3TNLJL*q$B=GC#xkHidQ4Vf-Vi-hGs$ zUw5#cI&8n)pO0bu4rOP8h~bk8vvN(x^J%lX&PyRejh|1O=qZ5xD^Sv!3yoih&Z(ny^-Vz+oRT6c3kB*jML&s)1u}JE* zzLoH8^r?c+4>;Yufqf_87K{S4djtCi39q*g!xcQbui{a{>-GO6;kyFkf&GE*r_OxO zR=lb=Gcl3Qs)%f;zJ-pl7`lSEw$ zy~D$CD;0On$(Ov!oSLY(#k`Z7DHi9*B_Ns27eUivj-qj#{?YE?KBw<^FYg<1a@Ddk z>6UW5Ii&Xs$Ge9IdkCc`xzlHhsogtD5bYy@)4Lb)&=z#ZS zam@#R)B&It%};JTF0=4Qy_O-2`-VU21yIzLWTKmN2T|F?ag7Im)D@r@2l2xW^aS~H z%?KIw2dE_@MdKG(zuRCP{86WX^5+0Ij2}J!66x6g7#mSTJp;O6VC}!1_^AIZVVo1x z<^Drn9`=5{Fa7XX!B!<9=x>Q1=ycGScv1WGYY~5Z?t|`$Trl{f+s7mR_#6U7eGmI+ z{NED)P8e9i2>l{(p~xS?;(d$ECuL!Cy|p$KfBOxc7V`VrHeoMM)fgujj9AhzHL#o*#&vSie1V(6B$kK%$9= Uy6mp!-@pYpwHt=`$Eg3m0k}Z)kN^Mx literal 0 HcmV?d00001 diff --git a/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/md.py b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/md.py new file mode 100644 index 0000000..d834db0 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/md.py @@ -0,0 +1,628 @@ +from functools import lru_cache +from logging import getLogger +from typing import List, Optional + +from .constant import ( + COMMON_SAFE_ASCII_CHARACTERS, + TRACE, + UNICODE_SECONDARY_RANGE_KEYWORD, +) +from .utils import ( + is_accentuated, + is_arabic, + is_arabic_isolated_form, + is_case_variable, + is_cjk, + is_emoticon, + is_hangul, + is_hiragana, + is_katakana, + is_latin, + is_punctuation, + is_separator, + is_symbol, + is_thai, + is_unprintable, + remove_accent, + unicode_range, +) + + +class MessDetectorPlugin: + """ + Base abstract class used for mess detection plugins. + All detectors MUST extend and implement given methods. + """ + + def eligible(self, character: str) -> bool: + """ + Determine if given character should be fed in. + """ + raise NotImplementedError # pragma: nocover + + def feed(self, character: str) -> None: + """ + The main routine to be executed upon character. + Insert the logic in witch the text would be considered chaotic. + """ + raise NotImplementedError # pragma: nocover + + def reset(self) -> None: # pragma: no cover + """ + Permit to reset the plugin to the initial state. + """ + raise NotImplementedError + + @property + def ratio(self) -> float: + """ + Compute the chaos ratio based on what your feed() has seen. + Must NOT be lower than 0.; No restriction gt 0. + """ + raise NotImplementedError # pragma: nocover + + +class TooManySymbolOrPunctuationPlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._punctuation_count: int = 0 + self._symbol_count: int = 0 + self._character_count: int = 0 + + self._last_printable_char: Optional[str] = None + self._frenzy_symbol_in_word: bool = False + + def eligible(self, character: str) -> bool: + return character.isprintable() + + def feed(self, character: str) -> None: + self._character_count += 1 + + if ( + character != self._last_printable_char + and character not in COMMON_SAFE_ASCII_CHARACTERS + ): + if is_punctuation(character): + self._punctuation_count += 1 + elif ( + character.isdigit() is False + and is_symbol(character) + and is_emoticon(character) is False + ): + self._symbol_count += 2 + + self._last_printable_char = character + + def reset(self) -> None: # pragma: no cover + self._punctuation_count = 0 + self._character_count = 0 + self._symbol_count = 0 + + @property + def ratio(self) -> float: + if self._character_count == 0: + return 0.0 + + ratio_of_punctuation: float = ( + self._punctuation_count + self._symbol_count + ) / self._character_count + + return ratio_of_punctuation if ratio_of_punctuation >= 0.3 else 0.0 + + +class TooManyAccentuatedPlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._character_count: int = 0 + self._accentuated_count: int = 0 + + def eligible(self, character: str) -> bool: + return character.isalpha() + + def feed(self, character: str) -> None: + self._character_count += 1 + + if is_accentuated(character): + self._accentuated_count += 1 + + def reset(self) -> None: # pragma: no cover + self._character_count = 0 + self._accentuated_count = 0 + + @property + def ratio(self) -> float: + if self._character_count < 8: + return 0.0 + + ratio_of_accentuation: float = self._accentuated_count / self._character_count + return ratio_of_accentuation if ratio_of_accentuation >= 0.35 else 0.0 + + +class UnprintablePlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._unprintable_count: int = 0 + self._character_count: int = 0 + + def eligible(self, character: str) -> bool: + return True + + def feed(self, character: str) -> None: + if is_unprintable(character): + self._unprintable_count += 1 + self._character_count += 1 + + def reset(self) -> None: # pragma: no cover + self._unprintable_count = 0 + + @property + def ratio(self) -> float: + if self._character_count == 0: + return 0.0 + + return (self._unprintable_count * 8) / self._character_count + + +class SuspiciousDuplicateAccentPlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._successive_count: int = 0 + self._character_count: int = 0 + + self._last_latin_character: Optional[str] = None + + def eligible(self, character: str) -> bool: + return character.isalpha() and is_latin(character) + + def feed(self, character: str) -> None: + self._character_count += 1 + if ( + self._last_latin_character is not None + and is_accentuated(character) + and is_accentuated(self._last_latin_character) + ): + if character.isupper() and self._last_latin_character.isupper(): + self._successive_count += 1 + # Worse if its the same char duplicated with different accent. + if remove_accent(character) == remove_accent(self._last_latin_character): + self._successive_count += 1 + self._last_latin_character = character + + def reset(self) -> None: # pragma: no cover + self._successive_count = 0 + self._character_count = 0 + self._last_latin_character = None + + @property + def ratio(self) -> float: + if self._character_count == 0: + return 0.0 + + return (self._successive_count * 2) / self._character_count + + +class SuspiciousRange(MessDetectorPlugin): + def __init__(self) -> None: + self._suspicious_successive_range_count: int = 0 + self._character_count: int = 0 + self._last_printable_seen: Optional[str] = None + + def eligible(self, character: str) -> bool: + return character.isprintable() + + def feed(self, character: str) -> None: + self._character_count += 1 + + if ( + character.isspace() + or is_punctuation(character) + or character in COMMON_SAFE_ASCII_CHARACTERS + ): + self._last_printable_seen = None + return + + if self._last_printable_seen is None: + self._last_printable_seen = character + return + + unicode_range_a: Optional[str] = unicode_range(self._last_printable_seen) + unicode_range_b: Optional[str] = unicode_range(character) + + if is_suspiciously_successive_range(unicode_range_a, unicode_range_b): + self._suspicious_successive_range_count += 1 + + self._last_printable_seen = character + + def reset(self) -> None: # pragma: no cover + self._character_count = 0 + self._suspicious_successive_range_count = 0 + self._last_printable_seen = None + + @property + def ratio(self) -> float: + if self._character_count <= 13: + return 0.0 + + ratio_of_suspicious_range_usage: float = ( + self._suspicious_successive_range_count * 2 + ) / self._character_count + + return ratio_of_suspicious_range_usage + + +class SuperWeirdWordPlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._word_count: int = 0 + self._bad_word_count: int = 0 + self._foreign_long_count: int = 0 + + self._is_current_word_bad: bool = False + self._foreign_long_watch: bool = False + + self._character_count: int = 0 + self._bad_character_count: int = 0 + + self._buffer: str = "" + self._buffer_accent_count: int = 0 + self._buffer_glyph_count: int = 0 + + def eligible(self, character: str) -> bool: + return True + + def feed(self, character: str) -> None: + if character.isalpha(): + self._buffer += character + if is_accentuated(character): + self._buffer_accent_count += 1 + if ( + self._foreign_long_watch is False + and (is_latin(character) is False or is_accentuated(character)) + and is_cjk(character) is False + and is_hangul(character) is False + and is_katakana(character) is False + and is_hiragana(character) is False + and is_thai(character) is False + ): + self._foreign_long_watch = True + if ( + is_cjk(character) + or is_hangul(character) + or is_katakana(character) + or is_hiragana(character) + or is_thai(character) + ): + self._buffer_glyph_count += 1 + return + if not self._buffer: + return + if ( + character.isspace() or is_punctuation(character) or is_separator(character) + ) and self._buffer: + self._word_count += 1 + buffer_length: int = len(self._buffer) + + self._character_count += buffer_length + + if buffer_length >= 4: + if self._buffer_accent_count / buffer_length >= 0.5: + self._is_current_word_bad = True + # Word/Buffer ending with an upper case accentuated letter are so rare, + # that we will consider them all as suspicious. Same weight as foreign_long suspicious. + elif ( + is_accentuated(self._buffer[-1]) + and self._buffer[-1].isupper() + and all(_.isupper() for _ in self._buffer) is False + ): + self._foreign_long_count += 1 + self._is_current_word_bad = True + elif self._buffer_glyph_count == 1: + self._is_current_word_bad = True + self._foreign_long_count += 1 + if buffer_length >= 24 and self._foreign_long_watch: + camel_case_dst = [ + i + for c, i in zip(self._buffer, range(0, buffer_length)) + if c.isupper() + ] + probable_camel_cased: bool = False + + if camel_case_dst and (len(camel_case_dst) / buffer_length <= 0.3): + probable_camel_cased = True + + if not probable_camel_cased: + self._foreign_long_count += 1 + self._is_current_word_bad = True + + if self._is_current_word_bad: + self._bad_word_count += 1 + self._bad_character_count += len(self._buffer) + self._is_current_word_bad = False + + self._foreign_long_watch = False + self._buffer = "" + self._buffer_accent_count = 0 + self._buffer_glyph_count = 0 + elif ( + character not in {"<", ">", "-", "=", "~", "|", "_"} + and character.isdigit() is False + and is_symbol(character) + ): + self._is_current_word_bad = True + self._buffer += character + + def reset(self) -> None: # pragma: no cover + self._buffer = "" + self._is_current_word_bad = False + self._foreign_long_watch = False + self._bad_word_count = 0 + self._word_count = 0 + self._character_count = 0 + self._bad_character_count = 0 + self._foreign_long_count = 0 + + @property + def ratio(self) -> float: + if self._word_count <= 10 and self._foreign_long_count == 0: + return 0.0 + + return self._bad_character_count / self._character_count + + +class CjkInvalidStopPlugin(MessDetectorPlugin): + """ + GB(Chinese) based encoding often render the stop incorrectly when the content does not fit and + can be easily detected. Searching for the overuse of '丅' and '丄'. + """ + + def __init__(self) -> None: + self._wrong_stop_count: int = 0 + self._cjk_character_count: int = 0 + + def eligible(self, character: str) -> bool: + return True + + def feed(self, character: str) -> None: + if character in {"丅", "丄"}: + self._wrong_stop_count += 1 + return + if is_cjk(character): + self._cjk_character_count += 1 + + def reset(self) -> None: # pragma: no cover + self._wrong_stop_count = 0 + self._cjk_character_count = 0 + + @property + def ratio(self) -> float: + if self._cjk_character_count < 16: + return 0.0 + return self._wrong_stop_count / self._cjk_character_count + + +class ArchaicUpperLowerPlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._buf: bool = False + + self._character_count_since_last_sep: int = 0 + + self._successive_upper_lower_count: int = 0 + self._successive_upper_lower_count_final: int = 0 + + self._character_count: int = 0 + + self._last_alpha_seen: Optional[str] = None + self._current_ascii_only: bool = True + + def eligible(self, character: str) -> bool: + return True + + def feed(self, character: str) -> None: + is_concerned = character.isalpha() and is_case_variable(character) + chunk_sep = is_concerned is False + + if chunk_sep and self._character_count_since_last_sep > 0: + if ( + self._character_count_since_last_sep <= 64 + and character.isdigit() is False + and self._current_ascii_only is False + ): + self._successive_upper_lower_count_final += ( + self._successive_upper_lower_count + ) + + self._successive_upper_lower_count = 0 + self._character_count_since_last_sep = 0 + self._last_alpha_seen = None + self._buf = False + self._character_count += 1 + self._current_ascii_only = True + + return + + if self._current_ascii_only is True and character.isascii() is False: + self._current_ascii_only = False + + if self._last_alpha_seen is not None: + if (character.isupper() and self._last_alpha_seen.islower()) or ( + character.islower() and self._last_alpha_seen.isupper() + ): + if self._buf is True: + self._successive_upper_lower_count += 2 + self._buf = False + else: + self._buf = True + else: + self._buf = False + + self._character_count += 1 + self._character_count_since_last_sep += 1 + self._last_alpha_seen = character + + def reset(self) -> None: # pragma: no cover + self._character_count = 0 + self._character_count_since_last_sep = 0 + self._successive_upper_lower_count = 0 + self._successive_upper_lower_count_final = 0 + self._last_alpha_seen = None + self._buf = False + self._current_ascii_only = True + + @property + def ratio(self) -> float: + if self._character_count == 0: + return 0.0 + + return self._successive_upper_lower_count_final / self._character_count + + +class ArabicIsolatedFormPlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._character_count: int = 0 + self._isolated_form_count: int = 0 + + def reset(self) -> None: # pragma: no cover + self._character_count = 0 + self._isolated_form_count = 0 + + def eligible(self, character: str) -> bool: + return is_arabic(character) + + def feed(self, character: str) -> None: + self._character_count += 1 + + if is_arabic_isolated_form(character): + self._isolated_form_count += 1 + + @property + def ratio(self) -> float: + if self._character_count < 8: + return 0.0 + + isolated_form_usage: float = self._isolated_form_count / self._character_count + + return isolated_form_usage + + +@lru_cache(maxsize=1024) +def is_suspiciously_successive_range( + unicode_range_a: Optional[str], unicode_range_b: Optional[str] +) -> bool: + """ + Determine if two Unicode range seen next to each other can be considered as suspicious. + """ + if unicode_range_a is None or unicode_range_b is None: + return True + + if unicode_range_a == unicode_range_b: + return False + + if "Latin" in unicode_range_a and "Latin" in unicode_range_b: + return False + + if "Emoticons" in unicode_range_a or "Emoticons" in unicode_range_b: + return False + + # Latin characters can be accompanied with a combining diacritical mark + # eg. Vietnamese. + if ("Latin" in unicode_range_a or "Latin" in unicode_range_b) and ( + "Combining" in unicode_range_a or "Combining" in unicode_range_b + ): + return False + + keywords_range_a, keywords_range_b = unicode_range_a.split( + " " + ), unicode_range_b.split(" ") + + for el in keywords_range_a: + if el in UNICODE_SECONDARY_RANGE_KEYWORD: + continue + if el in keywords_range_b: + return False + + # Japanese Exception + range_a_jp_chars, range_b_jp_chars = ( + unicode_range_a + in ( + "Hiragana", + "Katakana", + ), + unicode_range_b in ("Hiragana", "Katakana"), + ) + if (range_a_jp_chars or range_b_jp_chars) and ( + "CJK" in unicode_range_a or "CJK" in unicode_range_b + ): + return False + if range_a_jp_chars and range_b_jp_chars: + return False + + if "Hangul" in unicode_range_a or "Hangul" in unicode_range_b: + if "CJK" in unicode_range_a or "CJK" in unicode_range_b: + return False + if unicode_range_a == "Basic Latin" or unicode_range_b == "Basic Latin": + return False + + # Chinese/Japanese use dedicated range for punctuation and/or separators. + if ("CJK" in unicode_range_a or "CJK" in unicode_range_b) or ( + unicode_range_a in ["Katakana", "Hiragana"] + and unicode_range_b in ["Katakana", "Hiragana"] + ): + if "Punctuation" in unicode_range_a or "Punctuation" in unicode_range_b: + return False + if "Forms" in unicode_range_a or "Forms" in unicode_range_b: + return False + if unicode_range_a == "Basic Latin" or unicode_range_b == "Basic Latin": + return False + + return True + + +@lru_cache(maxsize=2048) +def mess_ratio( + decoded_sequence: str, maximum_threshold: float = 0.2, debug: bool = False +) -> float: + """ + Compute a mess ratio given a decoded bytes sequence. The maximum threshold does stop the computation earlier. + """ + + detectors: List[MessDetectorPlugin] = [ + md_class() for md_class in MessDetectorPlugin.__subclasses__() + ] + + length: int = len(decoded_sequence) + 1 + + mean_mess_ratio: float = 0.0 + + if length < 512: + intermediary_mean_mess_ratio_calc: int = 32 + elif length <= 1024: + intermediary_mean_mess_ratio_calc = 64 + else: + intermediary_mean_mess_ratio_calc = 128 + + for character, index in zip(decoded_sequence + "\n", range(length)): + for detector in detectors: + if detector.eligible(character): + detector.feed(character) + + if ( + index > 0 and index % intermediary_mean_mess_ratio_calc == 0 + ) or index == length - 1: + mean_mess_ratio = sum(dt.ratio for dt in detectors) + + if mean_mess_ratio >= maximum_threshold: + break + + if debug: + logger = getLogger("charset_normalizer") + + logger.log( + TRACE, + "Mess-detector extended-analysis start. " + f"intermediary_mean_mess_ratio_calc={intermediary_mean_mess_ratio_calc} mean_mess_ratio={mean_mess_ratio} " + f"maximum_threshold={maximum_threshold}", + ) + + if len(decoded_sequence) > 16: + logger.log(TRACE, f"Starting with: {decoded_sequence[:16]}") + logger.log(TRACE, f"Ending with: {decoded_sequence[-16::]}") + + for dt in detectors: # pragma: nocover + logger.log(TRACE, f"{dt.__class__}: {dt.ratio}") + + return round(mean_mess_ratio, 3) diff --git a/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/md__mypyc.cpython-311-x86_64-linux-gnu.so b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/md__mypyc.cpython-311-x86_64-linux-gnu.so new file mode 100755 index 0000000000000000000000000000000000000000..38d5e703c023b509774c050c1c7b6bda963e10e1 GIT binary patch literal 272696 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 literal 0 HcmV?d00001 diff --git a/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/models.py b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/models.py new file mode 100644 index 0000000..6f6b86b --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/models.py @@ -0,0 +1,359 @@ +from encodings.aliases import aliases +from hashlib import sha256 +from json import dumps +from re import sub +from typing import Any, Dict, Iterator, List, Optional, Tuple, Union + +from .constant import RE_POSSIBLE_ENCODING_INDICATION, TOO_BIG_SEQUENCE +from .utils import iana_name, is_multi_byte_encoding, unicode_range + + +class CharsetMatch: + def __init__( + self, + payload: bytes, + guessed_encoding: str, + mean_mess_ratio: float, + has_sig_or_bom: bool, + languages: "CoherenceMatches", + decoded_payload: Optional[str] = None, + preemptive_declaration: Optional[str] = None, + ): + self._payload: bytes = payload + + self._encoding: str = guessed_encoding + self._mean_mess_ratio: float = mean_mess_ratio + self._languages: CoherenceMatches = languages + self._has_sig_or_bom: bool = has_sig_or_bom + self._unicode_ranges: Optional[List[str]] = None + + self._leaves: List[CharsetMatch] = [] + self._mean_coherence_ratio: float = 0.0 + + self._output_payload: Optional[bytes] = None + self._output_encoding: Optional[str] = None + + self._string: Optional[str] = decoded_payload + + self._preemptive_declaration: Optional[str] = preemptive_declaration + + def __eq__(self, other: object) -> bool: + if not isinstance(other, CharsetMatch): + if isinstance(other, str): + return iana_name(other) == self.encoding + return False + return self.encoding == other.encoding and self.fingerprint == other.fingerprint + + def __lt__(self, other: object) -> bool: + """ + Implemented to make sorted available upon CharsetMatches items. + """ + if not isinstance(other, CharsetMatch): + raise ValueError + + chaos_difference: float = abs(self.chaos - other.chaos) + coherence_difference: float = abs(self.coherence - other.coherence) + + # Below 1% difference --> Use Coherence + if chaos_difference < 0.01 and coherence_difference > 0.02: + return self.coherence > other.coherence + elif chaos_difference < 0.01 and coherence_difference <= 0.02: + # When having a difficult decision, use the result that decoded as many multi-byte as possible. + # preserve RAM usage! + if len(self._payload) >= TOO_BIG_SEQUENCE: + return self.chaos < other.chaos + return self.multi_byte_usage > other.multi_byte_usage + + return self.chaos < other.chaos + + @property + def multi_byte_usage(self) -> float: + return 1.0 - (len(str(self)) / len(self.raw)) + + def __str__(self) -> str: + # Lazy Str Loading + if self._string is None: + self._string = str(self._payload, self._encoding, "strict") + return self._string + + def __repr__(self) -> str: + return "".format(self.encoding, self.fingerprint) + + def add_submatch(self, other: "CharsetMatch") -> None: + if not isinstance(other, CharsetMatch) or other == self: + raise ValueError( + "Unable to add instance <{}> as a submatch of a CharsetMatch".format( + other.__class__ + ) + ) + + other._string = None # Unload RAM usage; dirty trick. + self._leaves.append(other) + + @property + def encoding(self) -> str: + return self._encoding + + @property + def encoding_aliases(self) -> List[str]: + """ + Encoding name are known by many name, using this could help when searching for IBM855 when it's listed as CP855. + """ + also_known_as: List[str] = [] + for u, p in aliases.items(): + if self.encoding == u: + also_known_as.append(p) + elif self.encoding == p: + also_known_as.append(u) + return also_known_as + + @property + def bom(self) -> bool: + return self._has_sig_or_bom + + @property + def byte_order_mark(self) -> bool: + return self._has_sig_or_bom + + @property + def languages(self) -> List[str]: + """ + Return the complete list of possible languages found in decoded sequence. + Usually not really useful. Returned list may be empty even if 'language' property return something != 'Unknown'. + """ + return [e[0] for e in self._languages] + + @property + def language(self) -> str: + """ + Most probable language found in decoded sequence. If none were detected or inferred, the property will return + "Unknown". + """ + if not self._languages: + # Trying to infer the language based on the given encoding + # Its either English or we should not pronounce ourselves in certain cases. + if "ascii" in self.could_be_from_charset: + return "English" + + # doing it there to avoid circular import + from charset_normalizer.cd import encoding_languages, mb_encoding_languages + + languages = ( + mb_encoding_languages(self.encoding) + if is_multi_byte_encoding(self.encoding) + else encoding_languages(self.encoding) + ) + + if len(languages) == 0 or "Latin Based" in languages: + return "Unknown" + + return languages[0] + + return self._languages[0][0] + + @property + def chaos(self) -> float: + return self._mean_mess_ratio + + @property + def coherence(self) -> float: + if not self._languages: + return 0.0 + return self._languages[0][1] + + @property + def percent_chaos(self) -> float: + return round(self.chaos * 100, ndigits=3) + + @property + def percent_coherence(self) -> float: + return round(self.coherence * 100, ndigits=3) + + @property + def raw(self) -> bytes: + """ + Original untouched bytes. + """ + return self._payload + + @property + def submatch(self) -> List["CharsetMatch"]: + return self._leaves + + @property + def has_submatch(self) -> bool: + return len(self._leaves) > 0 + + @property + def alphabets(self) -> List[str]: + if self._unicode_ranges is not None: + return self._unicode_ranges + # list detected ranges + detected_ranges: List[Optional[str]] = [ + unicode_range(char) for char in str(self) + ] + # filter and sort + self._unicode_ranges = sorted(list({r for r in detected_ranges if r})) + return self._unicode_ranges + + @property + def could_be_from_charset(self) -> List[str]: + """ + The complete list of encoding that output the exact SAME str result and therefore could be the originating + encoding. + This list does include the encoding available in property 'encoding'. + """ + return [self._encoding] + [m.encoding for m in self._leaves] + + def output(self, encoding: str = "utf_8") -> bytes: + """ + Method to get re-encoded bytes payload using given target encoding. Default to UTF-8. + Any errors will be simply ignored by the encoder NOT replaced. + """ + if self._output_encoding is None or self._output_encoding != encoding: + self._output_encoding = encoding + decoded_string = str(self) + if ( + self._preemptive_declaration is not None + and self._preemptive_declaration.lower() + not in ["utf-8", "utf8", "utf_8"] + ): + patched_header = sub( + RE_POSSIBLE_ENCODING_INDICATION, + lambda m: m.string[m.span()[0] : m.span()[1]].replace( + m.groups()[0], iana_name(self._output_encoding) # type: ignore[arg-type] + ), + decoded_string[:8192], + 1, + ) + + decoded_string = patched_header + decoded_string[8192:] + + self._output_payload = decoded_string.encode(encoding, "replace") + + return self._output_payload # type: ignore + + @property + def fingerprint(self) -> str: + """ + Retrieve the unique SHA256 computed using the transformed (re-encoded) payload. Not the original one. + """ + return sha256(self.output()).hexdigest() + + +class CharsetMatches: + """ + Container with every CharsetMatch items ordered by default from most probable to the less one. + Act like a list(iterable) but does not implements all related methods. + """ + + def __init__(self, results: Optional[List[CharsetMatch]] = None): + self._results: List[CharsetMatch] = sorted(results) if results else [] + + def __iter__(self) -> Iterator[CharsetMatch]: + yield from self._results + + def __getitem__(self, item: Union[int, str]) -> CharsetMatch: + """ + Retrieve a single item either by its position or encoding name (alias may be used here). + Raise KeyError upon invalid index or encoding not present in results. + """ + if isinstance(item, int): + return self._results[item] + if isinstance(item, str): + item = iana_name(item, False) + for result in self._results: + if item in result.could_be_from_charset: + return result + raise KeyError + + def __len__(self) -> int: + return len(self._results) + + def __bool__(self) -> bool: + return len(self._results) > 0 + + def append(self, item: CharsetMatch) -> None: + """ + Insert a single match. Will be inserted accordingly to preserve sort. + Can be inserted as a submatch. + """ + if not isinstance(item, CharsetMatch): + raise ValueError( + "Cannot append instance '{}' to CharsetMatches".format( + str(item.__class__) + ) + ) + # We should disable the submatch factoring when the input file is too heavy (conserve RAM usage) + if len(item.raw) < TOO_BIG_SEQUENCE: + for match in self._results: + if match.fingerprint == item.fingerprint and match.chaos == item.chaos: + match.add_submatch(item) + return + self._results.append(item) + self._results = sorted(self._results) + + def best(self) -> Optional["CharsetMatch"]: + """ + Simply return the first match. Strict equivalent to matches[0]. + """ + if not self._results: + return None + return self._results[0] + + def first(self) -> Optional["CharsetMatch"]: + """ + Redundant method, call the method best(). Kept for BC reasons. + """ + return self.best() + + +CoherenceMatch = Tuple[str, float] +CoherenceMatches = List[CoherenceMatch] + + +class CliDetectionResult: + def __init__( + self, + path: str, + encoding: Optional[str], + encoding_aliases: List[str], + alternative_encodings: List[str], + language: str, + alphabets: List[str], + has_sig_or_bom: bool, + chaos: float, + coherence: float, + unicode_path: Optional[str], + is_preferred: bool, + ): + self.path: str = path + self.unicode_path: Optional[str] = unicode_path + self.encoding: Optional[str] = encoding + self.encoding_aliases: List[str] = encoding_aliases + self.alternative_encodings: List[str] = alternative_encodings + self.language: str = language + self.alphabets: List[str] = alphabets + self.has_sig_or_bom: bool = has_sig_or_bom + self.chaos: float = chaos + self.coherence: float = coherence + self.is_preferred: bool = is_preferred + + @property + def __dict__(self) -> Dict[str, Any]: # type: ignore + return { + "path": self.path, + "encoding": self.encoding, + "encoding_aliases": self.encoding_aliases, + "alternative_encodings": self.alternative_encodings, + "language": self.language, + "alphabets": self.alphabets, + "has_sig_or_bom": self.has_sig_or_bom, + "chaos": self.chaos, + "coherence": self.coherence, + "unicode_path": self.unicode_path, + "is_preferred": self.is_preferred, + } + + def to_json(self) -> str: + return dumps(self.__dict__, ensure_ascii=True, indent=4) diff --git a/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/py.typed b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/utils.py b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/utils.py new file mode 100644 index 0000000..e5cbbf4 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/utils.py @@ -0,0 +1,421 @@ +import importlib +import logging +import unicodedata +from codecs import IncrementalDecoder +from encodings.aliases import aliases +from functools import lru_cache +from re import findall +from typing import Generator, List, Optional, Set, Tuple, Union + +from _multibytecodec import MultibyteIncrementalDecoder + +from .constant import ( + ENCODING_MARKS, + IANA_SUPPORTED_SIMILAR, + RE_POSSIBLE_ENCODING_INDICATION, + UNICODE_RANGES_COMBINED, + UNICODE_SECONDARY_RANGE_KEYWORD, + UTF8_MAXIMAL_ALLOCATION, +) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_accentuated(character: str) -> bool: + try: + description: str = unicodedata.name(character) + except ValueError: + return False + return ( + "WITH GRAVE" in description + or "WITH ACUTE" in description + or "WITH CEDILLA" in description + or "WITH DIAERESIS" in description + or "WITH CIRCUMFLEX" in description + or "WITH TILDE" in description + or "WITH MACRON" in description + or "WITH RING ABOVE" in description + ) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def remove_accent(character: str) -> str: + decomposed: str = unicodedata.decomposition(character) + if not decomposed: + return character + + codes: List[str] = decomposed.split(" ") + + return chr(int(codes[0], 16)) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def unicode_range(character: str) -> Optional[str]: + """ + Retrieve the Unicode range official name from a single character. + """ + character_ord: int = ord(character) + + for range_name, ord_range in UNICODE_RANGES_COMBINED.items(): + if character_ord in ord_range: + return range_name + + return None + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_latin(character: str) -> bool: + try: + description: str = unicodedata.name(character) + except ValueError: + return False + return "LATIN" in description + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_punctuation(character: str) -> bool: + character_category: str = unicodedata.category(character) + + if "P" in character_category: + return True + + character_range: Optional[str] = unicode_range(character) + + if character_range is None: + return False + + return "Punctuation" in character_range + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_symbol(character: str) -> bool: + character_category: str = unicodedata.category(character) + + if "S" in character_category or "N" in character_category: + return True + + character_range: Optional[str] = unicode_range(character) + + if character_range is None: + return False + + return "Forms" in character_range and character_category != "Lo" + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_emoticon(character: str) -> bool: + character_range: Optional[str] = unicode_range(character) + + if character_range is None: + return False + + return "Emoticons" in character_range or "Pictographs" in character_range + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_separator(character: str) -> bool: + if character.isspace() or character in {"|", "+", "<", ">"}: + return True + + character_category: str = unicodedata.category(character) + + return "Z" in character_category or character_category in {"Po", "Pd", "Pc"} + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_case_variable(character: str) -> bool: + return character.islower() != character.isupper() + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_cjk(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: + return False + + return "CJK" in character_name + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_hiragana(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: + return False + + return "HIRAGANA" in character_name + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_katakana(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: + return False + + return "KATAKANA" in character_name + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_hangul(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: + return False + + return "HANGUL" in character_name + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_thai(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: + return False + + return "THAI" in character_name + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_arabic(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: + return False + + return "ARABIC" in character_name + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_arabic_isolated_form(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: + return False + + return "ARABIC" in character_name and "ISOLATED FORM" in character_name + + +@lru_cache(maxsize=len(UNICODE_RANGES_COMBINED)) +def is_unicode_range_secondary(range_name: str) -> bool: + return any(keyword in range_name for keyword in UNICODE_SECONDARY_RANGE_KEYWORD) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_unprintable(character: str) -> bool: + return ( + character.isspace() is False # includes \n \t \r \v + and character.isprintable() is False + and character != "\x1A" # Why? Its the ASCII substitute character. + and character != "\ufeff" # bug discovered in Python, + # Zero Width No-Break Space located in Arabic Presentation Forms-B, Unicode 1.1 not acknowledged as space. + ) + + +def any_specified_encoding(sequence: bytes, search_zone: int = 8192) -> Optional[str]: + """ + Extract using ASCII-only decoder any specified encoding in the first n-bytes. + """ + if not isinstance(sequence, bytes): + raise TypeError + + seq_len: int = len(sequence) + + results: List[str] = findall( + RE_POSSIBLE_ENCODING_INDICATION, + sequence[: min(seq_len, search_zone)].decode("ascii", errors="ignore"), + ) + + if len(results) == 0: + return None + + for specified_encoding in results: + specified_encoding = specified_encoding.lower().replace("-", "_") + + encoding_alias: str + encoding_iana: str + + for encoding_alias, encoding_iana in aliases.items(): + if encoding_alias == specified_encoding: + return encoding_iana + if encoding_iana == specified_encoding: + return encoding_iana + + return None + + +@lru_cache(maxsize=128) +def is_multi_byte_encoding(name: str) -> bool: + """ + Verify is a specific encoding is a multi byte one based on it IANA name + """ + return name in { + "utf_8", + "utf_8_sig", + "utf_16", + "utf_16_be", + "utf_16_le", + "utf_32", + "utf_32_le", + "utf_32_be", + "utf_7", + } or issubclass( + importlib.import_module("encodings.{}".format(name)).IncrementalDecoder, + MultibyteIncrementalDecoder, + ) + + +def identify_sig_or_bom(sequence: bytes) -> Tuple[Optional[str], bytes]: + """ + Identify and extract SIG/BOM in given sequence. + """ + + for iana_encoding in ENCODING_MARKS: + marks: Union[bytes, List[bytes]] = ENCODING_MARKS[iana_encoding] + + if isinstance(marks, bytes): + marks = [marks] + + for mark in marks: + if sequence.startswith(mark): + return iana_encoding, mark + + return None, b"" + + +def should_strip_sig_or_bom(iana_encoding: str) -> bool: + return iana_encoding not in {"utf_16", "utf_32"} + + +def iana_name(cp_name: str, strict: bool = True) -> str: + cp_name = cp_name.lower().replace("-", "_") + + encoding_alias: str + encoding_iana: str + + for encoding_alias, encoding_iana in aliases.items(): + if cp_name in [encoding_alias, encoding_iana]: + return encoding_iana + + if strict: + raise ValueError("Unable to retrieve IANA for '{}'".format(cp_name)) + + return cp_name + + +def range_scan(decoded_sequence: str) -> List[str]: + ranges: Set[str] = set() + + for character in decoded_sequence: + character_range: Optional[str] = unicode_range(character) + + if character_range is None: + continue + + ranges.add(character_range) + + return list(ranges) + + +def cp_similarity(iana_name_a: str, iana_name_b: str) -> float: + if is_multi_byte_encoding(iana_name_a) or is_multi_byte_encoding(iana_name_b): + return 0.0 + + decoder_a = importlib.import_module( + "encodings.{}".format(iana_name_a) + ).IncrementalDecoder + decoder_b = importlib.import_module( + "encodings.{}".format(iana_name_b) + ).IncrementalDecoder + + id_a: IncrementalDecoder = decoder_a(errors="ignore") + id_b: IncrementalDecoder = decoder_b(errors="ignore") + + character_match_count: int = 0 + + for i in range(255): + to_be_decoded: bytes = bytes([i]) + if id_a.decode(to_be_decoded) == id_b.decode(to_be_decoded): + character_match_count += 1 + + return character_match_count / 254 + + +def is_cp_similar(iana_name_a: str, iana_name_b: str) -> bool: + """ + Determine if two code page are at least 80% similar. IANA_SUPPORTED_SIMILAR dict was generated using + the function cp_similarity. + """ + return ( + iana_name_a in IANA_SUPPORTED_SIMILAR + and iana_name_b in IANA_SUPPORTED_SIMILAR[iana_name_a] + ) + + +def set_logging_handler( + name: str = "charset_normalizer", + level: int = logging.INFO, + format_string: str = "%(asctime)s | %(levelname)s | %(message)s", +) -> None: + logger = logging.getLogger(name) + logger.setLevel(level) + + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter(format_string)) + logger.addHandler(handler) + + +def cut_sequence_chunks( + sequences: bytes, + encoding_iana: str, + offsets: range, + chunk_size: int, + bom_or_sig_available: bool, + strip_sig_or_bom: bool, + sig_payload: bytes, + is_multi_byte_decoder: bool, + decoded_payload: Optional[str] = None, +) -> Generator[str, None, None]: + if decoded_payload and is_multi_byte_decoder is False: + for i in offsets: + chunk = decoded_payload[i : i + chunk_size] + if not chunk: + break + yield chunk + else: + for i in offsets: + chunk_end = i + chunk_size + if chunk_end > len(sequences) + 8: + continue + + cut_sequence = sequences[i : i + chunk_size] + + if bom_or_sig_available and strip_sig_or_bom is False: + cut_sequence = sig_payload + cut_sequence + + chunk = cut_sequence.decode( + encoding_iana, + errors="ignore" if is_multi_byte_decoder else "strict", + ) + + # multi-byte bad cutting detector and adjustment + # not the cleanest way to perform that fix but clever enough for now. + if is_multi_byte_decoder and i > 0: + chunk_partial_size_chk: int = min(chunk_size, 16) + + if ( + decoded_payload + and chunk[:chunk_partial_size_chk] not in decoded_payload + ): + for j in range(i, i - 4, -1): + cut_sequence = sequences[j:chunk_end] + + if bom_or_sig_available and strip_sig_or_bom is False: + cut_sequence = sig_payload + cut_sequence + + chunk = cut_sequence.decode(encoding_iana, errors="ignore") + + if chunk[:chunk_partial_size_chk] in decoded_payload: + break + + yield chunk diff --git a/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/version.py b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/version.py new file mode 100644 index 0000000..699990e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/charset_normalizer/version.py @@ -0,0 +1,6 @@ +""" +Expose version +""" + +__version__ = "3.4.0" +VERSION = __version__.split(".") diff --git a/lambdas/aws-dd-forwarder-3.127.0/customized_log_group.py b/lambdas/aws-dd-forwarder-3.127.0/customized_log_group.py new file mode 100644 index 0000000..ad63a95 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/customized_log_group.py @@ -0,0 +1,38 @@ +# Unless explicitly stated otherwise all files in this repository are licensed +# under the Apache License Version 2.0. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2021 Datadog, Inc. + + +import re + +""" +Customized log group is a log group shared by multiple applications of the same type. Based on the feedback from AWS, +customers may name the log group arbitrarily. E.g they can name a lambda log group as "/aws/vendedlogs/states/**", which is typically used for Stepfunctions +In addition, potentially, not just Lambda, any other AWS services can use a customized log group. +The workaround is to parse the logstream_name to get the source of logs. +""" + +# Example: "2023/11/06/test-customized-log-group1[$LATEST]13e304cba4b9446eb7ef082a00038990" +REX_LAMBDA_CUSTOMIZE_LOGSTREAM_NAME_PATTERN = re.compile( + "^[0-9]{4}\\/[01][0-9]\\/[0-3][0-9]\\/[0-9a-zA-Z_.-]{1,75}\\[(?:\\$LATEST|[0-9A-Za-z_-]{1,129})\\][0-9a-f]{32}$" +) + + +def is_lambda_customized_log_group(logstream_name): + return ( + REX_LAMBDA_CUSTOMIZE_LOGSTREAM_NAME_PATTERN.fullmatch(logstream_name) + is not None + ) + + +def get_lambda_function_name_from_logstream_name(logstream_name): + try: + # Not match the pattern for customized Lambda log group + if not is_lambda_customized_log_group(logstream_name): + return None + leftSquareBracketPos = logstream_name.index("[") + lastForwardSlashPos = logstream_name.rindex("/") + return logstream_name[lastForwardSlashPos + 1 : leftSquareBracketPos] + except: + return None diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/INSTALLER b/lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/METADATA b/lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/METADATA new file mode 100644 index 0000000..91be477 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/METADATA @@ -0,0 +1,228 @@ +Metadata-Version: 2.1 +Name: datadog +Version: 0.48.0 +Summary: The Datadog Python library +Project-URL: Bug Tracker, https://github.com/DataDog/datadogpy/issues +Project-URL: Documentation, https://datadogpy.readthedocs.io/en/latest/ +Project-URL: Source Code, https://github.com/DataDog/datadogpy +Author-email: "Datadog, Inc." +License: BSD-3-Clause +License-File: LICENSE +License-File: LICENSE-3rdparty.csv +Keywords: datadog +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7 +Requires-Dist: configparser<5; python_version < '3.0' +Requires-Dist: requests>=2.6.0 +Requires-Dist: typing; python_version < '3.5' +Description-Content-Type: text/markdown + +# The Datadog Python library + +[![Unit Tests](https://dev.azure.com/datadoghq/datadogpy/_apis/build/status/DataDog.datadogpy.unit?branchName=master)](https://dev.azure.com/datadoghq/datadogpy/_build/latest?definitionId=10&branchName=master) +[![Integration Tests](https://dev.azure.com/datadoghq/datadogpy/_apis/build/status/DataDog.datadogpy.integration?branchName=master)](https://dev.azure.com/datadoghq/datadogpy/_build/latest?definitionId=13&branchName=master) +[![Documentation Status](https://readthedocs.org/projects/datadogpy/badge/?version=latest)](https://readthedocs.org/projects/datadogpy/?badge=latest) +[![PyPI - Version](https://img.shields.io/pypi/v/datadog.svg)](https://pypi.org/project/datadog) +[![PyPI - Downloads](https://pepy.tech/badge/datadog)](https://pepy.tech/project/datadog) + +The Datadog Python Library is a collection of tools suitable for inclusion in existing Python projects or for the development of standalone scripts. It provides an abstraction on top of Datadog's raw HTTP interface and the Agent's DogStatsD metrics aggregation server, to interact with Datadog and efficiently report events and metrics. + +- Library Documentation: https://datadogpy.readthedocs.io/en/latest/ +- HTTP API Documentation: https://docs.datadoghq.com/api/ +- DatadogHQ: https://datadoghq.com + +See [CHANGELOG.md](https://github.com/DataDog/datadogpy/blob/master/CHANGELOG.md) for changes. + +## Installation + +To install from pip: + + pip install datadog + +To install from source: + + python setup.py install + +## Datadog API + +To support all Datadog HTTP APIs, a generated library is +available which will expose all the endpoints: +[datadog-api-client-python](https://github.com/DataDog/datadog-api-client-python). + +Find below a working example for submitting an event to your Event Stream: + +```python +from datadog import initialize, api + +options = { + "api_key": "", + "app_key": "", +} + +initialize(**options) + +title = "Something big happened!" +text = "And let me tell you all about it here!" +tags = ["version:1", "application:web"] + +api.Event.create(title=title, text=text, tags=tags) +``` + +**Consult the full list of supported Datadog API endpoints with working code examples in [the Datadog API documentation](https://docs.datadoghq.com/api/latest/?code-lang=python).** + +**Note**: The full list of available Datadog API endpoints is also available in the [Datadog Python Library documentation](https://datadogpy.readthedocs.io/en/latest/) + +#### Environment Variables + +As an alternate method to using the `initialize` function with the `options` parameters, set the environment variables `DATADOG_API_KEY` and `DATADOG_APP_KEY` within the context of your application. + +If `DATADOG_API_KEY` or `DATADOG_APP_KEY` are not set, the library attempts to fall back to Datadog's APM environment variable prefixes: `DD_API_KEY` and `DD_APP_KEY`. + +```python +from datadog import initialize, api + +# Assuming you've set `DD_API_KEY` and `DD_APP_KEY` in your env, +# initialize() will pick it up automatically +initialize() + +title = "Something big happened!" +text = "And let me tell you all about it here!" +tags = ["version:1", "application:web"] + +api.Event.create(title=title, text=text, tags=tags) +``` + +In development, you can disable any `statsd` metric collection using `DD_DOGSTATSD_DISABLE=True` (or any not-empty value). + +## DogStatsD + +In order to use DogStatsD metrics, the Agent must be [running and available](https://docs.datadoghq.com/developers/dogstatsd/?code-lang=python). + +### Instantiate the DogStatsD client with UDP + +Once the Datadog Python Library is installed, instantiate the StatsD client using UDP in your code: + +```python +from datadog import initialize, statsd + +options = { + "statsd_host": "127.0.0.1", + "statsd_port": 8125, +} + +initialize(**options) +``` + +See the full list of available [DogStatsD client instantiation parameters](https://docs.datadoghq.com/developers/dogstatsd/?code-lang=python#client-instantiation-parameters). + +#### Instantiate the DogStatsd client with UDS + +Once the Datadog Python Library is installed, instantiate the StatsD client using UDS in your code: +```python + +from datadog import initialize, statsd + +options = { + "statsd_socket_path": PATH_TO_SOCKET, +} + +initialize(**options) +``` + +#### Origin detection over UDP and UDS + +Origin detection is a method to detect which pod `DogStatsD` packets are coming from in order to add the pod's tags to the tag list. +The `DogStatsD` client attaches an internal tag, `entity_id`. The value of this tag is the content of the `DD_ENTITY_ID` environment variable if found, which is the pod's UID. The Datadog Agent uses this tag to add container tags to the metrics. To avoid overwriting this global tag, make sure to only `append` to the `constant_tags` list. + +To enable origin detection over UDP, add the following lines to your application manifest +```yaml +env: + - name: DD_ENTITY_ID + valueFrom: + fieldRef: + fieldPath: metadata.uid +``` + +### Usage +#### Metrics + +After the client is created, you can start sending custom metrics to Datadog. See the dedicated [Metric Submission: DogStatsD documentation](https://docs.datadoghq.com/metrics/dogstatsd_metrics_submission/?code-lang=python) to see how to submit all supported metric types to Datadog with working code examples: + +* [Submit a COUNT metric](https://docs.datadoghq.com/metrics/dogstatsd_metrics_submission/?code-lang=python#count). +* [Submit a GAUGE metric](https://docs.datadoghq.com/metrics/dogstatsd_metrics_submission/?code-lang=python#gauge). +* [Submit a SET metric](https://docs.datadoghq.com/metrics/dogstatsd_metrics_submission/?code-lang=python#set) +* [Submit a HISTOGRAM metric](https://docs.datadoghq.com/metrics/dogstatsd_metrics_submission/?code-lang=python#histogram) +* [Submit a TIMER metric](https://docs.datadoghq.com/metrics/dogstatsd_metrics_submission/?code-lang=python#timer) +* [Submit a DISTRIBUTION metric](https://docs.datadoghq.com/metrics/dogstatsd_metrics_submission/?code-lang=python#distribution) + +Some options are supported when submitting metrics, like [applying a Sample Rate to your metrics](https://docs.datadoghq.com/metrics/dogstatsd_metrics_submission/?code-lang=python#metric-submission-options) or [tagging your metrics with your custom tags](https://docs.datadoghq.com/metrics/dogstatsd_metrics_submission/?code-lang=python#metric-tagging). + +#### Events + +After the client is created, you can start sending events to your Datadog Event Stream. See the dedicated [Event Submission: DogStatsD documentation](https://docs.datadoghq.com/events/guides/dogstatsd/?code-lang=python) to see how to submit an event to your Datadog Event Stream. + +#### Service Checks + +After the client is created, you can start sending Service Checks to Datadog. See the dedicated [Service Check Submission: DogStatsD documentation](https://docs.datadoghq.com/developers/service_checks/dogstatsd_service_checks_submission/?code-lang=python) to see how to submit a Service Check to Datadog. + +### Monitoring this client + +This client automatically injects telemetry about itself in the DogStatsD stream. +Those metrics will not be counted as custom and will not be billed. This feature can be disabled using the `statsd.disable_telemetry()` method. + +See [Telemetry documentation](https://docs.datadoghq.com/developers/dogstatsd/high_throughput/?code-lang=python#client-side-telemetry) to learn more about it. + +### Benchmarks + +_Note: You will need to install `psutil` package before running the benchmarks._ + +If you would like to get an approximate idea on the throughput that your DogStatsD library +can handle on your system, you can run the included local benchmark code: + +```sh-session +$ # Python 2 Example +$ python2 -m unittest -vvv tests.performance.test_statsd_throughput + +$ # Python 3 Example +$ python3 -m unittest -vvv tests.performance.test_statsd_throughput +``` + +You can also add set `BENCHMARK_*` to customize the runs: +```sh-session +$ # Example #1 +$ BENCHMARK_NUM_RUNS=10 BENCHMARK_NUM_THREADS=1 BENCHMARK_NUM_DATAPOINTS=5000 BENCHMARK_TRANSPORT="UDP" python2 -m unittest -vvv tests.performance.test_statsd_throughput + +$ # Example #2 +$ BENCHMARK_NUM_THREADS=10 BENCHMARK_TRANSPORT="UDS" python3 -m unittest -vvv tests.performance.test_statsd_throughput +``` + +## Maximum packets size in high-throughput scenarios + +In order to have the most efficient use of this library in high-throughput scenarios, +default values for the maximum packets size have already been set for both UDS (8192 bytes) +and UDP (1432 bytes) in order to have the best usage of the underlying network. +However, if you perfectly know your network and you know that a different value for the maximum packets +size should be used, you can set it with the parameter `max_buffer_len`. Example: + +```python +from datadog import initialize + +options = { + "api_key": "", + "app_key": "", + "max_buffer_len": 4096, +} + +initialize(**options) +``` + +## Thread Safety + +`DogStatsD` and `ThreadStats` are thread-safe. diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/RECORD b/lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/RECORD new file mode 100644 index 0000000..dd845b1 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/RECORD @@ -0,0 +1,161 @@ +../../bin/dog,sha256=USXQo9Llj4ZKaLnsoeHfNyJv8BOTxhRN2K3ybH4P368,224 +../../bin/dogshell,sha256=USXQo9Llj4ZKaLnsoeHfNyJv8BOTxhRN2K3ybH4P368,224 +../../bin/dogshellwrap,sha256=F4Dt5QTVYjdzj46YMwEj3E_oJ5ccVicfzgwWTFFWhd8,229 +../../bin/dogwrap,sha256=F4Dt5QTVYjdzj46YMwEj3E_oJ5ccVicfzgwWTFFWhd8,229 +datadog-0.48.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +datadog-0.48.0.dist-info/METADATA,sha256=xQ5n174J3GUL9awAKYujVRMfD2vnAz_D-cPSF2kXCf8,10144 +datadog-0.48.0.dist-info/RECORD,, +datadog-0.48.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +datadog-0.48.0.dist-info/WHEEL,sha256=VYAwk8D_V6zmIA2XKK-k7Fem_KAtVk3hugaRru3yjGc,105 +datadog-0.48.0.dist-info/entry_points.txt,sha256=UD-9aGJqcY-y_BqSuZvhoZEmoZcHJaAtfwmPHO3kf3g,158 +datadog-0.48.0.dist-info/licenses/LICENSE,sha256=LUpuzY69IJ0kJ3YlYDI6c8_9l9eyeW5xHv8-gRhgozM,1503 +datadog-0.48.0.dist-info/licenses/LICENSE-3rdparty.csv,sha256=W2B3r48ALOEkEFjyoUMmuU1lm9soN-rG9QqtufdYjts,252 +datadog/__init__.py,sha256=64k7GZlnwUUNl8R7aQvDeswZYBpveECUD1_JIdC4C0s,5551 +datadog/__pycache__/__init__.cpython-311.pyc,, +datadog/__pycache__/version.cpython-311.pyc,, +datadog/api/__init__.py,sha256=s2vtkIF6TN26-AiD2XEYUr7I9upD8m8VdN5e-b2qCb4,1900 +datadog/api/__pycache__/__init__.cpython-311.pyc,, +datadog/api/__pycache__/api_client.cpython-311.pyc,, +datadog/api/__pycache__/aws_integration.cpython-311.pyc,, +datadog/api/__pycache__/aws_log_integration.cpython-311.pyc,, +datadog/api/__pycache__/azure_integration.cpython-311.pyc,, +datadog/api/__pycache__/comments.cpython-311.pyc,, +datadog/api/__pycache__/constants.cpython-311.pyc,, +datadog/api/__pycache__/dashboard_list_v2.cpython-311.pyc,, +datadog/api/__pycache__/dashboard_lists.cpython-311.pyc,, +datadog/api/__pycache__/dashboards.cpython-311.pyc,, +datadog/api/__pycache__/distributions.cpython-311.pyc,, +datadog/api/__pycache__/downtimes.cpython-311.pyc,, +datadog/api/__pycache__/events.cpython-311.pyc,, +datadog/api/__pycache__/exceptions.cpython-311.pyc,, +datadog/api/__pycache__/format.cpython-311.pyc,, +datadog/api/__pycache__/gcp_integration.cpython-311.pyc,, +datadog/api/__pycache__/graphs.cpython-311.pyc,, +datadog/api/__pycache__/hosts.cpython-311.pyc,, +datadog/api/__pycache__/http_client.cpython-311.pyc,, +datadog/api/__pycache__/infrastructure.cpython-311.pyc,, +datadog/api/__pycache__/logs.cpython-311.pyc,, +datadog/api/__pycache__/metadata.cpython-311.pyc,, +datadog/api/__pycache__/metrics.cpython-311.pyc,, +datadog/api/__pycache__/monitors.cpython-311.pyc,, +datadog/api/__pycache__/permissions.cpython-311.pyc,, +datadog/api/__pycache__/resources.cpython-311.pyc,, +datadog/api/__pycache__/roles.cpython-311.pyc,, +datadog/api/__pycache__/screenboards.cpython-311.pyc,, +datadog/api/__pycache__/service_checks.cpython-311.pyc,, +datadog/api/__pycache__/service_level_objectives.cpython-311.pyc,, +datadog/api/__pycache__/synthetics.cpython-311.pyc,, +datadog/api/__pycache__/tags.cpython-311.pyc,, +datadog/api/__pycache__/timeboards.cpython-311.pyc,, +datadog/api/__pycache__/users.cpython-311.pyc,, +datadog/api/api_client.py,sha256=56xtTsbuel6TJXHCKrONq_sfscUOCAX4W4sj-TZVwzQ,10340 +datadog/api/aws_integration.py,sha256=ED6sdQCK1C93Z7VV_mO6aLw492G-B-A9mY2UtCRNaaY,10926 +datadog/api/aws_log_integration.py,sha256=vIIOkwVHD1KFkkZibRbkppIg3IgA37j-fyny6_16--M,4434 +datadog/api/azure_integration.py,sha256=wEBgwbh3PGNnPejXbePNTVjuRGDvEJfUoVpoHHkfMQU,3204 +datadog/api/comments.py,sha256=-ujRDfrSlTYt_QAPRNrzDhIZ6ohxEOXGH9L5r3vjhBw,461 +datadog/api/constants.py,sha256=8XSSk69ZSdAzl0q8_sRSMydRRiVT8kkA0Fo3ABceDWA,806 +datadog/api/dashboard_list_v2.py,sha256=kNizRqfzAKc6yq1j_OBGbUN3TjojatxoxSHUlQnn154,677 +datadog/api/dashboard_lists.py,sha256=vKAdF3xhDPwkg615_yyCTnASRJSDLIKW2ZV0oRSZvdY,1166 +datadog/api/dashboards.py,sha256=UQPvh_TtWWQk4Fww9bOTYXz0P8yIp22s6rW9A4ZJGA8,623 +datadog/api/distributions.py,sha256=MEnpxEqw-ZNdXrazMq2vBIzCgkI5uA_kPo1ZaFXppZ8,1895 +datadog/api/downtimes.py,sha256=o7JswAJXaDnK-nqlFDFW3WKPjOKYcB5d86nBK3swgVU,1072 +datadog/api/events.py,sha256=_e3ThyCsWuguRypEgfJ-zC48kaks4E0cdHRtI3tKyYU,3376 +datadog/api/exceptions.py,sha256=eVe7czONQySBZS8Std67ABXep-msgc8UYWnPqBIadnM,3171 +datadog/api/format.py,sha256=6RahpimFhlYEqUVfH9RgB9C1D8mgWYMXxCm8788rS-Y,1209 +datadog/api/gcp_integration.py,sha256=jr9rl-S3V_dV8Qlk5kMCAKSQgjTRet7wWsyfmzPzwqc,3778 +datadog/api/graphs.py,sha256=6BuHX01mxyJ7x8NWI968kmkBLAGBaAKe0JU6LrkoVvM,2695 +datadog/api/hosts.py,sha256=Qr4C6mEexWiCdUp98oIUzelSW2xb-J_c65O_Fpf9jm8,2491 +datadog/api/http_client.py,sha256=9VirvttGWQiWFH-fUW5BmUcNcwJaH9LLTqhgqSd5gcE,6465 +datadog/api/infrastructure.py,sha256=XnY0UvbEv_TKfMMYHAu884F0Jz8RmcPar55qrIil9Zc,1014 +datadog/api/logs.py,sha256=OCI0PmiNON8kIDul5bh-rJPg4Sp8rupQl76fNyMz4sk,727 +datadog/api/metadata.py,sha256=TdrrmRulgc1arbFzCp7PGCTK1vBJx25NWSH9b0KHxZc,2290 +datadog/api/metrics.py,sha256=S2sBGWeFRzcSA8pqu_eFwfBDF55sIEQ80J-ixw2ROas,5217 +datadog/api/monitors.py,sha256=8qqDGUdr_x3mGWeT6fvgJwkf8ydHJUrYzjQXA8JRnS0,4859 +datadog/api/permissions.py,sha256=iZjyktAJrnADC1pstL59H4DZsM_oplIng2rci6lrDss,720 +datadog/api/resources.py,sha256=fY3c-G7LrysmZOJCqi4vYTkRLxFnrbFiLLR_g5dHhZ4,16128 +datadog/api/roles.py,sha256=R0D3WezC7kVFoYR_wfKdbkIRu4k7mgCNag9D_Md7uYk,2370 +datadog/api/screenboards.py,sha256=fNa6N9bSamxopesHeCyIXZo-a4_da8kz60nVkf_t8D4,1389 +datadog/api/service_checks.py,sha256=ejDB2HqzL2iDg3-MUuvs8fwYv89LAh81G71IhSpf6dg,1615 +datadog/api/service_level_objectives.py,sha256=gBs30qG3PjRU78bUkVcxzlxqGsczVGuto8IfvuYk7oE,6547 +datadog/api/synthetics.py,sha256=YM1jyOG4HOxg-xlbI7YTVJ5e-uVIcrLr_7eWJae2e_Q,6135 +datadog/api/tags.py,sha256=u7lrXrKuyn_n-xoCtsJ2N9simr79KvmygiQDnI3XLwk,1612 +datadog/api/timeboards.py,sha256=4vTKGYblmfHtT8NVQ8P_tb-0EEm2X09W_HuR_2Vvnhw,618 +datadog/api/users.py,sha256=wvcRRgeZLYESScFTw3Dont8dwOr0d-ltBZctqnnYNSM,1440 +datadog/dogshell/__init__.py,sha256=5frjaiaWH7bwTuho4RAxOQwLGV6PlWuPONtqNoxCwQ4,4306 +datadog/dogshell/__pycache__/__init__.cpython-311.pyc,, +datadog/dogshell/__pycache__/comment.cpython-311.pyc,, +datadog/dogshell/__pycache__/common.cpython-311.pyc,, +datadog/dogshell/__pycache__/dashboard.cpython-311.pyc,, +datadog/dogshell/__pycache__/dashboard_list.cpython-311.pyc,, +datadog/dogshell/__pycache__/downtime.cpython-311.pyc,, +datadog/dogshell/__pycache__/event.cpython-311.pyc,, +datadog/dogshell/__pycache__/host.cpython-311.pyc,, +datadog/dogshell/__pycache__/metric.cpython-311.pyc,, +datadog/dogshell/__pycache__/monitor.cpython-311.pyc,, +datadog/dogshell/__pycache__/screenboard.cpython-311.pyc,, +datadog/dogshell/__pycache__/search.cpython-311.pyc,, +datadog/dogshell/__pycache__/service_check.cpython-311.pyc,, +datadog/dogshell/__pycache__/service_level_objective.cpython-311.pyc,, +datadog/dogshell/__pycache__/tag.cpython-311.pyc,, +datadog/dogshell/__pycache__/timeboard.cpython-311.pyc,, +datadog/dogshell/__pycache__/wrap.cpython-311.pyc,, +datadog/dogshell/comment.py,sha256=-Sai_Bt0Lc1i1izMw-Y9swu1qTMqJersh_m734XphlM,6571 +datadog/dogshell/common.py,sha256=K9rTVyLOhhzmF_b7SloOC7YkrizT66c5_40zDe6thQs,5307 +datadog/dogshell/dashboard.py,sha256=FfEl8NCXg6Uw2af0itRVGZfCpiSPSqXhlpm2DlHDNso,6628 +datadog/dogshell/dashboard_list.py,sha256=Zk1EWwb6uVDfc_s17lW4LDbQe0ZdOhw76_4OyXuYXnA,12591 +datadog/dogshell/downtime.py,sha256=9DtKolbWCHiuhCSJWxgWUDh6TZrm431-Cf1IM-CBzDA,5324 +datadog/dogshell/event.py,sha256=7vIYeXR5hnoMH8QWL_gN5cahvERBZXBfOSYpPautvYw,7398 +datadog/dogshell/host.py,sha256=4hd3kpeyTw6HS_cOLF_zNm2rlXWXvsKakaLUZVkyzXk,2281 +datadog/dogshell/metric.py,sha256=6-D1QR_jhNiP70ltlGB-lYDTW1bDR3H0JAqVb01T1-k,2834 +datadog/dogshell/monitor.py,sha256=Bqb0U_JSGHE0aj7v1uUGHnR7YcEr5N6lz_cW-ivYY7g,16294 +datadog/dogshell/screenboard.py,sha256=q8Jq8chvurkOnUYJQgImMnD3Hl32btvd8_5OuszNLJk,11606 +datadog/dogshell/search.py,sha256=pZ9qoIpgQWh-AotSsgv1Y4GmDGvApHi4_xByk1ii0f0,1663 +datadog/dogshell/service_check.py,sha256=XgTeVqECRmNJU_ie5o14pm9BqAgt2Ka4RLdrNCJFZDY,2161 +datadog/dogshell/service_level_objective.py,sha256=LXCT2Ws4EKquYJhIZ-doY8dhRaGjnDgXdsewlBj5a7w,15044 +datadog/dogshell/tag.py,sha256=u3Dmfwx_HxBlA-5rZZQJMFX24XZ1nRgmJSzsyzEiZrM,4328 +datadog/dogshell/timeboard.py,sha256=9kwRqMtH2PTNKuDpyPn1-jdYhP2AbSgg9HF62HJvJDQ,13298 +datadog/dogshell/wrap.py,sha256=hxjQ2_USc42lsMflQ-GWTKUNi4yUQacyMsBZBd9E6MM,16934 +datadog/dogstatsd/__init__.py,sha256=4IoRSiHQXgIwlpDoC0BegFNS_G9Nq0tZOhTUKyzqCm0,294 +datadog/dogstatsd/__pycache__/__init__.cpython-311.pyc,, +datadog/dogstatsd/__pycache__/base.cpython-311.pyc,, +datadog/dogstatsd/__pycache__/container.cpython-311.pyc,, +datadog/dogstatsd/__pycache__/context.cpython-311.pyc,, +datadog/dogstatsd/__pycache__/context_async.cpython-311.pyc,, +datadog/dogstatsd/__pycache__/route.cpython-311.pyc,, +datadog/dogstatsd/base.py,sha256=tLuou8XH1Q9--1oS0yuFSO0B-UIE5YEolH0paqo81uI,52299 +datadog/dogstatsd/container.py,sha256=0doQtobT4ID8GKDwa-jUjUFr_NTsf0jgc2joaUT0y7o,2052 +datadog/dogstatsd/context.py,sha256=yZgl5pCTHf0GrGkiruAy0H9dVHWZDlvVxjkn6e_elcQ,2873 +datadog/dogstatsd/context_async.py,sha256=wJgbf9n_pHaN95I0I1RoxycjoK18L0ZBGUVrzcVsW4M,1543 +datadog/dogstatsd/route.py,sha256=VOoCuD5XD9PPtEydVjpbz_FldgGEOd8Yazpt2YoVD-U,1253 +datadog/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +datadog/threadstats/__init__.py,sha256=a8JfLr2QiPHpxhEC-U5gmRuh9UI5kaLjvv785o_qEsY,379 +datadog/threadstats/__pycache__/__init__.cpython-311.pyc,, +datadog/threadstats/__pycache__/aws_lambda.cpython-311.pyc,, +datadog/threadstats/__pycache__/base.cpython-311.pyc,, +datadog/threadstats/__pycache__/constants.cpython-311.pyc,, +datadog/threadstats/__pycache__/events.cpython-311.pyc,, +datadog/threadstats/__pycache__/metrics.cpython-311.pyc,, +datadog/threadstats/__pycache__/periodic_timer.cpython-311.pyc,, +datadog/threadstats/__pycache__/reporters.cpython-311.pyc,, +datadog/threadstats/aws_lambda.py,sha256=E71iKuW9p4tLW3HZN6QzCPFLmP4ICsSSHogXXefjCHs,3701 +datadog/threadstats/base.py,sha256=YfUWWYL0DptCSCAMUe0qxc35wciCLcqNHEtX8PRFZw8,19162 +datadog/threadstats/constants.py,sha256=3BDnCBKzznBZLsY2oKs8EQBT4vJnIStRcl19FlfxMtw,569 +datadog/threadstats/events.py,sha256=Sa69_TyFoe333mPhcG2vtkYPkeqm-JJTNZDDZWhP1kU,713 +datadog/threadstats/metrics.py,sha256=CAUUzmx6GL78MWLpGWBsm1eZ9RR9Jgs2yCGY24yIp80,6242 +datadog/threadstats/periodic_timer.py,sha256=8DlyzDLcfsVhpoG8sc_MpaJvm-YDx4A5JGkt9vLXVP4,1137 +datadog/threadstats/reporters.py,sha256=SJ45WtEYLModVIq8e6XdGgGAVxPFwW-Cri8d0-s_e1I,937 +datadog/util/__init__.py,sha256=nHOZxl1VhFT33JpvolN8S3QWGNPE-BptvlumBl8pCEo,233 +datadog/util/__pycache__/__init__.cpython-311.pyc,, +datadog/util/__pycache__/cli.cpython-311.pyc,, +datadog/util/__pycache__/compat.cpython-311.pyc,, +datadog/util/__pycache__/config.cpython-311.pyc,, +datadog/util/__pycache__/deprecation.cpython-311.pyc,, +datadog/util/__pycache__/format.cpython-311.pyc,, +datadog/util/__pycache__/hostname.cpython-311.pyc,, +datadog/util/cli.py,sha256=OCGeY63V_iARHFod1sXbe8Fin7zIAZrA_1zJGqvURMY,5013 +datadog/util/compat.py,sha256=0UxFczhysUXWXsC1ZLo80Rte2qEo_VVt5RdV-x3JmF8,3284 +datadog/util/config.py,sha256=4hT22Kb1jPC7_9nPkaznuwRS3AqQ-X0wFbfrJK11x7I,3922 +datadog/util/deprecation.py,sha256=Aznjj1YLEB0WDt9YO84BVSNFnnolEBdXH9Vwrq1Npx4,782 +datadog/util/format.py,sha256=9jXeqsvnHr44X6B008k25qcwPES6OqB05-s8wee9_0c,1339 +datadog/util/hostname.py,sha256=5yedKu2G59Iv7m3WmdUmhmxb3KgC4VQrHueL4Z1wyJg,10296 +datadog/version.py,sha256=bkYe4lEQZCEmFm0XRZaZkxTV1niMqR_lbp-tzKL6s6c,23 diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/REQUESTED b/lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/WHEEL b/lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/WHEEL new file mode 100644 index 0000000..a5543ba --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.21.0 +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/entry_points.txt b/lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/entry_points.txt new file mode 100644 index 0000000..4b946cb --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/entry_points.txt @@ -0,0 +1,5 @@ +[console_scripts] +dog = datadog.dogshell:main +dogshell = datadog.dogshell:main +dogshellwrap = datadog.dogshell.wrap:main +dogwrap = datadog.dogshell.wrap:main diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/licenses/LICENSE b/lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000..984d5d2 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/licenses/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2015-Present Datadog, Inc + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/licenses/LICENSE-3rdparty.csv b/lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/licenses/LICENSE-3rdparty.csv new file mode 100644 index 0000000..3afd934 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog-0.48.0.dist-info/licenses/LICENSE-3rdparty.csv @@ -0,0 +1,4 @@ +Component,Origin,License,Copyright +setup.py,decorator,BSD-2-Clause,Copyright (c) 2005-2018, Michele Simionato +setup.py,requests,Apache-2.0,Copyright 2019 Kenneth Reitz +setup.py,argparse,Python-2.0,2006-2009 Steven J. Bethard diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/.DS_Store b/lambdas/aws-dd-forwarder-3.127.0/datadog/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..590067708a2045e1ce3b3196da6c2277fc5df9cd GIT binary patch literal 6148 zcmeHLu};G<5IwgU3KddE#w>gQe-Nr5v3Er(ZG+lMsS?q4VDon{b>thEn3$RQ1r|Pm zceWeqBpz4*p*zWbiSN$$@><7riAW8%**VdGh%s&Fj_ceDK5fD5HYrv^H`RF+}_%FINobxv>P-wj?)%R3OctPn+x8G`F{mt@Wp%qOdM*9 R7=g(T0V9KSqQH+T@Cn&C&sYEe literal 0 HcmV?d00001 diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/__init__.py new file mode 100644 index 0000000..ffe4b64 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/__init__.py @@ -0,0 +1,138 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +""" +Datadogpy is a collection of Datadog Python tools. +It contains: +* datadog.api: a Python client for Datadog REST API. +* datadog.dogstatsd: a DogStatsd Python client. +* datadog.threadstats: an alternative tool to DogStatsd client for collecting application metrics +without hindering performance. +* datadog.dogshell: a command-line tool, wrapping datadog.api, to interact with Datadog REST API. +""" +# stdlib +import logging +import os +import os.path +from typing import Any, List, Optional + +# datadog +from datadog import api +from datadog.dogstatsd import DogStatsd, statsd # noqa +from datadog.threadstats import ThreadStats, datadog_lambda_wrapper, lambda_metric # noqa +from datadog.util.compat import iteritems, NullHandler, text +from datadog.util.hostname import get_hostname +from datadog.version import __version__ # noqa + +# Loggers +logging.getLogger("datadog.api").addHandler(NullHandler()) +logging.getLogger("datadog.dogstatsd").addHandler(NullHandler()) +logging.getLogger("datadog.threadstats").addHandler(NullHandler()) + + +def initialize( + api_key=None, # type: Optional[str] + app_key=None, # type: Optional[str] + host_name=None, # type: Optional[str] + api_host=None, # type: Optional[str] + statsd_host=None, # type: Optional[str] + statsd_port=None, # type: Optional[int] + statsd_disable_buffering=True, # type: bool + statsd_use_default_route=False, # type: bool + statsd_socket_path=None, # type: Optional[str] + statsd_namespace=None, # type: Optional[str] + statsd_constant_tags=None, # type: Optional[List[str]] + return_raw_response=False, # type: bool + hostname_from_config=True, # type: bool + **kwargs # type: Any +): + # type: (...) -> None + """ + Initialize and configure Datadog.api and Datadog.statsd modules + + :param api_key: Datadog API key + :type api_key: string + + :param app_key: Datadog application key + :type app_key: string + + :param host_name: Set a specific hostname + :type host_name: string + + :param proxies: Proxy to use to connect to Datadog API; + for example, 'proxies': {'http': "http::@:/"} + :type proxies: dictionary mapping protocol to the URL of the proxy. + + :param api_host: Datadog API endpoint + :type api_host: url + + :param statsd_host: Host of DogStatsd server or statsd daemon + :type statsd_host: address + + :param statsd_port: Port of DogStatsd server or statsd daemon + :type statsd_port: port + + :param statsd_disable_buffering: Enable/disable statsd client buffering support + (default: True). + :type statsd_disable_buffering: boolean + + :param statsd_use_default_route: Dynamically set the statsd host to the default route + (Useful when running the client in a container) + :type statsd_use_default_route: boolean + + :param statsd_socket_path: path to the DogStatsd UNIX socket. Supersedes statsd_host + and stats_port if provided. + + :param statsd_constant_tags: A list of tags to be applied to all metrics ("tag", "tag:value") + :type statsd_constant_tags: list of string + + :param cacert: Path to local certificate file used to verify SSL \ + certificates. Can also be set to True (default) to use the systems \ + certificate store, or False to skip SSL verification + :type cacert: path or boolean + + :param mute: Mute any ApiError or ClientError before they escape \ + from datadog.api.HTTPClient (default: True). + :type mute: boolean + + :param return_raw_response: Whether or not to return the raw response object in addition \ + to the decoded response content (default: False) + :type return_raw_response: boolean + + :param hostname_from_config: Set the hostname from the Datadog agent config (agent 5). Will be deprecated + :type hostname_from_config: boolean + """ + # API configuration + api._api_key = api_key or api._api_key or os.environ.get("DATADOG_API_KEY", os.environ.get("DD_API_KEY")) + api._application_key = ( + app_key or api._application_key or os.environ.get("DATADOG_APP_KEY", os.environ.get("DD_APP_KEY")) + ) + api._hostname_from_config = hostname_from_config + api._host_name = host_name or api._host_name or get_hostname(hostname_from_config) + api._api_host = api_host or api._api_host or os.environ.get("DATADOG_HOST", "https://api.datadoghq.com") + + # Statsd configuration + # ...overrides the default `statsd` instance attributes + if statsd_socket_path: + statsd.socket_path = statsd_socket_path + statsd.host = None + statsd.port = None + else: + if statsd_host or statsd_use_default_route: + statsd.host = statsd.resolve_host(statsd_host, statsd_use_default_route) + if statsd_port: + statsd.port = int(statsd_port) + statsd.close_socket() + if statsd_namespace: + statsd.namespace = text(statsd_namespace) + if statsd_constant_tags: + statsd.constant_tags += statsd_constant_tags + + statsd.disable_buffering = statsd_disable_buffering + + api._return_raw_response = return_raw_response + + # HTTP client and API options + for key, value in iteritems(kwargs): + attribute = "_{}".format(key) + setattr(api, attribute, value) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/__init__.py new file mode 100644 index 0000000..eb477c9 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/__init__.py @@ -0,0 +1,52 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +# flake8: noqa + +from typing import Optional + +# API settings +_api_key = None # type: Optional[str] +_application_key = None # type: Optional[str] +_api_version = "v1" +_api_host = None # type: Optional[str] +_host_name = None # type: Optional[str] +_hostname_from_config = True +_cacert = True + +# HTTP(S) settings +_proxies = None +_timeout = 60 +_max_timeouts = 3 +_max_retries = 3 +_backoff_period = 300 +_mute = True +_return_raw_response = False + +# Resources +from datadog.api.comments import Comment +from datadog.api.dashboard_lists import DashboardList +from datadog.api.distributions import Distribution +from datadog.api.downtimes import Downtime +from datadog.api.timeboards import Timeboard +from datadog.api.dashboards import Dashboard +from datadog.api.events import Event +from datadog.api.infrastructure import Infrastructure +from datadog.api.metadata import Metadata +from datadog.api.metrics import Metric +from datadog.api.monitors import Monitor +from datadog.api.screenboards import Screenboard +from datadog.api.graphs import Graph, Embed +from datadog.api.hosts import Host, Hosts +from datadog.api.service_checks import ServiceCheck +from datadog.api.tags import Tag +from datadog.api.users import User +from datadog.api.aws_integration import AwsIntegration +from datadog.api.aws_log_integration import AwsLogsIntegration +from datadog.api.azure_integration import AzureIntegration +from datadog.api.gcp_integration import GcpIntegration +from datadog.api.roles import Roles +from datadog.api.permissions import Permissions +from datadog.api.service_level_objectives import ServiceLevelObjective +from datadog.api.synthetics import Synthetics +from datadog.api.logs import Logs diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/api_client.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/api_client.py new file mode 100644 index 0000000..db34873 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/api_client.py @@ -0,0 +1,290 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +# stdlib +import json +import logging +import time +import zlib + +# datadog +from datadog.api import _api_version, _max_timeouts, _backoff_period +from datadog.api.exceptions import ClientError, ApiError, HttpBackoff, HttpTimeout, ApiNotInitialized +from datadog.api.http_client import resolve_http_client +from datadog.util.compat import is_p3k +from datadog.util.format import construct_url, normalize_tags + + +log = logging.getLogger("datadog.api") + + +class APIClient(object): + """ + Datadog API client: format and submit API calls to Datadog. + Embeds a HTTP client. + """ + + # HTTP transport parameters + _backoff_period = _backoff_period + _max_timeouts = _max_timeouts + _backoff_timestamp = None + _timeout_counter = 0 + _sort_keys = False + + # Plugged HTTP client + _http_client = None + + @classmethod + def _get_http_client(cls): + """ + Getter for the embedded HTTP client. + """ + if not cls._http_client: + cls._http_client = resolve_http_client() + + return cls._http_client + + @classmethod + def submit( + cls, + method, + path, + api_version=None, + body=None, + attach_host_name=False, + response_formatter=None, + error_formatter=None, + suppress_response_errors_on_codes=None, + compress_payload=False, + **params + ): + """ + Make an HTTP API request + + :param method: HTTP method to use to contact API endpoint + :type method: HTTP method string + + :param path: API endpoint url + :type path: url + + :param api_version: The API version used + + :param body: dictionary to be sent in the body of the request + :type body: dictionary + + :param response_formatter: function to format JSON response from HTTP API request + :type response_formatter: JSON input function + + :param error_formatter: function to format JSON error response from HTTP API request + :type error_formatter: JSON input function + + :param attach_host_name: link the new resource object to the host name + :type attach_host_name: bool + + :param suppress_response_errors_on_codes: suppress ApiError on `errors` key in the response for the given HTTP + status codes + :type suppress_response_errors_on_codes: None|list(int) + + :param compress_payload: compress the payload using zlib + :type compress_payload: bool + + :param params: dictionary to be sent in the query string of the request + :type params: dictionary + + :returns: JSON or formatted response from HTTP API request + """ + try: + # Check if it's ok to submit + if not cls._should_submit(): + _, backoff_time_left = cls._backoff_status() + raise HttpBackoff(backoff_time_left) + + # Import API, User and HTTP settings + from datadog.api import ( + _api_key, + _application_key, + _api_host, + _mute, + _host_name, + _proxies, + _max_retries, + _timeout, + _cacert, + _return_raw_response, + ) + + # Check keys and add then to params + if _api_key is None: + raise ApiNotInitialized("API key is not set." " Please run 'initialize' method first.") + + # Set api and app keys in headers + headers = {} + headers["DD-API-KEY"] = _api_key + if _application_key: + headers["DD-APPLICATION-KEY"] = _application_key + + # Check if the api_version is provided + if not api_version: + api_version = _api_version + + # Attach host name to body + if attach_host_name and body: + # Is it a 'series' list of objects ? + if "series" in body: + # Adding the host name to all objects + for obj_params in body["series"]: + if obj_params.get("host", "") == "": + obj_params["host"] = _host_name + else: + if body.get("host", "") == "": + body["host"] = _host_name + + # If defined, make sure tags are defined as a comma-separated string + if "tags" in params and isinstance(params["tags"], list): + tag_list = normalize_tags(params["tags"]) + params["tags"] = ",".join(tag_list) + + # If defined, make sure monitor_ids are defined as a comma-separated string + if "monitor_ids" in params and isinstance(params["monitor_ids"], list): + params["monitor_ids"] = ",".join(str(i) for i in params["monitor_ids"]) + + # Process the body, if necessary + if isinstance(body, dict): + body = json.dumps(body, sort_keys=cls._sort_keys) + headers["Content-Type"] = "application/json" + + if compress_payload: + body = zlib.compress(body.encode("utf-8")) + headers["Content-Encoding"] = "deflate" + + # Construct the URL + url = construct_url(_api_host, api_version, path) + + # Process requesting + start_time = time.time() + + result = cls._get_http_client().request( + method=method, + url=url, + headers=headers, + params=params, + data=body, + timeout=_timeout, + max_retries=_max_retries, + proxies=_proxies, + verify=_cacert, + ) + + # Request succeeded: log it and reset the timeout counter + duration = round((time.time() - start_time) * 1000.0, 4) + log.info("%s %s %s (%sms)" % (result.status_code, method, url, duration)) + cls._timeout_counter = 0 + + # Format response content + content = result.content + + if content: + try: + if is_p3k(): + response_obj = json.loads(content.decode("utf-8")) + else: + response_obj = json.loads(content) + except ValueError: + raise ValueError("Invalid JSON response: {0}".format(content)) + + # response_obj can be a bool and not a dict + if isinstance(response_obj, dict): + if response_obj and "errors" in response_obj: + # suppress ApiError when specified and just return the response + if not ( + suppress_response_errors_on_codes + and result.status_code in suppress_response_errors_on_codes + ): + raise ApiError(response_obj) + else: + response_obj = None + + if response_formatter is not None: + response_obj = response_formatter(response_obj) + + if _return_raw_response: + return response_obj, result + else: + return response_obj + + except HttpTimeout: + cls._timeout_counter += 1 + raise + except ClientError as e: + if _mute: + log.error(str(e)) + if error_formatter is None: + return {"errors": e.args[0]} + else: + return error_formatter({"errors": e.args[0]}) + else: + raise + except ApiError as e: + if _mute: + for error in e.args[0].get("errors") or []: + log.error(error) + if error_formatter is None: + return e.args[0] + else: + return error_formatter(e.args[0]) + else: + raise + + @classmethod + def _should_submit(cls): + """ + Returns True if we're in a state where we should make a request + (backoff expired, no backoff in effect), false otherwise. + """ + now = time.time() + should_submit = False + + # If we're not backing off, but the timeout counter exceeds the max + # number of timeouts, then enter the backoff state, recording the time + # we started backing off + if not cls._backoff_timestamp and cls._timeout_counter >= cls._max_timeouts: + log.info( + "Max number of datadog timeouts exceeded, backing off for %s seconds", + cls._backoff_period, + ) + cls._backoff_timestamp = now + should_submit = False + + # If we are backing off but the we've waiting sufficiently long enough + # (backoff_retry_age), exit the backoff state and reset the timeout + # counter so that we try submitting metrics again + elif cls._backoff_timestamp: + backed_off_time, backoff_time_left = cls._backoff_status() + if backoff_time_left < 0: + log.info( + "Exiting backoff state after %s seconds, will try to submit metrics again", + backed_off_time, + ) + cls._backoff_timestamp = None + cls._timeout_counter = 0 + should_submit = True + else: + log.info( + "In backoff state, won't submit metrics for another %s seconds", + backoff_time_left, + ) + should_submit = False + else: + should_submit = True + + return should_submit + + @classmethod + def _backoff_status(cls): + """ + Get a backoff report, i.e. backoff total and remaining time. + """ + now = time.time() + backed_off_time = now - cls._backoff_timestamp + backoff_time_left = cls._backoff_period - backed_off_time + return round(backed_off_time, 2), round(backoff_time_left, 2) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/aws_integration.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/aws_integration.py new file mode 100644 index 0000000..eb4358b --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/aws_integration.py @@ -0,0 +1,248 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datadog.api.resources import ( + GetableAPIResource, + CreateableAPIResource, + DeletableAPIResource, + UpdatableAPIResource, + UpdatableAPISubResource, + ListableAPISubResource, +) + + +class AwsIntegration( + GetableAPIResource, + CreateableAPIResource, + DeletableAPIResource, + ListableAPISubResource, + UpdatableAPIResource, + UpdatableAPISubResource, +): + """ + A wrapper around AWS Integration API. + """ + + _resource_name = "integration" + _resource_id = "aws" + + @classmethod + def list(cls, **params): + """ + List all Datadog-AWS integrations available in your Datadog organization. + + >>> api.AwsIntegration.list() + """ + return super(AwsIntegration, cls).get(id=cls._resource_id, **params) + + @classmethod + def create(cls, **params): + """ + Add a new AWS integration config. + + :param account_id: Your AWS Account ID without dashes. \ + Consult the Datadog AWS integration to learn more about \ + your AWS account ID. + :type account_id: string + + :param access_key_id: If your AWS account is a GovCloud \ + or China account, enter the corresponding Access Key ID. + :type access_key_id: string + + :param role_name: Your Datadog role delegation name. \ + For more information about you AWS account Role name, \ + see the Datadog AWS integration configuration info. + :type role_name: string + + :param filter_tags: The array of EC2 tags (in the form key:value) \ + defines a filter that Datadog uses when collecting metrics from EC2. \ + Wildcards, such as ? (for single characters) and * (for multiple characters) \ + can also be used. Only hosts that match one of the defined tags will be imported \ + into Datadog. The rest will be ignored. Host matching a given tag can also be \ + excluded by adding ! before the tag. e.x. \ + env:production,instance-type:c1.*,!region:us-east-1 For more information \ + on EC2 tagging, see the AWS tagging documentation. + :type filter_tags: list of strings + + :param host_tags: Array of tags (in the form key:value) to add to all hosts and \ + metrics reporting through this integration. + :type host_tags: list of strings + + :param account_specific_namespace_rules: An object (in the form \ + {"namespace1":true/false, "namespace2":true/false}) that enables \ + or disables metric collection for specific AWS namespaces for this \ + AWS account only. A list of namespaces can be found at the \ + /v1/integration/aws/available_namespace_rules endpoint. + :type account_specific_namespace_rules: dictionary + + :param excluded_regions: An array of AWS regions to exclude \ + from metrics collection. + :type excluded_regions: list of strings + + :returns: Dictionary representing the API's JSON response + + >>> account_id = "" + >>> access_key_id = "" + >>> role_name = "DatadogAwsRole" + >>> filter_tags = [":"] + >>> host_tags = [":"] + >>> account_specific_namespace_rules = {"namespace1":true/false, "namespace2":true/false} + >>> excluded_regions = ["us-east-1", "us-west-1"] + + >>> api.AwsIntegration.create(account_id=account_id, role_name=role_name, \ + filter_tags=filter_tags,host_tags=host_tags,\ + account_specific_namespace_rules=account_specific_namespace_rules \ + excluded_regions=excluded_regions) + """ + return super(AwsIntegration, cls).create(id=cls._resource_id, **params) + + @classmethod + def update(cls, **body): + """ + Update an AWS integration config. + + :param account_id: Your existing AWS Account ID without dashes. \ + Consult the Datadog AWS integration to learn more about \ + your AWS account ID. + :type account_id: string + + :param new_account_id: Your new AWS Account ID without dashes. \ + Consult the Datadog AWS integration to learn more about \ + your AWS account ID. This is the account to be updated. + :type new_account_id: string + + :param role_name: Your existing Datadog role delegation name. \ + For more information about you AWS account Role name, \ + see the Datadog AWS integration configuration info. + :type role_name: string + + :param new_role_name: Your new Datadog role delegation name. \ + For more information about you AWS account Role name, \ + see the Datadog AWS integration configuration info. \ + This is the role_name to be updated. + :type new_role_name: string + + :param access_key_id: If your AWS account is a GovCloud \ + or China account, enter the existing Access Key ID. + :type access_key_id: string + + :param new_access_key_id: If your AWS account is a GovCloud \ + or China account, enter the new Access Key ID to be set. + :type new_access_key_id: string + + :param secret_access_key: If your AWS account is a GovCloud \ + or China account, enter the existing Secret Access Key. + :type secret_access_key: string + + :param new_secret_access_key: If your AWS account is a GovCloud \ + or China account, enter the new key to be set. + :type new_secret_access_key: string + + :param filter_tags: The array of EC2 tags (in the form key:value) \ + defines a filter that Datadog uses when collecting metrics from EC2. \ + Wildcards, such as ? (for single characters) and * (for multiple characters) \ + can also be used. Only hosts that match one of the defined tags will be imported \ + into Datadog. The rest will be ignored. Host matching a given tag can also be \ + excluded by adding ! before the tag. e.x. \ + env:production,instance-type:c1.*,!region:us-east-1 For more information \ + on EC2 tagging, see the AWS tagging documentation. + :type filter_tags: list of strings + + :param host_tags: Array of tags (in the form key:value) to add to all hosts and \ + metrics reporting through this integration. + :type host_tags: list of strings + + :param account_specific_namespace_rules: An object (in the form \ + {"namespace1":true/false, "namespace2":true/false}) that enables \ + or disables metric collection for specific AWS namespaces for this \ + AWS account only. A list of namespaces can be found at the \ + /v1/integration/aws/available_namespace_rules endpoint. + :type account_specific_namespace_rules: dictionary + + :param excluded_regions: An array of AWS regions to exclude \ + from metrics collection. + :type excluded_regions: list of strings + + :returns: Dictionary representing the API's JSON response + + The following will depend on whether role delegation or access keys are being used. + If using role delegation, use the fields for role_name and account_id. + For access keys, use fields for access_key_id and secret_access_key. + + Both the existing fields and new fields are required no matter what. i.e. If the config is \ + account_id/role_name based, then `account_id`, `role_name`, `new_account_id`, and \ + `new_role_name` are all required. + + For access_key based accounts, `access_key_id`, `secret_access_key`, `new_access_key_id`, \ + and `new_secret_access_key` are all required. + + >>> account_id = "" + >>> role_name = "" + >>> access_key_id = "" + >>> secret_access_key = "" + >>> new_account_id = "" + >>> new_role_name = "" + >>> new_access_key_id = "" + >>> new_secret_access_key = "" + >>> filter_tags = [":"] + >>> host_tags = [":"] + >>> account_specific_namespace_rules = {"namespace1":true/false, "namespace2":true/false} + >>> excluded_regions = ["us-east-1", "us-west-1"] + + >>> api.AwsIntegration.update(account_id=account_id, role_name=role_name, \ + new_account_id=new_account_id, new_role_name=new_role_name, \ + filter_tags=filter_tags,host_tags=host_tags,\ + account_specific_namespace_rules=account_specific_namespace_rules, \ + excluded_regions=excluded_regions) + """ + params = {} + if body.get("account_id") and body.get("role_name"): + params["account_id"] = body.pop("account_id") + params["role_name"] = body.pop("role_name") + if body.get("new_account_id"): + body["account_id"] = body.pop("new_account_id") + if body.get("new_role_name"): + body["role_name"] = body.pop("new_role_name") + if body.get("access_key_id") and body.get("secret_access_key"): + params["access_key_id"] = body.pop("access_key_id") + params["secret_access_key"] = body.pop("secret_access_key") + if body.get("new_access_key_id"): + body["access_key_id"] = body.pop("new_access_key_id") + if body.get("new_secret_access_key"): + body["secret_access_key"] = body.pop("new_secret_access_key") + return super(AwsIntegration, cls).update(id=cls._resource_id, params=params, **body) + + @classmethod + def delete(cls, **body): + """ + Delete a given Datadog-AWS integration. + + >>> account_id = "" + >>> role_name = "" + + >>> api.AwsIntegration.delete() + """ + return super(AwsIntegration, cls).delete(id=cls._resource_id, body=body) + + @classmethod + def list_namespace_rules(cls, **params): + """ + List all namespace rules available as options. + + >>> api.AwsIntegration.list_namespace_rules() + """ + cls._sub_resource_name = "available_namespace_rules" + return super(AwsIntegration, cls).get_items(id=cls._resource_id, **params) + + @classmethod + def generate_new_external_id(cls, **params): + """ + Generate a new AWS external id for a given AWS account id and role name pair. + + >>> account_id = "" + >>> role_name = "" + + >>> api.AwsIntegration.generate_new_external_id() + """ + cls._sub_resource_name = "generate_new_external_id" + return super(AwsIntegration, cls).update_items(id=cls._resource_id, **params) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/aws_log_integration.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/aws_log_integration.py new file mode 100644 index 0000000..3528435 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/aws_log_integration.py @@ -0,0 +1,111 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datadog.api.resources import DeletableAPISubResource, ListableAPISubResource, AddableAPISubResource + + +class AwsLogsIntegration(DeletableAPISubResource, ListableAPISubResource, AddableAPISubResource): + """ + A wrapper around AWS Logs API. + """ + + _resource_name = "integration" + _resource_id = "aws" + + @classmethod + def list_log_services(cls, **params): + """ + List all namespace rules available as options. + + >>> api.AwsLogsIntegration.list_log_services() + """ + cls._sub_resource_name = "logs/services" + return super(AwsLogsIntegration, cls).get_items(id=cls._resource_id, **params) + + @classmethod + def add_log_lambda_arn(cls, **params): + """ + Attach the Lambda ARN of the Lambda created for the Datadog-AWS \ + log collection to your AWS account ID to enable log collection. + + >>> account_id = "" + >>> lambda_arn = "" + + >>> api.AwsLogsIntegration.add_log_lambda_arn(account_id=account_id, lambda_arn=lambda_arn) + """ + cls._sub_resource_name = "logs" + return super(AwsLogsIntegration, cls).add_items(id=cls._resource_id, **params) + + @classmethod + def save_services(cls, **params): + """ + Enable Automatic Log collection for your AWS services. + + >>> account_id = "" + >>> services = ["s3", "elb", "elbv2", "cloudfront", "redshift", "lambda"] + + >>> api.AwsLogsIntegration.save_services() + """ + cls._sub_resource_name = "logs/services" + return super(AwsLogsIntegration, cls).add_items(id=cls._resource_id, **params) + + @classmethod + def delete_config(cls, **params): + """ + Delete a Datadog-AWS log collection configuration by removing the specific Lambda ARN \ + associated with a given AWS account. + + >>> account_id = "" + >>> lambda_arn = "" + + >>> api.AwsLogsIntegration.delete_config(account_id=account_id, lambda_arn=lambda_arn) + """ + cls._sub_resource_name = "logs" + return super(AwsLogsIntegration, cls).delete_items(id=cls._resource_id, **params) + + @classmethod + def check_lambda(cls, **params): + """ + Check function to see if a lambda_arn exists within an account. \ + This sends a job on our side if it does not exist, then immediately returns \ + the status of that job. Subsequent requests will always repeat the above, so this endpoint \ + can be polled intermittently instead of blocking. + + Returns a status of 'created' when it's checking if the Lambda exists in the account. + Returns a status of 'waiting' while checking. + Returns a status of 'checked and ok' if the Lambda exists. + Returns a status of 'error' if the Lambda does not exist. + + >>> account_id = "" + >>> lambda_arn = "" + + >>> api.AwsLogsIntegration.check_lambda(account_id=account_id, lambda_arn=lambda_arn) + """ + cls._sub_resource_name = "logs/check_async" + return super(AwsLogsIntegration, cls).add_items(id=cls._resource_id, **params) + + @classmethod + def check_services(cls, **params): + """ + Test if permissions are present to add log-forwarding triggers for the \ + given services + AWS account. Input is the same as for save_services. + Done async, so can be repeatedly polled in a non-blocking fashion until \ + the async request completes + + >>> account_id = "" + >>> services = ["s3", "elb", "elbv2", "cloudfront", "redshift", "lambda"] + + >>> api.AwsLogsIntegration.check_services() + """ + cls._sub_resource_name = "logs/services_async" + return super(AwsLogsIntegration, cls).add_items(id=cls._resource_id, **params) + + @classmethod + def list(cls, **params): + """ + List all Datadog-AWS Logs integrations available in your Datadog organization. + + >>> api.AwsLogsIntegration.list() + """ + cls._sub_resource_name = "logs" + return super(AwsLogsIntegration, cls).get_items(id=cls._resource_id, **params) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/azure_integration.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/azure_integration.py new file mode 100644 index 0000000..2bb1cea --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/azure_integration.py @@ -0,0 +1,91 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datadog.api.resources import ( + GetableAPIResource, + CreateableAPIResource, + DeletableAPIResource, + UpdatableAPIResource, + AddableAPISubResource, +) + + +class AzureIntegration( + GetableAPIResource, CreateableAPIResource, DeletableAPIResource, UpdatableAPIResource, AddableAPISubResource +): + """ + A wrapper around Azure integration API. + """ + + _resource_name = "integration" + _resource_id = "azure" + + @classmethod + def list(cls, **params): + """ + List all Datadog-Azure integrations available in your Datadog organization. + + >>> api.AzureIntegration.list() + """ + return super(AzureIntegration, cls).get(id=cls._resource_id, **params) + + @classmethod + def create(cls, **params): + """ + Add a new Azure integration config. + + >>> tenant_name = "" + >>> client_id = "" + >>> client_secret = "" + >>> host_filters = [":"] + + >>> api.AzureIntegration.create(tenant_name=tenant_name, client_id=client_id, \ + client_secret=client_secret,host_filters=host_filters) + """ + return super(AzureIntegration, cls).create(id=cls._resource_id, **params) + + @classmethod + def delete(cls, **body): + """ + Delete a given Datadog-Azure integration. + + >>> tenant_name = "" + >>> client_id = "" + + >>> api.AzureIntegration.delete(tenant_name=tenant_name, client_id=client_id) + """ + return super(AzureIntegration, cls).delete(id=cls._resource_id, body=body) + + @classmethod + def update_host_filters(cls, **params): + """ + Update the defined list of host filters for a given Datadog-Azure integration. \ + + >>> tenant_name = "" + >>> client_id = "" + >>> host_filters = ":" + + >>> api.AzureIntegration.update_host_filters(tenant_name=tenant_name, client_id=client_id, \ + host_filters=host_filters) + """ + cls._sub_resource_name = "host_filters" + return super(AzureIntegration, cls).add_items(id=cls._resource_id, **params) + + @classmethod + def update(cls, **body): + """ + Update an Azure account configuration. + + >>> tenant_name = "" + >>> client_id = "" + >>> new_tenant_name = "" + >>> new_client_id = "" + >>> client_secret = "" + >>> host_filters = ":" + + >>> api.AzureIntegration.update(tenant_name=tenant_name, client_id=client_id, \ + new_tenant_name=new_tenant_name, new_client_id=new_client_id,\ + client_secret=client_secret, host_filters=host_filters) + """ + params = {} + return super(AzureIntegration, cls).update(id=cls._resource_id, params=params, **body) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/comments.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/comments.py new file mode 100644 index 0000000..7ecd506 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/comments.py @@ -0,0 +1,12 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datadog.api.resources import CreateableAPIResource, UpdatableAPIResource + + +class Comment(CreateableAPIResource, UpdatableAPIResource): + """ + A wrapper around Comment HTTP API. + """ + + _resource_name = "comments" diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/constants.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/constants.py new file mode 100644 index 0000000..a7e02b8 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/constants.py @@ -0,0 +1,25 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc + + +class CheckStatus(object): + OK = 0 + WARNING = 1 + CRITICAL = 2 + UNKNOWN = 3 + ALL = (OK, WARNING, CRITICAL, UNKNOWN) + + +class MonitorType(object): + # From https://docs.datadoghq.com/api/?lang=bash#create-a-monitor + QUERY_ALERT = "query alert" + COMPOSITE = "composite" + SERVICE_CHECK = "service check" + PROCESS_ALERT = "process alert" + LOG_ALERT = "log alert" + METRIC_ALERT = "metric alert" + RUM_ALERT = "rum alert" + EVENT_ALERT = "event alert" + SYNTHETICS_ALERT = "synthetics alert" + TRACE_ANALYTICS = "trace-analytics alert" diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/dashboard_list_v2.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/dashboard_list_v2.py new file mode 100644 index 0000000..127fca9 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/dashboard_list_v2.py @@ -0,0 +1,19 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datadog.api.resources import ( + AddableAPISubResource, + DeletableAPISubResource, + ListableAPISubResource, + UpdatableAPISubResource, +) + + +class DashboardListV2(ListableAPISubResource, AddableAPISubResource, UpdatableAPISubResource, DeletableAPISubResource): + """ + A wrapper around Dashboard List HTTP API. + """ + + _resource_name = "dashboard/lists/manual" + _sub_resource_name = "dashboards" + _api_version = "v2" diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/dashboard_lists.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/dashboard_lists.py new file mode 100644 index 0000000..e83785f --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/dashboard_lists.py @@ -0,0 +1,39 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datadog.api.resources import ( + AddableAPISubResource, + CreateableAPIResource, + DeletableAPIResource, + DeletableAPISubResource, + GetableAPIResource, + ListableAPIResource, + ListableAPISubResource, + UpdatableAPIResource, + UpdatableAPISubResource, +) + +from datadog.api.dashboard_list_v2 import DashboardListV2 + + +class DashboardList( + AddableAPISubResource, + CreateableAPIResource, + DeletableAPIResource, + DeletableAPISubResource, + GetableAPIResource, + ListableAPIResource, + ListableAPISubResource, + UpdatableAPIResource, + UpdatableAPISubResource, +): + """ + A wrapper around Dashboard List HTTP API. + """ + + _resource_name = "dashboard/lists/manual" + _sub_resource_name = "dashboards" + + # Support for new API version (api.DashboardList.v2) + # Note: This needs to be removed after complete migration of these endpoints from v1 to v2. + v2 = DashboardListV2() diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/dashboards.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/dashboards.py new file mode 100644 index 0000000..dab9b4d --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/dashboards.py @@ -0,0 +1,20 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datadog.api.resources import ( + GetableAPIResource, + CreateableAPIResource, + UpdatableAPIResource, + DeletableAPIResource, + ListableAPIResource, +) + + +class Dashboard( + GetableAPIResource, CreateableAPIResource, UpdatableAPIResource, DeletableAPIResource, ListableAPIResource +): + """ + A wrapper around Dashboard HTTP API. + """ + + _resource_name = "dashboard" diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/distributions.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/distributions.py new file mode 100644 index 0000000..918f7d8 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/distributions.py @@ -0,0 +1,45 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +# datadog +from datadog.api.format import format_points +from datadog.api.resources import SendableAPIResource + + +class Distribution(SendableAPIResource): + """A wrapper around Distribution HTTP API""" + + _resource_name = "distribution_points" + + @classmethod + def send(cls, distributions=None, attach_host_name=True, compress_payload=False, **distribution): + """ + Submit a distribution metric or a list of distribution metrics to the distribution metric + API + + :param compress_payload: compress the payload using zlib + :type compress_payload: bool + :param metric: the name of the time series + :type metric: string + :param points: a (timestamp, [list of values]) pair or + list of (timestamp, [list of values]) pairs + :type points: list + :param host: host name that produced the metric + :type host: string + :param tags: list of tags associated with the metric. + :type tags: string list + :returns: Dictionary representing the API's JSON response + """ + if distributions: + # Multiple distributions are sent + for d in distributions: + if isinstance(d, dict): + d["points"] = format_points(d["points"]) + series_dict = {"series": distributions} + else: + # One distribution is sent + distribution["points"] = format_points(distribution["points"]) + series_dict = {"series": [distribution]} + return super(Distribution, cls).send( + attach_host_name=attach_host_name, compress_payload=compress_payload, **series_dict + ) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/downtimes.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/downtimes.py new file mode 100644 index 0000000..567ed9e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/downtimes.py @@ -0,0 +1,38 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datadog.api.resources import ( + GetableAPIResource, + CreateableAPIResource, + UpdatableAPIResource, + ListableAPIResource, + DeletableAPIResource, + ActionAPIResource, +) + + +class Downtime( + GetableAPIResource, + CreateableAPIResource, + UpdatableAPIResource, + ListableAPIResource, + DeletableAPIResource, + ActionAPIResource, +): + """ + A wrapper around Monitor Downtiming HTTP API. + """ + + _resource_name = "downtime" + + @classmethod + def cancel_downtime_by_scope(cls, **body): + """ + Cancels all downtimes matching the scope. + + :param scope: scope to cancel downtimes by + :type scope: string + + :returns: Dictionary representing the API's JSON response + """ + return super(Downtime, cls)._trigger_class_action("POST", "cancel/by_scope", **body) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/events.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/events.py new file mode 100644 index 0000000..55b176f --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/events.py @@ -0,0 +1,95 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datadog.api.exceptions import ApiError +from datadog.api.resources import GetableAPIResource, CreateableAPIResource, SearchableAPIResource +from datadog.util.compat import iteritems + + +class Event(GetableAPIResource, CreateableAPIResource, SearchableAPIResource): + """ + A wrapper around Event HTTP API. + """ + + _resource_name = "events" + _timestamp_keys = set(["start", "end"]) + + @classmethod + def create(cls, attach_host_name=True, **params): + """ + Post an event. + + :param title: title for the new event + :type title: string + + :param text: event message + :type text: string + + :param aggregation_key: key by which to group events in event stream + :type aggregation_key: string + + :param alert_type: "error", "warning", "info" or "success". + :type alert_type: string + + :param date_happened: when the event occurred. if unset defaults to the current time. \ + (POSIX timestamp) + :type date_happened: integer + + :param handle: user to post the event as. defaults to owner of the application key used \ + to submit. + :type handle: string + + :param priority: priority to post the event as. ("normal" or "low", defaults to "normal") + :type priority: string + + :param related_event_id: post event as a child of the given event + :type related_event_id: id + + :param tags: tags to post the event with + :type tags: list of strings + + :param host: host to post the event with + :type host: string + + :param device_name: device_name to post the event with + :type device_name: list of strings + + :returns: Dictionary representing the API's JSON response + + >>> title = "Something big happened!" + >>> text = 'And let me tell you all about it here!' + >>> tags = ['version:1', 'application:web'] + + >>> api.Event.create(title=title, text=text, tags=tags) + """ + if params.get("alert_type"): + if params["alert_type"] not in ["error", "warning", "info", "success"]: + raise ApiError("Parameter alert_type must be either error, warning, info or success") + + return super(Event, cls).create(attach_host_name=attach_host_name, **params) + + @classmethod + def query(cls, **params): + """ + Get the events that occurred between the *start* and *end* POSIX timestamps, + optional filtered by *priority* ("low" or "normal"), *sources* and + *tags*. + + See the `event API documentation `_ for the + event data format. + + :returns: Dictionary representing the API's JSON response + + >>> api.Event.query(start=1313769783, end=1419436870, priority="normal", \ + tags=["application:web"]) + """ + + def timestamp_to_integer(k, v): + if k in cls._timestamp_keys: + return int(v) + else: + return v + + params = dict((k, timestamp_to_integer(k, v)) for k, v in iteritems(params)) + + return super(Event, cls)._search(**params) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/exceptions.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/exceptions.py new file mode 100644 index 0000000..afdfa36 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/exceptions.py @@ -0,0 +1,105 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +""" +API & HTTP Clients exceptions. +""" + + +class DatadogException(Exception): + """ + Base class for Datadog API exceptions. Use this for patterns like the following: + + try: + # do something with the Datadog API + except datadog.api.exceptions.DatadogException: + # handle any Datadog-specific exceptions + """ + + +class ProxyError(DatadogException): + """ + HTTP connection to the configured proxy server failed. + """ + + def __init__(self, method, url, exception): + message = ( + u"Could not request {method} {url}: Unable to connect to proxy. " + u"Please check the proxy configuration and try again.".format(method=method, url=url) + ) + super(ProxyError, self).__init__(message) + + +class ClientError(DatadogException): + """ + HTTP connection to Datadog endpoint is not possible. + """ + + def __init__(self, method, url, exception): + message = ( + u"Could not request {method} {url}: {exception}. " + u"Please check the network connection or try again later. " + u"If the problem persists, please contact support@datadoghq.com".format( + method=method, url=url, exception=exception + ) + ) + super(ClientError, self).__init__(message) + + +class HttpTimeout(DatadogException): + """ + HTTP connection timeout. + """ + + def __init__(self, method, url, timeout): + message = ( + u"{method} {url} timed out after {timeout}. " + u"Please try again later. " + u"If the problem persists, please contact support@datadoghq.com".format( + method=method, url=url, timeout=timeout + ) + ) + super(HttpTimeout, self).__init__(message) + + +class HttpBackoff(DatadogException): + """ + Backing off after too many timeouts. + """ + + def __init__(self, backoff_period): + message = u"Too many timeouts. Won't try again for {backoff_period} seconds. ".format( + backoff_period=backoff_period + ) + super(HttpBackoff, self).__init__(message) + + +class HTTPError(DatadogException): + """ + Datadog returned a HTTP error. + """ + + def __init__(self, status_code=None, reason=None): + reason = u" - {reason}".format(reason=reason) if reason else u"" + message = ( + u"Datadog returned a bad HTTP response code: {status_code}{reason}. " + u"Please try again later. " + u"If the problem persists, please contact support@datadoghq.com".format( + status_code=status_code, + reason=reason, + ) + ) + + super(HTTPError, self).__init__(message) + + +class ApiError(DatadogException): + """ + Datadog returned an API error (known HTTPError). + + Matches the following status codes: 400, 401, 403, 404, 409, 429. + """ + + +class ApiNotInitialized(DatadogException): + "No API key is set" diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/format.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/format.py new file mode 100644 index 0000000..d3e5b72 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/format.py @@ -0,0 +1,44 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from numbers import Number +import sys +import time + +if sys.version_info[0] >= 3: + from collections.abc import Iterable +else: + from collections import Iterable + + +def format_points(points): + """ + Format `points` parameter. + + Input: + a value or (timestamp, value) pair or a list of value or (timestamp, value) pairs + + Returns: + list of (timestamp, float value) pairs + + """ + now = time.time() + if not isinstance(points, list): + points = [points] + + formatted_points = [] + for point in points: + if isinstance(point, Number): + timestamp = now + value = float(point) + # Distributions contain a list of points + else: + timestamp = point[0] + if isinstance(point[1], Iterable): + value = [float(p) for p in point[1]] + else: + value = float(point[1]) + + formatted_points.append((timestamp, value)) + + return formatted_points diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/gcp_integration.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/gcp_integration.py new file mode 100644 index 0000000..978e1ae --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/gcp_integration.py @@ -0,0 +1,93 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datadog.api.resources import GetableAPIResource, CreateableAPIResource, DeletableAPIResource, UpdatableAPIResource + + +class GcpIntegration(GetableAPIResource, CreateableAPIResource, DeletableAPIResource, UpdatableAPIResource): + """ + A wrapper around GCP integration API. + """ + + _resource_name = "integration" + _resource_id = "gcp" + + @classmethod + def list(cls, **params): + """ + List all Datadog-Gcp integrations available in your Datadog organization. + + >>> api.GcpIntegration.list() + """ + return super(GcpIntegration, cls).get(id=cls._resource_id, **params) + + @classmethod + def delete(cls, **body): + """ + Delete a given Datadog-GCP integration. + + >>> project_id="" + >>> client_email="" + + >>> api.GcpIntegration.delete(project_id=project_id, client_email=client_email) + """ + return super(GcpIntegration, cls).delete(id=cls._resource_id, body=body) + + @classmethod + def create(cls, **params): + """ + Add a new GCP integration config. + + All of the following fields values are provided by the \ + JSON service account key file created in the GCP Console \ + for service accounts; Refer to the Datadog-Google Cloud \ + Platform integration installation instructions to see how \ + to generate one for your organization. For further references, \ + consult the Google Cloud service account documentation. + + >>> type="service_account" + >>> project_id="" + >>> private_key_id="" + >>> private_key="" + >>> client_email="" + >>> client_id="" + >>> auth_uri=">> token_uri="" + >>> auth_provider_x509_cert_url="" + >>> client_x509_cert_url="" + >>> host_filters=":,:" + + >>> api.GcpIntegration.create(type=type, project_id=project_id, \ + private_key_id=private_key_id,private_key=private_key, \ + client_email=client_email, client_id=client_id, \ + auth_uri=auth_uri, token_uri=token_uri, \ + auth_provider_x509_cert_url=auth_provider_x509_cert_url, \ + client_x509_cert_url=client_x509_cert_url, host_filters=host_filters) + """ + return super(GcpIntegration, cls).create(id=cls._resource_id, **params) + + @classmethod + def update(cls, **body): + """ + Update an existing service account partially (one or multiple fields), \ + by supplying a new value for the field(s) to be updated. + + `project_id` and `client_email` are required, in order to identify the \ + right service account to update. \ + The unspecified fields will keep their original values. + + The only use case for updating this integration is to change \ + host filtering and automute settings. Otherwise, an entirely \ + new integration config is needed. + + >>> project_id="" + >>> client_email="" + >>> host_filters="" + >>> automute=true #boolean + + >>> api.GcpIntegration.update(project_id=project_id, \ + client_email=client_email, host_filters=host_filters, \ + automute=automute) + """ + params = {} + return super(GcpIntegration, cls).update(id=cls._resource_id, params=params, **body) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/graphs.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/graphs.py new file mode 100644 index 0000000..ef29d70 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/graphs.py @@ -0,0 +1,84 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datadog.util.compat import urlparse +from datadog.api.resources import CreateableAPIResource, ActionAPIResource, GetableAPIResource, ListableAPIResource + + +class Graph(CreateableAPIResource, ActionAPIResource): + """ + A wrapper around Graph HTTP API. + """ + + _resource_name = "graph/snapshot" + + @classmethod + def create(cls, **params): + """ + Take a snapshot of a graph, returning the full url to the snapshot. + + :param metric_query: metric query + :type metric_query: string query + + :param start: query start timestamp + :type start: POSIX timestamp + + :param end: query end timestamp + :type end: POSIX timestamp + + :param event_query: a query that will add event bands to the graph + :type event_query: string query + + :returns: Dictionary representing the API's JSON response + """ + return super(Graph, cls).create(method="GET", **params) + + @classmethod + def status(cls, snapshot_url): + """ + Returns the status code of snapshot. Can be used to know when the + snapshot is ready for download. + + :param snapshot_url: snapshot URL to check + :type snapshot_url: string url + + :returns: Dictionary representing the API's JSON response + """ + snap_path = urlparse(snapshot_url).path + snap_path = snap_path.split("/snapshot/view/")[1].split(".png")[0] + + snapshot_status_url = "graph/snapshot_status/{0}".format(snap_path) + + return super(Graph, cls)._trigger_action("GET", snapshot_status_url) + + +class Embed(ListableAPIResource, GetableAPIResource, ActionAPIResource, CreateableAPIResource): + """ + A wrapper around Embed HTTP API. + """ + + _resource_name = "graph/embed" + + @classmethod + def enable(cls, embed_id): + """ + Enable a specified embed. + + :param embed_id: embed token + :type embed_id: string embed token + + :returns: Dictionary representing the API's JSON response + """ + return super(Embed, cls)._trigger_class_action("GET", id=embed_id, action_name="enable") + + @classmethod + def revoke(cls, embed_id): + """ + Revoke a specified embed. + + :param embed_id: embed token + :type embed_id: string embed token + + :returns: Dictionary representing the API's JSON response + """ + return super(Embed, cls)._trigger_class_action("GET", id=embed_id, action_name="revoke") diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/hosts.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/hosts.py new file mode 100644 index 0000000..5bc2a32 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/hosts.py @@ -0,0 +1,91 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datadog.api.resources import ActionAPIResource, SearchableAPIResource + + +class Host(ActionAPIResource): + """ + A wrapper around Host HTTP API. + """ + + _resource_name = "host" + + @classmethod + def mute(cls, host_name, **body): + """ + Mute a host. + + :param host_name: hostname + :type host_name: string + + :param end: timestamp to end muting + :type end: POSIX timestamp + + :param override: if true and the host is already muted, will override\ + existing end on the host + :type override: bool + + :param message: message to associate with the muting of this host + :type message: string + + :returns: Dictionary representing the API's JSON response + + """ + return super(Host, cls)._trigger_class_action("POST", "mute", host_name, **body) + + @classmethod + def unmute(cls, host_name): + """ + Unmute a host. + + :param host_name: hostname + :type host_name: string + + :returns: Dictionary representing the API's JSON response + + """ + return super(Host, cls)._trigger_class_action("POST", "unmute", host_name) + + +class Hosts(ActionAPIResource, SearchableAPIResource): + """ + A wrapper around Hosts HTTP API. + """ + + _resource_name = "hosts" + + @classmethod + def search(cls, **params): + """ + Search among hosts live within the past 2 hours. Max 100 + results at a time. + + :param filter: query to filter search results + :type filter: string + + :param sort_field: "status", "apps", "cpu", "iowait", or "load" + :type sort_field: string + + :param sort_dir: "asc" or "desc" + :type sort_dir: string + + :param start: host result to start at + :type start: integer + + :param count: number of host results to return + :type count: integer + + :returns: Dictionary representing the API's JSOn response + + """ + return super(Hosts, cls)._search(**params) + + @classmethod + def totals(cls): + """ + Get total number of hosts active and up. + + :returns: Dictionary representing the API's JSON response + """ + return super(Hosts, cls)._trigger_class_action("GET", "totals") diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/http_client.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/http_client.py new file mode 100644 index 0000000..f058393 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/http_client.py @@ -0,0 +1,195 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +""" +Available HTTP Client for Datadog API client. + +Priority: +1. `requests` 3p module +2. `urlfetch` 3p module - Google App Engine only +""" +# stdlib +import copy +import logging +import platform +import urllib +from threading import Lock + +# 3p +try: + import requests + import requests.adapters +except ImportError: + requests = None # type: ignore + +try: + from google.appengine.api import urlfetch, urlfetch_errors +except ImportError: + urlfetch, urlfetch_errors = None, None + +# datadog +from datadog.api.exceptions import ProxyError, ClientError, HTTPError, HttpTimeout + + +log = logging.getLogger("datadog.api") + + +def _get_user_agent_header(): + from datadog import version + + return "datadogpy/{version} (python {pyver}; os {os}; arch {arch})".format( + version=version.__version__, + pyver=platform.python_version(), + os=platform.system().lower(), + arch=platform.machine().lower(), + ) + + +def _remove_context(exc): + """Python3: remove context from chained exceptions to prevent leaking API keys in tracebacks.""" + exc.__cause__ = None + return exc + + +class HTTPClient(object): + """ + An abstract generic HTTP client. Subclasses must implement the `request` methods. + """ + + @classmethod + def request(cls, method, url, headers, params, data, timeout, proxies, verify, max_retries): + """ + Main method to be implemented by HTTP clients. + + The returned data structure has the following fields: + * `content`: string containing the response from the server + * `status_code`: HTTP status code returned by the server + + Can raise the following exceptions: + * `ClientError`: server cannot be contacted + * `HttpTimeout`: connection timed out + * `HTTPError`: unexpected HTTP response code + """ + raise NotImplementedError(u"Must be implemented by HTTPClient subclasses.") + + +class RequestClient(HTTPClient): + """ + HTTP client based on 3rd party `requests` module, using a single session. + This allows us to keep the session alive to spare some execution time. + """ + + _session = None + _session_lock = Lock() + + @classmethod + def request(cls, method, url, headers, params, data, timeout, proxies, verify, max_retries): + try: + + with cls._session_lock: + if cls._session is None: + cls._session = requests.Session() + http_adapter = requests.adapters.HTTPAdapter(max_retries=max_retries) + cls._session.mount("https://", http_adapter) + cls._session.headers.update({"User-Agent": _get_user_agent_header()}) + + result = cls._session.request( + method, url, headers=headers, params=params, data=data, timeout=timeout, proxies=proxies, verify=verify + ) + + result.raise_for_status() + + except requests.exceptions.ProxyError as e: + raise _remove_context(ProxyError(method, url, e)) + except requests.ConnectionError as e: + raise _remove_context(ClientError(method, url, e)) + except requests.exceptions.Timeout: + raise _remove_context(HttpTimeout(method, url, timeout)) + except requests.exceptions.HTTPError as e: + if e.response.status_code in (400, 401, 403, 404, 409, 429): + # This gets caught afterwards and raises an ApiError exception + pass + else: + raise _remove_context(HTTPError(e.response.status_code, result.reason)) + except TypeError: + raise TypeError( + u"Your installed version of `requests` library seems not compatible with" + u"Datadog's usage. We recommend upgrading it ('pip install -U requests')." + u"If you need help or have any question, please contact support@datadoghq.com" + ) + + return result + + +class URLFetchClient(HTTPClient): + """ + HTTP client based on Google App Engine `urlfetch` module. + """ + + @classmethod + def request(cls, method, url, headers, params, data, timeout, proxies, verify, max_retries): + """ + Wrapper around `urlfetch.fetch` method. + + TO IMPLEMENT: + * `max_retries` + """ + # No local certificate file can be used on Google App Engine + validate_certificate = True if verify else False + + # Encode parameters in the url + url_with_params = "{url}?{params}".format(url=url, params=urllib.urlencode(params)) + newheaders = copy.deepcopy(headers) + newheaders["User-Agent"] = _get_user_agent_header() + + try: + result = urlfetch.fetch( + url=url_with_params, + method=method, + headers=newheaders, + validate_certificate=validate_certificate, + deadline=timeout, + payload=data, + # setting follow_redirects=False may be slightly faster: + # https://cloud.google.com/appengine/docs/python/microservice-performance#use_the_shortest_route + follow_redirects=False, + ) + + cls.raise_on_status(result) + + except urlfetch.DownloadError as e: + raise ClientError(method, url, e) + except urlfetch_errors.DeadlineExceededError: + raise HttpTimeout(method, url, timeout) + + return result + + @classmethod + def raise_on_status(cls, result): + """ + Raise on HTTP status code errors. + """ + status_code = result.status_code + + if (status_code / 100) != 2: + if status_code in (400, 401, 403, 404, 409, 429): + pass + else: + raise HTTPError(status_code) + + +def resolve_http_client(): + """ + Resolve an appropriate HTTP client based the defined priority and user environment. + """ + if requests: + log.debug(u"Use `requests` based HTTP client.") + return RequestClient + + if urlfetch and urlfetch_errors: + log.debug(u"Use `urlfetch` based HTTP client.") + return URLFetchClient + + raise ImportError( + u"Datadog API client was unable to resolve a HTTP client. " u" Please install `requests` library." + ) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/infrastructure.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/infrastructure.py new file mode 100644 index 0000000..806a051 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/infrastructure.py @@ -0,0 +1,28 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datadog.api.resources import SearchableAPIResource + + +class Infrastructure(SearchableAPIResource): + """ + A wrapper around Infrastructure HTTP API. + """ + + _resource_name = "search" + + @classmethod + def search(cls, **params): + """ + Search for entities in Datadog. + + :param q: a query to search for host and metrics + :type q: string query + + :returns: Dictionary representing the API's JSON response + """ + # Deprecate the hosts search param + query = params.get("q", "").split(":") + if len(query) > 1 and query[0] == "hosts": + print("[DEPRECATION] Infrastructure.search() is deprecated for ", "hosts. Use `Hosts.search` instead.") + return super(Infrastructure, cls)._search(**params) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/logs.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/logs.py new file mode 100644 index 0000000..a87efa2 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/logs.py @@ -0,0 +1,22 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datadog.api.resources import CreateableAPIResource +from datadog.api.api_client import APIClient + + +class Logs(CreateableAPIResource): + """ + A wrapper around Log HTTP API. + """ + + _resource_name = "logs-queries" + + @classmethod + def list(cls, data): + path = "{resource_name}/list".format( + resource_name=cls._resource_name, + ) + api_version = getattr(cls, "_api_version", None) + + return APIClient.submit("POST", path, api_version, data) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/metadata.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/metadata.py new file mode 100644 index 0000000..6c251e5 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/metadata.py @@ -0,0 +1,64 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +# datadog +from datadog.api.resources import GetableAPIResource, UpdatableAPIResource + + +class Metadata(GetableAPIResource, UpdatableAPIResource): + """ + A wrapper around Metric Metadata HTTP API + """ + + _resource_name = "metrics" + + @classmethod + def get(cls, metric_name): + """ + Get metadata information on an existing Datadog metric + + param metric_name: metric name (ex. system.cpu.idle) + + :returns: Dictionary representing the API's JSON response + """ + if not metric_name: + raise KeyError("'metric_name' parameter is required") + + return super(Metadata, cls).get(metric_name) + + @classmethod + def update(cls, metric_name, **params): + """ + Update metadata fields for an existing Datadog metric. + If the metadata does not exist for the metric it is created by + the update. + + :param type: type of metric (ex. "gauge", "rate", etc.) + see http://docs.datadoghq.com/metrictypes/ + :type type: string + + :param description: description of the metric + :type description: string + + :param short_name: short name of the metric + :type short_name: string + + :param unit: unit type associated with the metric (ex. "byte", "operation") + see http://docs.datadoghq.com/units/ for full list + :type unit: string + + :param per_unit: per unit type (ex. "second" as in "queries per second") + see http://docs.datadoghq.com/units/ for full list + :type per_unit: string + + :param statsd_interval: statsd flush interval for metric in seconds (if applicable) + :type statsd_interval: integer + + :returns: Dictionary representing the API's JSON response + + >>> api.Metadata.update(metric_name='api.requests.served', metric_type="counter") + """ + if not metric_name: + raise KeyError("'metric_name' parameter is required") + + return super(Metadata, cls).update(id=metric_name, **params) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/metrics.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/metrics.py new file mode 100644 index 0000000..252ea88 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/metrics.py @@ -0,0 +1,147 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +# datadog +from datadog.api.exceptions import ApiError +from datadog.api.format import format_points +from datadog.api.resources import SearchableAPIResource, SendableAPIResource, ListableAPIResource + + +class Metric(SearchableAPIResource, SendableAPIResource, ListableAPIResource): + """ + A wrapper around Metric HTTP API + """ + + _resource_name = None + + _METRIC_QUERY_ENDPOINT = "query" + _METRIC_SUBMIT_ENDPOINT = "series" + _METRIC_LIST_ENDPOINT = "metrics" + + @classmethod + def list(cls, from_epoch): + """ + Get a list of active metrics since a given time (Unix Epoc) + + :param from_epoch: Start time in Unix Epoc (seconds) + + :returns: Dictionary containing a list of active metrics + """ + + cls._resource_name = cls._METRIC_LIST_ENDPOINT + + try: + seconds = int(from_epoch) + params = {"from": seconds} + except ValueError: + raise ApiError("Parameter 'from_epoch' must be an integer") + + return super(Metric, cls).get_all(**params) + + @staticmethod + def _rename_metric_type(metric): + """ + FIXME DROPME in 1.0: + + API documentation was illegitimately promoting usage of `metric_type` parameter + instead of `type`. + To be consistent and avoid 'backward incompatibilities', properly rename this parameter. + """ + if "metric_type" in metric: + metric["type"] = metric.pop("metric_type") + + @classmethod + def send(cls, metrics=None, attach_host_name=True, compress_payload=False, **single_metric): + """ + Submit a metric or a list of metrics to the metric API + A metric dictionary should consist of 5 keys: metric, points, host, tags, type (some of which optional), + see below: + + :param metric: the name of the time series + :type metric: string + + :param compress_payload: compress the payload using zlib + :type compress_payload: bool + + :param metrics: a list of dictionaries, each item being a metric to send + :type metrics: list + + :param points: a (timestamp, value) pair or list of (timestamp, value) pairs + :type points: list + + :param host: host name that produced the metric + :type host: string + + :param tags: list of tags associated with the metric. + :type tags: string list + + :param type: type of the metric + :type type: 'gauge' or 'count' or 'rate' string + + >>> api.Metric.send(metric='my.series', points=[(now, 15), (future_10s, 16)]) + + >>> metrics = [{'metric': 'my.series', 'type': 'gauge', 'points': [(now, 15), (future_10s, 16)]}, + {'metric': 'my.series2', 'type': 'gauge', 'points': [(now, 15), (future_10s, 16)]}] + >>> api.Metric.send(metrics=metrics) + + :returns: Dictionary representing the API's JSON response + """ + # Set the right endpoint + cls._resource_name = cls._METRIC_SUBMIT_ENDPOINT + + # Format the payload + try: + if metrics: + for metric in metrics: + if isinstance(metric, dict): + cls._rename_metric_type(metric) + metric["points"] = format_points(metric["points"]) + metrics_dict = {"series": metrics} + else: + cls._rename_metric_type(single_metric) + single_metric["points"] = format_points(single_metric["points"]) + metrics = [single_metric] + metrics_dict = {"series": metrics} + + except KeyError: + raise KeyError("'points' parameter is required") + + return super(Metric, cls).send( + attach_host_name=attach_host_name, compress_payload=compress_payload, **metrics_dict + ) + + @classmethod + def query(cls, **params): + """ + Query metrics from Datadog + + :param start: query start timestamp + :type start: POSIX timestamp + + :param end: query end timestamp + :type end: POSIX timestamp + + :param query: metric query + :type query: string query + + :returns: Dictionary representing the API's JSON response + + *start* and *end* should be less than 24 hours apart. + It is *not* meant to retrieve metric data in bulk. + + >>> api.Metric.query(start=int(time.time()) - 3600, end=int(time.time()), + query='avg:system.cpu.idle{*}') + """ + # Set the right endpoint + cls._resource_name = cls._METRIC_QUERY_ENDPOINT + + # `from` is a reserved keyword in Python, therefore + # `api.Metric.query(from=...)` is not permitted + # -> map `start` to `from` and `end` to `to` + try: + params["from"] = params.pop("start") + params["to"] = params.pop("end") + except KeyError as e: + raise ApiError("The parameter '{0}' is required".format(e.args[0])) + + return super(Metric, cls)._search(**params) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/monitors.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/monitors.py new file mode 100644 index 0000000..a2d9e74 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/monitors.py @@ -0,0 +1,157 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datadog.api.resources import ( + GetableAPIResource, + CreateableAPIResource, + UpdatableAPIResource, + ListableAPIResource, + DeletableAPIResource, + ActionAPIResource, +) + + +class Monitor( + GetableAPIResource, + CreateableAPIResource, + UpdatableAPIResource, + ListableAPIResource, + DeletableAPIResource, + ActionAPIResource, +): + """ + A wrapper around Monitor HTTP API. + """ + + _resource_name = "monitor" + + @classmethod + def get(cls, id, **params): + """ + Get monitor's details. + + :param id: monitor to retrieve + :type id: id + + :param group_states: string list indicating what, if any, group states to include + :type group_states: string list, strings are chosen from one or more \ + from 'all', 'alert', 'warn', or 'no data' + + :returns: Dictionary representing the API's JSON response + """ + if "group_states" in params and isinstance(params["group_states"], list): + params["group_states"] = ",".join(params["group_states"]) + + return super(Monitor, cls).get(id, **params) + + @classmethod + def get_all(cls, **params): + """ + Get all monitor details. + + :param group_states: string list indicating what, if any, group states to include + :type group_states: string list, strings are chosen from one or more \ + from 'all', 'alert', 'warn', or 'no data' + + :param name: name to filter the list of monitors by + :type name: string + + :param tags: tags to filter the list of monitors by scope + :type tags: string list + + :param monitor_tags: list indicating what service and/or custom tags, if any, \ + should be used to filter the list of monitors + :type monitor_tags: string list + + :returns: Dictionary representing the API's JSON response + """ + for p in ["group_states", "tags", "monitor_tags"]: + if p in params and isinstance(params[p], list): + params[p] = ",".join(params[p]) + + return super(Monitor, cls).get_all(**params) + + @classmethod + def mute(cls, id, **body): + """ + Mute a monitor. + + :param scope: scope to apply the mute + :type scope: string + + :param end: timestamp for when the mute should end + :type end: POSIX timestamp + + + :returns: Dictionary representing the API's JSON response + """ + return super(Monitor, cls)._trigger_class_action("POST", "mute", id, **body) + + @classmethod + def unmute(cls, id, **body): + """ + Unmute a monitor. + + :param scope: scope to apply the unmute + :type scope: string + + :param all_scopes: if True, clears mute settings for all scopes + :type all_scopes: boolean + + :returns: Dictionary representing the API's JSON response + """ + return super(Monitor, cls)._trigger_class_action("POST", "unmute", id, **body) + + @classmethod + def mute_all(cls): + """ + Globally mute monitors. + + :returns: Dictionary representing the API's JSON response + """ + return super(Monitor, cls)._trigger_class_action("POST", "mute_all") + + @classmethod + def unmute_all(cls): + """ + Cancel global monitor mute setting (does not remove mute settings for individual monitors). + + :returns: Dictionary representing the API's JSON response + """ + return super(Monitor, cls)._trigger_class_action("POST", "unmute_all") + + @classmethod + def search(cls, **params): + """ + Search monitors. + + :returns: Dictionary representing the API's JSON response + """ + return super(Monitor, cls)._trigger_class_action("GET", "search", params=params) + + @classmethod + def search_groups(cls, **params): + """ + Search monitor groups. + + :returns: Dictionary representing the API's JSON response + """ + return super(Monitor, cls)._trigger_class_action("GET", "groups/search", params=params) + + @classmethod + def can_delete(cls, **params): + """ + Checks if the monitors corresponding to the monitor ids can be deleted. + + :returns: Dictionary representing the API's JSON response + """ + return super(Monitor, cls)._trigger_class_action("GET", "can_delete", params=params) + + @classmethod + def validate(cls, **body): + """ + Checks if the monitors definition is valid. + + :returns: Dictionary representing the API's JSON response + """ + return super(Monitor, cls)._trigger_class_action("POST", "validate", **body) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/permissions.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/permissions.py new file mode 100644 index 0000000..f12dad7 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/permissions.py @@ -0,0 +1,27 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datadog.api.resources import ( + ActionAPIResource, + CreateableAPIResource, + CustomUpdatableAPIResource, + DeletableAPIResource, + GetableAPIResource, + ListableAPIResource, +) + + +class Permissions( + ActionAPIResource, + CreateableAPIResource, + CustomUpdatableAPIResource, + GetableAPIResource, + ListableAPIResource, + DeletableAPIResource, +): + """ + A wrapper around Tag HTTP API. + """ + + _resource_name = "permissions" + _api_version = "v2" diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/resources.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/resources.py new file mode 100644 index 0000000..67bcc39 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/resources.py @@ -0,0 +1,539 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +""" +Datadog API resources. +""" + +from datadog.api.api_client import APIClient + + +class CreateableAPIResource(object): + """ + Creatable API Resource + """ + + @classmethod + def create(cls, attach_host_name=False, method="POST", id=None, params=None, **body): + """ + Create a new API resource object + + :param attach_host_name: link the new resource object to the host name + :type attach_host_name: bool + + :param method: HTTP method to use to contact API endpoint + :type method: HTTP method string + + :param id: create a new resource object as a child of the given object + :type id: id + + :param params: new resource object source + :type params: dictionary + + :param body: new resource object attributes + :type body: dictionary + + :returns: Dictionary representing the API's JSON response + """ + if params is None: + params = {} + + path = cls._resource_name + api_version = getattr(cls, "_api_version", None) + + if method == "GET": + return APIClient.submit("GET", path, api_version, **body) + if id is None: + return APIClient.submit("POST", path, api_version, body, attach_host_name=attach_host_name, **params) + + path = "{resource_name}/{resource_id}".format(resource_name=cls._resource_name, resource_id=id) + return APIClient.submit("POST", path, api_version, body, attach_host_name=attach_host_name, **params) + + +class SendableAPIResource(object): + """ + Fork of CreateableAPIResource class with different method names + """ + + @classmethod + def send(cls, attach_host_name=False, id=None, compress_payload=False, **body): + """ + Create an API resource object + + :param attach_host_name: link the new resource object to the host name + :type attach_host_name: bool + + :param id: create a new resource object as a child of the given object + :type id: id + + :param compress_payload: compress the payload using zlib + :type compress_payload: bool + + :param body: new resource object attributes + :type body: dictionary + + :returns: Dictionary representing the API's JSON response + """ + api_version = getattr(cls, "_api_version", None) + + if id is None: + return APIClient.submit( + "POST", + cls._resource_name, + api_version, + body, + attach_host_name=attach_host_name, + compress_payload=compress_payload, + ) + + path = "{resource_name}/{resource_id}".format(resource_name=cls._resource_name, resource_id=id) + return APIClient.submit( + "POST", path, api_version, body, attach_host_name=attach_host_name, compress_payload=compress_payload + ) + + +class UpdatableAPIResource(object): + """ + Updatable API Resource + """ + + @classmethod + def update(cls, id, params=None, **body): + """ + Update an API resource object + + :param params: updated resource object source + :type params: dictionary + + :param body: updated resource object attributes + :type body: dictionary + + :returns: Dictionary representing the API's JSON response + """ + if params is None: + params = {} + + path = "{resource_name}/{resource_id}".format(resource_name=cls._resource_name, resource_id=id) + api_version = getattr(cls, "_api_version", None) + + return APIClient.submit("PUT", path, api_version, body, **params) + + +class CustomUpdatableAPIResource(object): + """ + Updatable API Resource with custom HTTP Verb + """ + + @classmethod + def update(cls, method=None, id=None, params=None, **body): + """ + Update an API resource object + + :param method: HTTP method, defaults to PUT + :type params: string + + :param params: updatable resource id + :type params: string + + :param params: updated resource object source + :type params: dictionary + + :param body: updated resource object attributes + :type body: dictionary + + :returns: Dictionary representing the API's JSON response + """ + + if method is None: + method = "PUT" + if params is None: + params = {} + + path = "{resource_name}/{resource_id}".format(resource_name=cls._resource_name, resource_id=id) + api_version = getattr(cls, "_api_version", None) + + return APIClient.submit(method, path, api_version, body, **params) + + +class DeletableAPIResource(object): + """ + Deletable API Resource + """ + + @classmethod + def delete(cls, id, **params): + """ + Delete an API resource object + + :param id: resource object to delete + :type id: id + + :returns: Dictionary representing the API's JSON response + """ + path = "{resource_name}/{resource_id}".format(resource_name=cls._resource_name, resource_id=id) + api_version = getattr(cls, "_api_version", None) + + return APIClient.submit("DELETE", path, api_version, **params) + + +class GetableAPIResource(object): + """ + Getable API Resource + """ + + @classmethod + def get(cls, id, **params): + """ + Get information about an API resource object + + :param id: resource object id to retrieve + :type id: id + + :param params: parameters to filter API resource stream + :type params: dictionary + + :returns: Dictionary representing the API's JSON response + """ + path = "{resource_name}/{resource_id}".format(resource_name=cls._resource_name, resource_id=id) + api_version = getattr(cls, "_api_version", None) + + return APIClient.submit("GET", path, api_version, **params) + + +class ListableAPIResource(object): + """ + Listable API Resource + """ + + @classmethod + def get_all(cls, **params): + """ + List API resource objects + + :param params: parameters to filter API resource stream + :type params: dictionary + + :returns: Dictionary representing the API's JSON response + """ + api_version = getattr(cls, "_api_version", None) + + return APIClient.submit("GET", cls._resource_name, api_version, **params) + + +class ListableAPISubResource(object): + """ + Listable API Sub-Resource + """ + + @classmethod + def get_items(cls, id, **params): + """ + List API sub-resource objects from a resource + + :param id: resource id to retrieve sub-resource objects from + :type id: id + + :param params: parameters to filter API sub-resource stream + :type params: dictionary + + :returns: Dictionary representing the API's JSON response + """ + + path = "{resource_name}/{resource_id}/{sub_resource_name}".format( + resource_name=cls._resource_name, resource_id=id, sub_resource_name=cls._sub_resource_name + ) + api_version = getattr(cls, "_api_version", None) + + return APIClient.submit("GET", path, api_version, **params) + + +class AddableAPISubResource(object): + """ + Addable API Sub-Resource + """ + + @classmethod + def add_items(cls, id, params=None, **body): + """ + Add new API sub-resource objects to a resource + + :param id: resource id to add sub-resource objects to + :type id: id + + :param params: request parameters + :type params: dictionary + + :param body: new sub-resource objects attributes + :type body: dictionary + + :returns: Dictionary representing the API's JSON response + """ + if params is None: + params = {} + + path = "{resource_name}/{resource_id}/{sub_resource_name}".format( + resource_name=cls._resource_name, resource_id=id, sub_resource_name=cls._sub_resource_name + ) + api_version = getattr(cls, "_api_version", None) + + return APIClient.submit("POST", path, api_version, body, **params) + + +class UpdatableAPISubResource(object): + """ + Updatable API Sub-Resource + """ + + @classmethod + def update_items(cls, id, params=None, **body): + """ + Update API sub-resource objects of a resource + + :param id: resource id to update sub-resource objects from + :type id: id + + :param params: request parameters + :type params: dictionary + + :param body: updated sub-resource objects attributes + :type body: dictionary + + :returns: Dictionary representing the API's JSON response + """ + if params is None: + params = {} + + path = "{resource_name}/{resource_id}/{sub_resource_name}".format( + resource_name=cls._resource_name, resource_id=id, sub_resource_name=cls._sub_resource_name + ) + api_version = getattr(cls, "_api_version", None) + + return APIClient.submit("PUT", path, api_version, body, **params) + + +class DeletableAPISubResource(object): + """ + Deletable API Sub-Resource + """ + + @classmethod + def delete_items(cls, id, params=None, **body): + """ + Delete API sub-resource objects from a resource + + :param id: resource id to delete sub-resource objects from + :type id: id + + :param params: request parameters + :type params: dictionary + + :param body: deleted sub-resource objects attributes + :type body: dictionary + + :returns: Dictionary representing the API's JSON response + """ + if params is None: + params = {} + + path = "{resource_name}/{resource_id}/{sub_resource_name}".format( + resource_name=cls._resource_name, resource_id=id, sub_resource_name=cls._sub_resource_name + ) + api_version = getattr(cls, "_api_version", None) + + return APIClient.submit("DELETE", path, api_version, body, **params) + + +class SearchableAPIResource(object): + """ + Fork of ListableAPIResource class with different method names + """ + + @classmethod + def _search(cls, **params): + """ + Query an API resource stream + + :param params: parameters to filter API resource stream + :type params: dictionary + + :returns: Dictionary representing the API's JSON response + """ + api_version = getattr(cls, "_api_version", None) + + return APIClient.submit("GET", cls._resource_name, api_version, **params) + + +class ActionAPIResource(object): + """ + Actionable API Resource + """ + + @classmethod + def _trigger_class_action(cls, method, action_name, id=None, params=None, **body): + """ + Trigger an action + + :param method: HTTP method to use to contact API endpoint + :type method: HTTP method string + + :param action_name: action name + :type action_name: string + + :param id: trigger the action for the specified resource object + :type id: id + + :param params: action parameters + :type params: dictionary + + :param body: action body + :type body: dictionary + + :returns: Dictionary representing the API's JSON response + """ + if params is None: + params = {} + + api_version = getattr(cls, "_api_version", None) + + if id is None: + path = "{resource_name}/{action_name}".format(resource_name=cls._resource_name, action_name=action_name) + else: + path = "{resource_name}/{resource_id}/{action_name}".format( + resource_name=cls._resource_name, resource_id=id, action_name=action_name + ) + if method == "GET": + # Do not add body to GET requests, it causes 400 Bad request responses on EU site + body = None + return APIClient.submit(method, path, api_version, body, **params) + + @classmethod + def _trigger_action(cls, method, name, id=None, **body): + """ + Trigger an action + + :param method: HTTP method to use to contact API endpoint + :type method: HTTP method string + + :param name: action name + :type name: string + + :param id: trigger the action for the specified resource object + :type id: id + + :param body: action body + :type body: dictionary + + :returns: Dictionary representing the API's JSON response + """ + api_version = getattr(cls, "_api_version", None) + if id is None: + return APIClient.submit(method, name, api_version, body) + + path = "{action_name}/{resource_id}".format(action_name=name, resource_id=id) + if method == "GET": + # Do not add body to GET requests, it causes 400 Bad request responses on EU site + body = None + return APIClient.submit(method, path, api_version, body) + + +class UpdatableAPISyntheticsSubResource(object): + """ + Update Synthetics sub resource + """ + + @classmethod + def update_synthetics_items(cls, id, params=None, **body): + """ + Update API sub-resource objects of a resource + + :param id: resource id to update sub-resource objects from + :type id: id + + :param params: request parameters + :type params: dictionary + + :param body: updated sub-resource objects attributes + :type body: dictionary + + :returns: Dictionary representing the API's JSON response + """ + if params is None: + params = {} + + path = "{resource_name}/tests/{resource_id}/{sub_resource_name}".format( + resource_name=cls._resource_name, resource_id=id, sub_resource_name=cls._sub_resource_name + ) + api_version = getattr(cls, "_api_version", None) + + return APIClient.submit("PUT", path, api_version, body, **params) + + +class UpdatableAPISyntheticsResource(object): + """ + Update Synthetics resource + """ + + @classmethod + def update_synthetics(cls, id, params=None, **body): + """ + Update an API resource object + + :param params: updated resource object source + :type params: dictionary + + :param body: updated resource object attributes + :type body: dictionary + + :returns: Dictionary representing the API's JSON response + """ + if params is None: + params = {} + + path = "{resource_name}/tests/{resource_id}".format(resource_name=cls._resource_name, resource_id=id) + api_version = getattr(cls, "_api_version", None) + + return APIClient.submit("PUT", path, api_version, body, **params) + + +class ActionAPISyntheticsResource(object): + """ + Actionable Synthetics API Resource + """ + + @classmethod + def _trigger_synthetics_class_action(cls, method, name, id=None, params=None, **body): + """ + Trigger an action + + :param method: HTTP method to use to contact API endpoint + :type method: HTTP method string + + :param name: action name + :type name: string + + :param id: trigger the action for the specified resource object + :type id: id + + :param params: action parameters + :type params: dictionary + + :param body: action body + :type body: dictionary + + :returns: Dictionary representing the API's JSON response + """ + if params is None: + params = {} + + api_version = getattr(cls, "_api_version", None) + + if id is None: + path = "{resource_name}/{action_name}".format(resource_name=cls._resource_name, action_name=name) + else: + path = "{resource_name}/{action_name}/{resource_id}".format( + resource_name=cls._resource_name, resource_id=id, action_name=name + ) + if method == "GET": + # Do not add body to GET requests, it causes 400 Bad request responses on EU site + body = None + return APIClient.submit(method, path, api_version, body, **params) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/roles.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/roles.py new file mode 100644 index 0000000..2fce1dd --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/roles.py @@ -0,0 +1,71 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datadog.api.resources import ( + ActionAPIResource, + CreateableAPIResource, + CustomUpdatableAPIResource, + DeletableAPIResource, + GetableAPIResource, + ListableAPIResource, +) + +from datadog.api.api_client import APIClient + + +class Roles( + ActionAPIResource, + CreateableAPIResource, + CustomUpdatableAPIResource, + GetableAPIResource, + ListableAPIResource, + DeletableAPIResource, +): + """ + A wrapper around Tag HTTP API. + """ + + _resource_name = "roles" + _api_version = "v2" + + @classmethod + def update(cls, id, **body): + """ + Update a role's attributes + + :param id: uuid of the role + :param body: dict with type of the input, role `id`, and modified attributes + :returns: Dictionary representing the API's JSON response + """ + params = {} + return super(Roles, cls).update("PATCH", id, params=params, **body) + + @classmethod + def assign_permission(cls, id, **body): + """ + Assign permission to a role + + :param id: uuid of the role to assign permission to + :param body: dict with "type": "permissions" and uuid of permission to assign + :returns: Dictionary representing the API's JSON response + """ + params = {} + path = "{resource_name}/{resource_id}/permissions".format(resource_name=cls._resource_name, resource_id=id) + api_version = getattr(cls, "_api_version", None) + + return APIClient.submit("POST", path, api_version, body, **params) + + @classmethod + def unassign_permission(cls, id, **body): + """ + Unassign permission from a role + + :param id: uuid of the role to unassign permission from + :param body: dict with "type": "permissions" and uuid of permission to unassign + :returns: Dictionary representing the API's JSON response + """ + params = {} + path = "{resource_name}/{resource_id}/permissions".format(resource_name=cls._resource_name, resource_id=id) + api_version = getattr(cls, "_api_version", None) + + return APIClient.submit("DELETE", path, api_version, body, **params) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/screenboards.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/screenboards.py new file mode 100644 index 0000000..9367ab7 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/screenboards.py @@ -0,0 +1,50 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datadog.api.resources import ( + GetableAPIResource, + CreateableAPIResource, + UpdatableAPIResource, + DeletableAPIResource, + ActionAPIResource, + ListableAPIResource, +) + + +class Screenboard( + GetableAPIResource, + CreateableAPIResource, + UpdatableAPIResource, + DeletableAPIResource, + ActionAPIResource, + ListableAPIResource, +): + """ + A wrapper around Screenboard HTTP API. + """ + + _resource_name = "screen" + + @classmethod + def share(cls, board_id): + """ + Share the screenboard with given id + + :param board_id: screenboard to share + :type board_id: id + + :returns: Dictionary representing the API's JSON response + """ + return super(Screenboard, cls)._trigger_action("POST", "screen/share", board_id) + + @classmethod + def revoke(cls, board_id): + """ + Revoke a shared screenboard with given id + + :param board_id: screenboard to revoke + :type board_id: id + + :returns: Dictionary representing the API's JSON response + """ + return super(Screenboard, cls)._trigger_action("DELETE", "screen/share", board_id) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/service_checks.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/service_checks.py new file mode 100644 index 0000000..72fcb9a --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/service_checks.py @@ -0,0 +1,45 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datadog.api.constants import CheckStatus +from datadog.api.exceptions import ApiError +from datadog.api.resources import ActionAPIResource + + +class ServiceCheck(ActionAPIResource): + """ + A wrapper around ServiceCheck HTTP API. + """ + + @classmethod + def check(cls, **body): + """ + Post check statuses for use with monitors + + :param check: text for the message + :type check: string + + :param host_name: name of the host submitting the check + :type host_name: string + + :param status: integer for the status of the check + :type status: Options: '0': OK, '1': WARNING, '2': CRITICAL, '3': UNKNOWN + + :param timestamp: timestamp of the event + :type timestamp: POSIX timestamp + + :param message: description of why this status occurred + :type message: string + + :param tags: list of tags for this check + :type tags: string list + + :returns: Dictionary representing the API's JSON response + """ + + # Validate checks, include only non-null values + for param, value in body.items(): + if param == "status" and body[param] not in CheckStatus.ALL: + raise ApiError("Invalid status, expected one of: %s" % ", ".join(str(v) for v in CheckStatus.ALL)) + + return super(ServiceCheck, cls)._trigger_action("POST", "check_run", **body) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/service_level_objectives.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/service_level_objectives.py new file mode 100644 index 0000000..abb5a5d --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/service_level_objectives.py @@ -0,0 +1,213 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datadog.util.format import force_to_epoch_seconds +from datadog.api.resources import ( + GetableAPIResource, + CreateableAPIResource, + UpdatableAPIResource, + ListableAPIResource, + DeletableAPIResource, + ActionAPIResource, +) + + +class ServiceLevelObjective( + GetableAPIResource, + CreateableAPIResource, + UpdatableAPIResource, + ListableAPIResource, + DeletableAPIResource, + ActionAPIResource, +): + """ + A wrapper around Service Level Objective HTTP API. + """ + + _resource_name = "slo" + + @classmethod + def create(cls, attach_host_name=False, method="POST", id=None, params=None, **body): + """ + Create a SLO + + :returns: created SLO details + """ + return super(ServiceLevelObjective, cls).create( + attach_host_name=False, method="POST", id=None, params=params, **body + ) + + @classmethod + def get(cls, id, **params): + """ + Get a specific SLO details. + + :param id: SLO id to get details for + :type id: str + + :returns: SLO details + """ + return super(ServiceLevelObjective, cls).get(id, **params) + + @classmethod + def get_all(cls, query=None, tags_query=None, metrics_query=None, ids=None, offset=0, limit=100, **params): + """ + Get all SLO details. + + :param query: optional search query to filter results for SLO name + :type query: str + + :param tags_query: optional search query to filter results for a single SLO tag + :type query: str + + :param metrics_query: optional search query to filter results based on SLO numerator and denominator + :type query: str + + :param ids: optional list of SLO ids to get many specific SLOs at once. + :type ids: list(str) + + :param offset: offset of results to use (default 0) + :type offset: int + + :param limit: limit of results to return (default: 100) + :type limit: int + + :returns: SLOs matching the query + """ + search_terms = {} + if query: + search_terms["query"] = query + if ids: + search_terms["ids"] = ids + if tags_query: + search_terms["tags_query"] = tags_query + if metrics_query: + search_terms["metrics_query"] = metrics_query + search_terms["offset"] = offset + search_terms["limit"] = limit + + return super(ServiceLevelObjective, cls).get_all(**search_terms) + + @classmethod + def update(cls, id, params=None, **body): + """ + Update a specific SLO details. + + :param id: SLO id to update details for + :type id: str + + :returns: SLO details + """ + return super(ServiceLevelObjective, cls).update(id, params, **body) + + @classmethod + def delete(cls, id, **params): + """ + Delete a specific SLO. + + :param id: SLO id to delete + :type id: str + + :returns: SLO ids removed + """ + return super(ServiceLevelObjective, cls).delete(id, **params) + + @classmethod + def bulk_delete(cls, ops, **params): + """ + Bulk Delete Timeframes from multiple SLOs. + + :param ops: a dictionary mapping of SLO ID to timeframes to remove. + :type ops: dict(str, list(str)) + + :returns: Dictionary representing the API's JSON response + `errors` - errors with operation + `data` - updates and deletions + """ + return super(ServiceLevelObjective, cls)._trigger_class_action( + "POST", + "bulk_delete", + body=ops, + params=params, + suppress_response_errors_on_codes=[200], + ) + + @classmethod + def delete_many(cls, ids, **params): + """ + Delete Multiple SLOs + + :param ids: a list of SLO IDs to remove + :type ids: list(str) + + :returns: Dictionary representing the API's JSON response see `data` list(slo ids) && `errors` + """ + return super(ServiceLevelObjective, cls)._trigger_class_action( + "DELETE", + "", + params=params, + body={"ids": ids}, + suppress_response_errors_on_codes=[200], + ) + + @classmethod + def can_delete(cls, ids, **params): + """ + Check if the following SLOs can be safely deleted. + + This is used to check if SLO has any references to it. + + :param ids: a list of SLO IDs to check + :type ids: list(str) + + :returns: Dictionary representing the API's JSON response + "data.ok" represents a list of SLO ids that have no known references. + "errors" contains a dictionary of SLO ID to known reference(s). + """ + params["ids"] = ids + return super(ServiceLevelObjective, cls)._trigger_class_action( + "GET", + "can_delete", + params=params, + body=None, + suppress_response_errors_on_codes=[200], + ) + + @classmethod + def history(cls, id, from_ts, to_ts, **params): + """ + Get the SLO's history from the given time range. + + :param id: SLO ID to query + :type id: str + + :param from_ts: `from` timestamp in epoch seconds to query + :type from_ts: int|datetime.datetime + + :param to_ts: `to` timestamp in epoch seconds to query, must be > `from_ts` + :type to_ts: int|datetime.datetime + + :returns: Dictionary representing the API's JSON response + "data.ok" represents a list of SLO ids that have no known references. + "errors" contains a dictionary of SLO ID to known reference(s). + """ + params["id"] = id + params["from_ts"] = force_to_epoch_seconds(from_ts) + params["to_ts"] = force_to_epoch_seconds(to_ts) + return super(ServiceLevelObjective, cls)._trigger_class_action( + "GET", + "history", + id=id, + params=params, + body=None, + suppress_response_errors_on_codes=[200], + ) + + @classmethod + def search(cls, **params): + """ + Search SLOs. + + :returns: Dictionary representing the API's JSON response + """ + return super(ServiceLevelObjective, cls)._trigger_class_action("GET", "search", params=params) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/synthetics.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/synthetics.py new file mode 100644 index 0000000..88c0e3a --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/synthetics.py @@ -0,0 +1,214 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datadog.api.exceptions import ApiError +from datadog.api.resources import ( + CreateableAPIResource, + GetableAPIResource, + ActionAPIResource, + UpdatableAPISyntheticsResource, + UpdatableAPISyntheticsSubResource, + ActionAPISyntheticsResource, +) + + +class Synthetics( + ActionAPIResource, + ActionAPISyntheticsResource, + CreateableAPIResource, + GetableAPIResource, + UpdatableAPISyntheticsResource, + UpdatableAPISyntheticsSubResource, +): + """ + A wrapper around Sythetics HTTP API. + """ + + _resource_name = "synthetics" + _sub_resource_name = "status" + + @classmethod + def get_test(cls, id, **params): + """ + Get test's details. + + :param id: public id of the test to retrieve + :type id: string + + :returns: Dictionary representing the API's JSON response + """ + + # API path = "synthetics/tests/ + + name = "tests" + + return super(Synthetics, cls)._trigger_synthetics_class_action("GET", id=id, name=name, params=params) + + @classmethod + def get_all_tests(cls, **params): + """ + Get all tests' details. + + :returns: Dictionary representing the API's JSON response + """ + + for p in ["locations", "tags"]: + if p in params and isinstance(params[p], list): + params[p] = ",".join(params[p]) + + # API path = "synthetics/tests" + + return super(Synthetics, cls).get(id="tests", params=params) + + @classmethod + def get_devices(cls, **params): + """ + Get a list of devices for browser checks + + :returns: Dictionary representing the API's JSON response + """ + + # API path = "synthetics/browser/devices" + + name = "browser/devices" + + return super(Synthetics, cls)._trigger_synthetics_class_action("GET", name=name, params=params) + + @classmethod + def get_locations(cls, **params): + """ + Get a list of all available locations + + :return: Dictionary representing the API's JSON response + """ + + name = "locations" + + # API path = "synthetics/locations + + return super(Synthetics, cls)._trigger_synthetics_class_action("GET", name=name, params=params) + + @classmethod + def get_results(cls, id, **params): + """ + Get the most recent results for a test + + :param id: public id of the test to retrieve results for + :type id: id + + :return: Dictionary representing the API's JSON response + """ + + # API path = "synthetics/tests//results + + path = "tests/{}/results".format(id) + + return super(Synthetics, cls)._trigger_synthetics_class_action("GET", path, params=params) + + @classmethod + def get_result(cls, id, result_id, **params): + """ + Get a specific result for a given test. + + :param id: public ID of the test to retrieve the most recent result for + :type id: id + + :param result_id: result ID of the test to retrieve the most recent result for + :type result_id: id + + :returns: Dictionary representing the API's JSON response + """ + + # API path = "synthetics/tests/results/ + + path = "tests/{}/results/{}".format(id, result_id) + + return super(Synthetics, cls)._trigger_synthetics_class_action("GET", path, params=params) + + @classmethod + def create_test(cls, **params): + """ + Create a test + + :param name: A unique name for the test + :type name: string + + :param type: The type of test. Valid values are api and browser + :type type: string + + :param subtype: required for SSL test - For a SSL API test, specify ssl as the value. + :Otherwise, you should omit this argument. + :type subtype: string + + :param config: The test configuration, contains the request specification and the assertions. + :type config: dict + + :param options: List of options to customize the test + :type options: dict + + :param message: A description of the test + :type message: string + + :param locations: A list of the locations to send the tests from + :type locations: list + + :param tags: A list of tags used to filter the test + :type tags: list + + :return: Dictionary representing the API's JSON response + """ + + # API path = "synthetics/tests" + + return super(Synthetics, cls).create(id="tests", **params) + + @classmethod + def edit_test(cls, id, **params): + """ + Edit a test + + :param id: Public id of the test to edit + :type id: string + + :return: Dictionary representing the API's JSON response + """ + + # API path = "synthetics/tests/" + + return super(Synthetics, cls).update_synthetics(id=id, **params) + + @classmethod + def start_or_pause_test(cls, id, **body): + """ + Pause a given test + + :param id: public id of the test to pause + :type id: string + + :param new_status: mew status for the test + :type id: string + + :returns: Dictionary representing the API's JSON response + """ + + # API path = "synthetics/tests//status" + + return super(Synthetics, cls).update_synthetics_items(id=id, **body) + + @classmethod + def delete_test(cls, **body): + """ + Delete a test + + :param public_ids: list of public IDs to delete corresponding tests + :type public_ids: list of strings + + :return: Dictionary representing the API's JSON response + """ + + if not isinstance(body["public_ids"], list): + raise ApiError("Parameter 'public_ids' must be a list") + + # API path = "synthetics/tests/delete + + return super(Synthetics, cls)._trigger_action("POST", name="synthetics", id="tests/delete", **body) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/tags.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/tags.py new file mode 100644 index 0000000..2226cdb --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/tags.py @@ -0,0 +1,54 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datadog.api.resources import ( + CreateableAPIResource, + UpdatableAPIResource, + DeletableAPIResource, + GetableAPIResource, + ListableAPIResource, +) + + +class Tag(CreateableAPIResource, UpdatableAPIResource, GetableAPIResource, ListableAPIResource, DeletableAPIResource): + """ + A wrapper around Tag HTTP API. + """ + + _resource_name = "tags/hosts" + + @classmethod + def create(cls, host, **body): + """ + Add tags to a host + + :param tags: list of tags to apply to the host + :type tags: string list + + :param source: source of the tags + :type source: string + + :returns: Dictionary representing the API's JSON response + """ + params = {} + if "source" in body: + params["source"] = body["source"] + return super(Tag, cls).create(id=host, params=params, **body) + + @classmethod + def update(cls, host, **body): + """ + Update all tags for a given host + + :param tags: list of tags to apply to the host + :type tags: string list + + :param source: source of the tags + :type source: string + + :returns: Dictionary representing the API's JSON response + """ + params = {} + if "source" in body: + params["source"] = body["source"] + return super(Tag, cls).update(id=host, params=params, **body) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/timeboards.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/timeboards.py new file mode 100644 index 0000000..42d34da --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/timeboards.py @@ -0,0 +1,20 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datadog.api.resources import ( + GetableAPIResource, + CreateableAPIResource, + UpdatableAPIResource, + ListableAPIResource, + DeletableAPIResource, +) + + +class Timeboard( + GetableAPIResource, CreateableAPIResource, UpdatableAPIResource, ListableAPIResource, DeletableAPIResource +): + """ + A wrapper around Timeboard HTTP API. + """ + + _resource_name = "dash" diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/api/users.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/users.py new file mode 100644 index 0000000..ff0b2f2 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/api/users.py @@ -0,0 +1,50 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datadog.api.resources import ( + ActionAPIResource, + GetableAPIResource, + CreateableAPIResource, + UpdatableAPIResource, + ListableAPIResource, + DeletableAPIResource, +) + + +class User( + ActionAPIResource, + GetableAPIResource, + CreateableAPIResource, + UpdatableAPIResource, + ListableAPIResource, + DeletableAPIResource, +): + + _resource_name = "user" + + """ + A wrapper around User HTTP API. + """ + + @classmethod + def invite(cls, emails): + """ + Send an invite to join datadog to each of the email addresses in the + *emails* list. If *emails* is a string, it will be wrapped in a list and + sent. Returns a list of email addresses for which an email was sent. + + :param emails: emails addresses to invite to join datadog + :type emails: string list + + :returns: Dictionary representing the API's JSON response + """ + print("[DEPRECATION] User.invite() is deprecated. Use `create` instead.") + + if not isinstance(emails, list): + emails = [emails] + + body = { + "emails": emails, + } + + return super(User, cls)._trigger_action("POST", "/invite_users", **body) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/__init__.py new file mode 100644 index 0000000..cb4aab6 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/__init__.py @@ -0,0 +1,113 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +# stdlib +import os +import warnings +import sys + +# 3p +import argparse + +# datadog +from datadog import initialize, __version__ +from datadog.dogshell.comment import CommentClient +from datadog.dogshell.common import DogshellConfig +from datadog.dogshell.dashboard_list import DashboardListClient +from datadog.dogshell.downtime import DowntimeClient +from datadog.dogshell.event import EventClient +from datadog.dogshell.host import HostClient +from datadog.dogshell.metric import MetricClient +from datadog.dogshell.monitor import MonitorClient +from datadog.dogshell.screenboard import ScreenboardClient +from datadog.dogshell.search import SearchClient +from datadog.dogshell.service_check import ServiceCheckClient +from datadog.dogshell.service_level_objective import ServiceLevelObjectiveClient +from datadog.dogshell.tag import TagClient +from datadog.dogshell.timeboard import TimeboardClient +from datadog.dogshell.dashboard import DashboardClient + + +def main(): + if sys.argv[0].endswith("dog"): + warnings.warn("dog is pending deprecation. Please use dogshell instead.", PendingDeprecationWarning) + + parser = argparse.ArgumentParser( + description="Interact with the Datadog API", formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + parser.add_argument( + "--config", help="location of your dogrc file (default ~/.dogrc)", default=os.path.expanduser("~/.dogrc") + ) + parser.add_argument( + "--api-key", + help="your API key, from " + "https://app.datadoghq.com/account/settings#api. " + "You can also set the environment variables DATADOG_API_KEY or DD_API_KEY", + dest="api_key", + default=os.environ.get("DATADOG_API_KEY", os.environ.get("DD_API_KEY")), + ) + parser.add_argument( + "--application-key", + help="your Application key, from " + "https://app.datadoghq.com/account/settings#api. " + "You can also set the environment variables DATADOG_APP_KEY or DD_APP_KEY", + dest="app_key", + default=os.environ.get("DATADOG_APP_KEY", os.environ.get("DD_APP_KEY")), + ) + parser.add_argument( + "--pretty", + help="pretty-print output (suitable for human consumption, " "less useful for scripting)", + dest="format", + action="store_const", + const="pretty", + ) + parser.add_argument( + "--raw", help="raw JSON as returned by the HTTP service", dest="format", action="store_const", const="raw" + ) + parser.add_argument( + "--timeout", help="time to wait in seconds before timing" " out an API call (default 10)", default=10, type=int + ) + parser.add_argument( + "-v", "--version", help="Dog API version", action="version", version="%(prog)s {0}".format(__version__) + ) + + parser.add_argument( + "--api_host", + help="Datadog site to send data, us (datadoghq.com), eu (datadoghq.eu), us3 (us3.datadoghq.com), \ + us5 (us5.datadoghq.com), ap1 (ap1.datadoghq.com), gov (ddog-gov.com), or custom url. default: us", + dest="api_host", + ) + + config = DogshellConfig() + + # Set up subparsers for each service + subparsers = parser.add_subparsers(title="Modes", dest="mode") + subparsers.required = True + + CommentClient.setup_parser(subparsers) + SearchClient.setup_parser(subparsers) + MetricClient.setup_parser(subparsers) + TagClient.setup_parser(subparsers) + EventClient.setup_parser(subparsers) + MonitorClient.setup_parser(subparsers) + TimeboardClient.setup_parser(subparsers) + DashboardClient.setup_parser(subparsers) + ScreenboardClient.setup_parser(subparsers) + DashboardListClient.setup_parser(subparsers) + HostClient.setup_parser(subparsers) + DowntimeClient.setup_parser(subparsers) + ServiceCheckClient.setup_parser(subparsers) + ServiceLevelObjectiveClient.setup_parser(subparsers) + + args = parser.parse_args() + + config.load(args.config, args.api_key, args.app_key, args.api_host) + + # Initialize datadog.api package + initialize(**config) + + args.func(args) + + +if __name__ == "__main__": + main() diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/comment.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/comment.py new file mode 100644 index 0000000..208d009 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/comment.py @@ -0,0 +1,152 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +# stdlib +import json +import sys + +# datadog +from datadog import api +from datadog.dogshell.common import report_errors, report_warnings + + +class CommentClient(object): + @classmethod + def setup_parser(cls, subparsers): + parser = subparsers.add_parser("comment", help="Post, update, and delete comments.") + + verb_parsers = parser.add_subparsers(title="Verbs", dest="verb") + verb_parsers.required = True + + post_parser = verb_parsers.add_parser("post", help="Post comments.") + post_parser.add_argument("handle", help="handle to post as.") + post_parser.add_argument("comment", help="comment message to post. if unset," " reads from stdin.", nargs="?") + post_parser.set_defaults(func=cls._post) + + update_parser = verb_parsers.add_parser("update", help="Update existing comments.") + update_parser.add_argument("comment_id", help="comment to update (by id)") + update_parser.add_argument("handle", help="handle to post as.") + update_parser.add_argument("comment", help="comment message to post." " if unset, reads from stdin.", nargs="?") + update_parser.set_defaults(func=cls._update) + + reply_parser = verb_parsers.add_parser("reply", help="Reply to existing comments.") + reply_parser.add_argument("comment_id", help="comment to reply to (by id)") + reply_parser.add_argument("handle", help="handle to post as.") + reply_parser.add_argument("comment", help="comment message to post." " if unset, reads from stdin.", nargs="?") + reply_parser.set_defaults(func=cls._reply) + + show_parser = verb_parsers.add_parser("show", help="Show comment details.") + show_parser.add_argument("comment_id", help="comment to show") + show_parser.set_defaults(func=cls._show) + + @classmethod + def _post(cls, args): + api._timeout = args.timeout + handle = args.handle + comment = args.comment + format = args.format + if comment is None: + comment = sys.stdin.read() + res = api.Comment.create(handle=handle, message=comment) + report_warnings(res) + report_errors(res) + if format == "pretty": + message = res["comment"]["message"] + lines = message.split("\n") + message = "\n".join([" " + line for line in lines]) + print("id\t\t" + str(res["comment"]["id"])) + print("url\t\t" + res["comment"]["url"]) + print("resource\t" + res["comment"]["resource"]) + print("handle\t\t" + res["comment"]["handle"]) + print("message\n" + message) + elif format == "raw": + print(json.dumps(res)) + else: + print("id\t\t" + str(res["comment"]["id"])) + print("url\t\t" + res["comment"]["url"]) + print("resource\t" + res["comment"]["resource"]) + print("handle\t\t" + res["comment"]["handle"]) + print("message\t\t" + res["comment"]["message"].__repr__()) + + @classmethod + def _update(cls, args): + handle = args.handle + comment = args.comment + id = args.comment_id + format = args.format + if comment is None: + comment = sys.stdin.read() + res = api.Comment.update(id, handle=handle, message=comment) + report_warnings(res) + report_errors(res) + if format == "pretty": + message = res["comment"]["message"] + lines = message.split("\n") + message = "\n".join([" " + line for line in lines]) + print("id\t\t" + str(res["comment"]["id"])) + print("url\t\t" + res["comment"]["url"]) + print("resource\t" + res["comment"]["resource"]) + print("handle\t\t" + res["comment"]["handle"]) + print("message\n" + message) + elif format == "raw": + print(json.dumps(res)) + else: + print("id\t\t" + str(res["comment"]["id"])) + print("url\t\t" + res["comment"]["url"]) + print("resource\t" + res["comment"]["resource"]) + print("handle\t\t" + res["comment"]["handle"]) + print("message\t\t" + res["comment"]["message"].__repr__()) + + @classmethod + def _reply(cls, args): + api._timeout = args.timeout + handle = args.handle + comment = args.comment + id = args.comment_id + format = args.format + if comment is None: + comment = sys.stdin.read() + res = api.Comment.create(handle=handle, message=comment, related_event_id=id) + report_warnings(res) + report_errors(res) + if format == "pretty": + message = res["comment"]["message"] + lines = message.split("\n") + message = "\n".join([" " + line for line in lines]) + print("id\t\t" + str(res["comment"]["id"])) + print("url\t\t" + res["comment"]["url"]) + print("resource\t" + res["comment"]["resource"]) + print("handle\t\t" + res["comment"]["handle"]) + print("message\n" + message) + elif format == "raw": + print(json.dumps(res)) + else: + print("id\t\t" + str(res["comment"]["id"])) + print("url\t\t" + res["comment"]["url"]) + print("resource\t" + res["comment"]["resource"]) + print("handle\t\t" + res["comment"]["handle"]) + print("message\t\t" + res["comment"]["message"].__repr__()) + + @classmethod + def _show(cls, args): + api._timeout = args.timeout + id = args.comment_id + format = args.format + res = api.Event.get(id) + report_warnings(res) + report_errors(res) + if format == "pretty": + message = res["event"]["text"] + lines = message.split("\n") + message = "\n".join([" " + line for line in lines]) + print("id\t\t" + str(res["event"]["id"])) + print("url\t\t" + res["event"]["url"]) + print("resource\t" + res["event"]["resource"]) + print("message\n" + message) + elif format == "raw": + print(json.dumps(res)) + else: + print("id\t\t" + str(res["event"]["id"])) + print("url\t\t" + res["event"]["url"]) + print("resource\t" + res["event"]["resource"]) + print("message\t\t" + res["event"]["text"].__repr__()) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/common.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/common.py new file mode 100644 index 0000000..251e658 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/common.py @@ -0,0 +1,122 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +# stdlib +from __future__ import print_function +import os +import sys + +# datadog +from datadog.util.compat import is_p3k, configparser, IterableUserDict, get_input + + +def print_err(msg): + if is_p3k(): + print(msg + "\n", file=sys.stderr) + else: + sys.stderr.write(msg + "\n") + sys.stderr.flush() + + +def report_errors(res): + if "errors" in res: + errors = res["errors"] + if isinstance(errors, list): + for error in errors: + print_err("ERROR: {}".format(error)) + else: + print_err("ERROR: {}".format(errors)) + sys.exit(1) + return False + + +def report_warnings(res): + if "warnings" in res: + warnings = res["warnings"] + if isinstance(warnings, list): + for warning in warnings: + print_err("WARNING: {}".format(warning)) + else: + print_err("WARNING: {}".format(warnings)) + return True + return False + + +class DogshellConfig(IterableUserDict): + def load(self, config_file, api_key, app_key, api_host): + config = configparser.ConfigParser() + + if api_host is not None: + if api_host in ("datadoghq.com", "us"): + self["api_host"] = "https://api.datadoghq.com" + elif api_host in ("datadoghq.eu", "eu"): + self["api_host"] = "https://api.datadoghq.eu" + elif api_host in ("us3.datadoghq.com", "us3"): + self["api_host"] = "https://api.us3.datadoghq.com" + elif api_host in ("us5.datadoghq.com", "us5"): + self["api_host"] = "https://api.us5.datadoghq.com" + elif api_host in ("ap1.datadoghq.com", "ap1"): + self["api_host"] = "https://api.ap1.datadoghq.com" + elif api_host in ("ddog-gov.com", "gov"): + self["api_host"] = "https://api.ddog-gov.com" + else: + self["api_host"] = api_host + if api_key is not None and app_key is not None: + self["api_key"] = api_key + self["app_key"] = app_key + else: + if os.access(config_file, os.F_OK): + config.read(config_file) + if not config.has_section("Connection"): + report_errors({"errors": ["%s has no [Connection] section" % config_file]}) + else: + try: + response = None + while response is None or response.strip().lower() not in ["", "y", "n"]: + response = get_input("%s does not exist. Would you like to" " create it? [Y/n] " % config_file) + if response.strip().lower() in ["", "y"]: + # Read the api and app keys from stdin + while True: + api_key = get_input( + "What is your api key? (Get it here: " + "https://app.datadoghq.com/account/settings#api) " + ) + if api_key.isalnum(): + break + print("Datadog api keys can only contain alphanumeric characters.") + while True: + app_key = get_input( + "What is your app key? (Get it here: " + "https://app.datadoghq.com/account/settings#api) " + ) + if app_key.isalnum(): + break + print("Datadog app keys can only contain alphanumeric characters.") + + # Write the config file + config.add_section("Connection") + config.set("Connection", "apikey", api_key) + config.set("Connection", "appkey", app_key) + + f = open(config_file, "w") + config.write(f) + f.close() + print("Wrote %s" % config_file) + elif response.strip().lower() == "n": + # Abort + print_err("Exiting\n") + sys.exit(1) + except (KeyboardInterrupt, EOFError): + # Abort + print_err("\nExiting") + sys.exit(1) + + self["api_key"] = config.get("Connection", "apikey") + self["app_key"] = config.get("Connection", "appkey") + if config.has_section("Proxy"): + self["proxies"] = dict(config.items("Proxy")) + if config.has_option("Connection", "host_name"): + self["host_name"] = config.get("Connection", "host_name") + if config.has_option("Connection", "api_host"): + self["api_host"] = config.get("Connection", "api_host") + assert self["api_key"] is not None and self["app_key"] is not None diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/dashboard.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/dashboard.py new file mode 100644 index 0000000..bc37bd6 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/dashboard.py @@ -0,0 +1,174 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +# stdlib +import json +import sys + +# 3p +import argparse + +# datadog +from datadog import api +from datadog.dogshell.common import report_errors, report_warnings +from datadog.util.format import pretty_json + + +class DashboardClient(object): + @classmethod + def setup_parser(cls, subparsers): + parser = subparsers.add_parser("dashboard", help="Create, edit, and delete dashboards") + + verb_parsers = parser.add_subparsers(title="Verbs", dest="verb") + verb_parsers.required = True + + post_parser = verb_parsers.add_parser("post", help="Create dashboards") + # Required arguments: + post_parser.add_argument("title", help="title for the new dashboard") + post_parser.add_argument( + "widgets", help="widget definitions as a JSON string. If unset," " reads from stdin.", nargs="?" + ) + post_parser.add_argument("layout_type", choices=["ordered", "free"], help="Layout type of the dashboard.") + # Optional arguments: + post_parser.add_argument("--description", help="Short description of the dashboard") + post_parser.add_argument( + "--read_only", + help="Whether this dashboard is read-only. " "If True, only the author and admins can make changes to it.", + action="store_true", + ) + post_parser.add_argument( + "--notify_list", + type=_json_string, + help="A json list of user handles, e.g. " '\'["user1@domain.com", "user2@domain.com"]\'', + ) + post_parser.add_argument( + "--template_variables", + type=_json_string, + help="A json list of template variable dicts, e.g. " + '\'[{"name": "host", "prefix": "host", ' + '"default": "my-host"}]\'', + ) + post_parser.set_defaults(func=cls._post) + + update_parser = verb_parsers.add_parser("update", help="Update existing dashboards") + # Required arguments: + update_parser.add_argument("dashboard_id", help="Dashboard to replace" " with the new definition") + update_parser.add_argument("title", help="New title for the dashboard") + update_parser.add_argument( + "widgets", help="Widget definitions as a JSON string." " If unset, reads from stdin", nargs="?" + ) + update_parser.add_argument("layout_type", choices=["ordered", "free"], help="Layout type of the dashboard.") + # Optional arguments: + update_parser.add_argument("--description", help="Short description of the dashboard") + update_parser.add_argument( + "--read_only", + help="Whether this dashboard is read-only. " "If True, only the author and admins can make changes to it.", + action="store_true", + ) + update_parser.add_argument( + "--notify_list", + type=_json_string, + help="A json list of user handles, e.g. " '\'["user1@domain.com", "user2@domain.com"]\'', + ) + update_parser.add_argument( + "--template_variables", + type=_json_string, + help="A json list of template variable dicts, e.g. " + '\'[{"name": "host", "prefix": "host", ' + '"default": "my-host"}]\'', + ) + update_parser.set_defaults(func=cls._update) + + show_parser = verb_parsers.add_parser("show", help="Show a dashboard definition") + show_parser.add_argument("dashboard_id", help="Dashboard to show") + show_parser.set_defaults(func=cls._show) + + delete_parser = verb_parsers.add_parser("delete", help="Delete dashboards") + delete_parser.add_argument("dashboard_id", help="Dashboard to delete") + delete_parser.set_defaults(func=cls._delete) + + @classmethod + def _post(cls, args): + api._timeout = args.timeout + format = args.format + widgets = args.widgets + if args.widgets is None: + widgets = sys.stdin.read() + widgets = json.loads(widgets) + + # Required arguments + payload = {"title": args.title, "widgets": widgets, "layout_type": args.layout_type} + # Optional arguments + if args.description: + payload["description"] = args.description + if args.read_only: + payload["is_read_only"] = args.read_only + if args.notify_list: + payload["notify_list"] = args.notify_list + if args.template_variables: + payload["template_variables"] = args.template_variables + + res = api.Dashboard.create(**payload) + report_warnings(res) + report_errors(res) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _update(cls, args): + api._timeout = args.timeout + format = args.format + widgets = args.widgets + if args.widgets is None: + widgets = sys.stdin.read() + widgets = json.loads(widgets) + + # Required arguments + payload = {"title": args.title, "widgets": widgets, "layout_type": args.layout_type} + # Optional arguments + if args.description: + payload["description"] = args.description + if args.read_only: + payload["is_read_only"] = args.read_only + if args.notify_list: + payload["notify_list"] = args.notify_list + if args.template_variables: + payload["template_variables"] = args.template_variables + + res = api.Dashboard.update(args.dashboard_id, **payload) + report_warnings(res) + report_errors(res) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _show(cls, args): + api._timeout = args.timeout + format = args.format + res = api.Dashboard.get(args.dashboard_id) + report_warnings(res) + report_errors(res) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _delete(cls, args): + api._timeout = args.timeout + res = api.Dashboard.delete(args.dashboard_id) + if res is not None: + report_warnings(res) + report_errors(res) + + +def _json_string(str): + try: + return json.loads(str) + except Exception: + raise argparse.ArgumentTypeError("bad json parameter") diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/dashboard_list.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/dashboard_list.py new file mode 100644 index 0000000..9164ba7 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/dashboard_list.py @@ -0,0 +1,339 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +# stdlib +import json + +# 3p +from datadog.util.format import pretty_json + +# datadog +from datadog import api +from datadog.dogshell.common import report_errors, report_warnings + + +class DashboardListClient(object): + @classmethod + def setup_parser(cls, subparsers): + parser = subparsers.add_parser("dashboard_list", help="Create, edit, and delete dashboard lists") + verb_parsers = parser.add_subparsers(title="Verbs", dest="verb") + verb_parsers.required = True + + # Create Dashboard List parser + post_parser = verb_parsers.add_parser("post", help="Create a dashboard list") + post_parser.add_argument("name", help="Name for the dashboard list") + post_parser.set_defaults(func=cls._post) + + # Update Dashboard List parser + update_parser = verb_parsers.add_parser("update", help="Update existing dashboard list") + update_parser.add_argument("dashboard_list_id", help="Dashboard list to replace with the new definition") + update_parser.add_argument("name", help="Name for the dashboard list") + update_parser.set_defaults(func=cls._update) + + # Show Dashboard List parser + show_parser = verb_parsers.add_parser("show", help="Show a dashboard list definition") + show_parser.add_argument("dashboard_list_id", help="Dashboard list to show") + show_parser.set_defaults(func=cls._show) + + # Show All Dashboard Lists parser + show_all_parser = verb_parsers.add_parser("show_all", help="Show a list of all dashboard lists") + show_all_parser.set_defaults(func=cls._show_all) + + # Delete Dashboard List parser + delete_parser = verb_parsers.add_parser("delete", help="Delete existing dashboard list") + delete_parser.add_argument("dashboard_list_id", help="Dashboard list to delete") + delete_parser.set_defaults(func=cls._delete) + + # Get Dashboards for Dashboard List parser + get_dashboards_parser = verb_parsers.add_parser( + "show_dashboards", help="Show a list of all dashboards for an existing dashboard list" + ) + get_dashboards_parser.add_argument("dashboard_list_id", help="Dashboard list to show dashboards from") + get_dashboards_parser.set_defaults(func=cls._show_dashboards) + + # Get Dashboards for Dashboard List parser (v2) + get_dashboards_v2_parser = verb_parsers.add_parser( + "show_dashboards_v2", help="Show a list of all dashboards for an existing dashboard list" + ) + get_dashboards_v2_parser.add_argument("dashboard_list_id", help="Dashboard list to show dashboards from") + get_dashboards_v2_parser.set_defaults(func=cls._show_dashboards_v2) + + # Add Dashboards to Dashboard List parser + add_dashboards_parser = verb_parsers.add_parser( + "add_dashboards", help="Add dashboards to an existing dashboard list" + ) + add_dashboards_parser.add_argument("dashboard_list_id", help="Dashboard list to add dashboards to") + + add_dashboards_parser.add_argument( + "dashboards", + help="A JSON list of dashboard dicts, e.g. " + + '[{"type": "custom_timeboard", "id": 1234}, ' + + '{"type": "custom_screenboard", "id": 123}]', + ) + add_dashboards_parser.set_defaults(func=cls._add_dashboards) + + # Add Dashboards to Dashboard List parser (v2) + add_dashboards_v2_parser = verb_parsers.add_parser( + "add_dashboards_v2", help="Add dashboards to an existing dashboard list" + ) + add_dashboards_v2_parser.add_argument("dashboard_list_id", help="Dashboard list to add dashboards to") + add_dashboards_v2_parser.add_argument( + "dashboards", + help="A JSON list of dashboard dicts, e.g. " + + '[{"type": "custom_timeboard", "id": "ewc-a4f-8ps"}, ' + + '{"type": "custom_screenboard", "id": "kwj-3t3-d3m"}]', + ) + add_dashboards_v2_parser.set_defaults(func=cls._add_dashboards_v2) + + # Update Dashboards of Dashboard List parser + update_dashboards_parser = verb_parsers.add_parser( + "update_dashboards", help="Update dashboards of an existing dashboard list" + ) + update_dashboards_parser.add_argument("dashboard_list_id", help="Dashboard list to update with dashboards") + update_dashboards_parser.add_argument( + "dashboards", + help="A JSON list of dashboard dicts, e.g. " + + '[{"type": "custom_timeboard", "id": 1234}, ' + + '{"type": "custom_screenboard", "id": 123}]', + ) + update_dashboards_parser.set_defaults(func=cls._update_dashboards) + + # Update Dashboards of Dashboard List parser (v2) + update_dashboards_v2_parser = verb_parsers.add_parser( + "update_dashboards_v2", help="Update dashboards of an existing dashboard list" + ) + update_dashboards_v2_parser.add_argument("dashboard_list_id", help="Dashboard list to update with dashboards") + update_dashboards_v2_parser.add_argument( + "dashboards", + help="A JSON list of dashboard dicts, e.g. " + + '[{"type": "custom_timeboard", "id": "ewc-a4f-8ps"}, ' + + '{"type": "custom_screenboard", "id": "kwj-3t3-d3m"}]', + ) + update_dashboards_v2_parser.set_defaults(func=cls._update_dashboards_v2) + + # Delete Dashboards from Dashboard List parser + delete_dashboards_parser = verb_parsers.add_parser( + "delete_dashboards", help="Delete dashboards from an existing dashboard list" + ) + delete_dashboards_parser.add_argument("dashboard_list_id", help="Dashboard list to delete dashboards from") + delete_dashboards_parser.add_argument( + "dashboards", + help="A JSON list of dashboard dicts, e.g. " + + '[{"type": "custom_timeboard", "id": 1234}, ' + + '{"type": "custom_screenboard", "id": 123}]', + ) + delete_dashboards_parser.set_defaults(func=cls._delete_dashboards) + + # Delete Dashboards from Dashboard List parser + delete_dashboards_v2_parser = verb_parsers.add_parser( + "delete_dashboards_v2", help="Delete dashboards from an existing dashboard list" + ) + delete_dashboards_v2_parser.add_argument("dashboard_list_id", help="Dashboard list to delete dashboards from") + delete_dashboards_v2_parser.add_argument( + "dashboards", + help="A JSON list of dashboard dicts, e.g. " + + '[{"type": "custom_timeboard", "id": "ewc-a4f-8ps"}, ' + + '{"type": "custom_screenboard", "id": "kwj-3t3-d3m"}]', + ) + delete_dashboards_v2_parser.set_defaults(func=cls._delete_dashboards_v2) + + @classmethod + def _post(cls, args): + api._timeout = args.timeout + format = args.format + name = args.name + + res = api.DashboardList.create(name=name) + report_warnings(res) + report_errors(res) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _update(cls, args): + api._timeout = args.timeout + format = args.format + dashboard_list_id = args.dashboard_list_id + name = args.name + + res = api.DashboardList.update(dashboard_list_id, name=name) + report_warnings(res) + report_errors(res) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _show(cls, args): + api._timeout = args.timeout + format = args.format + dashboard_list_id = args.dashboard_list_id + + res = api.DashboardList.get(dashboard_list_id) + report_warnings(res) + report_errors(res) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _show_all(cls, args): + api._timeout = args.timeout + format = args.format + + res = api.DashboardList.get_all() + report_warnings(res) + report_errors(res) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _delete(cls, args): + api._timeout = args.timeout + format = args.format + dashboard_list_id = args.dashboard_list_id + + res = api.DashboardList.delete(dashboard_list_id) + report_warnings(res) + report_errors(res) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _show_dashboards(cls, args): + api._timeout = args.timeout + format = args.format + dashboard_list_id = args.dashboard_list_id + + res = api.DashboardList.get_items(dashboard_list_id) + report_warnings(res) + report_errors(res) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _show_dashboards_v2(cls, args): + api._timeout = args.timeout + format = args.format + dashboard_list_id = args.dashboard_list_id + + res = api.DashboardList.v2.get_items(dashboard_list_id) + report_warnings(res) + report_errors(res) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _add_dashboards(cls, args): + api._timeout = args.timeout + format = args.format + dashboard_list_id = args.dashboard_list_id + dashboards = json.loads(args.dashboards) + + res = api.DashboardList.add_items(dashboard_list_id, dashboards=dashboards) + report_warnings(res) + report_errors(res) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _add_dashboards_v2(cls, args): + api._timeout = args.timeout + format = args.format + dashboard_list_id = args.dashboard_list_id + dashboards = json.loads(args.dashboards) + + res = api.DashboardList.v2.add_items(dashboard_list_id, dashboards=dashboards) + report_warnings(res) + report_errors(res) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _update_dashboards(cls, args): + api._timeout = args.timeout + format = args.format + dashboard_list_id = args.dashboard_list_id + dashboards = json.loads(args.dashboards) + + res = api.DashboardList.update_items(dashboard_list_id, dashboards=dashboards) + report_warnings(res) + report_errors(res) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _update_dashboards_v2(cls, args): + api._timeout = args.timeout + format = args.format + dashboard_list_id = args.dashboard_list_id + dashboards = json.loads(args.dashboards) + + res = api.DashboardList.v2.update_items(dashboard_list_id, dashboards=dashboards) + report_warnings(res) + report_errors(res) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _delete_dashboards(cls, args): + api._timeout = args.timeout + format = args.format + dashboard_list_id = args.dashboard_list_id + dashboards = json.loads(args.dashboards) + + res = api.DashboardList.delete_items(dashboard_list_id, dashboards=dashboards) + report_warnings(res) + report_errors(res) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _delete_dashboards_v2(cls, args): + api._timeout = args.timeout + format = args.format + dashboard_list_id = args.dashboard_list_id + dashboards = json.loads(args.dashboards) + + res = api.DashboardList.v2.delete_items(dashboard_list_id, dashboards=dashboards) + report_warnings(res) + report_errors(res) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/downtime.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/downtime.py new file mode 100644 index 0000000..1c53b46 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/downtime.py @@ -0,0 +1,132 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +# stdlib +import json + +# 3p +from datadog.util.format import pretty_json + +# datadog +from datadog import api +from datadog.dogshell.common import report_errors, report_warnings + + +class DowntimeClient(object): + @classmethod + def setup_parser(cls, subparsers): + parser = subparsers.add_parser("downtime", help="Create, edit, and delete downtimes") + parser.add_argument( + "--string_ids", + action="store_true", + dest="string_ids", + help="Represent downtime IDs as strings instead of ints in JSON", + ) + + verb_parsers = parser.add_subparsers(title="Verbs", dest="verb") + verb_parsers.required = True + + post_parser = verb_parsers.add_parser("post", help="Create a downtime") + post_parser.add_argument("scope", help="scope to apply downtime to") + post_parser.add_argument("start", help="POSIX timestamp to start the downtime", default=None) + post_parser.add_argument("--end", help="POSIX timestamp to end the downtime", default=None) + post_parser.add_argument( + "--message", help="message to include with notifications" " for this downtime", default=None + ) + post_parser.set_defaults(func=cls._schedule_downtime) + + update_parser = verb_parsers.add_parser("update", help="Update existing downtime") + update_parser.add_argument("downtime_id", help="downtime to replace" " with the new definition") + update_parser.add_argument("--scope", help="scope to apply downtime to") + update_parser.add_argument("--start", help="POSIX timestamp to start" " the downtime", default=None) + update_parser.add_argument("--end", help="POSIX timestamp to" " end the downtime", default=None) + update_parser.add_argument( + "--message", help="message to include with notifications" " for this downtime", default=None + ) + update_parser.set_defaults(func=cls._update_downtime) + + show_parser = verb_parsers.add_parser("show", help="Show a downtime definition") + show_parser.add_argument("downtime_id", help="downtime to show") + show_parser.set_defaults(func=cls._show_downtime) + + show_all_parser = verb_parsers.add_parser("show_all", help="Show a list of all downtimes") + show_all_parser.add_argument( + "--current_only", help="only return downtimes that" " are active when the request is made", default=None + ) + show_all_parser.set_defaults(func=cls._show_all_downtime) + + delete_parser = verb_parsers.add_parser("delete", help="Delete a downtime") + delete_parser.add_argument("downtime_id", help="downtime to delete") + delete_parser.set_defaults(func=cls._cancel_downtime) + + cancel_parser = verb_parsers.add_parser("cancel_by_scope", help="Cancel all downtimes with a given scope") + cancel_parser.add_argument("scope", help="The scope of the downtimes to cancel") + cancel_parser.set_defaults(func=cls._cancel_downtime_by_scope) + + @classmethod + def _schedule_downtime(cls, args): + api._timeout = args.timeout + format = args.format + res = api.Downtime.create(scope=args.scope, start=args.start, end=args.end, message=args.message) + report_warnings(res) + report_errors(res) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _update_downtime(cls, args): + api._timeout = args.timeout + format = args.format + res = api.Downtime.update( + args.downtime_id, scope=args.scope, start=args.start, end=args.end, message=args.message + ) + report_warnings(res) + report_errors(res) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _cancel_downtime(cls, args): + api._timeout = args.timeout + res = api.Downtime.delete(args.downtime_id) + if res is not None: + report_warnings(res) + report_errors(res) + + @classmethod + def _show_downtime(cls, args): + api._timeout = args.timeout + format = args.format + res = api.Downtime.get(args.downtime_id) + report_warnings(res) + report_errors(res) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _show_all_downtime(cls, args): + api._timeout = args.timeout + format = args.format + res = api.Downtime.get_all(current_only=args.current_only) + report_warnings(res) + report_errors(res) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _cancel_downtime_by_scope(cls, args): + api._timeout = args.timeout + format = args.format + res = api.Downtime.cancel_downtime_by_scope(scope=args.scope) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/event.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/event.py new file mode 100644 index 0000000..89d68a6 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/event.py @@ -0,0 +1,201 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +# stdlib +import datetime +import time +import re +import sys +import json + +# datadog +from datadog import api +from datadog.dogshell.common import report_errors, report_warnings + + +time_pat = re.compile(r"(?P[0-9]*\.?[0-9]+)(?P[mhd])") + + +def prettyprint_event(event): + title = event["title"] or "" + text = event.get("text", "") or "" + handle = event.get("handle", "") or "" + date = event["date_happened"] + dt = datetime.datetime.fromtimestamp(date) + link = event["url"] + + # Print + print((title + " " + text + " " + " (" + handle + ")").strip()) + print(dt.isoformat(" ") + " | " + link) + + +def print_event(event): + prettyprint_event(event) + + +def prettyprint_event_details(event): + prettyprint_event(event) + + +def print_event_details(event): + prettyprint_event(event) + + +def parse_time(timestring): + now = time.mktime(datetime.datetime.now().timetuple()) + if timestring is None: + t = now + else: + try: + t = int(timestring) + except Exception: + match = time_pat.match(timestring) + if match is None: + raise Exception + delta = float(match.group("delta")) + unit = match.group("unit") + if unit == "m": + delta = delta * 60 + if unit == "h": + delta = delta * 60 * 60 + if unit == "d": + delta = delta * 60 * 60 * 24 + t = now - int(delta) + return int(t) + + +class EventClient(object): + @classmethod + def setup_parser(cls, subparsers): + parser = subparsers.add_parser("event", help="Post events, get event details," " and view the event stream.") + verb_parsers = parser.add_subparsers(title="Verbs", dest="verb") + verb_parsers.required = True + + post_parser = verb_parsers.add_parser("post", help="Post events.") + post_parser.add_argument("title", help="event title") + post_parser.add_argument( + "--date_happened", + type=int, + help="POSIX timestamp" " when the event occurred. if unset defaults to the current time.", + ) + post_parser.add_argument("--handle", help="user to post as. if unset, submits " "as the generic API user.") + post_parser.add_argument("--priority", help='"normal" or "low". defaults to "normal"', default="normal") + post_parser.add_argument( + "--related_event_id", help="event to post as a child of." " if unset, posts a top-level event" + ) + post_parser.add_argument("--tags", help="comma separated list of tags") + post_parser.add_argument("--host", help="related host (default to the local host name)", default="") + post_parser.add_argument( + "--no_host", help="no host is associated with the event" " (overrides --host))", action="store_true" + ) + post_parser.add_argument("--device", help="related device (e.g. eth0, /dev/sda1)") + post_parser.add_argument("--aggregation_key", help="key to aggregate the event with") + post_parser.add_argument("--type", help="type of event, e.g. nagios, jenkins, etc.") + post_parser.add_argument("--alert_type", help='"error", "warning", "info" or "success". defaults to "info"') + post_parser.add_argument("message", help="event message body. " "if unset, reads from stdin.", nargs="?") + post_parser.set_defaults(func=cls._post) + + show_parser = verb_parsers.add_parser("show", help="Show event details.") + show_parser.add_argument("event_id", help="event to show") + show_parser.set_defaults(func=cls._show) + + stream_parser = verb_parsers.add_parser( + "stream", + help="Retrieve events from the Event Stream", + description="Stream start and end times can be specified as either a POSIX" + " timestamp (e.g. the output of `date +%s`) or as a period of" + " time in the past (e.g. '5m', '6h', '3d').", + ) + stream_parser.add_argument("start", help="start date for the stream request") + stream_parser.add_argument("end", help="end date for the stream request " "(defaults to 'now')", nargs="?") + stream_parser.add_argument("--priority", help="filter by priority." " 'normal' or 'low'. defaults to 'normal'") + stream_parser.add_argument("--sources", help="comma separated list of sources to filter by") + stream_parser.add_argument("--tags", help="comma separated list of tags to filter by") + stream_parser.set_defaults(func=cls._stream) + + @classmethod + def _post(cls, args): + """ + Post an event. + """ + api._timeout = args.timeout + format = args.format + message = args.message + if message is None: + message = sys.stdin.read() + if args.tags is not None: + tags = [t.strip() for t in args.tags.split(",")] + else: + tags = None + + host = None if args.no_host else args.host + + # Submit event + res = api.Event.create( + title=args.title, + text=message, + date_happened=args.date_happened, + handle=args.handle, + priority=args.priority, + related_event_id=args.related_event_id, + tags=tags, + host=host, + device=args.device, + aggregation_key=args.aggregation_key, + source_type_name=args.type, + alert_type=args.alert_type, + ) + + # Report + report_warnings(res) + report_errors(res) + if format == "pretty": + prettyprint_event(res["event"]) + elif format == "raw": + print(json.dumps(res)) + else: + print_event(res["event"]) + + @classmethod + def _show(cls, args): + api._timeout = args.timeout + format = args.format + res = api.Event.get(args.event_id) + report_warnings(res) + report_errors(res) + if format == "pretty": + prettyprint_event_details(res["event"]) + elif format == "raw": + print(json.dumps(res)) + else: + print_event_details(res["event"]) + + @classmethod + def _stream(cls, args): + api._timeout = args.timeout + format = args.format + if args.sources is not None: + sources = [s.strip() for s in args.sources.split(",")] + else: + sources = None + if args.tags is not None: + tags = [t.strip() for t in args.tags.split(",")] + else: + tags = None + start = parse_time(args.start) + end = parse_time(args.end) + # res = api.Event.query(start=start, end=end) + # TODO FIXME + res = api.Event.query(start=start, end=end, priority=args.priority, sources=sources, tags=tags) + report_warnings(res) + report_errors(res) + if format == "pretty": + for event in res["events"]: + prettyprint_event(event) + print() + elif format == "raw": + print(json.dumps(res)) + else: + for event in res["events"]: + print_event(event) + print() diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/host.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/host.py new file mode 100644 index 0000000..1f93a78 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/host.py @@ -0,0 +1,61 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +# stdlib +import json + +# 3p +from datadog.util.format import pretty_json + +# datadog +from datadog import api +from datadog.dogshell.common import report_errors, report_warnings + + +class HostClient(object): + @classmethod + def setup_parser(cls, subparsers): + parser = subparsers.add_parser("host", help="Mute, unmute hosts") + verb_parsers = parser.add_subparsers(title="Verbs", dest="verb") + verb_parsers.required = True + + mute_parser = verb_parsers.add_parser("mute", help="Mute a host") + mute_parser.add_argument("host_name", help="host to mute") + mute_parser.add_argument( + "--end", help="POSIX timestamp, if omitted," " host will be muted until explicitly unmuted", default=None + ) + mute_parser.add_argument("--message", help="string to associate with the" " muting of this host", default=None) + mute_parser.add_argument( + "--override", + help="true/false, if true and the host is already" " muted, will overwrite existing end on the host", + action="store_true", + ) + mute_parser.set_defaults(func=cls._mute) + + unmute_parser = verb_parsers.add_parser("unmute", help="Unmute a host") + unmute_parser.add_argument("host_name", help="host to mute") + unmute_parser.set_defaults(func=cls._unmute) + + @classmethod + def _mute(cls, args): + api._timeout = args.timeout + format = args.format + res = api.Host.mute(args.host_name, end=args.end, message=args.message, override=args.override) + report_warnings(res) + report_errors(res) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _unmute(cls, args): + api._timeout = args.timeout + format = args.format + res = api.Host.unmute(args.host_name) + report_warnings(res) + report_errors(res) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/metric.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/metric.py new file mode 100644 index 0000000..135e4b9 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/metric.py @@ -0,0 +1,72 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +# stdlib +from collections import defaultdict + +# datadog +from datadog import api +from datadog.dogshell.common import report_errors, report_warnings + + +class MetricClient(object): + @classmethod + def setup_parser(cls, subparsers): + parser = subparsers.add_parser("metric", help="Post metrics.") + verb_parsers = parser.add_subparsers(title="Verbs", dest="verb") + verb_parsers.required = True + + post_parser = verb_parsers.add_parser("post", help="Post metrics") + post_parser.add_argument("name", help="metric name") + post_parser.add_argument("value", help="metric value (integer or decimal value)", type=float) + post_parser.add_argument( + "--host", help="scopes your metric to a specific host " "(default to the local host name)", default="" + ) + post_parser.add_argument( + "--no_host", help="no host is associated with the metric" " (overrides --host))", action="store_true" + ) + post_parser.add_argument("--device", help="scopes your metric to a specific device", default=None) + post_parser.add_argument("--tags", help="comma-separated list of tags", default=None) + post_parser.add_argument( + "--localhostname", + help="deprecated, used to force `--host`" + " to the local hostname " + "(now default when no `--host` is specified)", + action="store_true", + ) + post_parser.add_argument( + "--type", help="type of the metric - gauge(32bit float)" " or counter(64bit integer)", default=None + ) + parser.set_defaults(func=cls._post) + + @classmethod + def _post(cls, args): + """ + Post a metric. + """ + # Format parameters + api._timeout = args.timeout + + host = None if args.no_host else args.host + + if args.tags: + tags = sorted(set([t.strip() for t in args.tags.split(",") if t])) + else: + tags = None + + # Submit metric + res = api.Metric.send( + metric=args.name, points=args.value, host=host, device=args.device, tags=tags, metric_type=args.type + ) + + # Report + res = defaultdict(list, res) + + if args.localhostname: + # Warn about`--localhostname` command line flag deprecation + res["warnings"].append( + u"`--localhostname` command line flag is deprecated, made default when no `--host` " + u"is specified. See the `--host` option for more information." + ) + report_warnings(res) + report_errors(res) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/monitor.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/monitor.py new file mode 100644 index 0000000..ddc207e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/monitor.py @@ -0,0 +1,431 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +# stdlib +import argparse +import json + +# 3p +from datadog.util.format import pretty_json + +# datadog +from datadog import api +from datadog.dogshell.common import report_errors, report_warnings, print_err + + +class MonitorClient(object): + @classmethod + def setup_parser(cls, subparsers): + parser = subparsers.add_parser("monitor", help="Create, edit, and delete monitors") + parser.add_argument( + "--string_ids", + action="store_true", + dest="string_ids", + help="Represent monitor IDs as strings instead of ints in JSON", + ) + + verb_parsers = parser.add_subparsers(title="Verbs", dest="verb") + verb_parsers.required = True + + post_parser = verb_parsers.add_parser("post", help="Create a monitor") + post_parser.add_argument("type", help="type of the monitor, e.g." "'metric alert' 'service check'") + post_parser.add_argument( + "query", help="query to notify on with syntax varying " "depending on what type of monitor you are creating" + ) + post_parser.add_argument("--name", help="name of the alert", default=None) + post_parser.add_argument( + "--message", help="message to include with notifications" " for this monitor", default=None + ) + post_parser.add_argument("--tags", help="comma-separated list of tags", default=None) + post_parser.add_argument( + "--priority", + help="Integer from 1 (high) to 5 (low) indicating alert severity.", + default=None + ) + post_parser.add_argument("--options", help="json options for the monitor", default=None) + post_parser.set_defaults(func=cls._post) + + file_post_parser = verb_parsers.add_parser("fpost", help="Create a monitor from file") + file_post_parser.add_argument("file", help="json file holding all details", type=argparse.FileType("r")) + file_post_parser.set_defaults(func=cls._file_post) + + update_parser = verb_parsers.add_parser("update", help="Update existing monitor") + update_parser.add_argument("monitor_id", help="monitor to replace with the new definition") + update_parser.add_argument( + "type", + nargs="?", + help="[Deprecated] optional argument preferred" "type of the monitor, e.g. 'metric alert' 'service check'", + default=None, + ) + update_parser.add_argument( + "query", + nargs="?", + help="[Deprecated] optional argument preferred" + "query to notify on with syntax varying depending on monitor type", + default=None, + ) + update_parser.add_argument( + "--type", help="type of the monitor, e.g. " "'metric alert' 'service check'", default=None, dest="type_opt" + ) + update_parser.add_argument( + "--query", + help="query to notify on with syntax varying" " depending on monitor type", + default=None, + dest="query_opt", + ) + update_parser.add_argument("--name", help="name of the alert", default=None) + update_parser.add_argument("--tags", help="comma-separated list of tags", default=None) + update_parser.add_argument( + "--message", help="message to include with " "notifications for this monitor", default=None + ) + update_parser.add_argument( + "--priority", + help="Integer from 1 (high) to 5 (low) indicating alert severity.", + default=None + ) + update_parser.add_argument("--options", help="json options for the monitor", default=None) + update_parser.set_defaults(func=cls._update) + + file_update_parser = verb_parsers.add_parser("fupdate", help="Update existing" " monitor from file") + file_update_parser.add_argument("file", help="json file holding all details", type=argparse.FileType("r")) + file_update_parser.set_defaults(func=cls._file_update) + + show_parser = verb_parsers.add_parser("show", help="Show a monitor definition") + show_parser.add_argument("monitor_id", help="monitor to show") + show_parser.set_defaults(func=cls._show) + + show_all_parser = verb_parsers.add_parser("show_all", help="Show a list of all monitors") + show_all_parser.add_argument( + "--group_states", + help="comma separated list of group states to filter by" + "(choose one or more from 'all', 'alert', 'warn', or 'no data')", + ) + show_all_parser.add_argument("--name", help="string to filter monitors by name") + show_all_parser.add_argument( + "--tags", + help="comma separated list indicating what tags, if any, " + "should be used to filter the list of monitors by scope (e.g. 'host:host0')", + ) + show_all_parser.add_argument( + "--monitor_tags", + help="comma separated list indicating what service " + "and/or custom tags, if any, should be used to filter the list of monitors", + ) + + show_all_parser.set_defaults(func=cls._show_all) + + delete_parser = verb_parsers.add_parser("delete", help="Delete a monitor") + delete_parser.add_argument("monitor_id", help="monitor to delete") + delete_parser.set_defaults(func=cls._delete) + + mute_all_parser = verb_parsers.add_parser("mute_all", help="Globally mute " "monitors (downtime over *)") + mute_all_parser.set_defaults(func=cls._mute_all) + + unmute_all_parser = verb_parsers.add_parser( + "unmute_all", help="Globally unmute " "monitors (cancel downtime over *)" + ) + unmute_all_parser.set_defaults(func=cls._unmute_all) + + mute_parser = verb_parsers.add_parser("mute", help="Mute a monitor") + mute_parser.add_argument("monitor_id", help="monitor to mute") + mute_parser.add_argument("--scope", help="scope to apply the mute to," " e.g. role:db (optional)", default=[]) + mute_parser.add_argument( + "--end", help="POSIX timestamp for when" " the mute should end (optional)", default=None + ) + mute_parser.set_defaults(func=cls._mute) + + unmute_parser = verb_parsers.add_parser("unmute", help="Unmute a monitor") + unmute_parser.add_argument("monitor_id", help="monitor to unmute") + unmute_parser.add_argument("--scope", help="scope to unmute (must be muted), " "e.g. role:db", default=[]) + unmute_parser.add_argument("--all_scopes", help="clear muting across all scopes", action="store_true") + unmute_parser.set_defaults(func=cls._unmute) + + can_delete_parser = verb_parsers.add_parser("can_delete", help="Check if you can delete some monitors") + can_delete_parser.add_argument("monitor_ids", help="monitors to check if they can be deleted") + can_delete_parser.set_defaults(func=cls._can_delete) + + validate_parser = verb_parsers.add_parser("validate", help="Validates if a monitor definition is correct") + validate_parser.add_argument("type", help="type of the monitor, e.g." "'metric alert' 'service check'") + validate_parser.add_argument("query", help="the monitor query") + validate_parser.add_argument("--name", help="name of the alert", default=None) + validate_parser.add_argument( + "--message", help="message to include with notifications" " for this monitor", default=None + ) + validate_parser.add_argument("--tags", help="comma-separated list of tags", default=None) + validate_parser.add_argument("--options", help="json options for the monitor", default=None) + validate_parser.set_defaults(func=cls._validate) + + @classmethod + def _post(cls, args): + api._timeout = args.timeout + format = args.format + options = None + if args.options is not None: + options = json.loads(args.options) + + if args.tags: + tags = sorted(set([t.strip() for t in args.tags.split(",") if t.strip()])) + else: + tags = None + + body = { + "type": args.type, + "query": args.query, + "name": args.name, + "message": args.message, + "options": options + } + if tags: + body["tags"] = tags + if args.priority: + body["priority"] = args.priority + + res = api.Monitor.create(**body) + report_warnings(res) + report_errors(res) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _file_post(cls, args): + api._timeout = args.timeout + format = args.format + monitor = json.load(args.file) + body = { + "type": monitor["type"], + "query": monitor["query"], + "name": monitor["name"], + "message": monitor["message"], + "options": monitor["options"] + } + tags = monitor.get("tags", None) + if tags: + body["tags"] = tags + priority = monitor.get("priority", None) + if priority: + body["priority"] = priority + + res = api.Monitor.create(**body) + report_warnings(res) + report_errors(res) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _update(cls, args): + api._timeout = args.timeout + format = args.format + + to_update = {} + if args.type: + if args.type_opt: + msg = "Duplicate arguments for `type`. Using optional value --type" + print_err("WARNING: {}".format(msg)) + else: + to_update["type"] = args.type + msg = "[DEPRECATION] `type` is no longer required to `update` and may be omitted" + print_err("WARNING: {}".format(msg)) + if args.query: + if args.query_opt: + msg = "Duplicate arguments for `query`. Using optional value --query" + print_err("WARNING: {}".format(msg)) + else: + to_update["query"] = args.query + msg = "[DEPRECATION] `query` is no longer required to `update` and may be omitted" + print_err("WARNING: {}".format(msg)) + if args.name: + to_update["name"] = args.name + if args.message: + to_update["message"] = args.message + if args.type_opt: + to_update["type"] = args.type_opt + if args.query_opt: + to_update["query"] = args.query_opt + if args.tags: + to_update["tags"] = sorted(set([t.strip() for t in args.tags.split(",") if t.strip()])) + if args.priority: + to_update["priority"] = args.priority + + if args.options is not None: + to_update["options"] = json.loads(args.options) + + res = api.Monitor.update(args.monitor_id, **to_update) + + report_warnings(res) + report_errors(res) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _file_update(cls, args): + api._timeout = args.timeout + format = args.format + monitor = json.load(args.file) + body = { + "type": monitor["type"], + "query": monitor["query"], + "name": monitor["name"], + "message": monitor["message"], + "options": monitor["options"] + } + tags = monitor.get("tags", None) + if tags: + body["tags"] = tags + priority = monitor.get("priority", None) + if priority: + body["priority"] = priority + + res = api.Monitor.update(monitor["id"], **body) + + report_warnings(res) + report_errors(res) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _show(cls, args): + api._timeout = args.timeout + format = args.format + res = api.Monitor.get(args.monitor_id) + report_warnings(res) + report_errors(res) + + if args.string_ids: + res["id"] = str(res["id"]) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _show_all(cls, args): + api._timeout = args.timeout + format = args.format + + res = api.Monitor.get_all( + group_states=args.group_states, name=args.name, tags=args.tags, monitor_tags=args.monitor_tags + ) + report_warnings(res) + report_errors(res) + + if args.string_ids: + for d in res: + d["id"] = str(d["id"]) + + if format == "pretty": + print(pretty_json(res)) + elif format == "raw": + print(json.dumps(res)) + else: + for d in res: + print( + "\t".join( + [ + (str(d["id"])), + (cls._escape(d["message"])), + (cls._escape(d["name"])), + (str(d["options"])), + (str(d["org_id"])), + (d["query"]), + (d["type"]), + ] + ) + ) + + @classmethod + def _delete(cls, args): + api._timeout = args.timeout + # TODO CHECK + res = api.Monitor.delete(args.monitor_id) + if res is not None: + report_warnings(res) + report_errors(res) + + @classmethod + def _escape(cls, s): + return s.replace("\r", "\\r").replace("\n", "\\n").replace("\t", "\\t") + + @classmethod + def _mute_all(cls, args): + api._timeout = args.timeout + format = args.format + res = api.Monitor.mute_all() + report_warnings(res) + report_errors(res) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _unmute_all(cls, args): + api._timeout = args.timeout + res = api.Monitor.unmute_all() + if res is not None: + report_warnings(res) + report_errors(res) + + @classmethod + def _mute(cls, args): + api._timeout = args.timeout + format = args.format + res = api.Monitor.mute(args.monitor_id, scope=args.scope, end=args.end) + report_warnings(res) + report_errors(res) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _unmute(cls, args): + api._timeout = args.timeout + res = api.Monitor.unmute(args.monitor_id, scope=args.scope, all_scopes=args.all_scopes) + report_warnings(res) + report_errors(res) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _can_delete(cls, args): + api._timeout = args.timeout + monitor_ids = [i.strip() for i in args.monitor_ids.split(",") if i.strip()] + res = api.Monitor.can_delete(monitor_ids=monitor_ids) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _validate(cls, args): + api._timeout = args.timeout + format = args.format + options = None + if args.options is not None: + options = json.loads(args.options) + + if args.tags: + tags = sorted(set([t.strip() for t in args.tags.split(",") if t.strip()])) + else: + tags = None + + res = api.Monitor.validate( + type=args.type, query=args.query, name=args.name, message=args.message, tags=tags, options=options + ) + # report_warnings(res) + # report_errors(res) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/screenboard.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/screenboard.py new file mode 100644 index 0000000..093a3e1 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/screenboard.py @@ -0,0 +1,308 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +# stdlib +import argparse +import json +import platform +import sys +import webbrowser + +# 3p +from datadog.util.format import pretty_json + +# datadog +from datadog import api +from datadog.dogshell.common import report_errors, report_warnings, print_err +from datetime import datetime + + +class ScreenboardClient(object): + @classmethod + def setup_parser(cls, subparsers): + parser = subparsers.add_parser("screenboard", help="Create, edit, and delete screenboards.") + parser.add_argument( + "--string_ids", + action="store_true", + dest="string_ids", + help="Represent screenboard IDs as strings instead of ints in JSON", + ) + + verb_parsers = parser.add_subparsers(title="Verbs", dest="verb") + verb_parsers.required = True + + post_parser = verb_parsers.add_parser("post", help="Create screenboards.") + post_parser.add_argument("title", help="title for the new screenboard") + post_parser.add_argument("description", help="short description of the screenboard") + post_parser.add_argument( + "graphs", help="graph definitions as a JSON string." " if unset, reads from stdin.", nargs="?" + ) + post_parser.add_argument( + "--template_variables", + type=_template_variables, + default=[], + help="a json list of template variable dicts, e.g. " + "[{'name': 'host', 'prefix': 'host', 'default': 'host:my-host'}]", + ) + post_parser.add_argument("--width", type=int, default=None, help="screenboard width in pixels") + post_parser.add_argument("--height", type=int, default=None, help="screenboard height in pixels") + post_parser.set_defaults(func=cls._post) + + update_parser = verb_parsers.add_parser("update", help="Update existing screenboards.") + update_parser.add_argument("screenboard_id", help="screenboard to replace " " with the new definition") + update_parser.add_argument("title", help="title for the new screenboard") + update_parser.add_argument("description", help="short description of the screenboard") + update_parser.add_argument( + "graphs", help="graph definitions as a JSON string." " if unset, reads from stdin.", nargs="?" + ) + update_parser.add_argument( + "--template_variables", + type=_template_variables, + default=[], + help="a json list of template variable dicts, e.g. " + "[{'name': 'host', 'prefix': 'host', 'default': " + "'host:my-host'}]", + ) + update_parser.add_argument("--width", type=int, default=None, help="screenboard width in pixels") + update_parser.add_argument("--height", type=int, default=None, help="screenboard height in pixels") + update_parser.set_defaults(func=cls._update) + + show_parser = verb_parsers.add_parser("show", help="Show a screenboard definition.") + show_parser.add_argument("screenboard_id", help="screenboard to show") + show_parser.set_defaults(func=cls._show) + + delete_parser = verb_parsers.add_parser("delete", help="Delete a screenboard.") + delete_parser.add_argument("screenboard_id", help="screenboard to delete") + delete_parser.set_defaults(func=cls._delete) + + share_parser = verb_parsers.add_parser("share", help="Share an existing screenboard's" " with a public URL.") + share_parser.add_argument("screenboard_id", help="screenboard to share") + share_parser.set_defaults(func=cls._share) + + revoke_parser = verb_parsers.add_parser("revoke", help="Revoke an existing screenboard's" " with a public URL.") + revoke_parser.add_argument("screenboard_id", help="screenboard to revoke") + revoke_parser.set_defaults(func=cls._revoke) + + pull_parser = verb_parsers.add_parser("pull", help="Pull a screenboard on the server" " into a local file") + pull_parser.add_argument("screenboard_id", help="ID of screenboard to pull") + pull_parser.add_argument("filename", help="file to pull screenboard into") + pull_parser.set_defaults(func=cls._pull) + + push_parser = verb_parsers.add_parser( + "push", help="Push updates to screenboards" " from local files to the server" + ) + push_parser.add_argument( + "--append_auto_text", + action="store_true", + dest="append_auto_text", + help="When pushing to the server, appends filename and" + " timestamp to the end of the screenboard description", + ) + push_parser.add_argument( + "file", help="screenboard files to push to the server", nargs="+", type=argparse.FileType("r") + ) + push_parser.set_defaults(func=cls._push) + + new_file_parser = verb_parsers.add_parser( + "new_file", help="Create a new screenboard" " and put its contents in a file" + ) + new_file_parser.add_argument("filename", help="name of file to create with" " empty screenboard") + new_file_parser.add_argument( + "graphs", help="graph definitions as a JSON string." " if unset, reads from stdin.", nargs="?" + ) + new_file_parser.set_defaults(func=cls._new_file) + + @classmethod + def _pull(cls, args): + cls._write_screen_to_file(args.screenboard_id, args.filename, args.timeout, args.format, args.string_ids) + + # TODO Is there a test for this one ? + @classmethod + def _push(cls, args): + api._timeout = args.timeout + for f in args.file: + screen_obj = json.load(f) + + if args.append_auto_text: + datetime_str = datetime.now().strftime("%x %X") + auto_text = "
\nUpdated at {0} from {1} ({2}) on {3}".format( + datetime_str, f.name, screen_obj["id"], platform.node() + ) + screen_obj["description"] += auto_text + + if "id" in screen_obj: + # Always convert to int, in case it was originally a string. + screen_obj["id"] = int(screen_obj["id"]) + res = api.Screenboard.update(**screen_obj) + else: + res = api.Screenboard.create(**screen_obj) + + if "errors" in res: + print_err("Upload of screenboard {0} from file {1} failed.".format(screen_obj["id"], f.name)) + + report_warnings(res) + report_errors(res) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + if args.format == "pretty": + print("Uploaded file {0} (screenboard {1})".format(f.name, screen_obj["id"])) + + @classmethod + def _write_screen_to_file(cls, screenboard_id, filename, timeout, format="raw", string_ids=False): + with open(filename, "w") as f: + res = api.Screenboard.get(screenboard_id) + report_warnings(res) + report_errors(res) + + screen_obj = res + if "resource" in screen_obj: + del screen_obj["resource"] + if "url" in screen_obj: + del screen_obj["url"] + + if string_ids: + screen_obj["id"] = str(screen_obj["id"]) + + json.dump(screen_obj, f, indent=2) + + if format == "pretty": + print("Downloaded screenboard {0} to file {1}".format(screenboard_id, filename)) + else: + print("{0} {1}".format(screenboard_id, filename)) + + @classmethod + def _post(cls, args): + graphs = sys.stdin.read() + api._timeout = args.timeout + format = args.format + graphs = args.graphs + if args.graphs is None: + graphs = sys.stdin.read() + graphs = json.loads(graphs) + res = api.Screenboard.create( + title=args.title, + description=args.description, + graphs=[graphs], + template_variables=args.template_variables, + width=args.width, + height=args.height, + ) + report_warnings(res) + report_errors(res) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _update(cls, args): + api._timeout = args.timeout + format = args.format + graphs = args.graphs + if args.graphs is None: + graphs = sys.stdin.read() + graphs = json.loads(graphs) + + res = api.Screenboard.update( + args.screenboard_id, + board_title=args.title, + description=args.description, + widgets=graphs, + template_variables=args.template_variables, + width=args.width, + height=args.height, + ) + report_warnings(res) + report_errors(res) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _web_view(cls, args): + dash_id = json.load(args.file)["id"] + url = api._api_host + "/dash/dash/{0}".format(dash_id) + webbrowser.open(url) + + @classmethod + def _show(cls, args): + api._timeout = args.timeout + format = args.format + res = api.Screenboard.get(args.screenboard_id) + report_warnings(res) + report_errors(res) + + if args.string_ids: + res["id"] = str(res["id"]) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _delete(cls, args): + api._timeout = args.timeout + # TODO CHECK + res = api.Screenboard.delete(args.screenboard_id) + if res is not None: + report_warnings(res) + report_errors(res) + + @classmethod + def _share(cls, args): + api._timeout = args.timeout + format = args.format + res = api.Screenboard.share(args.screenboard_id) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _revoke(cls, args): + api._timeout = args.timeout + format = args.format + res = api.Screenboard.revoke(args.screenboard_id) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _new_file(cls, args): + api._timeout = args.timeout + format = args.format + graphs = args.graphs + if args.graphs is None: + graphs = sys.stdin.read() + graphs = json.loads(graphs) + res = api.Screenboard.create( + board_title=args.filename, description="Description for {0}".format(args.filename), widgets=[graphs] + ) + report_warnings(res) + report_errors(res) + + cls._write_screen_to_file(res["id"], args.filename, args.timeout, format, args.string_ids) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + +def _template_variables(tpl_var_input): + if "[" not in tpl_var_input: + return [v.strip() for v in tpl_var_input.split(",")] + else: + try: + return json.loads(tpl_var_input) + except Exception: + raise argparse.ArgumentTypeError("bad template_variable json parameter") diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/search.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/search.py new file mode 100644 index 0000000..9c1cb47 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/search.py @@ -0,0 +1,43 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +# stdlib +import json + +# datadog +from datadog import api +from datadog.dogshell.common import report_errors, report_warnings + + +# TODO IS there a test ? +class SearchClient(object): + @classmethod + def setup_parser(cls, subparsers): + parser = subparsers.add_parser("search", help="search datadog") + verb_parsers = parser.add_subparsers(title="Verbs", dest="verb") + verb_parsers.required = True + + query_parser = verb_parsers.add_parser("query", help="Search datadog.") + query_parser.add_argument("query", help="optionally faceted search query") + query_parser.set_defaults(func=cls._query) + + @classmethod + def _query(cls, args): + api._timeout = args.timeout + res = api.Infrastructure.search(q=args.query) + report_warnings(res) + report_errors(res) + if format == "pretty": + for facet, results in list(res["results"].items()): + for idx, result in enumerate(results): + if idx == 0: + print("\n") + print("%s\t%s" % (facet, result)) + else: + print("%s\t%s" % (" " * len(facet), result)) + elif format == "raw": + print(json.dumps(res)) + else: + for facet, results in list(res["results"].items()): + for result in results: + print("%s\t%s" % (facet, result)) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/service_check.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/service_check.py new file mode 100644 index 0000000..b30f33c --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/service_check.py @@ -0,0 +1,55 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +# stdlib +import json + +# 3p +from datadog.util.format import pretty_json + +# datadog +from datadog import api +from datadog.dogshell.common import report_errors, report_warnings + + +class ServiceCheckClient(object): + @classmethod + def setup_parser(cls, subparsers): + parser = subparsers.add_parser("service_check", help="Perform service checks") + verb_parsers = parser.add_subparsers(title="Verbs", dest="verb") + verb_parsers.required = True + + check_parser = verb_parsers.add_parser("check", help="text for the message") + check_parser.add_argument("check", help="text for the message") + check_parser.add_argument("host_name", help="name of the host submitting the check") + check_parser.add_argument( + "status", + help="integer for the status of the check." " i.e: '0': OK, '1': WARNING, '2': CRITICAL, '3': UNKNOWN", + ) + check_parser.add_argument("--timestamp", help="POSIX timestamp of the event", default=None) + check_parser.add_argument("--message", help="description of why this status occurred", default=None) + check_parser.add_argument("--tags", help="comma separated list of tags", default=None) + check_parser.set_defaults(func=cls._check) + + @classmethod + def _check(cls, args): + api._timeout = args.timeout + format = args.format + if args.tags: + tags = sorted(set([t.strip() for t in args.tags.split(",") if t.strip()])) + else: + tags = None + res = api.ServiceCheck.check( + check=args.check, + host_name=args.host_name, + status=int(args.status), + timestamp=args.timestamp, + message=args.message, + tags=tags, + ) + report_warnings(res) + report_errors(res) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/service_level_objective.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/service_level_objective.py new file mode 100644 index 0000000..13ec928 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/service_level_objective.py @@ -0,0 +1,426 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +# stdlib +import argparse +import json + +# 3p +from datadog.util.cli import ( + set_of_ints, + comma_set, + comma_list_or_empty, + parse_date_as_epoch_timestamp, +) +from datadog.util.format import pretty_json + +# datadog +from datadog import api +from datadog.dogshell.common import report_errors, report_warnings + + +class ServiceLevelObjectiveClient(object): + @classmethod + def setup_parser(cls, subparsers): + parser = subparsers.add_parser( + "service_level_objective", + help="Create, edit, and delete service level objectives", + ) + + verb_parsers = parser.add_subparsers(title="Verbs", dest="verb") + verb_parsers.required = True + + create_parser = verb_parsers.add_parser("create", help="Create a SLO") + create_parser.add_argument( + "--type", + help="type of the SLO, e.g.", + choices=["metric", "monitor"], + ) + create_parser.add_argument("--name", help="name of the SLO", default=None) + create_parser.add_argument("--description", help="description of the SLO", default=None) + create_parser.add_argument( + "--tags", + help="comma-separated list of tags", + default=None, + type=comma_list_or_empty, + ) + create_parser.add_argument( + "--thresholds", + help="comma separated list of :[:[:[:]]", + ) + create_parser.add_argument( + "--numerator", + help="numerator metric query (sum of good events)", + default=None, + ) + create_parser.add_argument( + "--denominator", + help="denominator metric query (sum of total events)", + default=None, + ) + create_parser.add_argument( + "--monitor_ids", + help="explicit monitor_ids to use (CSV)", + default=None, + type=set_of_ints, + ) + create_parser.add_argument("--monitor_search", help="monitor search terms to use", default=None) + create_parser.add_argument( + "--groups", + help="for a single monitor you can specify the specific groups as a pipe (|) delimited string", + default=None, + type=comma_list_or_empty, + ) + create_parser.set_defaults(func=cls._create) + + file_create_parser = verb_parsers.add_parser("fcreate", help="Create a SLO from file") + file_create_parser.add_argument("file", help="json file holding all details", type=argparse.FileType("r")) + file_create_parser.set_defaults(func=cls._file_create) + + update_parser = verb_parsers.add_parser("update", help="Update existing SLO") + update_parser.add_argument("slo_id", help="SLO to replace with the new definition") + update_parser.add_argument( + "--type", + help="type of the SLO (must specify it's original type)", + choices=["metric", "monitor"], + ) + update_parser.add_argument("--name", help="name of the SLO", default=None) + update_parser.add_argument("--description", help="description of the SLO", default=None) + update_parser.add_argument( + "--thresholds", + help="comma separated list of :[:[:[:]]", + ) + update_parser.add_argument( + "--tags", + help="comma-separated list of tags", + default=None, + type=comma_list_or_empty, + ) + update_parser.add_argument( + "--numerator", + help="numerator metric query (sum of good events)", + default=None, + ) + update_parser.add_argument( + "--denominator", + help="denominator metric query (sum of total events)", + default=None, + ) + update_parser.add_argument( + "--monitor_ids", + help="explicit monitor_ids to use (CSV)", + default=[], + type=list, + ) + update_parser.add_argument("--monitor_search", help="monitor search terms to use", default=None) + update_parser.add_argument( + "--groups", + help="for a single monitor you can specify the specific groups as a pipe (|) delimited string", + default=None, + ) + update_parser.set_defaults(func=cls._update) + + file_update_parser = verb_parsers.add_parser("fupdate", help="Update existing SLO from file") + file_update_parser.add_argument("file", help="json file holding all details", type=argparse.FileType("r")) + file_update_parser.set_defaults(func=cls._file_update) + + show_parser = verb_parsers.add_parser("show", help="Show a SLO definition") + show_parser.add_argument("slo_id", help="SLO to show") + show_parser.set_defaults(func=cls._show) + + show_all_parser = verb_parsers.add_parser("show_all", help="Show a list of all SLOs") + show_all_parser.add_argument("--query", help="string to filter SLOs by query (see UI or documentation)") + show_all_parser.add_argument( + "--slo_ids", + help="comma separated list indicating what SLO IDs to get at once", + type=comma_set, + ) + show_all_parser.add_argument("--offset", help="offset of query pagination", default=0) + show_all_parser.add_argument("--limit", help="limit of query pagination", default=100) + show_all_parser.set_defaults(func=cls._show_all) + + delete_parser = verb_parsers.add_parser("delete", help="Delete a SLO") + delete_parser.add_argument("slo_id", help="SLO to delete") + delete_parser.set_defaults(func=cls._delete) + + delete_many_parser = verb_parsers.add_parser("delete_many", help="Delete a SLO") + delete_many_parser.add_argument("slo_ids", help="comma separated list of SLO IDs to delete", type=comma_set) + delete_many_parser.set_defaults(func=cls._delete_many) + + delete_timeframe_parser = verb_parsers.add_parser("delete_many_timeframe", help="Delete a SLO timeframe") + delete_timeframe_parser.add_argument("slo_id", help="SLO ID to update") + delete_timeframe_parser.add_argument( + "timeframes", + help="CSV of timeframes to delete, e.g. 7d,30d,90d", + type=comma_set, + ) + delete_timeframe_parser.set_defaults(func=cls._delete_timeframe) + + can_delete_parser = verb_parsers.add_parser("can_delete", help="Check if can delete SLOs") + can_delete_parser.add_argument("slo_ids", help="comma separated list of SLO IDs to delete", type=comma_set) + can_delete_parser.set_defaults(func=cls._can_delete) + + history_parser = verb_parsers.add_parser("history", help="Get the SLO history") + history_parser.add_argument("slo_id", help="SLO to query the history") + history_parser.add_argument( + "from_ts", + type=parse_date_as_epoch_timestamp, + help="`from` date or timestamp", + ) + history_parser.add_argument( + "to_ts", + type=parse_date_as_epoch_timestamp, + help="`to` date or timestamp", + ) + history_parser.set_defaults(func=cls._history) + + @classmethod + def _create(cls, args): + api._timeout = args.timeout + format = args.format + + params = {"type": args.type, "name": args.name} + + if args.tags: + tags = sorted(set([t.strip() for t in args.tags.split(",") if t.strip()])) + params["tags"] = tags + + thresholds = [] + for threshold_str in args.thresholds.split(","): + parts = threshold_str.split(":") + timeframe = parts[0] + target = float(parts[1]) + + threshold = {"timeframe": timeframe, "target": target} + + if len(parts) > 2: + threshold["warning"] = float(parts[2]) + + if len(parts) > 3 and parts[3]: + threshold["target_display"] = parts[3] + + if len(parts) > 4 and parts[4]: + threshold["warning_display"] = parts[4] + + thresholds.append(threshold) + params["thresholds"] = thresholds + + if args.description: + params["description"] = args.description + + if args.type == "metric": + params["query"] = { + "numerator": args.numerator, + "denominator": args.denominator, + } + elif args.monitor_search: + params["monitor_search"] = args.monitor_search + else: + params["monitor_ids"] = list(args.monitor_ids) + if args.groups and len(args.monitor_ids) == 1: + groups = args.groups.split("|") + params["groups"] = groups + + if args.tags: + params["tags"] = args.tags + + res = api.ServiceLevelObjective.create(**params) + report_warnings(res) + report_errors(res) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _file_create(cls, args): + api._timeout = args.timeout + format = args.format + slo = json.load(args.file) + res = api.ServiceLevelObjective.create(return_raw=True, **slo) + report_warnings(res) + report_errors(res) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _update(cls, args): + api._timeout = args.timeout + format = args.format + + params = {"type": args.type} + + if args.thresholds: + thresholds = [] + for threshold_str in args.thresholds.split(","): + parts = threshold_str.split(":") + timeframe = parts[0] + target = parts[1] + + threshold = {"timeframe": timeframe, "target": target} + + if len(parts) > 2: + threshold["warning"] = float(parts[2]) + + if len(parts) > 3 and parts[3]: + threshold["target_display"] = parts[3] + + if len(parts) > 4 and parts[4]: + threshold["warning_display"] = parts[4] + + thresholds.append(threshold) + params["thresholds"] = thresholds + + if args.description: + params["description"] = args.description + + if args.type == "metric": + if args.numerator and args.denominator: + params["query"] = { + "numerator": args.numerator, + "denominator": args.denominator, + } + elif args.monitor_search: + params["monitor_search"] = args.monitor_search + else: + params["monitor_ids"] = args.monitor_ids + if args.groups and len(args.monitor_ids) == 1: + groups = args.groups.split("|") + params["groups"] = groups + + if args.tags: + tags = sorted(set([t.strip() for t in args.tags if t.strip()])) + params["tags"] = tags + res = api.ServiceLevelObjective.update(args.slo_id, return_raw=True, **params) + report_warnings(res) + report_errors(res) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _file_update(cls, args): + api._timeout = args.timeout + format = args.format + slo = json.load(args.file) + + res = api.ServiceLevelObjective.update(slo["id"], return_raw=True, **slo) + report_warnings(res) + report_errors(res) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _show(cls, args): + api._timeout = args.timeout + format = args.format + res = api.ServiceLevelObjective.get(args.slo_id, return_raw=True) + report_warnings(res) + report_errors(res) + + if args.string_ids: + res["id"] = str(res["id"]) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _show_all(cls, args): + api._timeout = args.timeout + format = args.format + + params = {"offset": args.offset, "limit": args.limit} + if args.query: + params["query"] = args.query + else: + params["ids"] = args.slo_ids + + res = api.ServiceLevelObjective.get_all(return_raw=True, **params) + report_warnings(res) + report_errors(res) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _delete(cls, args): + api._timeout = args.timeout + res = api.ServiceLevelObjective.delete(args.slo_id, return_raw=True) + if res is not None: + report_warnings(res) + report_errors(res) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _delete_many(cls, args): + api._timeout = args.timeout + res = api.ServiceLevelObjective.delete_many(args.slo_ids) + if res is not None: + report_warnings(res) + report_errors(res) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _delete_timeframe(cls, args): + api._timeout = args.timeout + + ops = {args.slo_id: args.timeframes} + + res = api.ServiceLevelObjective.bulk_delete(ops) + if res is not None: + report_warnings(res) + report_errors(res) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _can_delete(cls, args): + api._timeout = args.timeout + + res = api.ServiceLevelObjective.can_delete(args.slo_ids) + if res is not None: + report_warnings(res) + report_errors(res) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _history(cls, args): + api._timeout = args.timeout + + res = api.ServiceLevelObjective.history(args.slo_id) + if res is not None: + report_warnings(res) + report_errors(res) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _escape(cls, s): + return s.replace("\r", "\\r").replace("\n", "\\n").replace("\t", "\\t") diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/tag.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/tag.py new file mode 100644 index 0000000..3d4d2b9 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/tag.py @@ -0,0 +1,120 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +# stdlib +import json + +# datadog +from datadog import api +from datadog.dogshell.common import report_errors, report_warnings + + +class TagClient(object): + @classmethod + def setup_parser(cls, subparsers): + parser = subparsers.add_parser("tag", help="View and modify host tags.") + verb_parsers = parser.add_subparsers(title="Verbs", dest="verb") + verb_parsers.required = True + + add_parser = verb_parsers.add_parser( + "add", help="Add a host to one or more tags.", description="Hosts can be specified by name or id." + ) + add_parser.add_argument("host", help="host to add") + add_parser.add_argument("tag", help="tag to add host to (one or more, space separated)", nargs="+") + add_parser.set_defaults(func=cls._add) + + replace_parser = verb_parsers.add_parser( + "replace", + help="Replace all tags with one or more new tags.", + description="Hosts can be specified by name or id.", + ) + replace_parser.add_argument("host", help="host to modify") + replace_parser.add_argument("tag", help="list of tags to add host to", nargs="+") + replace_parser.set_defaults(func=cls._replace) + + show_parser = verb_parsers.add_parser( + "show", help="Show host tags.", description="Hosts can be specified by name or id." + ) + show_parser.add_argument("host", help="host to show (or 'all' to show all tags)") + show_parser.set_defaults(func=cls._show) + + detach_parser = verb_parsers.add_parser( + "detach", help="Remove a host from all tags.", description="Hosts can be specified by name or id." + ) + detach_parser.add_argument("host", help="host to detach") + detach_parser.set_defaults(func=cls._detach) + + @classmethod + def _add(cls, args): + api._timeout = args.timeout + format = args.format + res = api.Tag.create(args.host, tags=args.tag) + report_warnings(res) + report_errors(res) + if format == "pretty": + print("Tags for '%s':" % res["host"]) + for c in res["tags"]: + print(" " + c) + elif format == "raw": + print(json.dumps(res)) + else: + for c in res["tags"]: + print(c) + + @classmethod + def _replace(cls, args): + api._timeout = args.timeout + format = args.format + res = api.Tag.update(args.host, tags=args.tag) + report_warnings(res) + report_errors(res) + if format == "pretty": + print("Tags for '%s':" % res["host"]) + for c in res["tags"]: + print(" " + c) + elif format == "raw": + print(json.dumps(res)) + else: + for c in res["tags"]: + print(c) + + @classmethod + def _show(cls, args): + api._timeout = args.timeout + format = args.format + if args.host == "all": + res = api.Tag.get_all() + else: + res = api.Tag.get(args.host) + report_warnings(res) + report_errors(res) + if args.host == "all": + if format == "pretty": + for tag, hosts in list(res["tags"].items()): + for host in hosts: + print(tag) + print(" " + host) + print() + elif format == "raw": + print(json.dumps(res)) + else: + for tag, hosts in list(res["tags"].items()): + for host in hosts: + print(tag + "\t" + host) + else: + if format == "pretty": + for tag in res["tags"]: + print(tag) + elif format == "raw": + print(json.dumps(res)) + else: + for tag in res["tags"]: + print(tag) + + @classmethod + def _detach(cls, args): + api._timeout = args.timeout + res = api.Tag.delete(args.host) + if res is not None: + report_warnings(res) + report_errors(res) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/timeboard.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/timeboard.py new file mode 100644 index 0000000..477a1b6 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/timeboard.py @@ -0,0 +1,358 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +# stdlib +import json +import os.path +import platform +import sys +import webbrowser + +# 3p +import argparse + +# datadog +from datadog import api +from datadog.dogshell.common import report_errors, report_warnings, print_err +from datadog.util.format import pretty_json +from datetime import datetime + + +class TimeboardClient(object): + @classmethod + def setup_parser(cls, subparsers): + parser = subparsers.add_parser("timeboard", help="Create, edit, and delete timeboards") + parser.add_argument( + "--string_ids", + action="store_true", + dest="string_ids", + help="Represent timeboard IDs as strings instead of ints in JSON", + ) + + verb_parsers = parser.add_subparsers(title="Verbs", dest="verb") + verb_parsers.required = True + + post_parser = verb_parsers.add_parser("post", help="Create timeboards") + post_parser.add_argument("title", help="title for the new timeboard") + post_parser.add_argument("description", help="short description of the timeboard") + post_parser.add_argument( + "graphs", help="graph definitions as a JSON string. if unset," " reads from stdin.", nargs="?" + ) + post_parser.add_argument( + "--template_variables", + type=_template_variables, + default=[], + help="a json list of template variable dicts, e.g. " + "[{'name': 'host', 'prefix': 'host', " + "'default': 'host:my-host'}]'", + ) + + post_parser.set_defaults(func=cls._post) + + update_parser = verb_parsers.add_parser("update", help="Update existing timeboards") + update_parser.add_argument("timeboard_id", help="timeboard to replace" " with the new definition") + update_parser.add_argument("title", help="new title for the timeboard") + update_parser.add_argument("description", help="short description of the timeboard") + update_parser.add_argument( + "graphs", help="graph definitions as a JSON string." " if unset, reads from stdin", nargs="?" + ) + update_parser.add_argument( + "--template_variables", + type=_template_variables, + default=[], + help="a json list of template variable dicts, e.g. " + "[{'name': 'host', 'prefix': 'host', " + "'default': 'host:my-host'}]'", + ) + update_parser.set_defaults(func=cls._update) + + show_parser = verb_parsers.add_parser("show", help="Show a timeboard definition") + show_parser.add_argument("timeboard_id", help="timeboard to show") + show_parser.set_defaults(func=cls._show) + + show_all_parser = verb_parsers.add_parser("show_all", help="Show a list of all timeboards") + show_all_parser.set_defaults(func=cls._show_all) + + pull_parser = verb_parsers.add_parser("pull", help="Pull a timeboard on the server" " into a local file") + pull_parser.add_argument("timeboard_id", help="ID of timeboard to pull") + pull_parser.add_argument("filename", help="file to pull timeboard into") + pull_parser.set_defaults(func=cls._pull) + + pull_all_parser = verb_parsers.add_parser("pull_all", help="Pull all timeboards" " into files in a directory") + pull_all_parser.add_argument("pull_dir", help="directory to pull timeboards into") + pull_all_parser.set_defaults(func=cls._pull_all) + + push_parser = verb_parsers.add_parser( + "push", help="Push updates to timeboards" " from local files to the server" + ) + push_parser.add_argument( + "--append_auto_text", + action="store_true", + dest="append_auto_text", + help="When pushing to the server, appends filename" + " and timestamp to the end of the timeboard description", + ) + push_parser.add_argument( + "file", help="timeboard files to push to the server", nargs="+", type=argparse.FileType("r") + ) + push_parser.set_defaults(func=cls._push) + + new_file_parser = verb_parsers.add_parser( + "new_file", help="Create a new timeboard" " and put its contents in a file" + ) + new_file_parser.add_argument("filename", help="name of file to create with empty timeboard") + new_file_parser.add_argument( + "graphs", help="graph definitions as a JSON string." " if unset, reads from stdin.", nargs="?" + ) + new_file_parser.set_defaults(func=cls._new_file) + + web_view_parser = verb_parsers.add_parser("web_view", help="View the timeboard in a web browser") + web_view_parser.add_argument("file", help="timeboard file", type=argparse.FileType("r")) + web_view_parser.set_defaults(func=cls._web_view) + + delete_parser = verb_parsers.add_parser("delete", help="Delete timeboards") + delete_parser.add_argument("timeboard_id", help="timeboard to delete") + delete_parser.set_defaults(func=cls._delete) + + @classmethod + def _pull(cls, args): + cls._write_dash_to_file(args.timeboard_id, args.filename, args.timeout, args.format, args.string_ids) + + @classmethod + def _pull_all(cls, args): + api._timeout = args.timeout + + def _title_to_filename(title): + # Get a lowercased version with most punctuation stripped out... + no_punct = "".join([c for c in title.lower() if c.isalnum() or c in [" ", "_", "-"]]) + # Now replace all -'s, _'s and spaces with "_", and strip trailing _ + return no_punct.replace(" ", "_").replace("-", "_").strip("_") + + format = args.format + res = api.Timeboard.get_all() + report_warnings(res) + report_errors(res) + + if not os.path.exists(args.pull_dir): + os.mkdir(args.pull_dir, 0o755) + + used_filenames = set() + for dash_summary in res["dashes"]: + filename = _title_to_filename(dash_summary["title"]) + if filename in used_filenames: + filename = filename + "-" + dash_summary["id"] + used_filenames.add(filename) + + cls._write_dash_to_file( + dash_summary["id"], + os.path.join(args.pull_dir, filename + ".json"), + args.timeout, + format, + args.string_ids, + ) + if format == "pretty": + print( + ("\n### Total: {0} dashboards to {1} ###".format(len(used_filenames), os.path.realpath(args.pull_dir))) + ) + + @classmethod + def _new_file(cls, args): + api._timeout = args.timeout + format = args.format + graphs = args.graphs + if args.graphs is None: + graphs = sys.stdin.read() + graphs = json.loads(graphs) + res = api.Timeboard.create( + title=args.filename, description="Description for {0}".format(args.filename), graphs=[graphs] + ) + + report_warnings(res) + report_errors(res) + + cls._write_dash_to_file(res["dash"]["id"], args.filename, args.timeout, format, args.string_ids) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _write_dash_to_file(cls, dash_id, filename, timeout, format="raw", string_ids=False): + with open(filename, "w") as f: + res = api.Timeboard.get(dash_id) + report_warnings(res) + report_errors(res) + + dash_obj = res["dash"] + if "resource" in dash_obj: + del dash_obj["resource"] + if "url" in dash_obj: + del dash_obj["url"] + + if string_ids: + dash_obj["id"] = str(dash_obj["id"]) + + if not dash_obj.get("template_variables"): + dash_obj.pop("template_variables", None) + + json.dump(dash_obj, f, indent=2) + + if format == "pretty": + print(u"Downloaded dashboard {0} to file {1}".format(dash_id, filename)) + else: + print(u"{0} {1}".format(dash_id, filename)) + + @classmethod + def _push(cls, args): + api._timeout = args.timeout + for f in args.file: + try: + dash_obj = json.load(f) + except Exception as err: + raise Exception("Could not parse {0}: {1}".format(f.name, err)) + + if args.append_auto_text: + datetime_str = datetime.now().strftime("%x %X") + auto_text = "
\nUpdated at {0} from {1} ({2}) on {3}".format( + datetime_str, f.name, dash_obj["id"], platform.node() + ) + dash_obj["description"] += auto_text + tpl_vars = dash_obj.get("template_variables", []) + + if "id" in dash_obj: + # Always convert to int, in case it was originally a string. + dash_obj["id"] = int(dash_obj["id"]) + res = api.Timeboard.update( + dash_obj["id"], + title=dash_obj["title"], + description=dash_obj["description"], + graphs=dash_obj["graphs"], + template_variables=tpl_vars, + ) + else: + res = api.Timeboard.create( + title=dash_obj["title"], + description=dash_obj["description"], + graphs=dash_obj["graphs"], + template_variables=tpl_vars, + ) + + if "errors" in res: + print_err("Upload of dashboard {0} from file {1} failed.".format(dash_obj["id"], f.name)) + + report_warnings(res) + report_errors(res) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + if args.format == "pretty": + print("Uploaded file {0} (dashboard {1})".format(f.name, dash_obj["id"])) + + @classmethod + def _post(cls, args): + api._timeout = args.timeout + format = args.format + graphs = args.graphs + if args.graphs is None: + graphs = sys.stdin.read() + graphs = json.loads(graphs) + res = api.Timeboard.create( + title=args.title, description=args.description, graphs=[graphs], template_variables=args.template_variables + ) + report_warnings(res) + report_errors(res) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _update(cls, args): + api._timeout = args.timeout + format = args.format + graphs = args.graphs + if args.graphs is None: + graphs = sys.stdin.read() + graphs = json.loads(graphs) + + res = api.Timeboard.update( + args.timeboard_id, + title=args.title, + description=args.description, + graphs=graphs, + template_variables=args.template_variables, + ) + report_warnings(res) + report_errors(res) + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _show(cls, args): + api._timeout = args.timeout + format = args.format + res = api.Timeboard.get(args.timeboard_id) + report_warnings(res) + report_errors(res) + + if args.string_ids: + res["dash"]["id"] = str(res["dash"]["id"]) + + if format == "pretty": + print(pretty_json(res)) + else: + print(json.dumps(res)) + + @classmethod + def _show_all(cls, args): + api._timeout = args.timeout + format = args.format + res = api.Timeboard.get_all() + report_warnings(res) + report_errors(res) + + if args.string_ids: + for d in res["dashes"]: + d["id"] = str(d["id"]) + + if format == "pretty": + print(pretty_json(res)) + elif format == "raw": + print(json.dumps(res)) + else: + for d in res["dashes"]: + print("\t".join([(d["id"]), (d["resource"]), (d["title"]), cls._escape(d["description"])])) + + @classmethod + def _delete(cls, args): + api._timeout = args.timeout + res = api.Timeboard.delete(args.timeboard_id) + if res is not None: + report_warnings(res) + report_errors(res) + + @classmethod + def _web_view(cls, args): + dash_id = json.load(args.file)["id"] + url = api._api_host + "/dash/dash/{0}".format(dash_id) + webbrowser.open(url) + + @classmethod + def _escape(cls, s): + return s.replace("\r", "\\r").replace("\n", "\\n").replace("\t", "\\t") if s else "" + + +def _template_variables(tpl_var_input): + if "[" not in tpl_var_input: + return [v.strip() for v in tpl_var_input.split(",")] + else: + try: + return json.loads(tpl_var_input) + except Exception: + raise argparse.ArgumentTypeError("bad template_variable json parameter") diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/wrap.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/wrap.py new file mode 100644 index 0000000..25df6d9 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogshell/wrap.py @@ -0,0 +1,520 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +""" + +Wraps shell commands and sends the result to Datadog as events. Ex: + +dogwrap -n test-job -k $API_KEY --submit_mode all "ls -lah" + +Note that you need to enclose your command in quotes to prevent python +from thinking the command line arguments belong to the python command +instead of the wrapped command. + +You can also have the script only send events if they fail: + +dogwrap -n test-job -k $API_KEY --submit_mode errors "ls -lah" + +And you can give the command a timeout too: + +dogwrap -n test-job -k $API_KEY --timeout=1 "sleep 3" + +""" +# stdlib +from __future__ import print_function + +import os +from copy import copy +import optparse +import subprocess +import sys +import threading +import time +import warnings + +# datadog +from datadog import initialize, api, __version__ +from datadog.util.compat import is_p3k + + +SUCCESS = "success" +ERROR = "error" +WARNING = "warning" + +MAX_EVENT_BODY_LENGTH = 3000 + + +class Timeout(Exception): + pass + + +class OutputReader(threading.Thread): + """ + Thread collecting the output of a subprocess, optionally forwarding it to + a given file descriptor and storing it for further retrieval. + """ + + def __init__(self, proc_out, fwd_out=None): + """ + Instantiates an OutputReader. + :param proc_out: the output to read + :type proc_out: file descriptor + :param fwd_out: the output to forward to (None to disable forwarding) + :type fwd_out: file descriptor or None + """ + threading.Thread.__init__(self) + self.daemon = True + self._out_content = b"" + self._out = proc_out + self._fwd_out = fwd_out + + def run(self): + """ + Thread's main loop: collects the output optionnally forwarding it to + the file descriptor passed in the constructor. + """ + for line in iter(self._out.readline, b""): + if self._fwd_out is not None: + self._fwd_out.write(line) + self._out_content += line + self._out.close() + + @property + def content(self): + """ + The content stored in out so far. (Not threadsafe, wait with .join()) + """ + return self._out_content + + +def poll_proc(proc, sleep_interval, timeout): + """ + Polls the process until it returns or a given timeout has been reached + """ + start_time = time.time() + returncode = None + while returncode is None: + returncode = proc.poll() + if time.time() - start_time > timeout: + raise Timeout() + else: + time.sleep(sleep_interval) + return returncode + + +def execute(cmd, cmd_timeout, sigterm_timeout, sigkill_timeout, proc_poll_interval, buffer_outs): + """ + Launches the process and monitors its outputs + """ + start_time = time.time() + returncode = -1 + stdout = b"" + stderr = b"" + try: + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) + except Exception: + print(u"Failed to execute %s" % (repr(cmd)), file=sys.stderr) + raise + try: + # Let's that the threads collecting the output from the command in the + # background + stdout_buffer = sys.stdout.buffer if is_p3k() else sys.stdout + stderr_buffer = sys.stderr.buffer if is_p3k() else sys.stderr + out_reader = OutputReader(proc.stdout, stdout_buffer if not buffer_outs else None) + err_reader = OutputReader(proc.stderr, stderr_buffer if not buffer_outs else None) + out_reader.start() + err_reader.start() + + # Let's quietly wait from the program's completion here to get the exit + # code when it finishes + returncode = poll_proc(proc, proc_poll_interval, cmd_timeout) + except Timeout: + returncode = Timeout + sigterm_start = time.time() + print("Command timed out after %.2fs, killing with SIGTERM" % (time.time() - start_time), file=sys.stderr) + try: + proc.terminate() + try: + poll_proc(proc, proc_poll_interval, sigterm_timeout) + except Timeout: + print( + "SIGTERM timeout failed after %.2fs, killing with SIGKILL" % (time.time() - sigterm_start), + file=sys.stderr, + ) + sigkill_start = time.time() + proc.kill() + try: + poll_proc(proc, proc_poll_interval, sigkill_timeout) + except Timeout: + print( + "SIGKILL timeout failed after %.2fs, exiting" % (time.time() - sigkill_start), file=sys.stderr + ) + except OSError as e: + # Ignore OSError 3: no process found. + if e.errno != 3: + raise + + # Let's harvest the outputs collected by our background threads + # after making sure they're done reading it. + out_reader.join() + err_reader.join() + stdout = out_reader.content + stderr = err_reader.content + + duration = time.time() - start_time + + return returncode, stdout, stderr, duration + + +def trim_text(text, max_len): + """ + Trim input text to fit the `max_len` condition. + + If trim is needed: keep the first 1/3rd of the budget on the top, + and the other 2 thirds on the bottom. + """ + if len(text) <= max_len: + return text + + trimmed_text = ( + u"{top_third}\n" + u"```\n" + u"*...trimmed...*\n" + u"```\n" + u"{bottom_two_third}\n".format( + top_third=text[: max_len // 3], bottom_two_third=text[len(text) - (2 * max_len) // 3 :] + ) + ) + + return trimmed_text + + +def build_event_body(cmd, returncode, stdout, stderr, notifications): + """ + Format and return an event body. + + Note: do not exceed MAX_EVENT_BODY_LENGTH length. + """ + fmt_stdout = u"" + fmt_stderr = u"" + fmt_notifications = u"" + + max_length = MAX_EVENT_BODY_LENGTH // 2 if stdout and stderr else MAX_EVENT_BODY_LENGTH + + if stdout: + fmt_stdout = u"**>>>> STDOUT <<<<**\n```\n{stdout} \n```\n".format( + stdout=trim_text(stdout.decode("utf-8", "replace"), max_length) + ) + + if stderr: + fmt_stderr = u"**>>>> STDERR <<<<**\n```\n{stderr} \n```\n".format( + stderr=trim_text(stderr.decode("utf-8", "replace"), max_length) + ) + + if notifications: + notifications = notifications.decode("utf-8", "replace") if isinstance(notifications, bytes) else notifications + fmt_notifications = u"**>>>> NOTIFICATIONS <<<<**\n\n {notifications}\n".format(notifications=notifications) + + return ( + u"%%%\n" + u"**>>>> CMD <<<<**\n```\n{command} \n```\n" + u"**>>>> EXIT CODE <<<<**\n\n {returncode}\n\n\n" + u"{stdout}" + u"{stderr}" + u"{notifications}" + u"%%%\n".format( + command=cmd, + returncode=returncode, + stdout=fmt_stdout, + stderr=fmt_stderr, + notifications=fmt_notifications, + ) + ) + + +def generate_warning_codes(option, opt, options_warning): + try: + # options_warning is a string e.g.: --warning_codes 123,456,789 + # we need to create a list from it + warning_codes = options_warning.split(",") + return warning_codes + except ValueError: + raise optparse.OptionValueError("option %s: invalid warning codes value(s): %r" % (opt, options_warning)) + + +class DogwrapOption(optparse.Option): + # https://docs.python.org/3.7/library/optparse.html#adding-new-types + TYPES = optparse.Option.TYPES + ("warning_codes",) + TYPE_CHECKER = copy(optparse.Option.TYPE_CHECKER) + TYPE_CHECKER["warning_codes"] = generate_warning_codes + + +def parse_options(raw_args=None): + """ + Parse the raw command line options into an options object and the remaining command string + """ + parser = optparse.OptionParser( + usage='%prog -n [event_name] -k [api_key] --submit_mode \ +[ all | errors | warnings] [options] "command". \n\nNote that you need to enclose your command in \ +quotes to prevent python executing as soon as there is a space in your command. \n \nNOTICE: In \ +normal mode, the whole stderr is printed before stdout, in flush_live mode they will be mixed but \ +there is not guarantee that messages sent by the command on both stderr and stdout are printed in \ +the order they were sent.', + version="%prog {0}".format(__version__), + option_class=DogwrapOption, + ) + + parser.add_option( + "-n", + "--name", + action="store", + type="string", + help="the name of the event \ +as it should appear on your Datadog stream", + ) + parser.add_option( + "-k", + "--api_key", + action="store", + type="string", + help="your DataDog API Key", + default=os.environ.get("DD_API_KEY"), + ) + parser.add_option( + "-s", + "--site", + action="store", + type="string", + default="datadoghq.com", + help="The site to send data. Accepts us (datadoghq.com), eu (datadoghq.eu), \ +us3 (us3.datadoghq.com), us5 (us5.datadoghq.com), or ap1 (ap1.datadoghq.com), \ +gov (ddog-gov.com), or custom url. default: us", + ) + parser.add_option( + "-m", + "--submit_mode", + action="store", + type="choice", + default="errors", + choices=["errors", "warnings", "all"], + help="[ all | errors | warnings ] if set \ +to error, an event will be sent only of the command exits with a non zero exit status or if it \ +times out. If set to warning, a list of exit codes need to be provided", + ) + parser.add_option( + "--warning_codes", + action="store", + type="warning_codes", + dest="warning_codes", + help="comma separated list of warning codes, e.g: 127,255", + ) + parser.add_option( + "-p", + "--priority", + action="store", + type="choice", + choices=["normal", "low"], + help="the priority of the event (default: 'normal')", + ) + parser.add_option( + "-t", + "--timeout", + action="store", + type="int", + default=60 * 60 * 24, + help="(in seconds) a timeout after which your command must be aborted. An \ +event will be sent to your DataDog stream (default: 24hours)", + ) + parser.add_option( + "--sigterm_timeout", + action="store", + type="int", + default=60 * 2, + help="(in seconds) When your command times out, the \ +process it triggers is sent a SIGTERM. If this sigterm_timeout is reached, it will be sent a \ +SIGKILL signal. (default: 2m)", + ) + parser.add_option( + "--sigkill_timeout", + action="store", + type="int", + default=60, + help="(in seconds) how long to wait at most after SIGKILL \ + has been sent (default: 60s)", + ) + parser.add_option( + "--proc_poll_interval", + action="store", + type="float", + default=0.5, + help="(in seconds). interval at which your command will be polled \ +(default: 500ms)", + ) + parser.add_option( + "--notify_success", + action="store", + type="string", + default="", + help="a message string and @people directives to send notifications in \ +case of success.", + ) + parser.add_option( + "--notify_error", + action="store", + type="string", + default="", + help="a message string and @people directives to send notifications in \ +case of error.", + ) + parser.add_option( + "--notify_warning", + action="store", + type="string", + default="", + help="a message string and @people directives to send notifications in \ + case of warning.", + ) + parser.add_option( + "-b", + "--buffer_outs", + action="store_true", + dest="buffer_outs", + default=False, + help="displays the stderr and stdout of the command only once it has \ +returned (the command outputs remains buffered in dogwrap meanwhile)", + ) + parser.add_option( + "--send_metric", + action="store_true", + dest="send_metric", + default=False, + help="sends a metric for event duration", + ) + parser.add_option( + "--tags", action="store", type="string", dest="tags", default="", help="comma separated list of tags" + ) + + options, args = parser.parse_args(args=raw_args) + + if is_p3k(): + cmd = " ".join(args) + else: + cmd = b" ".join(args).decode("utf-8") + + return options, cmd + + +def main(): + options, cmd = parse_options() + + # If silent is checked we force the outputs to be buffered (and therefore + # not forwarded to the Terminal streams) and we just avoid printing the + # buffers at the end + returncode, stdout, stderr, duration = execute( + cmd, + options.timeout, + options.sigterm_timeout, + options.sigkill_timeout, + options.proc_poll_interval, + options.buffer_outs, + ) + + if options.site in ("datadoghq.com", "us"): + api_host = "https://api.datadoghq.com" + elif options.site in ("datadoghq.eu", "eu"): + api_host = "https://api.datadoghq.eu" + elif options.site in ("us3.datadoghq.com", "us3"): + api_host = "https://api.us3.datadoghq.com" + elif options.site in ("us5.datadoghq.com", "us5"): + api_host = "https://api.us5.datadoghq.com" + elif options.site in ("ap1.datadoghq.com", "ap1"): + api_host = "https://api.ap1.datadoghq.com" + elif options.site in ("ddog-gov.com", "gov"): + api_host = "https://api.ddog-gov.com" + else: + api_host = options.site + + initialize(api_key=options.api_key, api_host=api_host) + host = api._host_name + + warning_codes = None + + if options.warning_codes: + # Convert warning codes from string to int since return codes will evaluate the latter + warning_codes = list(map(int, options.warning_codes)) + + if returncode == 0: + alert_type = SUCCESS + event_priority = "low" + event_title = u"[%s] %s succeeded in %.2fs" % (host, options.name, duration) + elif returncode != 0 and options.submit_mode == "warnings": + if not warning_codes: + # the list of warning codes is empty - the option was not specified + print("A comma separated list of exit codes need to be provided") + sys.exit() + elif returncode in warning_codes: + alert_type = WARNING + event_priority = "normal" + event_title = u"[%s] %s failed in %.2fs" % (host, options.name, duration) + else: + print("Command exited with a different exit code that the one(s) provided") + sys.exit() + else: + alert_type = ERROR + event_priority = "normal" + + if returncode is Timeout: + event_title = u"[%s] %s timed out after %.2fs" % (host, options.name, duration) + returncode = -1 + else: + event_title = u"[%s] %s failed in %.2fs" % (host, options.name, duration) + + notifications = "" + + if alert_type == SUCCESS and options.notify_success: + notifications = options.notify_success + elif alert_type == ERROR and options.notify_error: + notifications = options.notify_error + elif alert_type == WARNING and options.notify_warning: + notifications = options.notify_warning + + if options.tags: + tags = [t.strip() for t in options.tags.split(",")] + else: + tags = None + + event_body = build_event_body(cmd, returncode, stdout, stderr, notifications) + + event = { + "alert_type": alert_type, + "aggregation_key": options.name, + "host": host, + "priority": options.priority or event_priority, + "tags": tags, + } + + if options.buffer_outs: + if is_p3k(): + stderr = stderr.decode("utf-8") + stdout = stdout.decode("utf-8") + + print(stderr.strip(), file=sys.stderr) + print(stdout.strip(), file=sys.stdout) + + if options.submit_mode == "all" or returncode != 0: + if options.send_metric: + event_name_tag = "event_name:{}".format(options.name) + if tags: + duration_tags = tags + [event_name_tag] + else: + duration_tags = [event_name_tag] + api.Metric.send(metric="dogwrap.duration", points=duration, tags=duration_tags, type="gauge") + api.Event.create(title=event_title, text=event_body, **event) + + sys.exit(returncode) + + +if __name__ == "__main__": + if sys.argv[0].endswith("dogwrap"): + warnings.warn("dogwrap is pending deprecation. Please use dogshellwrap instead.", PendingDeprecationWarning) + main() diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/dogstatsd/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogstatsd/__init__.py new file mode 100644 index 0000000..d6fa527 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogstatsd/__init__.py @@ -0,0 +1,4 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datadog.dogstatsd.base import DogStatsd, statsd # noqa diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/dogstatsd/base.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogstatsd/base.py new file mode 100644 index 0000000..1f58fe5 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogstatsd/base.py @@ -0,0 +1,1398 @@ +#!/usr/bin/env python + +# Unless explicitly stated otherwise all files in this repository are licensed under +# the BSD-3-Clause License. This product includes software developed at Datadog +# (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +""" +DogStatsd is a Python client for DogStatsd, a Statsd fork for Datadog. +""" +# Standard libraries +from random import random +import logging +import os +import socket +import errno +import threading +import time +from threading import Lock, RLock +import weakref + +try: + import queue +except ImportError: + # pypy has the same module, but capitalized. + import Queue as queue # type: ignore[no-redef] + +from typing import Optional, List, Text, Union + +# Datadog libraries +from datadog.dogstatsd.context import ( + TimedContextManagerDecorator, + DistributedContextManagerDecorator, +) +from datadog.dogstatsd.route import get_default_route +from datadog.dogstatsd.container import ContainerID +from datadog.util.compat import is_p3k, text +from datadog.util.format import normalize_tags +from datadog.version import __version__ + +# Logging +log = logging.getLogger("datadog.dogstatsd") + +# Default config +DEFAULT_HOST = "localhost" +DEFAULT_PORT = 8125 + +# Buffering-related values (in seconds) +DEFAULT_FLUSH_INTERVAL = 0.3 +MIN_FLUSH_INTERVAL = 0.0001 + +# Tag name of entity_id +ENTITY_ID_TAG_NAME = "dd.internal.entity_id" + +# Env var name of entity_id +ENTITY_ID_ENV_VAR = "DD_ENTITY_ID" + +# Env var to enable/disable sending the container ID field +ORIGIN_DETECTION_ENABLED = "DD_ORIGIN_DETECTION_ENABLED" + +# Default buffer settings based on socket type +UDP_OPTIMAL_PAYLOAD_LENGTH = 1432 +UDS_OPTIMAL_PAYLOAD_LENGTH = 8192 + +# Socket options +MIN_SEND_BUFFER_SIZE = 32 * 1024 + +# Mapping of each "DD_" prefixed environment variable to a specific tag name +DD_ENV_TAGS_MAPPING = { + ENTITY_ID_ENV_VAR: ENTITY_ID_TAG_NAME, + "DD_ENV": "env", + "DD_SERVICE": "service", + "DD_VERSION": "version", +} + +# Telemetry minimum flush interval in seconds +DEFAULT_TELEMETRY_MIN_FLUSH_INTERVAL = 10 + +# Telemetry pre-computed formatting string. Pre-computation +# increases throughput of composing the result by 2-15% from basic +# '%'-based formatting with a `join`. +TELEMETRY_FORMATTING_STR = "\n".join( + [ + "datadog.dogstatsd.client.metrics:%s|c|#%s", + "datadog.dogstatsd.client.events:%s|c|#%s", + "datadog.dogstatsd.client.service_checks:%s|c|#%s", + "datadog.dogstatsd.client.bytes_sent:%s|c|#%s", + "datadog.dogstatsd.client.bytes_dropped:%s|c|#%s", + "datadog.dogstatsd.client.bytes_dropped_queue:%s|c|#%s", + "datadog.dogstatsd.client.bytes_dropped_writer:%s|c|#%s", + "datadog.dogstatsd.client.packets_sent:%s|c|#%s", + "datadog.dogstatsd.client.packets_dropped:%s|c|#%s", + "datadog.dogstatsd.client.packets_dropped_queue:%s|c|#%s", + "datadog.dogstatsd.client.packets_dropped_writer:%s|c|#%s", + ] +) + "\n" + +Stop = object() + +SUPPORTS_FORKING = hasattr(os, "register_at_fork") and not os.environ.get("DD_DOGSTATSD_DISABLE_FORK_SUPPORT", None) +TRACK_INSTANCES = not os.environ.get("DD_DOGSTATSD_DISABLE_INSTANCE_TRACKING", None) + +_instances = weakref.WeakSet() # type: weakref.WeakSet + + +def pre_fork(): + """Prepare all client instances for a process fork. + + If SUPPORTS_FORKING is true, this will be called automatically before os.fork(). + """ + for c in _instances: + c.pre_fork() + + +def post_fork(): + """Restore all client instances after a fork. + + If SUPPORTS_FORKING is true, this will be called automatically after os.fork(). + """ + for c in _instances: + c.post_fork() + + +if SUPPORTS_FORKING: + os.register_at_fork(before=pre_fork, after_in_child=post_fork, after_in_parent=post_fork) # type: ignore + + +# pylint: disable=useless-object-inheritance,too-many-instance-attributes +# pylint: disable=too-many-arguments,too-many-locals +class DogStatsd(object): + OK, WARNING, CRITICAL, UNKNOWN = (0, 1, 2, 3) + + def __init__( + self, + host=DEFAULT_HOST, # type: Text + port=DEFAULT_PORT, # type: int + max_buffer_size=None, # type: None + flush_interval=DEFAULT_FLUSH_INTERVAL, # type: float + disable_buffering=True, # type: bool + namespace=None, # type: Optional[Text] + constant_tags=None, # type: Optional[List[str]] + use_ms=False, # type: bool + use_default_route=False, # type: bool + socket_path=None, # type: Optional[Text] + default_sample_rate=1, # type: float + disable_telemetry=False, # type: bool + telemetry_min_flush_interval=(DEFAULT_TELEMETRY_MIN_FLUSH_INTERVAL), # type: int + telemetry_host=None, # type: Text + telemetry_port=None, # type: Union[str, int] + telemetry_socket_path=None, # type: Text + max_buffer_len=0, # type: int + container_id=None, # type: Optional[Text] + origin_detection_enabled=True, # type: bool + socket_timeout=0, # type: Optional[float] + telemetry_socket_timeout=0, # type: Optional[float] + disable_background_sender=True, # type: bool + sender_queue_size=0, # type: int + sender_queue_timeout=0, # type: Optional[float] + track_instance=True, # type: bool + ): # type: (...) -> None + """ + Initialize a DogStatsd object. + + >>> statsd = DogStatsd() + + :envvar DD_AGENT_HOST: the host of the DogStatsd server. + If set, it overrides default value. + :type DD_AGENT_HOST: string + + :envvar DD_DOGSTATSD_PORT: the port of the DogStatsd server. + If set, it overrides default value. + :type DD_DOGSTATSD_PORT: integer + + :envvar DATADOG_TAGS: Tags to attach to every metric reported by dogstatsd client. + :type DATADOG_TAGS: comma-delimited string + + :envvar DD_ENTITY_ID: Tag to identify the client entity. + :type DD_ENTITY_ID: string + + :envvar DD_ENV: the env of the service running the dogstatsd client. + If set, it is appended to the constant (global) tags of the statsd client. + :type DD_ENV: string + + :envvar DD_SERVICE: the name of the service running the dogstatsd client. + If set, it is appended to the constant (global) tags of the statsd client. + :type DD_SERVICE: string + + :envvar DD_VERSION: the version of the service running the dogstatsd client. + If set, it is appended to the constant (global) tags of the statsd client. + :type DD_VERSION: string + + :envvar DD_DOGSTATSD_DISABLE: Disable any statsd metric collection (default False) + :type DD_DOGSTATSD_DISABLE: boolean + + :envvar DD_TELEMETRY_HOST: the host for the dogstatsd server we wish to submit + telemetry stats to. If set, it overrides default value. + :type DD_TELEMETRY_HOST: string + + :envvar DD_TELEMETRY_PORT: the port for the dogstatsd server we wish to submit + telemetry stats to. If set, it overrides default value. + :type DD_TELEMETRY_PORT: integer + + :envvar DD_ORIGIN_DETECTION_ENABLED: Enable/disable sending the container ID field + for origin detection. + :type DD_ORIGIN_DETECTION_ENABLED: boolean + + :envvar DD_DOGSTATSD_DISABLE_FORK_SUPPORT: Don't install global fork hooks with os.register_at_fork. + Global fork hooks then need to be called manually before and after calling os.fork. + :type DD_DOGSTATSD_DISABLE_FORK_SUPPORT: boolean + + :envvar DD_DOGSTATSD_DISABLE_INSTANCE_TRACKING: Don't register instances of this class with global fork hooks. + :type DD_DOGSTATSD_DISABLE_INSTANCE_TRACKING: boolean + + :param host: the host of the DogStatsd server. + :type host: string + + :param port: the port of the DogStatsd server. + :type port: integer + + :max_buffer_size: Deprecated option, do not use it anymore. + :type max_buffer_type: None + + :flush_interval: Amount of time in seconds that the flush thread will + wait before trying to flush the buffered metrics to the server. If set, + it overrides the default value. + :type flush_interval: float + + :disable_buffering: If set, metrics are no longered buffered by the client and + all data is sent synchronously to the server + :type disable_buffering: bool + + :param namespace: Namespace to prefix all metric names + :type namespace: string + + :param constant_tags: Tags to attach to all metrics + :type constant_tags: list of strings + + :param use_ms: Report timed values in milliseconds instead of seconds (default False) + :type use_ms: boolean + + :param use_default_route: Dynamically set the DogStatsd host to the default route + (Useful when running the client in a container) (Linux only) + :type use_default_route: boolean + + :param socket_path: Communicate with dogstatsd through a UNIX socket instead of + UDP. If set, disables UDP transmission (Linux only) + :type socket_path: string + + :param default_sample_rate: Sample rate to use by default for all metrics + :type default_sample_rate: float + + :param max_buffer_len: Maximum number of bytes to buffer before sending to the server + if sending metrics in batch. If not specified it will be adjusted to a optimal value + depending on the connection type. + :type max_buffer_len: integer + + :param disable_telemetry: Should client telemetry be disabled + :type disable_telemetry: boolean + + :param telemetry_min_flush_interval: Minimum flush interval for telemetry in seconds + :type telemetry_min_flush_interval: integer + + :param telemetry_host: the host for the dogstatsd server we wish to submit + telemetry stats to. Optional. If telemetry is enabled and this is not specified + the default host will be used. + :type host: string + + :param telemetry_port: the port for the dogstatsd server we wish to submit + telemetry stats to. Optional. If telemetry is enabled and this is not specified + the default host will be used. + :type port: integer + + :param telemetry_socket_path: Submit client telemetry to dogstatsd through a UNIX + socket instead of UDP. If set, disables UDP transmission (Linux only) + :type telemetry_socket_path: string + + :param container_id: Allows passing the container ID, this will be used by the Agent to enrich + metrics with container tags. + This feature requires Datadog Agent version >=6.35.0 && <7.0.0 or Agent versions >=7.35.0. + When configured, the provided container ID is prioritized over the container ID discovered + via Origin Detection. When DD_ENTITY_ID is set, this value is ignored. + Default: None. + :type container_id: string + + :param origin_detection_enabled: Enable/disable the client origin detection. + This feature requires Datadog Agent version >=6.35.0 && <7.0.0 or Agent versions >=7.35.0. + When enabled, the client tries to discover its container ID and sends it to the Agent + to enrich the metrics with container tags. + Origin detection can be disabled by configuring the environment variabe DD_ORIGIN_DETECTION_ENABLED=false + The client tries to read the container ID by parsing the file /proc/self/cgroup. + This is not supported on Windows. + The client prioritizes the value passed via DD_ENTITY_ID (if set) over the container ID. + Default: True. + More on this: https://docs.datadoghq.com/developers/dogstatsd/?tab=kubernetes#origin-detection-over-udp + :type origin_detection_enabled: boolean + + :param socket_timeout: Set timeout for socket operations, in seconds. Optional. + If sets to zero, never wait if operation can not be completed immediately. If set to None, wait forever. + This option does not affect hostname resolution when using UDP. + :type socket_timeout: float + + :param telemetry_socket_timeout: Set timeout for the telemetry socket operations. Optional. + Effective only if either telemetry_host or telemetry_socket_path are set. + If sets to zero, never wait if operation can not be completed immediately. If set to None, wait forever. + This option does not affect hostname resolution when using UDP. + :type telemetry_socket_timeout: float + + :param disable_background_sender: Use a background thread to communicate with the dogstatsd server. Optional. + When enabled, a background thread will be used to send metric payloads to the Agent. + Applications should call stop() before exiting to make sure all pending payloads are sent. + Default: True. + :type disable_background_sender: boolean + + :param sender_queue_size: Set the maximum number of packets to queue for the sender. Optional + How may packets to queue before blocking or dropping the packet if the packet queue is already full. + Default: 0 (unlimited). + :type sender_queue_size: integer + + :param sender_queue_timeout: Set timeout for packet queue operations, in seconds. Optional. + How long the application thread is willing to wait for the queue clear up before dropping the metric packet. + If set to None, wait forever. + If set to zero drop the packet immediately if the queue is full. + Default: 0 (no wait) + :type sender_queue_timeout: float + + :param track_instance: Keep track of this instance and automatically handle cleanup when os.fork() is called, + if supported. + Default: True. + :type track_instance: boolean + """ + + self._socket_lock = Lock() + + # Check for deprecated option + if max_buffer_size is not None: + log.warning("The parameter max_buffer_size is now deprecated and is not used anymore") + + # Check host and port env vars + agent_host = os.environ.get("DD_AGENT_HOST") + if agent_host and host == DEFAULT_HOST: + host = agent_host + + dogstatsd_port = os.environ.get("DD_DOGSTATSD_PORT") + if dogstatsd_port and port == DEFAULT_PORT: + try: + port = int(dogstatsd_port) + except ValueError: + log.warning( + "Port number provided in DD_DOGSTATSD_PORT env var is not an integer: \ + %s, using %s as port number", + dogstatsd_port, + port, + ) + + # Assuming environment variables always override + telemetry_host = os.environ.get("DD_TELEMETRY_HOST", telemetry_host) + telemetry_port = os.environ.get("DD_TELEMETRY_PORT", telemetry_port) or port + + # Check enabled + if os.environ.get("DD_DOGSTATSD_DISABLE") not in {"True", "true", "yes", "1"}: + self._enabled = True + else: + self._enabled = False + + # Connection + self._max_buffer_len = max_buffer_len + self.socket_timeout = socket_timeout + if socket_path is not None: + self.socket_path = socket_path # type: Optional[text] + self.host = None + self.port = None + else: + self.socket_path = None + self.host = self.resolve_host(host, use_default_route) + self.port = int(port) + + self.telemetry_socket_path = telemetry_socket_path + self.telemetry_host = None + self.telemetry_port = None + self.telemetry_socket_timeout = telemetry_socket_timeout + if not telemetry_socket_path and telemetry_host: + self.telemetry_socket_path = None + self.telemetry_host = self.resolve_host(telemetry_host, use_default_route) + self.telemetry_port = int(telemetry_port) + + # Socket + self.socket = None + self.telemetry_socket = None + self.encoding = "utf-8" + + # Options + env_tags = [tag for tag in os.environ.get("DATADOG_TAGS", "").split(",") if tag] + # Inject values of DD_* environment variables as global tags. + has_entity_id = False + for var, tag_name in DD_ENV_TAGS_MAPPING.items(): + value = os.environ.get(var, "") + if value: + env_tags.append("{name}:{value}".format(name=tag_name, value=value)) + if var == ENTITY_ID_ENV_VAR: + has_entity_id = True + if constant_tags is None: + constant_tags = [] + self.constant_tags = constant_tags + env_tags + if namespace is not None: + namespace = text(namespace) + self.namespace = namespace + self.use_ms = use_ms + self.default_sample_rate = default_sample_rate + + # Origin detection + self._container_id = None + if not has_entity_id: + origin_detection_enabled = self._is_origin_detection_enabled( + container_id, origin_detection_enabled, has_entity_id + ) + self._set_container_id(container_id, origin_detection_enabled) + + # init telemetry version + self._client_tags = [ + "client:py", + "client_version:{}".format(__version__), + ] + self._reset_telemetry() + self._telemetry_flush_interval = telemetry_min_flush_interval + self._telemetry = not disable_telemetry + self._last_flush_time = time.time() + + self._current_buffer_total_size = 0 + self._buffer = [] # type: List[Text] + self._buffer_lock = RLock() + + self._reset_buffer() + + # This lock is used for all cases where client configuration is being changed: buffering, sender mode. + self._config_lock = RLock() + + # If buffering is disabled, we bypass the buffer function. + self._send = self._send_to_buffer + self._disable_buffering = disable_buffering + if self._disable_buffering: + self._send = self._send_to_server + log.debug("Statsd buffering is disabled") + + # Indicates if the process is about to fork, so we shouldn't start any new threads yet. + self._forking = False + + # Start the flush thread if buffering is enabled and the interval is above + # a reasonable range. This both prevents thrashing and allow us to use "0.0" + # as a value for disabling the automatic flush timer as well. + self._flush_interval = flush_interval + self._flush_thread_stop = threading.Event() + self._flush_thread = None + self._start_flush_thread(self._flush_interval) + + self._queue = None + self._sender_thread = None + self._sender_enabled = False + + if not disable_background_sender: + self.enable_background_sender(sender_queue_size, sender_queue_timeout) + + if TRACK_INSTANCES and track_instance: + _instances.add(self) + + @property + def socket_path(self): + return self._socket_path + + @socket_path.setter + def socket_path(self, path): + with self._socket_lock: + self._socket_path = path + if path is None: + self._transport = "udp" + self._max_payload_size = self._max_buffer_len or UDP_OPTIMAL_PAYLOAD_LENGTH + else: + self._transport = "uds" + self._max_payload_size = self._max_buffer_len or UDS_OPTIMAL_PAYLOAD_LENGTH + + def enable_background_sender(self, sender_queue_size=0, sender_queue_timeout=0): + """ + Use a background thread to communicate with the dogstatsd server. + When enabled, a background thread will be used to send metric payloads to the Agent. + + Applications should call stop() before exiting to make sure all pending payloads are sent. + + Compatible with os.fork() starting with Python 3.7. On earlier versions, compatible if applications + arrange to call pre_fork() and post_fork() module functions around calls to os.fork(). + + :param sender_queue_size: Set the maximum number of packets to queue for the sender. + How many packets to queue before blocking or dropping the packet if the packet queue is already full. + Default: 0 (unlimited). + :type sender_queue_size: integer, optional + :param sender_queue_timeout: Set timeout for packet queue operations, in seconds. + How long the application thread is willing to wait for the queue clear up before dropping the metric packet. + If set to None, wait forever. If set to zero drop the packet immediately if the queue is full. + Default: 0 (no wait). + :type sender_queue_timeout: float, optional + """ + + with self._config_lock: + self._sender_enabled = True + self._sender_queue_size = sender_queue_size + if sender_queue_timeout is None: + self._queue_blocking = True + self._queue_timeout = None + else: + self._queue_blocking = sender_queue_timeout > 0 + self._queue_timeout = max(0, sender_queue_timeout) + + self._start_sender_thread() + + def disable_background_sender(self): + """Disable background sender mode. + + This call will block until all previously queued payloads are sent. + """ + with self._config_lock: + self._sender_enabled = False + self._stop_sender_thread() + + def disable_telemetry(self): + self._telemetry = False + + def enable_telemetry(self): + self._telemetry = True + + # Note: Invocations of this method should be thread-safe + def _start_flush_thread(self, flush_interval): + if self._disable_buffering or self._flush_interval <= MIN_FLUSH_INTERVAL: + log.debug("Statsd periodic buffer flush is disabled") + return + + if self._forking: + return + + if self._flush_thread is not None: + return + + def _flush_thread_loop(self, flush_interval): + while not self._flush_thread_stop.is_set(): + time.sleep(flush_interval) + self.flush() + + self._flush_thread = threading.Thread( + name="{}_flush_thread".format(self.__class__.__name__), + target=_flush_thread_loop, + args=(self, flush_interval,), + ) + self._flush_thread.daemon = True + self._flush_thread.start() + + log.debug( + "Statsd flush thread registered with period of %s", + self._flush_interval, + ) + + # Note: Invocations of this method should be thread-safe + def _stop_flush_thread(self): + if not self._flush_thread: + return + + try: + self.flush() + finally: + pass + + self._flush_thread_stop.set() + + self._flush_thread.join() + self._flush_thread = None + + self._flush_thread_stop.clear() + + def _dedicated_telemetry_destination(self): + return bool(self.telemetry_socket_path or self.telemetry_host) + + # Context manager helper + def __enter__(self): + self.open_buffer() + return self + + # Context manager helper + def __exit__(self, exc_type, value, traceback): + self.close_buffer() + + @property + def disable_buffering(self): + with self._config_lock: + return self._disable_buffering + + @disable_buffering.setter + def disable_buffering(self, is_disabled): + with self._config_lock: + # If the toggle didn't change anything, this method is a noop + if self._disable_buffering == is_disabled: + return + + self._disable_buffering = is_disabled + + # If buffering has been disabled, flush and kill the background thread + # otherwise start up the flushing thread and enable the buffering. + if is_disabled: + self._send = self._send_to_server + self._stop_flush_thread() + log.debug("Statsd buffering is disabled") + else: + self._send = self._send_to_buffer + self._start_flush_thread(self._flush_interval) + + @staticmethod + def resolve_host(host, use_default_route): + """ + Resolve the DogStatsd host. + + :param host: host + :type host: string + :param use_default_route: Use the system default route as host (overrides `host` parameter) + :type use_default_route: bool + """ + if not use_default_route: + return host + + return get_default_route() + + def get_socket(self, telemetry=False): + """ + Return a connected socket. + + Note: connect the socket before assigning it to the class instance to + avoid bad thread race conditions. + """ + with self._socket_lock: + if telemetry and self._dedicated_telemetry_destination(): + if not self.telemetry_socket: + if self.telemetry_socket_path is not None: + self.telemetry_socket = self._get_uds_socket( + self.telemetry_socket_path, + self.telemetry_socket_timeout, + ) + else: + self.telemetry_socket = self._get_udp_socket( + self.telemetry_host, + self.telemetry_port, + self.telemetry_socket_timeout, + ) + + return self.telemetry_socket + + if not self.socket: + if self.socket_path is not None: + self.socket = self._get_uds_socket(self.socket_path, self.socket_timeout) + else: + self.socket = self._get_udp_socket( + self.host, + self.port, + self.socket_timeout, + ) + + return self.socket + + def set_socket_timeout(self, timeout): + """ + Set timeout for socket operations, in seconds. + + If set to zero, never wait if operation can not be completed immediately. If set to None, wait forever. + This option does not affect hostname resolution when using UDP. + """ + with self._socket_lock: + self.socket_timeout = timeout + if self.socket: + self.socket.settimeout(timeout) + + @classmethod + def _ensure_min_send_buffer_size(cls, sock, min_size=MIN_SEND_BUFFER_SIZE): + # Increase the receiving buffer size where needed (e.g. MacOS has 4k RX + # buffers which is half of the max packet size that the client will send. + if os.name == 'posix': + try: + recv_buff_size = sock.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF) + if recv_buff_size <= min_size: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, min_size) + log.debug("Socket send buffer increased to %dkb", min_size / 1024) + finally: + pass + + @classmethod + def _get_uds_socket(cls, socket_path, timeout): + sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) + sock.settimeout(timeout) + cls._ensure_min_send_buffer_size(sock) + sock.connect(socket_path) + return sock + + @classmethod + def _get_udp_socket(cls, host, port, timeout): + log.debug("Connecting to %s:%s", host, port) + addrinfo = socket.getaddrinfo(host, port, 0, socket.SOCK_DGRAM) + # Override gai.conf order for backwrads compatibility: prefer + # v4, so that a v4-only service on hosts with both addresses + # still works. + addrinfo.sort(key=lambda v: v[0] == socket.AF_INET, reverse=True) + lastaddr = len(addrinfo) - 1 + for i, (af, ty, proto, _, addr) in enumerate(addrinfo): + sock = None + try: + sock = socket.socket(af, ty, proto) + sock.settimeout(timeout) + cls._ensure_min_send_buffer_size(sock) + sock.connect(addr) + log.debug("Connected to: %s", addr) + return sock + except Exception as e: + if sock is not None: + sock.close() + log.debug("Failed to connect to %s: %s", addr, e) + if i < lastaddr: + continue + raise e + else: + raise ValueError("getaddrinfo returned no addresses to connect to") + + def open_buffer(self, max_buffer_size=None): + """ + Open a buffer to send a batch of metrics. + + To take advantage of automatic flushing, you should use the context manager instead + + >>> with DogStatsd() as batch: + >>> batch.gauge("users.online", 123) + >>> batch.gauge("active.connections", 1001) + + Note: This method must be called before close_buffer() matching invocation. + """ + + self._config_lock.acquire() + + # XXX Remove if `disable_buffering` default is changed to False + self._send = self._send_to_buffer + + if max_buffer_size is not None: + log.warning("The parameter max_buffer_size is now deprecated and is not used anymore") + + self._reset_buffer() + + def close_buffer(self): + """ + Flush the buffer and switch back to single metric packets. + + Note: This method must be called after a matching open_buffer() + invocation. + """ + try: + self.flush() + finally: + # XXX Remove if `disable_buffering` default is changed to False + if self._disable_buffering: + self._send = self._send_to_server + + self._config_lock.release() + + def _reset_buffer(self): + with self._buffer_lock: + self._current_buffer_total_size = 0 + self._buffer = [] + + def flush(self): + """ + Flush the metrics buffer by sending the data to the server. + """ + with self._buffer_lock: + # Only send packets if there are packets to send + if self._buffer: + self._send_to_server("\n".join(self._buffer)) + self._reset_buffer() + + def gauge( + self, + metric, # type: Text + value, # type: float + tags=None, # type: Optional[List[str]] + sample_rate=None, # type: Optional[float] + ): # type(...) -> None + """ + Record the value of a gauge, optionally setting a list of tags and a + sample rate. + + >>> statsd.gauge("users.online", 123) + >>> statsd.gauge("active.connections", 1001, tags=["protocol:http"]) + """ + return self._report(metric, "g", value, tags, sample_rate) + + def increment( + self, + metric, # type: Text + value=1, # type: float + tags=None, # type: Optional[List[str]] + sample_rate=None, # type: Optional[float] + ): # type: (...) -> None + """ + Increment a counter, optionally setting a value, tags and a sample + rate. + + >>> statsd.increment("page.views") + >>> statsd.increment("files.transferred", 124) + """ + self._report(metric, "c", value, tags, sample_rate) + + def decrement( + self, + metric, # type: Text + value=1, # type: float + tags=None, # type: Optional[List[str]] + sample_rate=None, # type: Optional[float] + ): # type(...) -> None + """ + Decrement a counter, optionally setting a value, tags and a sample + rate. + + >>> statsd.decrement("files.remaining") + >>> statsd.decrement("active.connections", 2) + """ + metric_value = -value if value else value + self._report(metric, "c", metric_value, tags, sample_rate) + + def histogram( + self, + metric, # type: Text + value, # type: float + tags=None, # type: Optional[List[str]] + sample_rate=None, # type: Optional[float] + ): # type(...) -> None + """ + Sample a histogram value, optionally setting tags and a sample rate. + + >>> statsd.histogram("uploaded.file.size", 1445) + >>> statsd.histogram("album.photo.count", 26, tags=["gender:female"]) + """ + self._report(metric, "h", value, tags, sample_rate) + + def distribution( + self, + metric, # type: Text + value, # type: float + tags=None, # type: Optional[List[str]] + sample_rate=None, # type: Optional[float] + ): # type(...) -> None + """ + Send a global distribution value, optionally setting tags and a sample rate. + + >>> statsd.distribution("uploaded.file.size", 1445) + >>> statsd.distribution("album.photo.count", 26, tags=["gender:female"]) + """ + self._report(metric, "d", value, tags, sample_rate) + + def timing( + self, + metric, # type: Text + value, # type: float + tags=None, # type: Optional[List[str]] + sample_rate=None, # type: Optional[float] + ): # type(...) -> None + """ + Record a timing, optionally setting tags and a sample rate. + + >>> statsd.timing("query.response.time", 1234) + """ + self._report(metric, "ms", value, tags, sample_rate) + + def timed(self, metric=None, tags=None, sample_rate=None, use_ms=None): + """ + A decorator or context manager that will measure the distribution of a + function's/context's run time. Optionally specify a list of tags or a + sample rate. If the metric is not defined as a decorator, the module + name and function name will be used. The metric is required as a context + manager. + :: + + @statsd.timed("user.query.time", sample_rate=0.5) + def get_user(user_id): + # Do what you need to ... + pass + + # Is equivalent to ... + with statsd.timed("user.query.time", sample_rate=0.5): + # Do what you need to ... + pass + + # Is equivalent to ... + start = time.time() + try: + get_user(user_id) + finally: + statsd.timing("user.query.time", time.time() - start) + """ + return TimedContextManagerDecorator(self, metric, tags, sample_rate, use_ms) + + def distributed(self, metric=None, tags=None, sample_rate=None, use_ms=None): + """ + A decorator or context manager that will measure the distribution of a + function's/context's run time using custom metric distribution. + Optionally specify a list of tags or a sample rate. If the metric is not + defined as a decorator, the module name and function name will be used. + The metric is required as a context manager. + :: + + @statsd.distributed("user.query.time", sample_rate=0.5) + def get_user(user_id): + # Do what you need to ... + pass + + # Is equivalent to ... + with statsd.distributed("user.query.time", sample_rate=0.5): + # Do what you need to ... + pass + + # Is equivalent to ... + start = time.time() + try: + get_user(user_id) + finally: + statsd.distribution("user.query.time", time.time() - start) + """ + return DistributedContextManagerDecorator(self, metric, tags, sample_rate, use_ms) + + def set(self, metric, value, tags=None, sample_rate=None): + """ + Sample a set value. + + >>> statsd.set("visitors.uniques", 999) + """ + self._report(metric, "s", value, tags, sample_rate) + + def close_socket(self): + """ + Closes connected socket if connected. + """ + with self._socket_lock: + if self.socket: + try: + self.socket.close() + except OSError as e: + log.error("Unexpected error: %s", str(e)) + self.socket = None + + if self.telemetry_socket: + try: + self.telemetry_socket.close() + except OSError as e: + log.error("Unexpected error: %s", str(e)) + self.telemetry_socket = None + + def _serialize_metric(self, metric, metric_type, value, tags, sample_rate=1): + # Create/format the metric packet + return "%s%s:%s|%s%s%s%s" % ( + (self.namespace + ".") if self.namespace else "", + metric, + value, + metric_type, + ("|@" + text(sample_rate)) if sample_rate != 1 else "", + ("|#" + ",".join(normalize_tags(tags))) if tags else "", + ("|c:" + self._container_id if self._container_id else "") + ) + + def _report(self, metric, metric_type, value, tags, sample_rate): + """ + Create a metric packet and send it. + + More information about the packets' format: http://docs.datadoghq.com/guides/dogstatsd/ + """ + if value is None: + return + + if self._enabled is not True: + return + + if self._telemetry: + self.metrics_count += 1 + + if sample_rate is None: + sample_rate = self.default_sample_rate + + if sample_rate != 1 and random() > sample_rate: + return + + # Resolve the full tag list + tags = self._add_constant_tags(tags) + payload = self._serialize_metric(metric, metric_type, value, tags, sample_rate) + + # Send it + self._send(payload) + + def _reset_telemetry(self): + self.metrics_count = 0 + self.events_count = 0 + self.service_checks_count = 0 + self.bytes_sent = 0 + self.bytes_dropped_queue = 0 + self.bytes_dropped_writer = 0 + self.packets_sent = 0 + self.packets_dropped_queue = 0 + self.packets_dropped_writer = 0 + self._last_flush_time = time.time() + + # Aliases for backwards compatibility. + @property + def packets_dropped(self): + return self.packets_dropped_queue + self.packets_dropped_writer + + @property + def bytes_dropped(self): + return self.bytes_dropped_queue + self.bytes_dropped_writer + + def _flush_telemetry(self): + tags = self._client_tags[:] + tags.append("client_transport:{}".format(self._transport)) + tags.extend(self.constant_tags) + telemetry_tags = ",".join(tags) + + return TELEMETRY_FORMATTING_STR % ( + self.metrics_count, + telemetry_tags, + self.events_count, + telemetry_tags, + self.service_checks_count, + telemetry_tags, + self.bytes_sent, + telemetry_tags, + self.bytes_dropped_queue + self.bytes_dropped_writer, + telemetry_tags, + self.bytes_dropped_queue, + telemetry_tags, + self.bytes_dropped_writer, + telemetry_tags, + self.packets_sent, + telemetry_tags, + self.packets_dropped_queue + self.packets_dropped_writer, + telemetry_tags, + self.packets_dropped_queue, + telemetry_tags, + self.packets_dropped_writer, + telemetry_tags, + ) + + def _is_telemetry_flush_time(self): + return self._telemetry and \ + self._last_flush_time + self._telemetry_flush_interval < time.time() + + def _send_to_server(self, packet): + # Skip the lock if the queue is None. There is no race with enable_background_sender. + if self._queue is not None: + # Prevent a race with disable_background_sender. + with self._buffer_lock: + if self._queue is not None: + try: + self._queue.put(packet + '\n', self._queue_blocking, self._queue_timeout) + except queue.Full: + self.packets_dropped_queue += 1 + self.bytes_dropped_queue += 1 + return + + self._xmit_packet_with_telemetry(packet + '\n') + + def _xmit_packet_with_telemetry(self, packet): + self._xmit_packet(packet, False) + + if self._is_telemetry_flush_time(): + telemetry = self._flush_telemetry() + if self._xmit_packet(telemetry, True): + self._reset_telemetry() + self.packets_sent += 1 + self.bytes_sent += len(telemetry) + else: + # Telemetry packet has been dropped, keep telemetry data for the next flush + self._last_flush_time = time.time() + self.bytes_dropped_writer += len(telemetry) + self.packets_dropped_writer += 1 + + def _xmit_packet(self, packet, is_telemetry): + try: + if is_telemetry and self._dedicated_telemetry_destination(): + mysocket = self.telemetry_socket or self.get_socket(telemetry=True) + else: + # If set, use socket directly + mysocket = self.socket or self.get_socket() + + mysocket.send(packet.encode(self.encoding)) + + if not is_telemetry and self._telemetry: + self.packets_sent += 1 + self.bytes_sent += len(packet) + + return True + except socket.timeout: + # dogstatsd is overflowing, drop the packets (mimics the UDP behaviour) + pass + except (socket.herror, socket.gaierror) as socket_err: + log.warning( + "Error submitting packet: %s, dropping the packet and closing the socket", + socket_err, + ) + self.close_socket() + except socket.error as socket_err: + if socket_err.errno == errno.EAGAIN: + log.debug("Socket send would block: %s, dropping the packet", socket_err) + elif socket_err.errno == errno.ENOBUFS: + log.debug("Socket buffer full: %s, dropping the packet", socket_err) + elif socket_err.errno == errno.EMSGSIZE: + log.debug( + "Packet size too big (size: %d): %s, dropping the packet", + len(packet.encode(self.encoding)), + socket_err) + else: + log.warning( + "Error submitting packet: %s, dropping the packet and closing the socket", + socket_err, + ) + self.close_socket() + except Exception as exc: + print("Unexpected error: %s", exc) + log.error("Unexpected error: %s", str(exc)) + + if not is_telemetry and self._telemetry: + self.bytes_dropped_writer += len(packet) + self.packets_dropped_writer += 1 + + return False + + def _send_to_buffer(self, packet): + with self._buffer_lock: + if self._should_flush(len(packet)): + self.flush() + + self._buffer.append(packet) + # Update the current buffer length, including line break to anticipate + # the final packet size + self._current_buffer_total_size += len(packet) + 1 + + def _should_flush(self, length_to_be_added): + if self._current_buffer_total_size + length_to_be_added + 1 > self._max_payload_size: + return True + return False + + @staticmethod + def _escape_event_content(string): + return string.replace("\n", "\\n") + + @staticmethod + def _escape_service_check_message(string): + return string.replace("\n", "\\n").replace("m:", "m\\:") + + def event( + self, + title, + message, + alert_type=None, + aggregation_key=None, + source_type_name=None, + date_happened=None, + priority=None, + tags=None, + hostname=None, + ): + """ + Send an event. Attributes are the same as the Event API. + http://docs.datadoghq.com/api/ + + >>> statsd.event("Man down!", "This server needs assistance.") + >>> statsd.event("Web server restart", "The web server is up", alert_type="success") # NOQA + """ + title = DogStatsd._escape_event_content(title) + message = DogStatsd._escape_event_content(message) + + # pylint: disable=undefined-variable + if not is_p3k(): + if not isinstance(title, unicode): # noqa: F821 + title = unicode(DogStatsd._escape_event_content(title), 'utf8') # noqa: F821 + if not isinstance(message, unicode): # noqa: F821 + message = unicode(DogStatsd._escape_event_content(message), 'utf8') # noqa: F821 + + # Append all client level tags to every event + tags = self._add_constant_tags(tags) + + string = u"_e{{{},{}}}:{}|{}".format( + len(title.encode('utf8', 'replace')), + len(message.encode('utf8', 'replace')), + title, + message, + ) + + if date_happened: + string = "%s|d:%d" % (string, date_happened) + if hostname: + string = "%s|h:%s" % (string, hostname) + if aggregation_key: + string = "%s|k:%s" % (string, aggregation_key) + if priority: + string = "%s|p:%s" % (string, priority) + if source_type_name: + string = "%s|s:%s" % (string, source_type_name) + if alert_type: + string = "%s|t:%s" % (string, alert_type) + if tags: + string = "%s|#%s" % (string, ",".join(tags)) + if self._container_id: + string = "%s|c:%s" % (string, self._container_id) + + if len(string) > 8 * 1024: + raise ValueError( + u'Event "{0}" payload is too big (>=8KB). Event discarded'.format( + title + ) + ) + + if self._telemetry: + self.events_count += 1 + + self._send(string) + + def service_check( + self, + check_name, + status, + tags=None, + timestamp=None, + hostname=None, + message=None, + ): + """ + Send a service check run. + + >>> statsd.service_check("my_service.check_name", DogStatsd.WARNING) + """ + message = DogStatsd._escape_service_check_message(message) if message is not None else "" + + string = u"_sc|{0}|{1}".format(check_name, status) + + # Append all client level tags to every status check + tags = self._add_constant_tags(tags) + + if timestamp: + string = u"{0}|d:{1}".format(string, timestamp) + if hostname: + string = u"{0}|h:{1}".format(string, hostname) + if tags: + string = u"{0}|#{1}".format(string, ",".join(tags)) + if message: + string = u"{0}|m:{1}".format(string, message) + if self._container_id: + string = u"{0}|c:{1}".format(string, self._container_id) + + if self._telemetry: + self.service_checks_count += 1 + + self._send(string) + + def _add_constant_tags(self, tags): + if self.constant_tags: + if tags: + return tags + self.constant_tags + + return self.constant_tags + return tags + + def _is_origin_detection_enabled(self, container_id, origin_detection_enabled, has_entity_id): + """ + Returns whether the client should fill the container field. + If DD_ENTITY_ID is set, we don't send the container ID + If a user-defined container ID is provided, we don't ignore origin detection + as dd.internal.entity_id is prioritized over the container field for backward compatibility. + If DD_ENTITY_ID is not set, we try to fill the container field automatically unless + DD_ORIGIN_DETECTION_ENABLED is explicitly set to false. + """ + if not origin_detection_enabled or has_entity_id or container_id is not None: + # origin detection is explicitly disabled + # or DD_ENTITY_ID was found + # or a user-defined container ID was provided + return False + value = os.environ.get(ORIGIN_DETECTION_ENABLED, "") + return value.lower() not in {"no", "false", "0", "n", "off"} + + def _set_container_id(self, container_id, origin_detection_enabled): + """ + Initializes the container ID. + It can either be provided by the user or read from cgroups. + """ + if container_id: + self._container_id = container_id + return + if origin_detection_enabled: + try: + reader = ContainerID() + self._container_id = reader.container_id + except Exception as e: + log.debug("Couldn't get container ID: %s", str(e)) + self._container_id = None + + def _start_sender_thread(self): + if not self._sender_enabled or self._forking: + return + + if self._queue is not None: + return + + self._queue = queue.Queue(self._sender_queue_size) + + log.debug("Starting background sender thread") + self._sender_thread = threading.Thread( + name="{}_sender_thread".format(self.__class__.__name__), + target=self._sender_main_loop, + args=(self._queue,) + ) + self._sender_thread.daemon = True + self._sender_thread.start() + + def _stop_sender_thread(self): + # Lock ensures that nothing gets added to the queue after we disable it. + with self._buffer_lock: + if not self._queue: + return + self._queue.put(Stop) + self._queue = None + + self._sender_thread.join() + self._sender_thread = None + + def _sender_main_loop(self, queue): + while True: + item = queue.get() + if item is Stop: + queue.task_done() + return + self._xmit_packet_with_telemetry(item) + queue.task_done() + + def wait_for_pending(self): + """ + Flush the buffer and wait for all queued payloads to be written to the server. + """ + + self.flush() + + # Avoid race with disable_background_sender. We don't need a + # lock, just copy the value so it doesn't change between the + # check and join later. + queue = self._queue + + if queue is not None: + queue.join() + + def pre_fork(self): + """Prepare client for a process fork. + + Flush any pending payloads, stop all background threads and + close the connection. Once the function returns. + + The client should not be used from this point until + post_fork() is called. + """ + log.debug("[%d] pre_fork for %s", os.getpid(), self) + + self._forking = True + + with self._config_lock: + self._stop_flush_thread() + self._stop_sender_thread() + self.close_socket() + + def post_fork(self): + """Restore the client state after a fork.""" + + log.debug("[%d] post_fork for %s", os.getpid(), self) + + with self._socket_lock: + if self.socket or self.telemetry_socket: + log.warning("Open socket detected after fork. Call pre_fork() before os.fork().") + self.close_socket() + + self._forking = False + + with self._config_lock: + self._start_flush_thread(self._flush_interval) + self._start_sender_thread() + + def stop(self): + """Stop the client. + + Disable buffering, background sender and flush any pending payloads to the server. + + Client remains usable after this method, but sending metrics may block if socket_timeout is enabled. + """ + + self.disable_background_sender() + self.disable_buffering = True + self.flush() + self.close_socket() + + +statsd = DogStatsd() diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/dogstatsd/container.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogstatsd/container.py new file mode 100644 index 0000000..fe2e71c --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogstatsd/container.py @@ -0,0 +1,57 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under +# the BSD-3-Clause License. This product includes software developed at Datadog +# (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc + +import errno +import re + + +class UnresolvableContainerID(Exception): + """ + Unable to get container ID from cgroup. + """ + + +class ContainerID(object): + """ + A reader class that retrieves the current container ID parsed from a the cgroup file. + + Returns: + object: ContainerID + + Raises: + `NotImplementedError`: No proc filesystem is found (non-Linux systems) + `UnresolvableContainerID`: Unable to read the container ID + """ + + CGROUP_PATH = "/proc/self/cgroup" + UUID_SOURCE = r"[0-9a-f]{8}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{12}" + CONTAINER_SOURCE = r"[0-9a-f]{64}" + TASK_SOURCE = r"[0-9a-f]{32}-\d+" + LINE_RE = re.compile(r"^(\d+):([^:]*):(.+)$") + CONTAINER_RE = re.compile(r"(?:.+)?({0}|{1}|{2})(?:\.scope)?$".format(UUID_SOURCE, CONTAINER_SOURCE, TASK_SOURCE)) + + def __init__(self): + self.container_id = self._read_container_id(self.CGROUP_PATH) + + def _read_container_id(self, fpath): + try: + with open(fpath, mode="r") as fp: + for line in fp: + line = line.strip() + match = self.LINE_RE.match(line) + if not match: + continue + _, _, path = match.groups() + parts = [p for p in path.split("/")] + if len(parts): + match = self.CONTAINER_RE.match(parts.pop()) + if match: + return match.group(1) + except IOError as e: + if e.errno != errno.ENOENT: + raise NotImplementedError("Unable to open {}.".format(self.CGROUP_PATH)) + except Exception as e: + raise UnresolvableContainerID("Unable to read the container ID: " + str(e)) + return None diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/dogstatsd/context.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogstatsd/context.py new file mode 100644 index 0000000..90e9ce9 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogstatsd/context.py @@ -0,0 +1,88 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +# stdlib +from functools import wraps + +try: + from time import monotonic # type: ignore[attr-defined] +except ImportError: + from time import time as monotonic + +# datadog +from datadog.dogstatsd.context_async import _get_wrapped_co +from datadog.util.compat import iscoroutinefunction + + +class TimedContextManagerDecorator(object): + """ + A context manager and a decorator which will report the elapsed time in + the context OR in a function call. + """ + + def __init__(self, statsd, metric=None, tags=None, sample_rate=1, use_ms=None): + self.statsd = statsd + self.timing_func = statsd.timing + self.metric = metric + self.tags = tags + self.sample_rate = sample_rate + self.use_ms = use_ms + self.elapsed = None + + def __call__(self, func): + """ + Decorator which returns the elapsed time of the function call. + + Default to the function name if metric was not provided. + """ + if not self.metric: + self.metric = "%s.%s" % (func.__module__, func.__name__) + + # Coroutines + if iscoroutinefunction(func): + return _get_wrapped_co(self, func) + + # Others + @wraps(func) + def wrapped(*args, **kwargs): + start = monotonic() + try: + return func(*args, **kwargs) + finally: + self._send(start) + + return wrapped + + def __enter__(self): + if not self.metric: + raise TypeError("Cannot used timed without a metric!") + self._start = monotonic() + return self + + def __exit__(self, type, value, traceback): + # Report the elapsed time of the context manager. + self._send(self._start) + + def _send(self, start): + elapsed = monotonic() - start + use_ms = self.use_ms if self.use_ms is not None else self.statsd.use_ms + elapsed = int(round(1000 * elapsed)) if use_ms else elapsed + self.timing_func(self.metric, elapsed, self.tags, self.sample_rate) + self.elapsed = elapsed + + def start(self): + self.__enter__() + + def stop(self): + self.__exit__(None, None, None) + + +class DistributedContextManagerDecorator(TimedContextManagerDecorator): + """ + A context manager and a decorator which will report the elapsed time in + the context OR in a function call using the custom distribution metric. + """ + + def __init__(self, statsd, metric=None, tags=None, sample_rate=1, use_ms=None): + super(DistributedContextManagerDecorator, self).__init__(statsd, metric, tags, sample_rate, use_ms) + self.timing_func = statsd.distribution diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/dogstatsd/context_async.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogstatsd/context_async.py new file mode 100644 index 0000000..d178d4e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogstatsd/context_async.py @@ -0,0 +1,52 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +""" +Decorator `timed` for coroutine methods. + +Warning: requires Python 3.5 or higher. +""" +# stdlib +import sys + + +# Wrap the Python 3.5+ function in a docstring to avoid syntax errors when +# running mypy in --py2 mode. Currently there is no way to have mypy skip an +# entire file if it has syntax errors. This solution is very hacky; another +# option is to specify the source files to process in mypy.ini (using glob +# inclusion patterns), and omit this file from the list. +# +# https://stackoverflow.com/a/57023749/3776794 +# https://github.com/python/mypy/issues/6897 +ASYNC_SOURCE = r''' +from functools import wraps +try: + from time import monotonic +except ImportError: + from time import time as monotonic + + +def _get_wrapped_co(self, func): + """ + `timed` wrapper for coroutine methods. + """ + @wraps(func) + async def wrapped_co(*args, **kwargs): + start = monotonic() + try: + result = await func(*args, **kwargs) + return result + finally: + self._send(start) + return wrapped_co +''' + + +def _get_wrapped_co(self, func): + raise NotImplementedError( + u"Decorator `timed` compatibility with coroutine functions" u" requires Python 3.5 or higher." + ) + + +if sys.version_info >= (3, 5): + exec(compile(ASYNC_SOURCE, __file__, "exec")) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/dogstatsd/route.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogstatsd/route.py new file mode 100644 index 0000000..c3fe779 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/dogstatsd/route.py @@ -0,0 +1,40 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +""" +Helper(s), resolve the system's default interface. +""" +# stdlib +import socket +import struct + + +class UnresolvableDefaultRoute(Exception): + """ + Unable to resolve system's default route. + """ + + +def get_default_route(): + """ + Return the system default interface using the proc filesystem. + + Returns: + string: default route + + Raises: + `NotImplementedError`: No proc filesystem is found (non-Linux systems) + `StopIteration`: No default route found + """ + try: + with open("/proc/net/route") as f: + for line in f.readlines(): + fields = line.strip().split() + if fields[1] == "00000000": + return socket.inet_ntoa(struct.pack(" 0: + should_flush = False + if should_flush: + _get_lambda_stats().flush(float("inf")) + + def __call__(self, *args, **kw): + warnings.warn("datadog_lambda_wrapper() is relocated to https://git.io/fjy8o", DeprecationWarning) + _LambdaDecorator._enter() + try: + return self.func(*args, **kw) + finally: + _LambdaDecorator._close() + + +_lambda_stats = None +datadog_lambda_wrapper = _LambdaDecorator + + +def _get_lambda_stats(): + global _lambda_stats + # This is not thread-safe, it should be called first by _LambdaDecorator + if _lambda_stats is None: + _lambda_stats = ThreadStats() + _lambda_stats.start(flush_in_greenlet=False, flush_in_thread=False) + return _lambda_stats + + +def lambda_metric(*args, **kw): + """ Alias to expose only distributions for lambda functions""" + _get_lambda_stats().distribution(*args, **kw) + + +def _init_api_client(): + """No-op GET to initialize the requests connection with DD's endpoints + + The goal here is to make the final flush faster: + we keep alive the Requests session, this means that we can re-use the connection + The consequence is that the HTTP Handshake, which can take hundreds of ms, + is now made at the beginning of a lambda instead of at the end. + + By making the initial request async, we spare a lot of execution time in the lambdas. + """ + try: + api.api_client.APIClient.submit("GET", "validate") + except Exception: + pass diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/base.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/base.py new file mode 100644 index 0000000..b5e7699 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/base.py @@ -0,0 +1,511 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +""" +ThreadStats is a tool for collecting application metrics without hindering +performance. It collects metrics in the application thread with very little overhead +and allows flushing metrics in process, in a thread or in a greenlet, depending +on your application's needs. +""" +import atexit +import logging +import os + +# stdlib +from contextlib import contextmanager +from functools import wraps +from time import time + +try: + from time import monotonic # type: ignore[attr-defined] +except ImportError: + from time import time as monotonic + +# datadog +from datadog.api.exceptions import ApiNotInitialized +from datadog.threadstats.constants import MetricType +from datadog.threadstats.events import EventsAggregator +from datadog.threadstats.metrics import MetricsAggregator, Counter, Gauge, Histogram, Timing, Distribution, Set +from datadog.threadstats.reporters import HttpReporter + +# Loggers +log = logging.getLogger("datadog.threadstats") + +DD_ENV_TAGS_MAPPING = { + "DD_ENV": "env", + "DD_SERVICE": "service", + "DD_VERSION": "version", +} + + +class ThreadStats(object): + def __init__(self, namespace="", constant_tags=None, compress_payload=False): + """ + Initialize a threadstats object. + + :param namespace: Namespace to prefix all metric names + :type namespace: string + + :param constant_tags: Tags to attach to every metric reported by this client + :type constant_tags: list of strings + + :param compress_payload: compress the payload using zlib + :type compress_payload: bool + + :envvar DATADOG_TAGS: Tags to attach to every metric reported by ThreadStats client + :type DATADOG_TAGS: comma-delimited string + + :envvar DD_ENV: the env of the service running the ThreadStats client. + If set, it is appended to the constant (global) tags of the client. + :type DD_ENV: string + + :envvar DD_SERVICE: the name of the service running the ThreadStats client. + If set, it is appended to the constant (global) tags of the client. + :type DD_SERVICE: string + + :envvar DD_VERSION: the version of the service running the ThreadStats client. + If set, it is appended to the constant (global) tags of the client. + :type DD_VERSION: string + """ + # Parameters + self.namespace = namespace + env_tags = [tag for tag in os.environ.get("DATADOG_TAGS", "").split(",") if tag] + for var, tag_name in DD_ENV_TAGS_MAPPING.items(): + value = os.environ.get(var, "") + if value: + env_tags.append("{name}:{value}".format(name=tag_name, value=value)) + if constant_tags is None: + constant_tags = [] + self.constant_tags = constant_tags + env_tags + + # State + self._disabled = True + self.compress_payload = compress_payload + + def start( + self, + flush_interval=10, + roll_up_interval=10, + device=None, + flush_in_thread=True, + flush_in_greenlet=False, + disabled=False, + ): + """ + Start the ThreadStats instance with the specified metric flushing method and preferences. + + By default, metrics will be flushed in a thread. + + >>> stats.start() + + If you're running a gevent server and want to flush metrics in a + greenlet, set *flush_in_greenlet* to True. Be sure to import and monkey + patch gevent before starting ThreadStats. :: + + >>> from gevent import monkey; monkey.patch_all() + >>> stats.start(flush_in_greenlet=True) + + If you'd like to flush metrics in process, set *flush_in_thread* + to False, though you'll have to call ``flush`` manually to post metrics + to the server. :: + + >>> stats.start(flush_in_thread=False) + + If for whatever reason, you need to disable metrics collection in a + hurry, set ``disabled`` to True and metrics won't be collected or flushed. + + >>> stats.start(disabled=True) + + *Note:* Please remember to set your API key before, + using datadog module ``initialize`` method. + + >>> from datadog import initialize, ThreadStats + >>> initialize(api_key="my_api_key") + >>> stats = ThreadStats() + >>> stats.start() + >>> stats.increment("home.page.hits") + + :param flush_interval: The number of seconds to wait between flushes. + :type flush_interval: int + :param flush_in_thread: True if you'd like to spawn a thread to flush metrics. + It will run every `flush_interval` seconds. + :type flush_in_thread: bool + :param flush_in_greenlet: Set to true if you'd like to flush in a gevent greenlet. + :type flush_in_greenlet: bool + :param disabled: Disable metrics collection + :type disabled: bool + """ + self.flush_interval = flush_interval + self.roll_up_interval = roll_up_interval + self.device = device + self._disabled = disabled + self._is_auto_flushing = False + + # Create an aggregator + self._metric_aggregator = MetricsAggregator(self.roll_up_interval) + self._event_aggregator = EventsAggregator() + + # The reporter is responsible for sending metrics off to their final destination. + # It's abstracted to support easy unit testing and in the near future, forwarding + # to the datadog agent. + self.reporter = HttpReporter(compress_payload=self.compress_payload) + + self._is_flush_in_progress = False + self.flush_count = 0 + if self._disabled: + log.info("ThreadStats instance is disabled. No metrics will flush.") + else: + if flush_in_greenlet: + self._start_flush_greenlet() + elif flush_in_thread: + self._start_flush_thread() + + # Flush all remaining metrics on exit + atexit.register(lambda: self.flush(float("inf"))) + + def stop(self): + if not self._is_auto_flushing: + return True + if self._flush_thread: + self._flush_thread.end() + self._is_auto_flushing = False + return True + + def event( + self, + title, + message, + alert_type=None, + aggregation_key=None, + source_type_name=None, + date_happened=None, + priority=None, + tags=None, + hostname=None, + ): + """ + Send an event. See http://docs.datadoghq.com/api/ for more info. + + >>> stats.event("Man down!", "This server needs assistance.") + >>> stats.event("The web server restarted", \ + "The web server is up again", alert_type="success") + """ + if not self._disabled: + # Append all client level tags to every event + event_tags = tags + if self.constant_tags: + if tags: + event_tags = tags + self.constant_tags + else: + event_tags = self.constant_tags + + self._event_aggregator.add_event( + title=title, + text=message, + alert_type=alert_type, + aggregation_key=aggregation_key, + source_type_name=source_type_name, + date_happened=date_happened, + priority=priority, + tags=event_tags, + host=hostname, + ) + + def gauge(self, metric_name, value, timestamp=None, tags=None, sample_rate=1, host=None): + """ + Record the current ``value`` of a metric. The most recent value in + a given flush interval will be recorded. Optionally, specify a set of + tags to associate with the metric. This should be used for sum values + such as total hard disk space, process uptime, total number of active + users, or number of rows in a database table. + + >>> stats.gauge("process.uptime", time.time() - process_start_time) + >>> stats.gauge("cache.bytes.free", cache.get_free_bytes(), tags=["version:1.0"]) + """ + if not self._disabled: + self._metric_aggregator.add_point( + metric_name, tags, timestamp or time(), value, Gauge, sample_rate=sample_rate, host=host + ) + + def set(self, metric_name, value, timestamp=None, tags=None, sample_rate=1, host=None): + """ + Add ``value`` to the current set. The length of the set is + flushed as a gauge to Datadog. Optionally, specify a set of + tags to associate with the metric. + + >>> stats.set("example_metric.set", "value_1", tags=["environment:dev"]) + """ + if not self._disabled: + self._metric_aggregator.add_point( + metric_name, tags, timestamp or time(), value, Set, sample_rate=sample_rate, host=host + ) + + def increment(self, metric_name, value=1, timestamp=None, tags=None, sample_rate=1, host=None): + """ + Increment the counter by the given ``value``. Optionally, specify a list of + ``tags`` to associate with the metric. This is useful for counting things + such as incrementing a counter each time a page is requested. + + >>> stats.increment('home.page.hits') + >>> stats.increment('bytes.processed', file.size()) + """ + if not self._disabled: + self._metric_aggregator.add_point( + metric_name, tags, timestamp or time(), value, Counter, sample_rate=sample_rate, host=host + ) + + def decrement(self, metric_name, value=1, timestamp=None, tags=None, sample_rate=1, host=None): + """ + Decrement a counter, optionally setting a value, tags and a sample + rate. + + >>> stats.decrement("files.remaining") + >>> stats.decrement("active.connections", 2) + """ + if not self._disabled: + self._metric_aggregator.add_point( + metric_name, tags, timestamp or time(), -value, Counter, sample_rate=sample_rate, host=host + ) + + def histogram(self, metric_name, value, timestamp=None, tags=None, sample_rate=1, host=None): + """ + Sample a histogram value. Histograms will produce metrics that + describe the distribution of the recorded values, namely the maximum, minimum, + average, count and the 75/85/95/99 percentiles. Optionally, specify + a list of ``tags`` to associate with the metric. + + >>> stats.histogram("uploaded_file.size", uploaded_file.size()) + """ + if not self._disabled: + self._metric_aggregator.add_point( + metric_name, tags, timestamp or time(), value, Histogram, sample_rate=sample_rate, host=host + ) + + def distribution(self, metric_name, value, timestamp=None, tags=None, sample_rate=1, host=None): + """ + Sample a distribution value. Distributions will produce metrics that + describe the distribution of the recorded values, namely the maximum, + median, average, count and the 50/75/90/95/99 percentiles. Optionally, + specify a list of ``tags`` to associate with the metric. + + >>> stats.distribution("uploaded_file.size", uploaded_file.size()) + """ + if not self._disabled: + self._metric_aggregator.add_point( + metric_name, tags, timestamp or time(), value, Distribution, sample_rate=sample_rate, host=host + ) + + def timing(self, metric_name, value, timestamp=None, tags=None, sample_rate=1, host=None): + """ + Record a timing, optionally setting tags and a sample rate. + + >>> stats.timing("query.response.time", 1234) + """ + if not self._disabled: + self._metric_aggregator.add_point( + metric_name, tags, timestamp or time(), value, Timing, sample_rate=sample_rate, host=host + ) + + @contextmanager + def timer(self, metric_name, sample_rate=1, tags=None, host=None): + """ + A context manager that will track the distribution of the contained code's run time. + Optionally specify a list of tags to associate with the metric. + :: + + def get_user(user_id): + with stats.timer("user.query.time"): + # Do what you need to ... + pass + + # Is equivalent to ... + def get_user(user_id): + start = time.time() + try: + # Do what you need to ... + pass + finally: + stats.histogram("user.query.time", time.time() - start) + """ + start = monotonic() + try: + yield + finally: + end = monotonic() + self.timing(metric_name, end - start, time(), tags=tags, sample_rate=sample_rate, host=host) + + def timed(self, metric_name, sample_rate=1, tags=None, host=None): + """ + A decorator that will track the distribution of a function's run time. + Optionally specify a list of tags to associate with the metric. + :: + + @stats.timed("user.query.time") + def get_user(user_id): + # Do what you need to ... + pass + + # Is equivalent to ... + start = time.time() + try: + get_user(user_id) + finally: + stats.histogram("user.query.time", time.time() - start) + """ + + def wrapper(func): + @wraps(func) + def wrapped(*args, **kwargs): + with self.timer(metric_name, sample_rate, tags, host): + result = func(*args, **kwargs) + return result + + return wrapped + + return wrapper + + def flush(self, timestamp=None): + """ + Flush and post all metrics to the server. Note that this is a blocking + call, so it is likely not suitable for user facing processes. In those + cases, it's probably best to flush in a thread or greenlet. + """ + try: + if self._is_flush_in_progress: + log.debug("A flush is already in progress. Skipping this one.") + return False + if self._disabled: + log.info("Not flushing because we're disabled.") + return False + + self._is_flush_in_progress = True + + # Process metrics + metrics, dists = self._get_aggregate_metrics_and_dists(timestamp or time()) + count_metrics = len(metrics) + if count_metrics: + self.flush_count += 1 + log.debug("Flush #%s sending %s metrics" % (self.flush_count, count_metrics)) + self.reporter.flush_metrics(metrics) + else: + log.debug("No metrics to flush. Continuing.") + + count_dists = len(dists) + if count_dists: + self.flush_count += 1 + log.debug("Flush #%s sending %s distributions" % (self.flush_count, count_dists)) + self.reporter.flush_distributions(dists) + else: + log.debug("No distributions to flush. Continuing.") + + # Process events + events = self._get_aggregate_events() + count_events = len(events) + if count_events: + self.flush_count += 1 + log.debug("Flush #%s sending %s events" % (self.flush_count, count_events)) + self.reporter.flush_events(events) + else: + log.debug("No events to flush. Continuing.") + except ApiNotInitialized: + raise + except Exception: + try: + log.exception("Error flushing metrics and events") + except Exception: + pass + finally: + self._is_flush_in_progress = False + + def _get_aggregate_metrics_and_dists(self, flush_time=None): + """ + Get, format and return the rolled up metrics from the aggregator. + """ + # Get rolled up metrics + rolled_up_metrics = self._metric_aggregator.flush(flush_time) + + # FIXME: emit a dictionary from the aggregator + metrics = [] + dists = [] + for timestamp, value, name, tags, host, metric_type, interval in rolled_up_metrics: + metric_tags = tags + metric_name = name + + # Append all client level tags to every metric + if self.constant_tags: + if tags: + metric_tags = tags + self.constant_tags + else: + metric_tags = self.constant_tags + + # Resolve the metric name + if self.namespace: + metric_name = self.namespace + "." + name + + metric = { + "metric": metric_name, + "points": [[timestamp, value]], + "type": metric_type, + "host": host, + "device": self.device, + "tags": metric_tags, + "interval": interval, + } + if metric_type == MetricType.Distribution: + dists.append(metric) + else: + metrics.append(metric) + return (metrics, dists) + + def _get_aggregate_events(self): + # Get events + events = self._event_aggregator.flush() + return events + + def _start_flush_thread(self): + """ Start a thread to flush metrics. """ + from datadog.threadstats.periodic_timer import PeriodicTimer + + if self._is_auto_flushing: + log.info("Autoflushing already started.") + return + self._is_auto_flushing = True + + # A small helper for logging and flushing. + def flush(): + try: + log.debug("Flushing metrics in thread") + self.flush() + except Exception: + try: + log.exception("Error flushing in thread") + except Exception: + pass + + log.info("Starting flush thread with interval %s." % self.flush_interval) + self._flush_thread = PeriodicTimer(self.flush_interval, flush) + self._flush_thread.start() + + def _start_flush_greenlet(self): + if self._is_auto_flushing: + log.info("Autoflushing already started.") + return + self._is_auto_flushing = True + + import gevent + + # A small helper for flushing. + def flush(): + while True: + try: + log.debug("Flushing metrics in greenlet") + self.flush() + gevent.sleep(self.flush_interval) + except Exception: + try: + log.exception("Error flushing in greenlet") + except Exception: + pass + + log.info("Starting flush greenlet with interval %s." % self.flush_interval) + gevent.spawn(flush) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/constants.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/constants.py new file mode 100644 index 0000000..63b565d --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/constants.py @@ -0,0 +1,18 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc + + +class MetricType(object): + Gauge = "gauge" + Counter = "counter" + Histogram = "histogram" + Rate = "rate" + Distribution = "distribution" + + +class MonitorType(object): + SERVICE_CHECK = "service check" + METRIC_ALERT = "metric alert" + QUERY_ALERT = "query alert" + ALL = (SERVICE_CHECK, METRIC_ALERT, QUERY_ALERT) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/events.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/events.py new file mode 100644 index 0000000..a85c798 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/events.py @@ -0,0 +1,27 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +""" +Event aggregator class. +""" + +from datadog.util.compat import iteritems + + +class EventsAggregator(object): + """ + A simple event aggregator + """ + + def __init__(self): + self._events = [] + + def add_event(self, **event): + # Clean empty values + event = dict((k, v) for k, v in iteritems(event) if v is not None) + self._events.append(event) + + def flush(self): + events = self._events + self._events = [] + return events diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/metrics.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/metrics.py new file mode 100644 index 0000000..aa9fef5 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/metrics.py @@ -0,0 +1,203 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +""" +Metric roll-up classes. +""" +from collections import defaultdict +import random +import itertools +import threading + +from datadog.util.compat import iternext +from datadog.threadstats.constants import MetricType + + +class Metric(object): + """ + A base metric class that accepts points, slices them into time intervals + and performs roll-ups within those intervals. + """ + + def add_point(self, value): + """ Add a point to the given metric. """ + raise NotImplementedError() + + def flush(self, timestamp, interval): + """ Flush all metrics up to the given timestamp. """ + raise NotImplementedError() + + +class Set(Metric): + """ A set metric. """ + + stats_tag = "g" + + def __init__(self, name, tags, host): + self.name = name + self.tags = tags + self.host = host + self.set = set() + + def add_point(self, value): + self.set.add(value) + + def flush(self, timestamp, interval): + return [(timestamp, len(self.set), self.name, self.tags, self.host, MetricType.Gauge, interval)] + + +class Gauge(Metric): + """ A gauge metric. """ + + stats_tag = "g" + + def __init__(self, name, tags, host): + self.name = name + self.tags = tags + self.host = host + self.value = None + + def add_point(self, value): + self.value = value + + def flush(self, timestamp, interval): + return [(timestamp, self.value, self.name, self.tags, self.host, MetricType.Gauge, interval)] + + +class Counter(Metric): + """ A metric that tracks a counter value. """ + + stats_tag = "c" + + def __init__(self, name, tags, host): + self.name = name + self.tags = tags + self.host = host + self.count = [] + + def add_point(self, value): + self.count.append(value) + + def flush(self, timestamp, interval): + count = sum(self.count, 0) + return [(timestamp, count / float(interval), self.name, self.tags, self.host, MetricType.Rate, interval)] + + +class Distribution(Metric): + """ A distribution metric. """ + + stats_tag = "d" + + def __init__(self, name, tags, host): + self.name = name + self.tags = tags + self.host = host + self.value = [] + + def add_point(self, value): + self.value.append(value) + + def flush(self, timestamp, interval): + return [(timestamp, self.value, self.name, self.tags, self.host, MetricType.Distribution, interval)] + + +class Histogram(Metric): + """ A histogram metric. """ + + stats_tag = "h" + + def __init__(self, name, tags, host): + self.name = name + self.tags = tags + self.host = host + self.max = float("-inf") + self.min = float("inf") + self.sum = [] + self.iter_counter = itertools.count() + self.count = iternext(self.iter_counter) + self.sample_size = 1000 + self.samples = [] + self.percentiles = [0.75, 0.85, 0.95, 0.99] + + def add_point(self, value): + self.max = self.max if self.max > value else value + self.min = self.min if self.min < value else value + self.sum.append(value) + if self.count < self.sample_size: + self.samples.append(value) + else: + self.samples[random.randrange(0, self.sample_size)] = value + self.count = iternext(self.iter_counter) + + def flush(self, timestamp, interval): + if not self.count: + return [] + metrics = [ + (timestamp, self.min, "%s.min" % self.name, self.tags, self.host, MetricType.Gauge, interval), + (timestamp, self.max, "%s.max" % self.name, self.tags, self.host, MetricType.Gauge, interval), + ( + timestamp, + self.count / float(interval), + "%s.count" % self.name, + self.tags, + self.host, + MetricType.Rate, + interval, + ), + (timestamp, self.average(), "%s.avg" % self.name, self.tags, self.host, MetricType.Gauge, interval), + ] + length = len(self.samples) + self.samples.sort() + for p in self.percentiles: + val = self.samples[int(round(p * length - 1))] + name = "%s.%spercentile" % (self.name, int(p * 100)) + metrics.append((timestamp, val, name, self.tags, self.host, MetricType.Gauge, interval)) + return metrics + + def average(self): + sum_metrics = sum(self.sum, 0) + return float(sum_metrics) / self.count + + +class Timing(Histogram): + """ + A timing metric. + Inherit from Histogram to workaround and support it in API mode + """ + + stats_tag = "ms" + + +class MetricsAggregator(object): + """ + A small class to handle the roll-ups of multiple metrics at once. + """ + + def __init__(self, roll_up_interval=10): + self._lock = threading.RLock() + self._metrics = defaultdict(lambda: {}) + self._roll_up_interval = roll_up_interval + + def add_point(self, metric, tags, timestamp, value, metric_class, sample_rate=1, host=None): + # The sample rate is currently ignored for in process stuff + interval = timestamp - timestamp % self._roll_up_interval + key = (metric, host, tuple(sorted(tags)) if tags else None) + with self._lock: + if key not in self._metrics[interval]: + self._metrics[interval][key] = metric_class(metric, tags, host) + self._metrics[interval][key].add_point(value) + + def flush(self, timestamp): + """ Flush all metrics up to the given timestamp. """ + if timestamp == float("inf"): + interval = float("inf") + else: + interval = timestamp - timestamp % self._roll_up_interval + + with self._lock: + past_intervals = [i for i in self._metrics.keys() if i < interval] + metrics = [] + for i in past_intervals: + for m in list(self._metrics.pop(i).values()): + metrics += m.flush(i, self._roll_up_interval) + return metrics diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/periodic_timer.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/periodic_timer.py new file mode 100644 index 0000000..ff4b583 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/periodic_timer.py @@ -0,0 +1,36 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +""" +A small class to run a task periodically in a thread. +""" + + +from threading import Thread, Event +import sys + + +class PeriodicTimer(Thread): + def __init__(self, interval, function, *args, **kwargs): + Thread.__init__(self) + self.daemon = True + assert interval > 0 + self.interval = interval + assert function + self.function = function + self.args = args + self.kwargs = kwargs + self.finished = Event() + + def end(self): + self.finished.set() + + def run(self): + while not self.finished.wait(self.interval): + try: + self.function(*self.args, **self.kwargs) + except Exception: + # If `sys` is None, it means the interpreter is shutting down + # and it's very likely the reason why we got an exception. + if sys is not None: + raise diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/reporters.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/reporters.py new file mode 100644 index 0000000..1324794 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/threadstats/reporters.py @@ -0,0 +1,34 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +""" +Reporter classes. +""" + + +from datadog import api + + +class Reporter(object): + def flush(self, metrics): + raise NotImplementedError() + + +class HttpReporter(Reporter): + def __init__(self, compress_payload=False): + self.compress_payload = compress_payload + + def flush_distributions(self, distributions): + api.Distribution.send(distributions, compress_payload=self.compress_payload) + + def flush_metrics(self, metrics): + api.Metric.send(metrics, compress_payload=self.compress_payload) + + def flush_events(self, events): + for event in events: + api.Event.create(**event) + + +class GraphiteReporter(Reporter): + def flush(self, metrics): + pass diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/util/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/util/__init__.py new file mode 100644 index 0000000..b3017a1 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/util/__init__.py @@ -0,0 +1,3 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/util/cli.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/util/cli.py new file mode 100644 index 0000000..f309980 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/util/cli.py @@ -0,0 +1,152 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +from datetime import datetime, timedelta +from argparse import ArgumentTypeError +import json +import re +from datadog.util.format import force_to_epoch_seconds +import time + + +def comma_list(list_str, item_func=None): + if not list_str: + raise ArgumentTypeError("Invalid comma list") + item_func = item_func or (lambda i: i) + return [item_func(i.strip()) for i in list_str.split(",") if i.strip()] + + +def comma_set(list_str, item_func=None): + return set(comma_list(list_str, item_func=item_func)) + + +def comma_list_or_empty(list_str): + if not list_str: + return [] + else: + return comma_list(list_str) + + +def list_of_ints(int_csv): + if not int_csv: + raise ArgumentTypeError("Invalid list of ints") + try: + # Try as a [1, 2, 3] list + j = json.loads(int_csv) + if isinstance(j, (list, set)): + j = [int(i) for i in j] + return j + except Exception: + pass + + try: + return [int(i.strip()) for i in int_csv.strip().split(",")] + except Exception: + raise ArgumentTypeError("Invalid list of ints: {0}".format(int_csv)) + + +def list_of_ints_and_strs(csv): + def int_or_str(item): + try: + return int(item) + except ValueError: + return item + + return comma_list(csv, int_or_str) + + +def set_of_ints(int_csv): + return set(list_of_ints(int_csv)) + + +class DateParsingError(Exception): + """Thrown if parse_date exhausts all possible parsings of a string""" + + +_date_fieldre = re.compile(r"(\d+)\s?(\w+) (ago|ahead)") + + +def _midnight(): + """ Truncate a date to midnight. Default to UTC midnight today.""" + return datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) + + +def parse_date_as_epoch_timestamp(date_str): + return parse_date(date_str, to_epoch_ts=True) + + +def _parse_date_noop_formatter(d): + """ NOOP - only here for pylint """ + return d + + +def parse_date(date_str, to_epoch_ts=False): + formatter = _parse_date_noop_formatter + if to_epoch_ts: + formatter = force_to_epoch_seconds + + if isinstance(date_str, datetime): + return formatter(date_str) + elif isinstance(date_str, time.struct_time): + return formatter(datetime.fromtimestamp(time.mktime(date_str))) + + # Parse relative dates. + if date_str == "today": + return formatter(_midnight()) + elif date_str == "yesterday": + return formatter(_midnight() - timedelta(days=1)) + elif date_str == "tomorrow": + return formatter(_midnight() + timedelta(days=1)) + elif date_str.endswith(("ago", "ahead")): + m = _date_fieldre.match(date_str) + if m: + fields = m.groups() + else: + fields = date_str.split(" ")[1:] + num = int(fields[0]) + short_unit = fields[1] + time_direction = {"ago": -1, "ahead": 1}[fields[2]] + assert short_unit, short_unit + units = ["weeks", "days", "hours", "minutes", "seconds"] + # translate 'h' -> 'hours' + short_units = dict([(u[:1], u) for u in units]) + unit = short_units.get(short_unit, short_unit) + # translate 'hour' -> 'hours' + if unit[-1] != "s": + unit += "s" # tolerate 1 hour + assert unit in units, "'%s' not in %s" % (unit, units) + return formatter(datetime.utcnow() + time_direction * timedelta(**{unit: num})) + elif date_str == "now": + return formatter(datetime.utcnow()) + + def _from_epoch_timestamp(seconds): + print("_from_epoch_timestamp({})".format(seconds)) + return datetime.utcfromtimestamp(float(seconds)) + + def _from_epoch_ms_timestamp(millis): + print("_from_epoch_ms_timestamp({})".format(millis)) + in_sec = float(millis) / 1000.0 + print("_from_epoch_ms_timestamp({}) -> {}".format(millis, in_sec)) + return _from_epoch_timestamp(in_sec) + + # Or parse date formats (most specific to least specific) + parse_funcs = [ + lambda d: datetime.strptime(d, "%Y-%m-%d %H:%M:%S.%f"), + lambda d: datetime.strptime(d, "%Y-%m-%d %H:%M:%S"), + lambda d: datetime.strptime(d, "%Y-%m-%dT%H:%M:%S.%f"), + lambda d: datetime.strptime(d, "%Y-%m-%dT%H:%M:%S"), + lambda d: datetime.strptime(d, "%Y-%m-%d %H:%M"), + lambda d: datetime.strptime(d, "%Y-%m-%d-%H"), + lambda d: datetime.strptime(d, "%Y-%m-%d"), + lambda d: datetime.strptime(d, "%Y-%m"), + lambda d: datetime.strptime(d, "%Y"), + _from_epoch_timestamp, # an epoch in seconds + _from_epoch_ms_timestamp, # an epoch in milliseconds + ] + + for parse_func in parse_funcs: + try: + return formatter(parse_func(date_str)) + except Exception: + pass + raise DateParsingError(u"Could not parse {0} as date".format(date_str)) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/util/compat.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/util/compat.py new file mode 100644 index 0000000..58927d1 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/util/compat.py @@ -0,0 +1,135 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +# flake8: noqa +""" +Imports for compatibility with Python 2, Python 3 and Google App Engine. +""" +from functools import wraps +import logging +import socket +import sys + +# Logging +log = logging.getLogger("datadog.util") + +# Note: using `sys.version_info` instead of the helper functions defined here +# so that mypy detects version-specific code paths. Currently, mypy doesn't +# support try/except imports for version-specific code paths either. +# +# https://mypy.readthedocs.io/en/stable/common_issues.html#python-version-and-system-platform-checks + +# Python 3.x +if sys.version_info[0] >= 3: + import builtins + from collections import UserDict as IterableUserDict + import configparser + from configparser import ConfigParser + from io import StringIO + from urllib.parse import urljoin, urlparse + import urllib.request as url_lib, urllib.error, urllib.parse + + imap = map + get_input = input + text = str + + def iteritems(d): + return iter(d.items()) + + def iternext(iter): + return next(iter) + + +# Python 2.x +else: + import __builtin__ as builtins + import ConfigParser as configparser + from configparser import ConfigParser + from cStringIO import StringIO + from itertools import imap + import urllib2 as url_lib + from urlparse import urljoin, urlparse + from UserDict import IterableUserDict + + get_input = raw_input + text = unicode + + def iteritems(d): + return d.iteritems() + + def iternext(iter): + return iter.next() + + +# Python >= 3.5 +if sys.version_info >= (3, 5): + from asyncio import iscoroutinefunction +# Others +else: + + def iscoroutinefunction(*args, **kwargs): + return False + + +# Python >= 2.7 +if sys.version_info >= (2, 7): + from logging import NullHandler +# Python 2.6.x +else: + from logging import Handler + + class NullHandler(Handler): + def emit(self, record): + pass + + +def _is_py_version_higher_than(major, minor=0): + """ + Assert that the Python version is higher than `$maj.$min`. + """ + return sys.version_info >= (major, minor) + + +def is_p3k(): + """ + Assert that Python is version 3 or higher. + """ + return _is_py_version_higher_than(3) + + +def is_higher_py32(): + """ + Assert that Python is version 3.2 or higher. + """ + return _is_py_version_higher_than(3, 2) + + +def is_higher_py35(): + """ + Assert that Python is version 3.5 or higher. + """ + return _is_py_version_higher_than(3, 5) + + +def is_pypy(): + """ + Assert that PyPy is being used (regardless of 2 or 3) + """ + return "__pypy__" in sys.builtin_module_names + + +def conditional_lru_cache(func): + """ + A decorator that conditionally enables a lru_cache of size 512 if + the version of Python can support it (>3.2) and otherwise returns + the original function + """ + if not is_higher_py32(): + return func + + log.debug("Enabling LRU cache for function %s", func.__name__) + + # pylint: disable=import-outside-toplevel + from functools import lru_cache + + return lru_cache(maxsize=512)(func) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/util/config.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/util/config.py new file mode 100644 index 0000000..cd186bc --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/util/config.py @@ -0,0 +1,148 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +import os +import string +import sys + +# datadog +from datadog.util.compat import configparser, StringIO, is_p3k +from datadog.version import __version__ + +# CONSTANTS +DATADOG_CONF = "datadog.conf" + + +class CfgNotFound(Exception): + pass + + +class PathNotFound(Exception): + pass + + +def get_os(): + "Human-friendly OS name" + if sys.platform == "darwin": + return "mac" + elif sys.platform.find("freebsd") != -1: + return "freebsd" + elif sys.platform.find("linux") != -1: + return "linux" + elif sys.platform.find("win32") != -1: + return "windows" + elif sys.platform.find("sunos") != -1: + return "solaris" + else: + return sys.platform + + +def skip_leading_wsp(f): + "Works on a file, returns a file-like object" + if is_p3k(): + return StringIO("\n".join(x.strip(" ") for x in f.readlines())) + else: + return StringIO("\n".join(map(string.strip, f.readlines()))) + + +def _windows_commondata_path(): + """Return the common appdata path, using ctypes + From http://stackoverflow.com/questions/626796/\ + how-do-i-find-the-windows-common-application-data-folder-using-python + """ + import ctypes + from ctypes import wintypes, windll + + CSIDL_COMMON_APPDATA = 35 + + _SHGetFolderPath = windll.shell32.SHGetFolderPathW + _SHGetFolderPath.argtypes = [wintypes.HWND, ctypes.c_int, wintypes.HANDLE, wintypes.DWORD, wintypes.LPCWSTR] + + path_buf = ctypes.create_unicode_buffer(wintypes.MAX_PATH) + _SHGetFolderPath(0, CSIDL_COMMON_APPDATA, 0, 0, path_buf) + return path_buf.value + + +def _windows_config_path(): + common_data = _windows_commondata_path() + path = os.path.join(common_data, "Datadog", DATADOG_CONF) + if os.path.exists(path): + return path + raise PathNotFound(path) + + +def _unix_config_path(): + path = os.path.join("/etc/dd-agent", DATADOG_CONF) + if os.path.exists(path): + return path + raise PathNotFound(path) + + +def _mac_config_path(): + path = os.path.join("~/.datadog-agent/agent", DATADOG_CONF) + path = os.path.expanduser(path) + if os.path.exists(path): + return path + raise PathNotFound(path) + + +def get_config_path(cfg_path=None, os_name=None): + # Check if there's an override and if it exists + if cfg_path is not None and os.path.exists(cfg_path): + return cfg_path + + if os_name is None: + os_name = get_os() + + # Check for an OS-specific path, continue on not-found exceptions + if os_name == "windows": + return _windows_config_path() + elif os_name == "mac": + return _mac_config_path() + else: + return _unix_config_path() + + +def get_config(cfg_path=None, options=None): + agentConfig = {} + + # Config handling + try: + # Find the right config file + path = os.path.realpath(__file__) + path = os.path.dirname(path) + + config_path = get_config_path(cfg_path, os_name=get_os()) + config = configparser.ConfigParser() + with open(config_path) as config_file: + if is_p3k(): + config.read_file(skip_leading_wsp(config_file)) + else: + config.readfp(skip_leading_wsp(config_file)) + + # bulk import + for option in config.options("Main"): + agentConfig[option] = config.get("Main", option) + + except Exception: + raise CfgNotFound + + return agentConfig + + +def get_pkg_version(): + """ + Resolve `datadog` package version. + + Deprecated: use `datadog.__version__` directly instead + """ + return __version__ + + +def get_version(): + """ + Resolve `datadog` package version. + + Deprecated: use `datadog.__version__` directly instead + """ + return __version__ diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/util/deprecation.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/util/deprecation.py new file mode 100644 index 0000000..57673ef --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/util/deprecation.py @@ -0,0 +1,24 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc + +import warnings +from functools import wraps + + +def deprecated(message): + def deprecated_decorator(func): + @wraps(func) + def deprecated_func(*args, **kwargs): + warnings.warn( + "'{0}' is a deprecated function. {1}".format(func.__name__, message), + category=DeprecationWarning, + stacklevel=2, + ) + warnings.simplefilter('default', DeprecationWarning) + + return func(*args, **kwargs) + + return deprecated_func + + return deprecated_decorator diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/util/format.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/util/format.py new file mode 100644 index 0000000..f6b1e96 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/util/format.py @@ -0,0 +1,42 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +# stdlib +import calendar +import datetime +import json +import re + +from datadog.util.compat import conditional_lru_cache + +TAG_INVALID_CHARS_RE = re.compile(r"[^\w\d_\-:/\.]", re.UNICODE) +TAG_INVALID_CHARS_SUBS = "_" + + +def pretty_json(obj): + return json.dumps(obj, sort_keys=True, indent=2) + + +def construct_url(host, api_version, path): + return "{}/api/{}/{}".format(host.strip("/"), api_version.strip("/"), path.strip("/")) + + +def construct_path(api_version, path): + return "{}/{}".format(api_version.strip("/"), path.strip("/")) + + +def force_to_epoch_seconds(epoch_sec_or_dt): + if isinstance(epoch_sec_or_dt, datetime.datetime): + return calendar.timegm(epoch_sec_or_dt.timetuple()) + return epoch_sec_or_dt + + +@conditional_lru_cache +def _normalize_tags_with_cache(tag_list): + return [TAG_INVALID_CHARS_RE.sub(TAG_INVALID_CHARS_SUBS, tag) for tag in tag_list] + + +def normalize_tags(tag_list): + # We have to turn our input tag list into a non-mutable tuple for it to + # be hashable (and thus usable) by the @lru_cache decorator. + return _normalize_tags_with_cache(tuple(tag_list)) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/util/hostname.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/util/hostname.py new file mode 100644 index 0000000..6a1f857 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/util/hostname.py @@ -0,0 +1,305 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the BSD-3-Clause License. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2015-Present Datadog, Inc +# stdlib +import json +import logging +import re +import socket +import subprocess +import types +from typing import Dict, Optional + +# datadog +from datadog.util.compat import url_lib, is_p3k, iteritems +from datadog.util.config import get_config, get_os, CfgNotFound + +VALID_HOSTNAME_RFC_1123_PATTERN = re.compile( + r"^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$" +) # noqa +MAX_HOSTNAME_LEN = 255 + +log = logging.getLogger("datadog.api") + + +def is_valid_hostname(hostname): + if hostname.lower() in set( + [ + "localhost", + "localhost.localdomain", + "localhost6.localdomain6", + "ip6-localhost", + ] + ): + log.warning("Hostname: %s is local" % hostname) + return False + if len(hostname) > MAX_HOSTNAME_LEN: + log.warning("Hostname: %s is too long (max length is %s characters)" % (hostname, MAX_HOSTNAME_LEN)) + return False + if VALID_HOSTNAME_RFC_1123_PATTERN.match(hostname) is None: + log.warning("Hostname: %s is not complying with RFC 1123" % hostname) + return False + return True + + +def get_hostname(hostname_from_config): + # type: (bool) -> Optional[str] + """ + Get the canonical host name this agent should identify as. This is + the authoritative source of the host name for the agent. + + Tries, in order: + + * agent config (datadog.conf, "hostname:") + * 'hostname -f' (on unix) + * socket.gethostname() + """ + + hostname = None + config = None + + # first, try the config if hostname_from_config is set to True + try: + if hostname_from_config: + config = get_config() + config_hostname = config.get("hostname") + if config_hostname and is_valid_hostname(config_hostname): + log.warning( + "Hostname lookup from agent configuration will be deprecated " + "in an upcoming version of datadogpy. Set hostname_from_config to False " + "to get rid of this warning" + ) + return config_hostname + except CfgNotFound: + log.info("No agent or invalid configuration file found") + + # Try to get GCE instance name + if hostname is None: + gce_hostname = GCE.get_hostname(config) + if gce_hostname is not None: + if is_valid_hostname(gce_hostname): + return gce_hostname + # then move on to os-specific detection + if hostname is None: + + def _get_hostname_unix(): + try: + # try fqdn + p = subprocess.Popen(["/bin/hostname", "-f"], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) + out, err = p.communicate() + if p.returncode == 0: + if is_p3k(): + return out.decode("utf-8").strip() + else: + return out.strip() + except Exception: + return None + + os_name = get_os() + if os_name in ["mac", "freebsd", "linux", "solaris"]: + unix_hostname = _get_hostname_unix() + if unix_hostname and is_valid_hostname(unix_hostname): + hostname = unix_hostname + + # if we have an ec2 default hostname, see if there's an instance-id available + if hostname is not None and True in [hostname.lower().startswith(p) for p in [u"ip-", u"domu"]]: + instanceid = EC2.get_instance_id(config) + if instanceid: + hostname = instanceid + + # fall back on socket.gethostname(), socket.getfqdn() is too unreliable + if hostname is None: + try: + socket_hostname = socket.gethostname() # type: Optional[str] + except socket.error: + socket_hostname = None + if socket_hostname and is_valid_hostname(socket_hostname): + hostname = socket_hostname + + if hostname is None: + log.warning( + u"Unable to reliably determine host name. You can define one in your `hosts` file, " + u"or in `datadog.conf` file if you have Datadog Agent installed." + ) + + return hostname + + +def get_ec2_instance_id(): + try: + # Remember the previous default timeout + old_timeout = socket.getdefaulttimeout() + + # Try to query the EC2 internal metadata service, but fail fast + socket.setdefaulttimeout(0.25) + + try: + return url_lib.urlopen(url_lib.Request("http://169.254.169.254/latest/" "meta-data/instance-id")).read() + finally: + # Reset the previous default timeout + socket.setdefaulttimeout(old_timeout) + except Exception: + return socket.gethostname() + + +class GCE(object): + URL = "http://169.254.169.254/computeMetadata/v1/?recursive=true" + TIMEOUT = 0.1 # second + SOURCE_TYPE_NAME = "google cloud platform" + metadata = None + + @staticmethod + def _get_metadata(agentConfig): + if GCE.metadata is not None: + return GCE.metadata + + if not agentConfig["collect_instance_metadata"]: + log.info("Instance metadata collection is disabled. Not collecting it.") + GCE.metadata = {} + return GCE.metadata + + socket_to = None + try: + socket_to = socket.getdefaulttimeout() + socket.setdefaulttimeout(GCE.TIMEOUT) + except Exception: + pass + + try: + opener = url_lib.build_opener() + opener.addheaders = [("X-Google-Metadata-Request", "True")] + GCE.metadata = json.loads(opener.open(GCE.URL).read().strip()) + + except Exception: + GCE.metadata = {} + + try: + if socket_to is None: + socket_to = 3 + socket.setdefaulttimeout(socket_to) + except Exception: + pass + return GCE.metadata + + @staticmethod + def get_hostname(agentConfig): + try: + host_metadata = GCE._get_metadata(agentConfig) + return host_metadata["instance"]["hostname"].split(".")[0] + except Exception: + return None + + +class EC2(object): + """Retrieve EC2 metadata""" + + URL = "http://169.254.169.254/latest/meta-data" + TIMEOUT = 0.1 # second + metadata = {} # type: Dict[str, str] + + @staticmethod + def get_tags(agentConfig): + if not agentConfig["collect_instance_metadata"]: + log.info("Instance metadata collection is disabled. Not collecting it.") + return [] + + socket_to = None + try: + socket_to = socket.getdefaulttimeout() + socket.setdefaulttimeout(EC2.TIMEOUT) + except Exception: + pass + + try: + iam_role = url_lib.urlopen(EC2.URL + "/iam/security-credentials").read().strip() + iam_params = json.loads( + url_lib.urlopen(EC2.URL + "/iam/security-credentials" + "/" + str(iam_role)).read().strip() + ) + from boto.ec2.connection import EC2Connection + + connection = EC2Connection( + aws_access_key_id=iam_params["AccessKeyId"], + aws_secret_access_key=iam_params["SecretAccessKey"], + security_token=iam_params["Token"], + ) + instance_object = connection.get_only_instances([EC2.metadata["instance-id"]])[0] + + EC2_tags = [u"%s:%s" % (tag_key, tag_value) for tag_key, tag_value in iteritems(instance_object.tags)] + + except Exception: + log.exception("Problem retrieving custom EC2 tags") + EC2_tags = [] + + try: + if socket_to is None: + socket_to = 3 + socket.setdefaulttimeout(socket_to) + except Exception: + pass + + return EC2_tags + + @staticmethod + def get_metadata(agentConfig): + """Use the ec2 http service to introspect the instance. This adds latency \ + if not running on EC2 + """ + # >>> import urllib2 + # >>> urllib2.urlopen('http://169.254.169.254/latest/', timeout=1).read() + # 'meta-data\nuser-data' + # >>> urllib2.urlopen('http://169.254.169.254/latest/meta-data', timeout=1).read() + # 'ami-id\nami-launch-index\nami-manifest-path\nhostname\ninstance-id\nlocal-ipv4\ + # npublic-keys/\nreservation-id\nsecurity-groups' + # >>> urllib2.urlopen('http://169.254.169.254/latest/meta-data/instance-id', + # timeout=1).read() + # 'i-deadbeef' + + # Every call may add TIMEOUT seconds in latency so don't abuse this call + # python 2.4 does not support an explicit timeout argument so force it here + # Rather than monkey-patching urllib2, just lower the timeout globally for these calls + + if not agentConfig["collect_instance_metadata"]: + log.info("Instance metadata collection is disabled. Not collecting it.") + return {} + + socket_to = None + try: + socket_to = socket.getdefaulttimeout() + socket.setdefaulttimeout(EC2.TIMEOUT) + except Exception: + pass + + for k in ( + "instance-id", + "hostname", + "local-hostname", + "public-hostname", + "ami-id", + "local-ipv4", + "public-keys", + "public-ipv4", + "reservation-id", + "security-groups", + ): + try: + v = url_lib.urlopen(EC2.URL + "/" + str(k)).read().strip() + assert type(v) in (types.StringType, types.UnicodeType) and len(v) > 0, "%s is not a string" % v + EC2.metadata[k] = v + except Exception: + pass + + try: + if socket_to is None: + socket_to = 3 + socket.setdefaulttimeout(socket_to) + except Exception: + pass + + return EC2.metadata + + @staticmethod + def get_instance_id(agentConfig): + try: + return EC2.get_metadata(agentConfig).get("instance-id", None) + except Exception: + return None diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog/version.py b/lambdas/aws-dd-forwarder-3.127.0/datadog/version.py new file mode 100644 index 0000000..3158ac8 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog/version.py @@ -0,0 +1 @@ +__version__ = "0.48.0" diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda-5.87.0.dist-info/INSTALLER b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda-5.87.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda-5.87.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda-5.87.0.dist-info/LICENSE b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda-5.87.0.dist-info/LICENSE new file mode 100644 index 0000000..8263325 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda-5.87.0.dist-info/LICENSE @@ -0,0 +1,203 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018 Datadog, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +SPDX-License-Identifier: Apache-2.0 \ No newline at end of file diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda-5.87.0.dist-info/METADATA b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda-5.87.0.dist-info/METADATA new file mode 100644 index 0000000..b1eba7e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda-5.87.0.dist-info/METADATA @@ -0,0 +1,106 @@ +Metadata-Version: 2.1 +Name: datadog-lambda +Version: 5.87.0 +Summary: The Datadog AWS Lambda Library +Home-page: https://github.com/DataDog/datadog-lambda-python +License: Apache-2.0 +Keywords: datadog,aws,lambda,layer +Author: Datadog, Inc. +Author-email: dev@datadoghq.com +Requires-Python: >=3.8.0,<4 +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Provides-Extra: dev +Requires-Dist: boto3 (>=1.28.0,<2.0.0) ; extra == "dev" +Requires-Dist: datadog (>=0.41.0,<1.0.0) +Requires-Dist: ddtrace (>=2.3.1) +Requires-Dist: flake8 (>=5.0.4,<6.0.0) ; extra == "dev" +Requires-Dist: importlib_metadata ; python_version < "3.8" +Requires-Dist: nose2 (>=0.9.1,<0.10.0) ; extra == "dev" +Requires-Dist: requests (>=2.22.0,<3.0.0) ; extra == "dev" +Requires-Dist: typing_extensions (>=4.0,<5.0) ; python_version < "3.8" +Requires-Dist: urllib3 (<2.0.0) ; python_version < "3.11" +Requires-Dist: urllib3 (<2.1.0) ; python_version >= "3.11" +Requires-Dist: wrapt (>=1.11.2,<2.0.0) +Project-URL: Repository, https://github.com/DataDog/datadog-lambda-python +Description-Content-Type: text/markdown + +# datadog-lambda-python + +![build](https://github.com/DataDog/datadog-lambda-python/workflows/build/badge.svg) +[![PyPI](https://img.shields.io/pypi/v/datadog-lambda)](https://pypi.org/project/datadog-lambda/) +![PyPI - Python Version](https://img.shields.io/pypi/pyversions/datadog-lambda) +[![Slack](https://chat.datadoghq.com/badge.svg?bg=632CA6)](https://chat.datadoghq.com/) +[![License](https://img.shields.io/badge/license-Apache--2.0-blue)](https://github.com/DataDog/datadog-lambda-python/blob/main/LICENSE) + +Datadog Lambda Library for Python (3.8, 3.9, 3.10, 3.11, and 3.12) enables [enhanced Lambda metrics](https://docs.datadoghq.com/serverless/enhanced_lambda_metrics), [distributed tracing](https://docs.datadoghq.com/serverless/distributed_tracing), and [custom metric submission](https://docs.datadoghq.com/serverless/custom_metrics) from AWS Lambda functions. + +## Installation + +Follow the [installation instructions](https://docs.datadoghq.com/serverless/installation/python/), and view your function's enhanced metrics, traces and logs in Datadog. + +## Configuration + +Follow the [configuration instructions](https://docs.datadoghq.com/serverless/configuration) to tag your telemetry, capture request/response payloads, filter or scrub sensitive information from logs or traces, and more. + +For additional tracing configuration options, check out the [official documentation for Datadog trace client](https://ddtrace.readthedocs.io/en/stable/configuration.html). + +Besides the environment variables supported by dd-trace-py, the datadog-lambda-python library added following environment variables. + +| Environment Variables | Description | Default Value | +| -------------------- | ------------ | ------------- | +| DD_ENCODE_AUTHORIZER_CONTEXT | When set to `true` for Lambda authorizers, the tracing context will be encoded into the response for propagation. Supported for NodeJS and Python. | `true` | +| DD_DECODE_AUTHORIZER_CONTEXT | When set to `true` for Lambdas that are authorized via Lambda authorizers, it will parse and use the encoded tracing context (if found). Supported for NodeJS and Python. | `true` | +| DD_COLD_START_TRACING | Set to `false` to disable Cold Start Tracing. Used in NodeJS and Python. | `true` | +| DD_MIN_COLD_START_DURATION | Sets the minimum duration (in milliseconds) for a module load event to be traced via Cold Start Tracing. Number. | `3` | +| DD_COLD_START_TRACE_SKIP_LIB | optionally skip creating Cold Start Spans for a comma-separated list of libraries. Useful to limit depth or skip known libraries. | `ddtrace.internal.compat,ddtrace.filters` | +| DD_CAPTURE_LAMBDA_PAYLOAD | [Captures incoming and outgoing AWS Lambda payloads][1] in the Datadog APM spans for Lambda invocations. | `false` | +| DD_CAPTURE_LAMBDA_PAYLOAD_MAX_DEPTH | Determines the level of detail captured from AWS Lambda payloads, which are then assigned as tags for the `aws.lambda` span. It specifies the nesting depth of the JSON payload structure to process. Once the specified maximum depth is reached, the tag's value is set to the stringified value of any nested elements beyond this level.
For example, given the input payload:
{
"lv1" : {
"lv2": {
"lv3": "val"
}
}
}
If the depth is set to `2`, the resulting tag's key is set to `function.request.lv1.lv2` and the value is `{\"lv3\": \"val\"}`.
If the depth is set to `0`, the resulting tag's key is set to `function.request` and value is `{\"lv1\":{\"lv2\":{\"lv3\": \"val\"}}}` | `10` | + + +## Opening Issues + +If you encounter a bug with this package, we want to hear about it. Before opening a new issue, search the existing issues to avoid duplicates. + +When opening an issue, include the Datadog Lambda Library version, Python version, and stack trace if available. In addition, include the steps to reproduce when appropriate. + +You can also open an issue for a feature request. + +## Lambda Profiling Beta + +Datadog's [Continuous Profiler](https://www.datadoghq.com/product/code-profiling/) is now available in beta for Python in version 4.62.0 and layer version 62 and above. This optional feature is enabled by setting the `DD_PROFILING_ENABLED` environment variable to `true`. During the beta period, profiling is available at no additional cost. + +The Continuous Profiler works by spawning a thread which periodically wakes up and takes a snapshot of the CPU and Heap of all running python code. This can include the profiler itself. If you want the Profiler to ignore itself, set `DD_PROFILING_IGNORE_PROFILER` to `true`. + +## Major Version Notes + +### 5.x / Layer version 86+ +- Python3.7 support has been [deprecated](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html) by AWS, and support removed from this library. + +### 4.x / Layer version 61+ + +- Python3.6 support has been [deprecated](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html) by AWS, and support removed from this library. +- `dd-trace` upgraded from 0.61 to 1.4, full release notes are available [here](https://ddtrace.readthedocs.io/en/stable/release_notes.html#v1-0-0) + - `get_correlation_ids()` has been changed to `get_log_correlation_context()`, which now returns a dictionary containing the active `span_id`, `trace_id`, as well as `service` and `env`. + +## Contributing + +If you find an issue with this package and have a fix, please feel free to open a pull request following the [procedures](CONTRIBUTING.md). + +## Community + +For product feedback and questions, join the `#serverless` channel in the [Datadog community on Slack](https://chat.datadoghq.com/). + +## License + +Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + +This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2019 Datadog, Inc. + +[1]: https://www.datadoghq.com/blog/troubleshoot-lambda-function-request-response-payloads/ + diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda-5.87.0.dist-info/RECORD b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda-5.87.0.dist-info/RECORD new file mode 100644 index 0000000..a275ee4 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda-5.87.0.dist-info/RECORD @@ -0,0 +1,44 @@ +datadog_lambda-5.87.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +datadog_lambda-5.87.0.dist-info/LICENSE,sha256=4yQmjpKp1MKL7DdRDPVHkKYc2W0aezm5SIDske8oAdM,11379 +datadog_lambda-5.87.0.dist-info/METADATA,sha256=s2sPTache99ImWP7igPhfpBOuNtxNUceNYeioFEi06w,7326 +datadog_lambda-5.87.0.dist-info/RECORD,, +datadog_lambda-5.87.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +datadog_lambda-5.87.0.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88 +datadog_lambda/__init__.py,sha256=fE7XRhgzSgTCjt1AAocMbqtAJM9pZiLbbcCi8BWE3fQ,538 +datadog_lambda/__pycache__/__init__.cpython-311.pyc,, +datadog_lambda/__pycache__/api.cpython-311.pyc,, +datadog_lambda/__pycache__/cold_start.cpython-311.pyc,, +datadog_lambda/__pycache__/constants.cpython-311.pyc,, +datadog_lambda/__pycache__/dogstatsd.cpython-311.pyc,, +datadog_lambda/__pycache__/extension.cpython-311.pyc,, +datadog_lambda/__pycache__/handler.cpython-311.pyc,, +datadog_lambda/__pycache__/metric.cpython-311.pyc,, +datadog_lambda/__pycache__/module_name.cpython-311.pyc,, +datadog_lambda/__pycache__/patch.cpython-311.pyc,, +datadog_lambda/__pycache__/stats_writer.cpython-311.pyc,, +datadog_lambda/__pycache__/statsd_writer.cpython-311.pyc,, +datadog_lambda/__pycache__/tag_object.cpython-311.pyc,, +datadog_lambda/__pycache__/tags.cpython-311.pyc,, +datadog_lambda/__pycache__/thread_stats_writer.cpython-311.pyc,, +datadog_lambda/__pycache__/tracing.cpython-311.pyc,, +datadog_lambda/__pycache__/trigger.cpython-311.pyc,, +datadog_lambda/__pycache__/wrapper.cpython-311.pyc,, +datadog_lambda/__pycache__/xray.cpython-311.pyc,, +datadog_lambda/api.py,sha256=TFg7gCek088_C53cZQQHDoLXGlTAhP2AD8NAuWYOVco,3653 +datadog_lambda/cold_start.py,sha256=aGpWlgPdMvQkyK9kVz5pEoLIxrVa0AqZoOy5ABXyXzA,7891 +datadog_lambda/constants.py,sha256=DeujbnguBT9nDioiaYlgQQdZ6Ps53sWXmYhruLVoCHE,1669 +datadog_lambda/dogstatsd.py,sha256=HCyl72oQUSF3E4y1ivrHaGTHL9WG1asGjB1Xo2D_Abc,4769 +datadog_lambda/extension.py,sha256=zQaBioG0TrWtZvk8c9z7ANUJt_oMzeAPMG-mGPL_cMw,1199 +datadog_lambda/handler.py,sha256=r2MiZoIfTWuVAN-f6iXXIjhdtd1t7m9bTnwplVm2SEY,994 +datadog_lambda/metric.py,sha256=jk4jRgb0pwxd_c4D2zzAZ3olN_8ci64fpYk3cuxbg0U,4707 +datadog_lambda/module_name.py,sha256=5FmOCjjgjq78b6a83QePZZFmqahAoy9XHdUNWdq2D1Q,139 +datadog_lambda/patch.py,sha256=Hr_zeekk9PeAizTDFoZ_ZwTWptjgtKjl9A-XHX5kA1k,4641 +datadog_lambda/stats_writer.py,sha256=SIac96wu45AxDOZ4GraCbK3r1RKr4AFgXcEPHg1VX0A,243 +datadog_lambda/statsd_writer.py,sha256=F4SCJ6-J6YfvQNh0uQfAkP6QYiAtV3-MCsxz4QnaBBI,403 +datadog_lambda/tag_object.py,sha256=Kcys4Mo4_4vdXxq4XS7ilWpCuSQQyVRSjDejgq6RJS4,2112 +datadog_lambda/tags.py,sha256=wIG1f5iq85dq3FNV-yi-D0XwqYOx8jE0x_8Re6Ucmso,3240 +datadog_lambda/thread_stats_writer.py,sha256=fkjMDgrzwACrK_ZrCwl9mHz5U3CMLEyrsaondjdM3r8,2522 +datadog_lambda/tracing.py,sha256=r9H77-RNsmXxHA3k8yaOzmShYD4FtYtz3yrbDin36cQ,46854 +datadog_lambda/trigger.py,sha256=_Sxpy9UpMDHdw_X1hD61G4OTex7CIYQw1guFu6dzByo,12082 +datadog_lambda/wrapper.py,sha256=NrM6_TCWi4sjIHSSGAjAZV7hdF8PxJwCurRQUCnjspo,15547 +datadog_lambda/xray.py,sha256=05-8xd3GOOIDtGaB4k2Ow1kbWn86Px2mhyKEUYIwKIc,3448 diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda-5.87.0.dist-info/REQUESTED b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda-5.87.0.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda-5.87.0.dist-info/WHEEL b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda-5.87.0.dist-info/WHEEL new file mode 100644 index 0000000..258a6ff --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda-5.87.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: poetry-core 1.6.1 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/__init__.py new file mode 100644 index 0000000..20b4244 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/__init__.py @@ -0,0 +1,17 @@ +import os +import logging +from datadog_lambda.cold_start import initialize_cold_start_tracing + +initialize_cold_start_tracing() + +# The minor version corresponds to the Lambda layer version. +# E.g.,, version 0.5.0 gets packaged into layer version 5. +try: + import importlib.metadata as importlib_metadata +except ModuleNotFoundError: + import importlib_metadata + +__version__ = importlib_metadata.version(__name__) + +logger = logging.getLogger(__name__) +logger.setLevel(logging.getLevelName(os.environ.get("DD_LOG_LEVEL", "INFO").upper())) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/api.py b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/api.py new file mode 100644 index 0000000..079f69d --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/api.py @@ -0,0 +1,93 @@ +import os +import logging +import base64 +from datadog_lambda.extension import should_use_extension + +logger = logging.getLogger(__name__) +KMS_ENCRYPTION_CONTEXT_KEY = "LambdaFunctionName" + + +def decrypt_kms_api_key(kms_client, ciphertext): + from botocore.exceptions import ClientError + + """ + Decodes and deciphers the base64-encoded ciphertext given as a parameter using KMS. + For this to work properly, the Lambda function must have the appropriate IAM permissions. + + Args: + kms_client: The KMS client to use for decryption + ciphertext (string): The base64-encoded ciphertext to decrypt + """ + decoded_bytes = base64.b64decode(ciphertext) + + """ + When the API key is encrypted using the AWS console, the function name is added as an + encryption context. When the API key is encrypted using the AWS CLI, no encryption context + is added. We need to try decrypting the API key both with and without the encryption context. + """ + # Try without encryption context, in case API key was encrypted using the AWS CLI + function_name = os.environ.get("AWS_LAMBDA_FUNCTION_NAME") + try: + plaintext = kms_client.decrypt(CiphertextBlob=decoded_bytes)[ + "Plaintext" + ].decode("utf-8") + except ClientError: + logger.debug( + "Failed to decrypt ciphertext without encryption context, \ + retrying with encryption context" + ) + # Try with encryption context, in case API key was encrypted using the AWS Console + plaintext = kms_client.decrypt( + CiphertextBlob=decoded_bytes, + EncryptionContext={ + KMS_ENCRYPTION_CONTEXT_KEY: function_name, + }, + )["Plaintext"].decode("utf-8") + + return plaintext + + +def init_api(): + if ( + not should_use_extension + and not os.environ.get("DD_FLUSH_TO_LOG", "").lower() == "true" + ): + # Make sure that this package would always be lazy-loaded/outside from the critical path + # since underlying packages are quite heavy to load + # and useless when the extension is present + from datadog import api + + if not api._api_key: + import boto3 + + DD_API_KEY_SECRET_ARN = os.environ.get("DD_API_KEY_SECRET_ARN", "") + DD_API_KEY_SSM_NAME = os.environ.get("DD_API_KEY_SSM_NAME", "") + DD_KMS_API_KEY = os.environ.get("DD_KMS_API_KEY", "") + DD_API_KEY = os.environ.get( + "DD_API_KEY", os.environ.get("DATADOG_API_KEY", "") + ) + + if DD_API_KEY_SECRET_ARN: + api._api_key = boto3.client("secretsmanager").get_secret_value( + SecretId=DD_API_KEY_SECRET_ARN + )["SecretString"] + elif DD_API_KEY_SSM_NAME: + api._api_key = boto3.client("ssm").get_parameter( + Name=DD_API_KEY_SSM_NAME, WithDecryption=True + )["Parameter"]["Value"] + elif DD_KMS_API_KEY: + kms_client = boto3.client("kms") + api._api_key = decrypt_kms_api_key(kms_client, DD_KMS_API_KEY) + else: + api._api_key = DD_API_KEY + + logger.debug("Setting DATADOG_API_KEY of length %d", len(api._api_key)) + + # Set DATADOG_HOST, to send data to a non-default Datadog datacenter + api._api_host = os.environ.get( + "DATADOG_HOST", "https://api." + os.environ.get("DD_SITE", "datadoghq.com") + ) + logger.debug("Setting DATADOG_HOST to %s", api._api_host) + + # Unmute exceptions from datadog api client, so we can catch and handle them + api._mute = False diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/cold_start.py b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/cold_start.py new file mode 100644 index 0000000..9da02e7 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/cold_start.py @@ -0,0 +1,252 @@ +import time +import os +from typing import List, Hashable +import logging + +logger = logging.getLogger(__name__) + +_cold_start = True +_proactive_initialization = False +_lambda_container_initialized = False + + +def set_cold_start(init_timestamp_ns): + """Set the value of the cold start global + + This should be executed once per Lambda execution before the execution + """ + global _cold_start + global _lambda_container_initialized + global _proactive_initialization + if not _lambda_container_initialized: + now = time.time_ns() + if (now - init_timestamp_ns) // 1_000_000_000 > 10: + _cold_start = False + _proactive_initialization = True + else: + _cold_start = not _lambda_container_initialized + else: + _cold_start = False + _proactive_initialization = False + _lambda_container_initialized = True + + +def is_cold_start(): + """Returns the value of the global cold_start""" + return _cold_start + + +def is_proactive_init(): + """Returns the value of the global proactive_initialization""" + return _proactive_initialization + + +def is_new_sandbox(): + return is_cold_start() or is_proactive_init() + + +def get_cold_start_tag(): + """Returns the cold start tag to be used in metrics""" + return "cold_start:{}".format(str(is_cold_start()).lower()) + + +def get_proactive_init_tag(): + """Returns the proactive init tag to be used in metrics""" + return "proactive_initialization:{}".format(str(is_proactive_init()).lower()) + + +class ImportNode(object): + def __init__(self, module_name, full_file_path, start_time_ns, end_time_ns=None): + self.module_name = module_name + self.full_file_path = full_file_path + self.start_time_ns = start_time_ns + self.end_time_ns = end_time_ns + self.children = [] + + +root_nodes: List[ImportNode] = [] +import_stack: List[ImportNode] = [] +already_wrapped_loaders = set() + + +def reset_node_stacks(): + global root_nodes + root_nodes = [] + global import_stack + import_stack = [] + + +def push_node(module_name, file_path): + node = ImportNode(module_name, file_path, time.time_ns()) + global import_stack + if import_stack: + import_stack[-1].children.append(node) + import_stack.append(node) + + +def pop_node(module_name): + global import_stack + if not import_stack: + return + node = import_stack.pop() + if node.module_name != module_name: + return + end_time_ns = time.time_ns() + node.end_time_ns = end_time_ns + if not import_stack: # import_stack empty, a root node has been found + global root_nodes + root_nodes.append(node) + + +def wrap_exec_module(original_exec_module): + def wrapped_method(module): + should_pop = False + try: + spec = module.__spec__ + push_node(spec.name, spec.origin) + should_pop = True + except Exception: + pass + try: + return original_exec_module(module) + finally: + if should_pop: + pop_node(spec.name) + + return wrapped_method + + +def wrap_find_spec(original_find_spec): + def wrapped_find_spec(*args, **kwargs): + spec = original_find_spec(*args, **kwargs) + if spec is None: + return None + loader = getattr(spec, "loader", None) + if ( + loader is not None + and isinstance(loader, Hashable) + and loader not in already_wrapped_loaders + ): + if hasattr(loader, "exec_module"): + try: + loader.exec_module = wrap_exec_module(loader.exec_module) + already_wrapped_loaders.add(loader) + except Exception as e: + logger.debug("Failed to wrap the loader. %s", e) + return spec + + return wrapped_find_spec + + +def initialize_cold_start_tracing(): + if ( + is_new_sandbox() + and os.environ.get("DD_TRACE_ENABLED", "true").lower() == "true" + and os.environ.get("DD_COLD_START_TRACING", "true").lower() == "true" + ): + from sys import meta_path + + for importer in meta_path: + try: + importer.find_spec = wrap_find_spec(importer.find_spec) + except Exception: + pass + + +class ColdStartTracer(object): + def __init__( + self, + tracer, + function_name, + current_span_start_time_ns, + trace_ctx, + min_duration_ms: int, + ignored_libs: List[str] = None, + ): + if ignored_libs is None: + ignored_libs = [] + self._tracer = tracer + self.function_name = function_name + self.current_span_start_time_ns = current_span_start_time_ns + self.min_duration_ms = min_duration_ms + self.trace_ctx = trace_ctx + self.ignored_libs = ignored_libs + self.need_to_reactivate_context = True + + def trace(self, root_nodes: List[ImportNode] = root_nodes): + if not root_nodes: + return + cold_start_span_start_time_ns = root_nodes[0].start_time_ns + cold_start_span_end_time_ns = min( + root_nodes[-1].end_time_ns, self.current_span_start_time_ns + ) + cold_start_span = self.create_cold_start_span(cold_start_span_start_time_ns) + while root_nodes: + root_node = root_nodes.pop() + self.trace_tree(root_node, cold_start_span) + self.finish_span(cold_start_span, cold_start_span_end_time_ns) + + def trace_tree(self, import_node: ImportNode, parent_span): + if ( + import_node.end_time_ns - import_node.start_time_ns + < self.min_duration_ms * 1e6 + or import_node.module_name in self.ignored_libs + ): + return + + span = self.start_span( + "aws.lambda.import", import_node.module_name, import_node.start_time_ns + ) + tags = { + "resource_names": import_node.module_name, + "resource.name": import_node.module_name, + "filename": import_node.full_file_path, + "operation_name": self.get_operation_name(import_node.full_file_path), + } + span.set_tags(tags) + if parent_span: + span.parent_id = parent_span.span_id + for child_node in import_node.children: + self.trace_tree(child_node, span) + self.finish_span(span, import_node.end_time_ns) + + def create_cold_start_span(self, start_time_ns): + span = self.start_span("aws.lambda.load", self.function_name, start_time_ns) + tags = { + "resource_names": self.function_name, + "resource.name": self.function_name, + "operation_name": "aws.lambda.load", + } + span.set_tags(tags) + return span + + def start_span(self, span_type, resource, start_time_ns): + if self.need_to_reactivate_context: + self._tracer.context_provider.activate( + self.trace_ctx + ) # reactivate required after each finish() call + self.need_to_reactivate_context = False + span_kwargs = { + "service": "aws.lambda", + "resource": resource, + "span_type": span_type, + } + span = self._tracer.trace(span_type, **span_kwargs) + span.start_ns = start_time_ns + return span + + def finish_span(self, span, finish_time_ns): + span.finish(finish_time_ns / 1e9) + self.need_to_reactivate_context = True + + def get_operation_name(self, filename: str): + if filename is None: + return "aws.lambda.import_core_module" + if not isinstance(filename, str): + return "aws.lambda.import" + if filename.startswith("/opt/"): + return "aws.lambda.import_layer" + elif filename.startswith("/var/lang/"): + return "aws.lambda.import_runtime" + else: + return "aws.lambda.import" diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/constants.py b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/constants.py new file mode 100644 index 0000000..fd8afb3 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/constants.py @@ -0,0 +1,53 @@ +# Unless explicitly stated otherwise all files in this repository are licensed +# under the Apache License Version 2.0. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2019 Datadog, Inc. + +# Datadog trace sampling priority + + +class SamplingPriority(object): + USER_REJECT = -1 + AUTO_REJECT = 0 + AUTO_KEEP = 1 + USER_KEEP = 2 + + +# Datadog trace headers +class TraceHeader(object): + TRACE_ID = "x-datadog-trace-id" + PARENT_ID = "x-datadog-parent-id" + SAMPLING_PRIORITY = "x-datadog-sampling-priority" + + +# X-Ray subsegment to save Datadog trace metadata +class XraySubsegment(object): + NAME = "datadog-metadata" + TRACE_KEY = "trace" + LAMBDA_FUNCTION_TAGS_KEY = "lambda_function_tags" + NAMESPACE = "datadog" + + +# TraceContextSource of datadog context. The DD_MERGE_XRAY_TRACES +# feature uses this to determine when to use X-Ray as the parent +# trace. +class TraceContextSource(object): + XRAY = "xray" + EVENT = "event" + DDTRACE = "ddtrace" + + +# X-Ray deamon +class XrayDaemon(object): + XRAY_TRACE_ID_HEADER_NAME = "_X_AMZN_TRACE_ID" + XRAY_DAEMON_ADDRESS = "AWS_XRAY_DAEMON_ADDRESS" + FUNCTION_NAME_HEADER_NAME = "AWS_LAMBDA_FUNCTION_NAME" + + +class Headers(object): + Parent_Span_Finish_Time = "x-datadog-parent-span-finish-time" + + # For one request from the client, the event.requestContext.requestIds in the authorizer lambda + # invocation and the main function invocation are IDENTICAL. Therefore we can use it to tell + # whether current invocation is the actual original authorizing request or a cached request. + Authorizing_Request_Id = "x-datadog-authorizing-requestid" diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/dogstatsd.py b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/dogstatsd.py new file mode 100644 index 0000000..a627492 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/dogstatsd.py @@ -0,0 +1,143 @@ +import logging +import os +import socket +import errno +import re +from threading import Lock + + +MIN_SEND_BUFFER_SIZE = 32 * 1024 +log = logging.getLogger("datadog_lambda.dogstatsd") + + +class DogStatsd(object): + def __init__(self): + self._socket_lock = Lock() + self.socket_path = None + self.host = "localhost" + self.port = 8125 + self.socket = None + self.encoding = "utf-8" + + def get_socket(self, telemetry=False): + """ + Return a connected socket. + + Note: connect the socket before assigning it to the class instance to + avoid bad thread race conditions. + """ + with self._socket_lock: + self.socket = self._get_udp_socket( + self.host, + self.port, + ) + return self.socket + + @classmethod + def _ensure_min_send_buffer_size(cls, sock, min_size=MIN_SEND_BUFFER_SIZE): + # Increase the receiving buffer size where needed (e.g. MacOS has 4k RX + # buffers which is half of the max packet size that the client will send. + if os.name == "posix": + try: + recv_buff_size = sock.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF) + if recv_buff_size <= min_size: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, min_size) + log.debug("Socket send buffer increased to %dkb", min_size / 1024) + finally: + pass + + @classmethod + def _get_udp_socket(cls, host, port): + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.setblocking(0) + cls._ensure_min_send_buffer_size(sock) + sock.connect((host, port)) + + return sock + + def distribution(self, metric, value, tags=None): + """ + Send a global distribution value, optionally setting tags. + + >>> statsd.distribution("uploaded.file.size", 1445) + >>> statsd.distribution("album.photo.count", 26, tags=["gender:female"]) + """ + self._report(metric, "d", value, tags) + + def close_socket(self): + """ + Closes connected socket if connected. + """ + with self._socket_lock: + if self.socket: + try: + self.socket.close() + except OSError as e: + log.error("Unexpected error: %s", str(e)) + self.socket = None + + def normalize_tags(self, tag_list): + TAG_INVALID_CHARS_RE = re.compile(r"[^\w\d_\-:/\.]", re.UNICODE) + TAG_INVALID_CHARS_SUBS = "_" + return [ + re.sub(TAG_INVALID_CHARS_RE, TAG_INVALID_CHARS_SUBS, tag) + for tag in tag_list + ] + + def _serialize_metric(self, metric, metric_type, value, tags): + # Create/format the metric packet + return "%s:%s|%s%s" % ( + metric, + value, + metric_type, + ("|#" + ",".join(self.normalize_tags(tags))) if tags else "", + ) + + def _report(self, metric, metric_type, value, tags): + if value is None: + return + + payload = self._serialize_metric(metric, metric_type, value, tags) + + # Send it + self._send_to_server(payload) + + def _send_to_server(self, packet): + try: + mysocket = self.socket or self.get_socket() + mysocket.send(packet.encode(self.encoding)) + return True + except socket.timeout: + # dogstatsd is overflowing, drop the packets (mimicks the UDP behaviour) + pass + except (socket.herror, socket.gaierror) as socket_err: + log.warning( + "Error submitting packet: %s, dropping the packet and closing the socket", + socket_err, + ) + self.close_socket() + except socket.error as socket_err: + if socket_err.errno == errno.EAGAIN: + log.debug( + "Socket send would block: %s, dropping the packet", socket_err + ) + elif socket_err.errno == errno.ENOBUFS: + log.debug("Socket buffer full: %s, dropping the packet", socket_err) + elif socket_err.errno == errno.EMSGSIZE: + log.debug( + "Packet size too big (size: %d): %s, dropping the packet", + len(packet.encode(self.encoding)), + socket_err, + ) + else: + log.warning( + "Error submitting packet: %s, dropping the packet and closing the socket", + socket_err, + ) + self.close_socket() + except Exception as e: + log.error("Unexpected error: %s", str(e)) + return False + + +statsd = DogStatsd() diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/extension.py b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/extension.py new file mode 100644 index 0000000..d66848f --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/extension.py @@ -0,0 +1,42 @@ +import logging +from os import path + +try: + # only available in python 3 + # not an issue since the extension is not compatible with python 2.x runtime + # https://docs.aws.amazon.com/lambda/latest/dg/using-extensions.html + import urllib.request +except ImportError: + # safe since both calls to urllib are protected with try/expect and will return false + urllib = None + +AGENT_URL = "http://127.0.0.1:8124" +HELLO_PATH = "/lambda/hello" +FLUSH_PATH = "/lambda/flush" +EXTENSION_PATH = "/opt/extensions/datadog-agent" + +logger = logging.getLogger(__name__) + + +def is_extension_running(): + if not path.exists(EXTENSION_PATH): + return False + try: + urllib.request.urlopen(AGENT_URL + HELLO_PATH) + except Exception as e: + logger.debug("Extension is not running, returned with error %s", e) + return False + return True + + +def flush_extension(): + try: + req = urllib.request.Request(AGENT_URL + FLUSH_PATH, "".encode("ascii")) + urllib.request.urlopen(req) + except Exception as e: + logger.debug("Failed to flush extension, returned with error %s", e) + return False + return True + + +should_use_extension = is_extension_running() diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/handler.py b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/handler.py new file mode 100644 index 0000000..09cc5e7 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/handler.py @@ -0,0 +1,31 @@ +# Unless explicitly stated otherwise all files in this repository are licensed +# under the Apache License Version 2.0. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2020 Datadog, Inc. + +from __future__ import absolute_import +from importlib import import_module + +import os +from datadog_lambda.wrapper import datadog_lambda_wrapper +from datadog_lambda.module_name import modify_module_name + + +class HandlerError(Exception): + pass + + +path = os.environ.get("DD_LAMBDA_HANDLER", None) +if path is None: + raise HandlerError( + "DD_LAMBDA_HANDLER is not defined. Can't use prebuilt datadog handler" + ) +parts = path.rsplit(".", 1) +if len(parts) != 2: + raise HandlerError("Value %s for DD_LAMBDA_HANDLER has invalid format." % path) + + +(mod_name, handler_name) = parts +modified_mod_name = modify_module_name(mod_name) +handler_module = import_module(modified_mod_name) +handler = datadog_lambda_wrapper(getattr(handler_module, handler_name)) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/metric.py b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/metric.py new file mode 100644 index 0000000..ca23ed9 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/metric.py @@ -0,0 +1,136 @@ +# Unless explicitly stated otherwise all files in this repository are licensed +# under the Apache License Version 2.0. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2019 Datadog, Inc. + +import os +import json +import time +import logging + +from datadog_lambda.extension import should_use_extension +from datadog_lambda.tags import get_enhanced_metrics_tags, tag_dd_lambda_layer +from datadog_lambda.api import init_api + +logger = logging.getLogger(__name__) + +lambda_stats = None + +init_api() + +if should_use_extension: + from datadog_lambda.statsd_writer import StatsDWriter + + lambda_stats = StatsDWriter() +else: + # Periodical flushing in a background thread is NOT guaranteed to succeed + # and leads to data loss. When disabled, metrics are only flushed at the + # end of invocation. To make metrics submitted from a long-running Lambda + # function available sooner, consider using the Datadog Lambda extension. + from datadog_lambda.thread_stats_writer import ThreadStatsWriter + + flush_in_thread = os.environ.get("DD_FLUSH_IN_THREAD", "").lower() == "true" + lambda_stats = ThreadStatsWriter(flush_in_thread) + + +def lambda_metric(metric_name, value, timestamp=None, tags=None, force_async=False): + """ + Submit a data point to Datadog distribution metrics. + https://docs.datadoghq.com/graphing/metrics/distributions/ + + When DD_FLUSH_TO_LOG is True, write metric to log, and + wait for the Datadog Log Forwarder Lambda function to submit + the metrics asynchronously. + + Otherwise, the metrics will be submitted to the Datadog API + periodically and at the end of the function execution in a + background thread. + + Note that if the extension is present, it will override the DD_FLUSH_TO_LOG value + and always use the layer to send metrics to the extension + """ + flush_to_logs = os.environ.get("DD_FLUSH_TO_LOG", "").lower() == "true" + tags = tag_dd_lambda_layer(tags) + + if should_use_extension: + logger.debug( + "Sending metric %s value %s to Datadog via extension", metric_name, value + ) + lambda_stats.distribution(metric_name, value, tags=tags, timestamp=timestamp) + else: + if flush_to_logs or force_async: + write_metric_point_to_stdout( + metric_name, value, timestamp=timestamp, tags=tags + ) + else: + lambda_stats.distribution( + metric_name, value, tags=tags, timestamp=timestamp + ) + + +def write_metric_point_to_stdout(metric_name, value, timestamp=None, tags=[]): + """Writes the specified metric point to standard output""" + logger.debug( + "Sending metric %s value %s to Datadog via log forwarder", metric_name, value + ) + print( + json.dumps( + { + "m": metric_name, + "v": value, + "e": timestamp or int(time.time()), + "t": tags, + } + ) + ) + + +def flush_stats(): + lambda_stats.flush() + + +def are_enhanced_metrics_enabled(): + """Check env var to find if enhanced metrics should be submitted + + Returns: + boolean for whether enhanced metrics are enabled + """ + # DD_ENHANCED_METRICS defaults to true + return os.environ.get("DD_ENHANCED_METRICS", "true").lower() == "true" + + +def submit_enhanced_metric(metric_name, lambda_context): + """Submits the enhanced metric with the given name + + Args: + metric_name (str): metric name w/o enhanced prefix i.e. "invocations" or "errors" + lambda_context (dict): Lambda context dict passed to the function by AWS + """ + if not are_enhanced_metrics_enabled(): + logger.debug( + "Not submitting enhanced metric %s because enhanced metrics are disabled", + metric_name, + ) + return + tags = get_enhanced_metrics_tags(lambda_context) + metric_name = "aws.lambda.enhanced." + metric_name + # Enhanced metrics always use an async submission method, (eg logs or extension). + lambda_metric(metric_name, 1, timestamp=None, tags=tags, force_async=True) + + +def submit_invocations_metric(lambda_context): + """Increment aws.lambda.enhanced.invocations by 1, applying runtime, layer, and cold_start tags + + Args: + lambda_context (dict): Lambda context dict passed to the function by AWS + """ + submit_enhanced_metric("invocations", lambda_context) + + +def submit_errors_metric(lambda_context): + """Increment aws.lambda.enhanced.errors by 1, applying runtime, layer, and cold_start tags + + Args: + lambda_context (dict): Lambda context dict passed to the function by AWS + """ + submit_enhanced_metric("errors", lambda_context) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/module_name.py b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/module_name.py new file mode 100644 index 0000000..9e4a93e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/module_name.py @@ -0,0 +1,3 @@ +def modify_module_name(module_name): + """Returns a valid modified module to get imported""" + return ".".join(module_name.split("/")) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/patch.py b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/patch.py new file mode 100644 index 0000000..0f6d28e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/patch.py @@ -0,0 +1,159 @@ +# Unless explicitly stated otherwise all files in this repository are licensed +# under the Apache License Version 2.0. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2019 Datadog, Inc. + +import json +import os +import sys +import logging +import zlib + +from wrapt import wrap_function_wrapper as wrap +from wrapt.importer import when_imported +from ddtrace import patch_all as patch_all_dd + +from datadog_lambda.tracing import ( + get_dd_trace_context, + dd_tracing_enabled, +) +from collections.abc import MutableMapping + +logger = logging.getLogger(__name__) + +_http_patched = False +_requests_patched = False +_integration_tests_patched = False + + +def patch_all(): + """ + Patch third-party libraries for tracing. + """ + _patch_for_integration_tests() + + if dd_tracing_enabled: + patch_all_dd() + else: + _patch_http() + _ensure_patch_requests() + + +def _patch_for_integration_tests(): + """ + Patch `requests` to log the outgoing requests for integration tests. + """ + global _integration_tests_patched + is_in_tests = os.environ.get("DD_INTEGRATION_TEST", "false").lower() == "true" + if not _integration_tests_patched and is_in_tests: + wrap("requests", "Session.send", _log_request) + _integration_tests_patched = True + + +def _patch_http(): + """ + Patch `http.client` (Python 3) module. + """ + global _http_patched + http_module = "http.client" + if not _http_patched: + _http_patched = True + wrap(http_module, "HTTPConnection.request", _wrap_http_request) + + logger.debug("Patched %s", http_module) + + +def _ensure_patch_requests(): + """ + `requests` is third-party, may not be installed or used, + but ensure it gets patched if installed and used. + """ + if "requests" in sys.modules: + # already imported, patch now + _patch_requests(sys.modules["requests"]) + else: + # patch when imported + when_imported("requests")(_patch_requests) + + +def _patch_requests(module): + """ + Patch the high-level HTTP client module `requests` + if it's installed. + """ + global _requests_patched + if not _requests_patched: + _requests_patched = True + try: + wrap("requests", "Session.request", _wrap_requests_request) + logger.debug("Patched requests") + except Exception: + logger.debug("Failed to patch requests", exc_info=True) + + +def _wrap_requests_request(func, instance, args, kwargs): + """ + Wrap `requests.Session.request` to inject the Datadog trace headers + into the outgoing requests. + """ + context = get_dd_trace_context() + if "headers" in kwargs and isinstance(kwargs["headers"], MutableMapping): + kwargs["headers"].update(context) + elif len(args) >= 5 and isinstance(args[4], MutableMapping): + args[4].update(context) + else: + kwargs["headers"] = context + + return func(*args, **kwargs) + + +def _wrap_http_request(func, instance, args, kwargs): + """ + Wrap `http.client` (python3) to inject + the Datadog trace headers into the outgoing requests. + """ + context = get_dd_trace_context() + if "headers" in kwargs and isinstance(kwargs["headers"], MutableMapping): + kwargs["headers"].update(context) + elif len(args) >= 4 and isinstance(args[3], MutableMapping): + args[3].update(context) + else: + kwargs["headers"] = context + + return func(*args, **kwargs) + + +def _log_request(func, instance, args, kwargs): + request = kwargs.get("request") or args[0] + _print_request_string(request) + return func(*args, **kwargs) + + +def _print_request_string(request): + """Print the request so that it can be checked in integration tests + + Only used by integration tests. + """ + method = request.method + url = request.url + + # Sort the datapoints POSTed by their name so that snapshots always align + data = request.body or "{}" + # If payload is compressed, decompress it so we can parse it + if request.headers.get("Content-Encoding") == "deflate": + data = zlib.decompress(data) + data_dict = json.loads(data) + data_dict.get("series", []).sort(key=lambda series: series.get("metric")) + sorted_data = json.dumps(data_dict) + + # Sort headers to prevent any differences in ordering + headers = request.headers or {} + sorted_headers = sorted( + "{}:{}".format(key, value) for key, value in headers.items() + ) + sorted_header_str = json.dumps(sorted_headers) + print( + "HTTP {} {} Headers: {} Data: {}".format( + method, url, sorted_header_str, sorted_data + ) + ) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/stats_writer.py b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/stats_writer.py new file mode 100644 index 0000000..d3919c3 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/stats_writer.py @@ -0,0 +1,9 @@ +class StatsWriter: + def distribution(self, metric_name, value, tags=[], timestamp=None): + raise NotImplementedError() + + def flush(self): + raise NotImplementedError() + + def stop(self): + raise NotImplementedError() diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/statsd_writer.py b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/statsd_writer.py new file mode 100644 index 0000000..33843dc --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/statsd_writer.py @@ -0,0 +1,17 @@ +from datadog_lambda.stats_writer import StatsWriter +from datadog_lambda.dogstatsd import statsd + + +class StatsDWriter(StatsWriter): + """ + Writes distribution metrics using StatsD protocol + """ + + def distribution(self, metric_name, value, tags=[], timestamp=None): + statsd.distribution(metric_name, value, tags=tags) + + def flush(self): + pass + + def stop(self): + pass diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/tag_object.py b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/tag_object.py new file mode 100644 index 0000000..ec1c5a6 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/tag_object.py @@ -0,0 +1,68 @@ +# Unless explicitly stated otherwise all files in this repository are licensed +# under the Apache License Version 2.0. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2021 Datadog, Inc. + +from decimal import Decimal +import json +import logging + +redactable_keys = ["authorization", "x-authorization", "password", "token"] +max_depth = 10 +logger = logging.getLogger(__name__) + + +def tag_object(span, key, obj, depth=0): + if obj is None: + return span.set_tag(key, obj) + if depth >= max_depth: + return span.set_tag(key, _redact_val(key, str(obj)[0:5000])) + depth += 1 + if _should_try_string(obj): + parsed = None + try: + parsed = json.loads(obj) + return tag_object(span, key, parsed, depth) + except ValueError: + redacted = _redact_val(key, obj[0:5000]) + return span.set_tag(key, redacted) + if isinstance(obj, int) or isinstance(obj, float) or isinstance(obj, Decimal): + return span.set_tag(key, str(obj)) + if isinstance(obj, list): + for k, v in enumerate(obj): + formatted_key = "{}.{}".format(key, k) + tag_object(span, formatted_key, v, depth) + return + if hasattr(obj, "items"): + for k, v in obj.items(): + formatted_key = "{}.{}".format(key, k) + tag_object(span, formatted_key, v, depth) + return + if hasattr(obj, "to_dict"): + for k, v in obj.to_dict().items(): + formatted_key = "{}.{}".format(key, k) + tag_object(span, formatted_key, v, depth) + return + try: + value_as_str = str(obj) + except Exception: + value_as_str = "UNKNOWN" + return span.set_tag(key, value_as_str) + + +def _should_try_string(obj): + try: + if isinstance(obj, str) or isinstance(obj, unicode): + return True + except NameError: + if isinstance(obj, bytes): + return True + + return False + + +def _redact_val(k, v): + split_key = k.split(".").pop() or k + if split_key in redactable_keys: + return "redacted" + return v diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/tags.py b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/tags.py new file mode 100644 index 0000000..cdaeb4e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/tags.py @@ -0,0 +1,104 @@ +import sys + +from platform import python_version_tuple + +from datadog_lambda import __version__ +from datadog_lambda.cold_start import get_cold_start_tag + + +def _format_dd_lambda_layer_tag(): + """ + Formats the dd_lambda_layer tag, e.g., 'dd_lambda_layer:datadog-python39_1' + """ + runtime = "python{}{}".format(sys.version_info[0], sys.version_info[1]) + return "dd_lambda_layer:datadog-{}_{}".format(runtime, __version__) + + +def tag_dd_lambda_layer(tags): + """ + Used by lambda_metric to insert the dd_lambda_layer tag + """ + dd_lambda_layer_tag = _format_dd_lambda_layer_tag() + if tags: + return tags + [dd_lambda_layer_tag] + else: + return [dd_lambda_layer_tag] + + +def parse_lambda_tags_from_arn(lambda_context): + """Generate the list of lambda tags based on the data in the arn + Args: + lambda_context: Aws lambda context object + ex: lambda_context.arn = arn:aws:lambda:us-east-1:123597598159:function:my-lambda:1 + """ + # Set up flag for extra testing to distinguish between a version or alias + hasAlias = False + # Cap the number of times to spli + split_arn = lambda_context.invoked_function_arn.split(":") + + if len(split_arn) > 7: + hasAlias = True + _, _, _, region, account_id, _, function_name, alias = split_arn + else: + _, _, _, region, account_id, _, function_name = split_arn + + # Add the standard tags to a list + tags = [ + "region:{}".format(region), + "account_id:{}".format(account_id), + "functionname:{}".format(function_name), + ] + + # Check if we have a version or alias + if hasAlias: + # If $Latest, drop the $ for datadog tag convention. A lambda alias can't start with $ + if alias.startswith("$"): + alias = alias[1:] + # Versions are numeric. Aliases need the executed version tag + elif not check_if_number(alias): + tags.append("executedversion:{}".format(lambda_context.function_version)) + # create resource tag with function name and alias/version + resource = "resource:{}:{}".format(function_name, alias) + else: + # Resource is only the function name otherwise + resource = "resource:{}".format(function_name) + + tags.append(resource) + + return tags + + +def get_runtime_tag(): + """Get the runtime tag from the current Python version""" + major_version, minor_version, _ = python_version_tuple() + + return "runtime:python{major}.{minor}".format( + major=major_version, minor=minor_version + ) + + +def get_library_version_tag(): + """Get datadog lambda library tag""" + return "datadog_lambda:v{}".format(__version__) + + +def get_enhanced_metrics_tags(lambda_context): + """Get the list of tags to apply to enhanced metrics""" + return parse_lambda_tags_from_arn(lambda_context) + [ + get_cold_start_tag(), + "memorysize:{}".format(lambda_context.memory_limit_in_mb), + get_runtime_tag(), + get_library_version_tag(), + ] + + +def check_if_number(alias): + """ + Check if the alias is a version or number. + Python 2 has no easy way to test this like Python 3 + """ + try: + float(alias) + return True + except ValueError: + return False diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/thread_stats_writer.py b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/thread_stats_writer.py new file mode 100644 index 0000000..bfcf3c9 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/thread_stats_writer.py @@ -0,0 +1,65 @@ +import logging + +# Make sure that this package would always be lazy-loaded/outside from the critical path +# since underlying packages are quite heavy to load and useless when the extension is present +from datadog.threadstats import ThreadStats +from datadog_lambda.stats_writer import StatsWriter + +logger = logging.getLogger(__name__) + + +class ThreadStatsWriter(StatsWriter): + """ + Writes distribution metrics using the ThreadStats class + """ + + def __init__(self, flush_in_thread): + self.thread_stats = ThreadStats(compress_payload=True) + self.thread_stats.start(flush_in_thread=flush_in_thread) + + def distribution(self, metric_name, value, tags=[], timestamp=None): + self.thread_stats.distribution( + metric_name, value, tags=tags, timestamp=timestamp + ) + + def flush(self): + """ "Flush distributions from ThreadStats to Datadog. + Modified based on `datadog.threadstats.base.ThreadStats.flush()`, + to gain better control over exception handling. + """ + _, dists = self.thread_stats._get_aggregate_metrics_and_dists(float("inf")) + count_dists = len(dists) + if not count_dists: + logger.debug("No distributions to flush. Continuing.") + + self.thread_stats.flush_count += 1 + logger.debug( + "Flush #%s sending %s distributions", + self.thread_stats.flush_count, + count_dists, + ) + try: + self.thread_stats.reporter.flush_distributions(dists) + except Exception as e: + # The nature of the root issue https://bugs.python.org/issue41345 is complex, + # but comprehensive tests suggest that it is safe to retry on this specific error. + if type(e).__name__ == "ClientError" and "RemoteDisconnected" in str(e): + logger.debug( + "Retry flush #%s due to RemoteDisconnected", + self.thread_stats.flush_count, + ) + try: + self.thread_stats.reporter.flush_distributions(dists) + except Exception: + logger.debug( + "Flush #%s failed after retry", + self.thread_stats.flush_count, + exc_info=True, + ) + else: + logger.debug( + "Flush #%s failed", self.thread_stats.flush_count, exc_info=True + ) + + def stop(self): + self.thread_stats.stop() diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/tracing.py b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/tracing.py new file mode 100644 index 0000000..dc7e32b --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/tracing.py @@ -0,0 +1,1308 @@ +# Unless explicitly stated otherwise all files in this repository are licensed +# under the Apache License Version 2.0. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2019 Datadog, Inc. +import hashlib +import logging +import os +import json +import base64 +from datetime import datetime, timezone +from typing import Optional, Dict + +from datadog_lambda.metric import submit_errors_metric + +try: + from typing import Literal +except ImportError: + # Literal was added to typing in python 3.8 + from typing_extensions import Literal + +from datadog_lambda.constants import ( + SamplingPriority, + TraceContextSource, + XrayDaemon, + Headers, +) +from datadog_lambda.xray import ( + send_segment, + parse_xray_header, +) +from ddtrace import tracer, patch, Span +from ddtrace import __version__ as ddtrace_version +from ddtrace.propagation.http import HTTPPropagator +from ddtrace.context import Context +from datadog_lambda import __version__ as datadog_lambda_version +from datadog_lambda.trigger import ( + _EventSource, + parse_event_source, + get_first_record, + EventTypes, + EventSubtypes, +) + +dd_trace_otel_enabled = ( + os.environ.get("DD_TRACE_OTEL_ENABLED", "false").lower() == "true" +) +if dd_trace_otel_enabled: + from opentelemetry.trace import set_tracer_provider + from ddtrace.opentelemetry import TracerProvider + + set_tracer_provider(TracerProvider()) + + +logger = logging.getLogger(__name__) + +dd_trace_context = None +dd_tracing_enabled = os.environ.get("DD_TRACE_ENABLED", "false").lower() == "true" +if dd_tracing_enabled: + # Enable the telemetry client if the user has opted in + if ( + os.environ.get("DD_INSTRUMENTATION_TELEMETRY_ENABLED", "false").lower() + == "true" + ): + from ddtrace.internal.telemetry import telemetry_writer + + telemetry_writer.enable() + +propagator = HTTPPropagator() + + +def _convert_xray_trace_id(xray_trace_id): + """ + Convert X-Ray trace id (hex)'s last 63 bits to a Datadog trace id (int). + """ + return 0x7FFFFFFFFFFFFFFF & int(xray_trace_id[-16:], 16) + + +def _convert_xray_entity_id(xray_entity_id): + """ + Convert X-Ray (sub)segement id (hex) to a Datadog span id (int). + """ + return int(xray_entity_id, 16) + + +def _convert_xray_sampling(xray_sampled): + """ + Convert X-Ray sampled (True/False) to its Datadog counterpart. + """ + return SamplingPriority.USER_KEEP if xray_sampled else SamplingPriority.USER_REJECT + + +def _get_xray_trace_context(): + if not is_lambda_context(): + return None + + xray_trace_entity = parse_xray_header( + os.environ.get(XrayDaemon.XRAY_TRACE_ID_HEADER_NAME, "") + ) + if xray_trace_entity is None: + return None + trace_context = Context( + trace_id=_convert_xray_trace_id(xray_trace_entity.get("trace_id")), + span_id=_convert_xray_entity_id(xray_trace_entity.get("parent_id")), + sampling_priority=_convert_xray_sampling(xray_trace_entity.get("sampled")), + ) + logger.debug( + "Converted trace context %s from X-Ray segment %s", + trace_context, + ( + xray_trace_entity["trace_id"], + xray_trace_entity["parent_id"], + xray_trace_entity["sampled"], + ), + ) + return trace_context + + +def _get_dd_trace_py_context(): + span = tracer.current_span() + if not span: + return None + + logger.debug( + "found dd trace context: %s", (span.context.trace_id, span.context.span_id) + ) + return span.context + + +def _is_context_complete(context): + return ( + context + and context.trace_id + and context.span_id + and context.sampling_priority is not None + ) + + +def create_dd_dummy_metadata_subsegment( + subsegment_metadata_value, subsegment_metadata_key +): + """ + Create a Datadog subsegment to pass the Datadog trace context or Lambda function + tags into its metadata field, so the X-Ray trace can be converted to a Datadog + trace in the Datadog backend with the correct context. + """ + send_segment(subsegment_metadata_key, subsegment_metadata_value) + + +def extract_context_from_lambda_context(lambda_context): + """ + Extract Datadog trace context from the `client_context` attr + from the Lambda `context` object. + + dd_trace libraries inject this trace context on synchronous invocations + """ + dd_data = None + client_context = lambda_context.client_context + if client_context and client_context.custom: + dd_data = client_context.custom + if "_datadog" in client_context.custom: + # Legacy trace propagation dict + dd_data = client_context.custom.get("_datadog") + return propagator.extract(dd_data) + + +def extract_context_from_http_event_or_context( + event, + lambda_context, + event_source: _EventSource, + decode_authorizer_context: bool = True, +): + """ + Extract Datadog trace context from the `headers` key in from the Lambda + `event` object. + + Falls back to lambda context if no trace data is found in the `headers` + """ + if decode_authorizer_context: + is_http_api = event_source.equals( + EventTypes.API_GATEWAY, subtype=EventSubtypes.HTTP_API + ) + injected_authorizer_data = get_injected_authorizer_data(event, is_http_api) + context = propagator.extract(injected_authorizer_data) + if _is_context_complete(context): + return context + + headers = event.get("headers") + context = propagator.extract(headers) + + if not _is_context_complete(context): + return extract_context_from_lambda_context(lambda_context) + + return context + + +def create_sns_event(message): + return { + "Records": [ + { + "EventSource": "aws:sns", + "EventVersion": "1.0", + "Sns": message, + } + ] + } + + +def extract_context_from_sqs_or_sns_event_or_context(event, lambda_context): + """ + Extract Datadog trace context from an SQS event. + + The extraction chain goes as follows: + EB => SQS (First records body contains EB context), or + SNS => SQS (First records body contains SNS context), or + SQS or SNS (`messageAttributes` for SQS context, + `MessageAttributes` for SNS context), else + Lambda Context. + + Falls back to lambda context if no trace data is found in the SQS message attributes. + """ + + # EventBridge => SQS + try: + context = _extract_context_from_eventbridge_sqs_event(event) + if _is_context_complete(context): + return context + except Exception: + logger.debug("Failed extracting context as EventBridge to SQS.") + + try: + first_record = event.get("Records")[0] + + # logic to deal with SNS => SQS event + if "body" in first_record: + body_str = first_record.get("body", {}) + try: + body = json.loads(body_str) + if body.get("Type", "") == "Notification" and "TopicArn" in body: + logger.debug("Found SNS message inside SQS event") + first_record = get_first_record(create_sns_event(body)) + except Exception: + first_record = event.get("Records")[0] + pass + + msg_attributes = first_record.get( + "messageAttributes", + first_record.get("Sns", {}).get("MessageAttributes", {}), + ) + dd_payload = msg_attributes.get("_datadog", {}) + # SQS uses dataType and binaryValue/stringValue + # SNS uses Type and Value + dd_json_data_type = dd_payload.get("Type", dd_payload.get("dataType", "")) + if dd_json_data_type == "Binary": + dd_json_data = dd_payload.get( + "binaryValue", + dd_payload.get("Value", r"{}"), + ) + dd_json_data = base64.b64decode(dd_json_data) + elif dd_json_data_type == "String": + dd_json_data = dd_payload.get( + "stringValue", + dd_payload.get("Value", r"{}"), + ) + else: + logger.debug( + "Datadog Lambda Python only supports extracting trace" + "context from String or Binary SQS/SNS message attributes" + ) + return extract_context_from_lambda_context(lambda_context) + dd_data = json.loads(dd_json_data) + return propagator.extract(dd_data) + except Exception as e: + logger.debug("The trace extractor returned with error %s", e) + return extract_context_from_lambda_context(lambda_context) + + +def _extract_context_from_eventbridge_sqs_event(event): + """ + Extracts Datadog trace context from an SQS event triggered by + EventBridge. + + This is only possible if first record in `Records` contains a + `body` field which contains the EventBridge `detail` as a JSON string. + """ + first_record = event.get("Records")[0] + body_str = first_record.get("body") + body = json.loads(body_str) + detail = body.get("detail") + dd_context = detail.get("_datadog") + return propagator.extract(dd_context) + + +def extract_context_from_eventbridge_event(event, lambda_context): + """ + Extract datadog trace context from an EventBridge message's Details. + This is only possible if Details is a JSON string. + """ + try: + detail = event.get("detail") + dd_context = detail.get("_datadog") + if not dd_context: + return extract_context_from_lambda_context(lambda_context) + return propagator.extract(dd_context) + except Exception as e: + logger.debug("The trace extractor returned with error %s", e) + return extract_context_from_lambda_context(lambda_context) + + +def extract_context_from_kinesis_event(event, lambda_context): + """ + Extract datadog trace context from a Kinesis Stream's base64 encoded data string + """ + try: + record = get_first_record(event) + data = record.get("kinesis", {}).get("data", None) + if data: + b64_bytes = data.encode("ascii") + str_bytes = base64.b64decode(b64_bytes) + data_str = str_bytes.decode("ascii") + data_obj = json.loads(data_str) + dd_ctx = data_obj.get("_datadog") + + if not dd_ctx: + return extract_context_from_lambda_context(lambda_context) + + return propagator.extract(dd_ctx) + except Exception as e: + logger.debug("The trace extractor returned with error %s", e) + return extract_context_from_lambda_context(lambda_context) + + +def _deterministic_md5_hash(s: str) -> int: + """MD5 here is to generate trace_id, not for any encryption.""" + hex_number = hashlib.md5(s.encode("ascii")).hexdigest() + binary = bin(int(hex_number, 16)) + binary_str = str(binary) + binary_str_remove_0b = binary_str[2:].rjust(128, "0") + most_significant_64_bits_without_leading_1 = "0" + binary_str_remove_0b[1:-64] + result = int(most_significant_64_bits_without_leading_1, 2) + if result == 0: + return 1 + return result + + +def extract_context_from_step_functions(event, lambda_context): + """ + Only extract datadog trace context when Step Functions Context Object is injected + into lambda's event dict. + """ + try: + execution_id = event.get("Execution").get("Id") + state_name = event.get("State").get("Name") + state_entered_time = event.get("State").get("EnteredTime") + trace_id = _deterministic_md5_hash(execution_id) + parent_id = _deterministic_md5_hash( + execution_id + "#" + state_name + "#" + state_entered_time + ) + sampling_priority = SamplingPriority.AUTO_KEEP + return Context( + trace_id=trace_id, span_id=parent_id, sampling_priority=sampling_priority + ) + except Exception as e: + logger.debug("The Step Functions trace extractor returned with error %s", e) + return extract_context_from_lambda_context(lambda_context) + + +def extract_context_custom_extractor(extractor, event, lambda_context): + """ + Extract Datadog trace context using a custom trace extractor function + """ + try: + ( + trace_id, + parent_id, + sampling_priority, + ) = extractor(event, lambda_context) + return Context( + trace_id=int(trace_id), + span_id=int(parent_id), + sampling_priority=int(sampling_priority), + ) + except Exception as e: + logger.debug("The trace extractor returned with error %s", e) + + +def is_authorizer_response(response) -> bool: + try: + return ( + response is not None + and response["principalId"] + and response["policyDocument"] + ) + except (KeyError, AttributeError): + pass + except Exception as e: + logger.debug("unknown error while checking is_authorizer_response %s", e) + return False + + +def get_injected_authorizer_data(event, is_http_api) -> dict: + try: + authorizer_headers = event.get("requestContext", {}).get("authorizer") + if not authorizer_headers: + return None + + dd_data_raw = ( + authorizer_headers.get("lambda", {}).get("_datadog") + if is_http_api + else authorizer_headers.get("_datadog") + ) + + if not dd_data_raw: + return None + + injected_data = json.loads(base64.b64decode(dd_data_raw)) + + # Lambda authorizer's results can be cached. But the payload will still have the injected + # data in cached requests. How to distinguish cached case and ignore the injected data ? + # APIGateway automatically injects a integrationLatency data in some cases. If it's >0 we + # know that it's not cached. But integrationLatency is not available for Http API case. In + # that case, we use the injected Authorizing_Request_Id to tell if it's cached. But token + # authorizers don't pass on the requestId. The Authorizing_Request_Id can't work for all + # cases neither. As a result, we combine both methods as shown below. + if authorizer_headers.get("integrationLatency", 0) > 0 or event.get( + "requestContext", {} + ).get("requestId") == injected_data.get(Headers.Authorizing_Request_Id): + return injected_data + else: + return None + + except Exception as e: + logger.debug("Failed to check if invocated by an authorizer. error %s", e) + return None + + +def extract_dd_trace_context( + event, lambda_context, extractor=None, decode_authorizer_context: bool = True +): + """ + Extract Datadog trace context from the Lambda `event` object. + + Write the context to a global `dd_trace_context`, so the trace + can be continued on the outgoing requests with the context injected. + """ + global dd_trace_context + trace_context_source = None + event_source = parse_event_source(event) + + if extractor is not None: + context = extract_context_custom_extractor(extractor, event, lambda_context) + elif isinstance(event, (set, dict)) and "headers" in event: + context = extract_context_from_http_event_or_context( + event, lambda_context, event_source, decode_authorizer_context + ) + elif event_source.equals(EventTypes.SNS) or event_source.equals(EventTypes.SQS): + context = extract_context_from_sqs_or_sns_event_or_context( + event, lambda_context + ) + elif event_source.equals(EventTypes.EVENTBRIDGE): + context = extract_context_from_eventbridge_event(event, lambda_context) + elif event_source.equals(EventTypes.KINESIS): + context = extract_context_from_kinesis_event(event, lambda_context) + elif event_source.equals(EventTypes.STEPFUNCTIONS): + context = extract_context_from_step_functions(event, lambda_context) + else: + context = extract_context_from_lambda_context(lambda_context) + + if _is_context_complete(context): + logger.debug("Extracted Datadog trace context from event or context") + dd_trace_context = context + trace_context_source = TraceContextSource.EVENT + else: + # AWS Lambda runtime caches global variables between invocations, + # reset to avoid using the context from the last invocation. + dd_trace_context = _get_xray_trace_context() + if dd_trace_context: + trace_context_source = TraceContextSource.XRAY + logger.debug("extracted dd trace context %s", dd_trace_context) + return dd_trace_context, trace_context_source, event_source + + +def get_dd_trace_context_obj(): + """ + Return the Datadog trace context to be propagated on the outgoing requests. + + If the Lambda function is invoked by a Datadog-traced service, a Datadog + trace context may already exist, and it should be used. Otherwise, use the + current X-Ray trace entity, or the dd-trace-py context if DD_TRACE_ENABLED is true. + + Most of widely-used HTTP clients are patched to inject the context + automatically, but this function can be used to manually inject the trace + context to an outgoing request. + """ + if dd_tracing_enabled: + dd_trace_py_context = _get_dd_trace_py_context() + if _is_context_complete(dd_trace_py_context): + return dd_trace_py_context + + global dd_trace_context + + try: + xray_context = _get_xray_trace_context() # xray (sub)segment + except Exception as e: + logger.debug( + "get_dd_trace_context couldn't read from segment from x-ray, with error %s" + % e + ) + if not xray_context: + return None + + if not _is_context_complete(dd_trace_context): + return xray_context + + logger.debug("Set parent id from xray trace context: %s", xray_context.span_id) + return Context( + trace_id=dd_trace_context.trace_id, + span_id=xray_context.span_id, + sampling_priority=dd_trace_context.sampling_priority, + meta=dd_trace_context._meta.copy(), + metrics=dd_trace_context._metrics.copy(), + ) + + +def get_dd_trace_context(): + """ + Return the Datadog trace context to be propagated on the outgoing requests, + as a dict of headers. + """ + headers = {} + context = get_dd_trace_context_obj() + if not _is_context_complete(context): + return headers + propagator.inject(context, headers) + return headers + + +def set_correlation_ids(): + """ + Create a dummy span, and overrides its trace_id and span_id, to make + ddtrace.helpers.get_log_correlation_context() return a dict containing the correct ids for both + auto and manual log correlations. + + TODO: Remove me when Datadog tracer is natively supported in Lambda. + """ + if not is_lambda_context(): + logger.debug("set_correlation_ids is only supported in LambdaContext") + return + if dd_tracing_enabled: + logger.debug("using ddtrace implementation for spans") + return + + context = get_dd_trace_context_obj() + if not _is_context_complete(context): + return + + tracer.context_provider.activate(context) + tracer.trace("dummy.span") + logger.debug("correlation ids set") + + +def inject_correlation_ids(): + """ + Override the formatter of LambdaLoggerHandler to inject datadog trace and + span id for log correlation. + + For manual injections to custom log handlers, use `ddtrace.helpers.get_log_correlation_context` + to retrieve a dict containing correlation ids (trace_id, span_id). + """ + # Override the log format of the AWS provided LambdaLoggerHandler + root_logger = logging.getLogger() + for handler in root_logger.handlers: + if handler.__class__.__name__ == "LambdaLoggerHandler" and isinstance( + handler.formatter, logging.Formatter + ): + handler.setFormatter( + logging.Formatter( + "[%(levelname)s]\t%(asctime)s.%(msecs)dZ\t%(aws_request_id)s\t" + "[dd.trace_id=%(dd.trace_id)s dd.span_id=%(dd.span_id)s]\t%(message)s\n", + "%Y-%m-%dT%H:%M:%S", + ) + ) + + # Patch `logging.Logger.makeRecord` to actually inject correlation ids + patch(logging=True) + + logger.debug("logs injection configured") + + +def is_lambda_context(): + """ + Return True if the X-Ray context is `LambdaContext`, rather than the + regular `Context` (e.g., when testing lambda functions locally). + """ + return os.environ.get(XrayDaemon.FUNCTION_NAME_HEADER_NAME, "") != "" + + +def set_dd_trace_py_root(trace_context_source, merge_xray_traces): + if trace_context_source == TraceContextSource.EVENT or merge_xray_traces: + context = Context( + trace_id=dd_trace_context.trace_id, + span_id=dd_trace_context.span_id, + sampling_priority=dd_trace_context.sampling_priority, + ) + if merge_xray_traces: + xray_context = _get_xray_trace_context() + if xray_context and xray_context.span_id: + context.span_id = xray_context.span_id + + tracer.context_provider.activate(context) + logger.debug( + "Set dd trace root context to: %s", + (context.trace_id, context.span_id), + ) + + +def create_inferred_span( + event, + context, + event_source: _EventSource = None, + decode_authorizer_context: bool = True, +): + if event_source is None: + event_source = parse_event_source(event) + try: + if event_source.equals( + EventTypes.API_GATEWAY, subtype=EventSubtypes.API_GATEWAY + ): + logger.debug("API Gateway event detected. Inferring a span") + return create_inferred_span_from_api_gateway_event( + event, context, decode_authorizer_context + ) + elif event_source.equals(EventTypes.LAMBDA_FUNCTION_URL): + logger.debug("Function URL event detected. Inferring a span") + return create_inferred_span_from_lambda_function_url_event(event, context) + elif event_source.equals( + EventTypes.API_GATEWAY, subtype=EventSubtypes.HTTP_API + ): + logger.debug("HTTP API event detected. Inferring a span") + return create_inferred_span_from_http_api_event( + event, context, decode_authorizer_context + ) + elif event_source.equals( + EventTypes.API_GATEWAY, subtype=EventSubtypes.WEBSOCKET + ): + logger.debug("API Gateway Websocket event detected. Inferring a span") + return create_inferred_span_from_api_gateway_websocket_event( + event, context, decode_authorizer_context + ) + elif event_source.equals(EventTypes.SQS): + logger.debug("SQS event detected. Inferring a span") + return create_inferred_span_from_sqs_event(event, context) + elif event_source.equals(EventTypes.SNS): + logger.debug("SNS event detected. Inferring a span") + return create_inferred_span_from_sns_event(event, context) + elif event_source.equals(EventTypes.KINESIS): + logger.debug("Kinesis event detected. Inferring a span") + return create_inferred_span_from_kinesis_event(event, context) + elif event_source.equals(EventTypes.DYNAMODB): + logger.debug("Dynamodb event detected. Inferring a span") + return create_inferred_span_from_dynamodb_event(event, context) + elif event_source.equals(EventTypes.S3): + logger.debug("S3 event detected. Inferring a span") + return create_inferred_span_from_s3_event(event, context) + elif event_source.equals(EventTypes.EVENTBRIDGE): + logger.debug("Eventbridge event detected. Inferring a span") + return create_inferred_span_from_eventbridge_event(event, context) + except Exception as e: + logger.debug( + "Unable to infer span. Detected type: %s. Reason: %s", + event_source.to_string(), + e, + ) + return None + logger.debug("Unable to infer a span: unknown event type") + return None + + +def create_service_mapping(val): + new_service_mapping = {} + for entry in val.split(","): + parts = entry.split(":") + if len(parts) == 2: + key = parts[0].strip() + value = parts[1].strip() + if key != value and key and value: + new_service_mapping[key] = value + return new_service_mapping + + +def determine_service_name(service_mapping, specific_key, generic_key, default_value): + service_name = service_mapping.get(specific_key) + if service_name is None: + service_name = service_mapping.get(generic_key, default_value) + return service_name + + +service_mapping = {} +# Initialization code +service_mapping_str = os.getenv("DD_SERVICE_MAPPING", "") +service_mapping = create_service_mapping(service_mapping_str) + + +def create_inferred_span_from_lambda_function_url_event(event, context): + request_context = event.get("requestContext") + api_id = request_context.get("apiId") + domain = request_context.get("domainName") + service_name = determine_service_name(service_mapping, api_id, "lambda_url", domain) + method = request_context.get("http", {}).get("method") + path = request_context.get("http", {}).get("path") + resource = "{0} {1}".format(method, path) + tags = { + "operation_name": "aws.lambda.url", + "http.url": domain + path, + "endpoint": path, + "http.method": method, + "resource_names": domain + path, + "request_id": context.aws_request_id, + } + request_time_epoch = request_context.get("timeEpoch") + args = { + "service": service_name, + "resource": resource, + "span_type": "http", + } + tracer.set_tags( + {"_dd.origin": "lambda"} + ) # function urls don't count as lambda_inferred, + # because they're in the same service as the inferring lambda function + span = tracer.trace("aws.lambda.url", **args) + InferredSpanInfo.set_tags(tags, tag_source="self", synchronicity="sync") + if span: + span.set_tags(tags) + span.start = request_time_epoch / 1000 + return span + + +def is_api_gateway_invocation_async(event): + return event.get("headers", {}).get("X-Amz-Invocation-Type") == "Event" + + +def insert_upstream_authorizer_span( + kwargs_to_start_span, other_tags_for_span, start_time_ns, finish_time_ns +): + """Insert the authorizer span. + Without this: parent span --child-> inferred span + With this insertion: parent span --child-> upstreamAuthorizerSpan --child-> inferred span + + Args: + kwargs_to_start_span (Dict): the same keyword arguments used for the inferred span + other_tags_for_span (Dict): the same tag keyword arguments used for the inferred span + start_time_ns (int): the start time of the span in nanoseconds + finish_time_ns (int): the finish time of the sapn in nanoseconds + """ + trace_ctx = tracer.current_trace_context() + upstream_authorizer_span = tracer.trace( + "aws.apigateway.authorizer", **kwargs_to_start_span + ) + upstream_authorizer_span.set_tags(other_tags_for_span) + upstream_authorizer_span.set_tag("operation_name", "aws.apigateway.authorizer") + # always sync for the authorizer invocation + InferredSpanInfo.set_tags_to_span(upstream_authorizer_span, synchronicity="sync") + upstream_authorizer_span.start_ns = int(start_time_ns) + upstream_authorizer_span.finish(finish_time_ns / 1e9) + # trace context needs to be set again as it is reset by finish() + tracer.context_provider.activate(trace_ctx) + return upstream_authorizer_span + + +def process_injected_data(event, request_time_epoch_ms, args, tags): + """ + This covers the ApiGateway RestAPI and Websocket cases. It doesn't cover Http API cases. + """ + injected_authorizer_data = get_injected_authorizer_data(event, False) + if injected_authorizer_data: + try: + start_time_ns = int( + injected_authorizer_data.get(Headers.Parent_Span_Finish_Time) + ) + finish_time_ns = ( + request_time_epoch_ms + + ( + int( + event["requestContext"]["authorizer"].get( + "integrationLatency", 0 + ) + ) + ) + ) * 1e6 + upstream_authorizer_span = insert_upstream_authorizer_span( + args, tags, start_time_ns, finish_time_ns + ) + return upstream_authorizer_span, finish_time_ns + except Exception as e: + logger.debug( + "Unable to insert authorizer span. Continue to generate the main span.\ + Reason: %s", + e, + ) + return None, None + else: + return None, None + + +def create_inferred_span_from_api_gateway_websocket_event( + event, context, decode_authorizer_context: bool = True +): + request_context = event.get("requestContext") + domain = request_context.get("domainName") + endpoint = request_context.get("routeKey") + api_id = request_context.get("apiId") + + service_name = determine_service_name( + service_mapping, api_id, "lambda_api_gateway", domain + ) + tags = { + "operation_name": "aws.apigateway.websocket", + "http.url": domain + endpoint, + "endpoint": endpoint, + "resource_names": endpoint, + "apiid": api_id, + "apiname": api_id, + "stage": request_context.get("stage"), + "request_id": context.aws_request_id, + "connection_id": request_context.get("connectionId"), + "event_type": request_context.get("eventType"), + "message_direction": request_context.get("messageDirection"), + } + request_time_epoch_ms = int(request_context.get("requestTimeEpoch")) + if is_api_gateway_invocation_async(event): + InferredSpanInfo.set_tags(tags, tag_source="self", synchronicity="async") + else: + InferredSpanInfo.set_tags(tags, tag_source="self", synchronicity="sync") + args = { + "service": service_name, + "resource": endpoint, + "span_type": "web", + } + tracer.set_tags({"_dd.origin": "lambda"}) + upstream_authorizer_span = None + finish_time_ns = None + if decode_authorizer_context: + upstream_authorizer_span, finish_time_ns = process_injected_data( + event, request_time_epoch_ms, args, tags + ) + span = tracer.trace("aws.apigateway.websocket", **args) + if span: + span.set_tags(tags) + span.start_ns = int( + finish_time_ns + if finish_time_ns is not None + else request_time_epoch_ms * 1e6 + ) + if upstream_authorizer_span: + span.parent_id = upstream_authorizer_span.span_id + return span + + +def create_inferred_span_from_api_gateway_event( + event, context, decode_authorizer_context: bool = True +): + request_context = event.get("requestContext") + domain = request_context.get("domainName", "") + api_id = request_context.get("apiId") + service_name = determine_service_name( + service_mapping, api_id, "lambda_api_gateway", domain + ) + method = event.get("httpMethod") + path = event.get("path") + resource = "{0} {1}".format(method, path) + tags = { + "operation_name": "aws.apigateway.rest", + "http.url": domain + path, + "endpoint": path, + "http.method": method, + "resource_names": resource, + "apiid": api_id, + "apiname": api_id, + "stage": request_context.get("stage"), + "request_id": context.aws_request_id, + } + request_time_epoch_ms = int(request_context.get("requestTimeEpoch")) + if is_api_gateway_invocation_async(event): + InferredSpanInfo.set_tags(tags, tag_source="self", synchronicity="async") + else: + InferredSpanInfo.set_tags(tags, tag_source="self", synchronicity="sync") + args = { + "service": service_name, + "resource": resource, + "span_type": "http", + } + tracer.set_tags({"_dd.origin": "lambda"}) + upstream_authorizer_span = None + finish_time_ns = None + if decode_authorizer_context: + upstream_authorizer_span, finish_time_ns = process_injected_data( + event, request_time_epoch_ms, args, tags + ) + span = tracer.trace("aws.apigateway", **args) + if span: + span.set_tags(tags) + # start time pushed by the inserted authorizer span + span.start_ns = int( + finish_time_ns + if finish_time_ns is not None + else request_time_epoch_ms * 1e6 + ) + if upstream_authorizer_span: + span.parent_id = upstream_authorizer_span.span_id + return span + + +def create_inferred_span_from_http_api_event( + event, context, decode_authorizer_context: bool = True +): + request_context = event.get("requestContext") + domain = request_context.get("domainName") + api_id = request_context.get("apiId") + service_name = determine_service_name( + service_mapping, api_id, "lambda_api_gateway", domain + ) + method = request_context.get("http", {}).get("method") + path = event.get("rawPath") + resource = "{0} {1}".format(method, path) + tags = { + "operation_name": "aws.httpapi", + "endpoint": path, + "http.url": domain + path, + "http.method": request_context.get("http", {}).get("method"), + "http.protocol": request_context.get("http", {}).get("protocol"), + "http.source_ip": request_context.get("http", {}).get("sourceIp"), + "http.user_agent": request_context.get("http", {}).get("userAgent"), + "resource_names": resource, + "request_id": context.aws_request_id, + "apiid": api_id, + "apiname": api_id, + "stage": request_context.get("stage"), + } + request_time_epoch_ms = int(request_context.get("timeEpoch")) + if is_api_gateway_invocation_async(event): + InferredSpanInfo.set_tags(tags, tag_source="self", synchronicity="async") + else: + InferredSpanInfo.set_tags(tags, tag_source="self", synchronicity="sync") + args = { + "service": service_name, + "resource": resource, + "span_type": "http", + } + tracer.set_tags({"_dd.origin": "lambda"}) + inferred_span_start_ns = request_time_epoch_ms * 1e6 + if decode_authorizer_context: + injected_authorizer_data = get_injected_authorizer_data(event, True) + if injected_authorizer_data: + inferred_span_start_ns = injected_authorizer_data.get( + Headers.Parent_Span_Finish_Time + ) + span = tracer.trace("aws.httpapi", **args) + if span: + span.set_tags(tags) + span.start_ns = int(inferred_span_start_ns) + return span + + +def create_inferred_span_from_sqs_event(event, context): + trace_ctx = tracer.current_trace_context() + + event_record = get_first_record(event) + event_source_arn = event_record.get("eventSourceARN") + queue_name = event_source_arn.split(":")[-1] + service_name = determine_service_name( + service_mapping, queue_name, "lambda_sqs", "sqs" + ) + tags = { + "operation_name": "aws.sqs", + "resource_names": queue_name, + "queuename": queue_name, + "event_source_arn": event_source_arn, + "receipt_handle": event_record.get("receiptHandle"), + "sender_id": event_record.get("attributes", {}).get("SenderId"), + } + InferredSpanInfo.set_tags(tags, tag_source="self", synchronicity="async") + request_time_epoch = event_record.get("attributes", {}).get("SentTimestamp") + args = { + "service": service_name, + "resource": queue_name, + "span_type": "web", + } + start_time = int(request_time_epoch) / 1000 + + upstream_span = None + if "body" in event_record: + body_str = event_record.get("body", {}) + try: + body = json.loads(body_str) + + # logic to deal with SNS => SQS event + if body.get("Type", "") == "Notification" and "TopicArn" in body: + logger.debug("Found SNS message inside SQS event") + upstream_span = create_inferred_span_from_sns_event( + create_sns_event(body), context + ) + upstream_span.finish(finish_time=start_time) + + # EventBridge => SQS + elif body.get("detail"): + detail = body.get("detail") + if detail.get("_datadog"): + logger.debug("Found an EventBridge message inside SQS event") + upstream_span = create_inferred_span_from_eventbridge_event( + body, context + ) + upstream_span.finish(finish_time=start_time) + + except Exception as e: + logger.debug( + "Unable to create upstream span from SQS message, with error %s" % e + ) + pass + + # trace context needs to be set again as it is reset + # when sns_span.finish executes + tracer.context_provider.activate(trace_ctx) + tracer.set_tags({"_dd.origin": "lambda"}) + span = tracer.trace("aws.sqs", **args) + if span: + span.set_tags(tags) + span.start = start_time + if upstream_span: + span.parent_id = upstream_span.span_id + + return span + + +def create_inferred_span_from_sns_event(event, context): + event_record = get_first_record(event) + sns_message = event_record.get("Sns") + topic_arn = event_record.get("Sns", {}).get("TopicArn") + topic_name = topic_arn.split(":")[-1] + service_name = determine_service_name( + service_mapping, topic_name, "lambda_sns", "sns" + ) + tags = { + "operation_name": "aws.sns", + "resource_names": topic_name, + "topicname": topic_name, + "topic_arn": topic_arn, + "message_id": sns_message.get("MessageId"), + "type": sns_message.get("Type"), + } + + # Subject not available in SNS => SQS scenario + if "Subject" in sns_message and sns_message["Subject"]: + tags["subject"] = sns_message.get("Subject") + + InferredSpanInfo.set_tags(tags, tag_source="self", synchronicity="async") + sns_dt_format = "%Y-%m-%dT%H:%M:%S.%fZ" + timestamp = event_record.get("Sns", {}).get("Timestamp") + dt = datetime.strptime(timestamp, sns_dt_format) + + args = { + "service": service_name, + "resource": topic_name, + "span_type": "web", + } + tracer.set_tags({"_dd.origin": "lambda"}) + span = tracer.trace("aws.sns", **args) + if span: + span.set_tags(tags) + span.start = dt.replace(tzinfo=timezone.utc).timestamp() + return span + + +def create_inferred_span_from_kinesis_event(event, context): + event_record = get_first_record(event) + event_source_arn = event_record.get("eventSourceARN") + event_id = event_record.get("eventID") + stream_name = event_source_arn.split(":")[-1] + shard_id = event_id.split(":")[0] + service_name = determine_service_name( + service_mapping, stream_name, "lambda_kinesis", "kinesis" + ) + tags = { + "operation_name": "aws.kinesis", + "resource_names": stream_name, + "streamname": stream_name, + "shardid": shard_id, + "event_source_arn": event_source_arn, + "event_id": event_id, + "event_name": event_record.get("eventName"), + "event_version": event_record.get("eventVersion"), + "partition_key": event_record.get("kinesis", {}).get("partitionKey"), + } + InferredSpanInfo.set_tags(tags, tag_source="self", synchronicity="async") + request_time_epoch = event_record.get("kinesis", {}).get( + "approximateArrivalTimestamp" + ) + + args = { + "service": service_name, + "resource": stream_name, + "span_type": "web", + } + tracer.set_tags({"_dd.origin": "lambda"}) + span = tracer.trace("aws.kinesis", **args) + if span: + span.set_tags(tags) + span.start = request_time_epoch + return span + + +def create_inferred_span_from_dynamodb_event(event, context): + event_record = get_first_record(event) + event_source_arn = event_record.get("eventSourceARN") + table_name = event_source_arn.split("/")[1] + service_name = determine_service_name( + service_mapping, table_name, "lambda_dynamodb", "dynamodb" + ) + dynamodb_message = event_record.get("dynamodb") + tags = { + "operation_name": "aws.dynamodb", + "resource_names": table_name, + "tablename": table_name, + "event_source_arn": event_source_arn, + "event_id": event_record.get("eventID"), + "event_name": event_record.get("eventName"), + "event_version": event_record.get("eventVersion"), + "stream_view_type": dynamodb_message.get("StreamViewType"), + "size_bytes": str(dynamodb_message.get("SizeBytes")), + } + InferredSpanInfo.set_tags(tags, synchronicity="async", tag_source="self") + request_time_epoch = event_record.get("dynamodb", {}).get( + "ApproximateCreationDateTime" + ) + args = { + "service": service_name, + "resource": table_name, + "span_type": "web", + } + tracer.set_tags({"_dd.origin": "lambda"}) + span = tracer.trace("aws.dynamodb", **args) + if span: + span.set_tags(tags) + + span.start = int(request_time_epoch) + return span + + +def create_inferred_span_from_s3_event(event, context): + event_record = get_first_record(event) + bucket_name = event_record.get("s3", {}).get("bucket", {}).get("name") + service_name = determine_service_name( + service_mapping, bucket_name, "lambda_s3", "s3" + ) + tags = { + "operation_name": "aws.s3", + "resource_names": bucket_name, + "event_name": event_record.get("eventName"), + "bucketname": bucket_name, + "bucket_arn": event_record.get("s3", {}).get("bucket", {}).get("arn"), + "object_key": event_record.get("s3", {}).get("object", {}).get("key"), + "object_size": str(event_record.get("s3", {}).get("object", {}).get("size")), + "object_etag": event_record.get("s3", {}).get("object", {}).get("eTag"), + } + InferredSpanInfo.set_tags(tags, synchronicity="async", tag_source="self") + dt_format = "%Y-%m-%dT%H:%M:%S.%fZ" + timestamp = event_record.get("eventTime") + dt = datetime.strptime(timestamp, dt_format) + + args = { + "service": service_name, + "resource": bucket_name, + "span_type": "web", + } + tracer.set_tags({"_dd.origin": "lambda"}) + span = tracer.trace("aws.s3", **args) + if span: + span.set_tags(tags) + span.start = dt.replace(tzinfo=timezone.utc).timestamp() + return span + + +def create_inferred_span_from_eventbridge_event(event, context): + source = event.get("source") + service_name = determine_service_name( + service_mapping, source, "lambda_eventbridge", "eventbridge" + ) + tags = { + "operation_name": "aws.eventbridge", + "resource_names": source, + "detail_type": event.get("detail-type"), + } + InferredSpanInfo.set_tags( + tags, + synchronicity="async", + tag_source="self", + ) + dt_format = "%Y-%m-%dT%H:%M:%SZ" + timestamp = event.get("time") + dt = datetime.strptime(timestamp, dt_format) + + args = { + "service": service_name, + "resource": source, + "span_type": "web", + } + tracer.set_tags({"_dd.origin": "lambda"}) + span = tracer.trace("aws.eventbridge", **args) + if span: + span.set_tags(tags) + span.start = dt.replace(tzinfo=timezone.utc).timestamp() + return span + + +def create_function_execution_span( + context, + function_name, + is_cold_start, + is_proactive_init, + trace_context_source, + merge_xray_traces, + trigger_tags, + parent_span=None, +): + tags = {} + if context: + function_arn = (context.invoked_function_arn or "").lower() + tk = function_arn.split(":") + function_arn = ":".join(tk[0:7]) if len(tk) > 7 else function_arn + function_version = tk[7] if len(tk) > 7 else "$LATEST" + tags = { + "cold_start": str(is_cold_start).lower(), + "function_arn": function_arn, + "function_version": function_version, + "request_id": context.aws_request_id, + "resource_names": context.function_name, + "functionname": context.function_name.lower() + if context.function_name + else None, + "datadog_lambda": datadog_lambda_version, + "dd_trace": ddtrace_version, + "span.name": "aws.lambda", + } + if is_proactive_init: + tags["proactive_initialization"] = str(is_proactive_init).lower() + if trace_context_source == TraceContextSource.XRAY and merge_xray_traces: + tags["_dd.parent_source"] = trace_context_source + tags.update(trigger_tags) + args = { + "service": "aws.lambda", + "resource": function_name, + "span_type": "serverless", + } + tracer.set_tags({"_dd.origin": "lambda"}) + span = tracer.trace("aws.lambda", **args) + if span: + span.set_tags(tags) + if parent_span: + span.parent_id = parent_span.span_id + return span + + +def mark_trace_as_error_for_5xx_responses(context, status_code, span): + if len(status_code) == 3 and status_code.startswith("5"): + submit_errors_metric(context) + if span: + span.error = 1 + + +class InferredSpanInfo(object): + BASE_NAME = "_inferred_span" + SYNCHRONICITY = f"{BASE_NAME}.synchronicity" + TAG_SOURCE = f"{BASE_NAME}.tag_source" + + @staticmethod + def set_tags( + tags: Dict[str, str], + synchronicity: Optional[Literal["sync", "async"]] = None, + tag_source: Optional[Literal["labmda", "self"]] = None, + ): + if synchronicity is not None: + tags[InferredSpanInfo.SYNCHRONICITY] = str(synchronicity) + if tag_source is not None: + tags[InferredSpanInfo.TAG_SOURCE] = str(tag_source) + + @staticmethod + def set_tags_to_span( + span: Span, + synchronicity: Optional[Literal["sync", "async"]] = None, + tag_source: Optional[Literal["labmda", "self"]] = None, + ): + if synchronicity is not None: + span.set_tags({InferredSpanInfo.SYNCHRONICITY: synchronicity}) + if tag_source is not None: + span.set_tags({InferredSpanInfo.TAG_SOURCE: str(tag_source)}) + + @staticmethod + def is_async(span: Span) -> bool: + if not span: + return False + try: + return span.get_tag(InferredSpanInfo.SYNCHRONICITY) == "async" + except Exception as e: + logger.debug( + "Unabled to read the %s tag, returning False. \ + Reason: %s.", + InferredSpanInfo.SYNCHRONICITY, + e, + ) + return False diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/trigger.py b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/trigger.py new file mode 100644 index 0000000..bbb44b3 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/trigger.py @@ -0,0 +1,352 @@ +# Unless explicitly stated otherwise all files in this repository are licensed +# under the Apache License Version 2.0. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2019 Datadog, Inc. + +import base64 +import gzip +import json +from io import BytesIO, BufferedReader +from enum import Enum +from typing import Any + + +class _stringTypedEnum(Enum): + """ + _stringTypedEnum provides a type-hinted convenience function for getting the string value of + an enum. + """ + + def get_string(self) -> str: + return self.value + + +class EventTypes(_stringTypedEnum): + """ + EventTypes is an enum of Lambda event types we care about. + """ + + UNKNOWN = "unknown" + API_GATEWAY = "api-gateway" + APPSYNC = "appsync" + ALB = "application-load-balancer" + CLOUDWATCH_LOGS = "cloudwatch-logs" + CLOUDWATCH_EVENTS = "cloudwatch-events" + CLOUDFRONT = "cloudfront" + DYNAMODB = "dynamodb" + EVENTBRIDGE = "eventbridge" + KINESIS = "kinesis" + LAMBDA_FUNCTION_URL = "lambda-function-url" + S3 = "s3" + SNS = "sns" + SQS = "sqs" + STEPFUNCTIONS = "states" + + +class EventSubtypes(_stringTypedEnum): + """ + EventSubtypes is an enum of Lambda event subtypes. + Currently, API Gateway events subtypes are supported, + e.g. HTTP-API and Websocket events vs vanilla API-Gateway events. + """ + + NONE = "none" + API_GATEWAY = "api-gateway" # regular API Gateway + WEBSOCKET = "websocket" + HTTP_API = "http-api" + + +class _EventSource: + """ + _EventSource holds an event's type and subtype. + """ + + def __init__( + self, + event_type: EventTypes, + subtype: EventSubtypes = EventSubtypes.NONE, + ): + self.event_type = event_type + self.subtype = subtype + + def to_string(self) -> str: + """ + to_string returns the string representation of an _EventSource. + Since to_string was added to support trigger tagging, + the event's subtype will never be included in the string. + """ + return self.event_type.get_string() + + def equals( + self, event_type: EventTypes, subtype: EventSubtypes = EventSubtypes.NONE + ) -> bool: + """ + equals provides syntactic sugar to determine whether this _EventSource has a given type + and subtype. + Unknown events will never equal other events. + """ + if self.event_type == EventTypes.UNKNOWN: + return False + if self.event_type != event_type: + return False + if self.subtype != subtype: + return False + return True + + +def get_aws_partition_by_region(region): + if region.startswith("us-gov-"): + return "aws-us-gov" + if region.startswith("cn-"): + return "aws-cn" + return "aws" + + +def get_first_record(event): + records = event.get("Records") + if records and len(records) > 0: + return records[0] + + +def parse_event_source(event: dict) -> _EventSource: + """Determines the source of the trigger event""" + if type(event) is not dict: + return _EventSource(EventTypes.UNKNOWN) + + event_source = _EventSource(EventTypes.UNKNOWN) + + request_context = event.get("requestContext") + if request_context and request_context.get("stage"): + if "domainName" in request_context and detect_lambda_function_url_domain( + request_context.get("domainName") + ): + return _EventSource(EventTypes.LAMBDA_FUNCTION_URL) + event_source = _EventSource(EventTypes.API_GATEWAY) + if "httpMethod" in event: + event_source.subtype = EventSubtypes.API_GATEWAY + if "routeKey" in event: + event_source.subtype = EventSubtypes.HTTP_API + if event.get("requestContext", {}).get("messageDirection"): + event_source.subtype = EventSubtypes.WEBSOCKET + + if request_context and request_context.get("elb"): + event_source = _EventSource(EventTypes.ALB) + + if event.get("awslogs"): + event_source = _EventSource(EventTypes.CLOUDWATCH_LOGS) + + if event.get("detail-type"): + event_source = _EventSource(EventTypes.EVENTBRIDGE) + + event_detail = event.get("detail") + has_event_categories = ( + isinstance(event_detail, dict) + and event_detail.get("EventCategories") is not None + ) + if event.get("source") == "aws.events" or has_event_categories: + event_source = _EventSource(EventTypes.CLOUDWATCH_EVENTS) + + if "Execution" in event and "StateMachine" in event and "State" in event: + event_source = _EventSource(EventTypes.STEPFUNCTIONS) + + event_record = get_first_record(event) + if event_record: + aws_event_source = event_record.get( + "eventSource", event_record.get("EventSource") + ) + + if aws_event_source == "aws:dynamodb": + event_source = _EventSource(EventTypes.DYNAMODB) + if aws_event_source == "aws:kinesis": + event_source = _EventSource(EventTypes.KINESIS) + if aws_event_source == "aws:s3": + event_source = _EventSource(EventTypes.S3) + if aws_event_source == "aws:sns": + event_source = _EventSource(EventTypes.SNS) + if aws_event_source == "aws:sqs": + event_source = _EventSource(EventTypes.SQS) + + if event_record.get("cf"): + event_source = _EventSource(EventTypes.CLOUDFRONT) + + return event_source + + +def detect_lambda_function_url_domain(domain: str) -> bool: + # e.g. "etsn5fibjr.lambda-url.eu-south-1.amazonaws.com" + domain_parts = domain.split(".") + if len(domain_parts) < 2: + return False + return domain_parts[1] == "lambda-url" + + +def parse_event_source_arn(source: _EventSource, event: dict, context: Any) -> str: + """ + Parses the trigger event for an available ARN. If an ARN field is not provided + in the event we stitch it together. + """ + split_function_arn = context.invoked_function_arn.split(":") + region = split_function_arn[3] + account_id = split_function_arn[4] + aws_arn = get_aws_partition_by_region(region) + + event_record = get_first_record(event) + # e.g. arn:aws:s3:::lambda-xyz123-abc890 + if source.to_string() == "s3": + return event_record.get("s3", {}).get("bucket", {}).get("arn") + + # e.g. arn:aws:sns:us-east-1:123456789012:sns-lambda + if source.to_string() == "sns": + return event_record.get("Sns", {}).get("TopicArn") + + # e.g. arn:aws:cloudfront::123456789012:distribution/ABC123XYZ + if source.event_type == EventTypes.CLOUDFRONT: + distribution_id = ( + event_record.get("cf", {}).get("config", {}).get("distributionId") + ) + return "arn:{}:cloudfront::{}:distribution/{}".format( + aws_arn, account_id, distribution_id + ) + + # e.g. arn:aws:lambda:::url:: + if source.equals(EventTypes.LAMBDA_FUNCTION_URL): + function_name = "" + if len(split_function_arn) >= 7: + function_name = split_function_arn[6] + function_arn = f"arn:aws:lambda:{region}:{account_id}:url:{function_name}" + function_qualifier = "" + if len(split_function_arn) >= 8: + function_qualifier = split_function_arn[7] + function_arn = function_arn + f":{function_qualifier}" + return function_arn + + # e.g. arn:aws:apigateway:us-east-1::/restapis/xyz123/stages/default + if source.event_type == EventTypes.API_GATEWAY: + request_context = event.get("requestContext") + return "arn:{}:apigateway:{}::/restapis/{}/stages/{}".format( + aws_arn, region, request_context.get("apiId"), request_context.get("stage") + ) + + # e.g. arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/lambda-xyz/123 + if source.event_type == EventTypes.ALB: + request_context = event.get("requestContext") + return request_context.get("elb", {}).get("targetGroupArn") + + # e.g. arn:aws:logs:us-west-1:123456789012:log-group:/my-log-group-xyz + if source.event_type == EventTypes.CLOUDWATCH_LOGS: + with gzip.GzipFile( + fileobj=BytesIO(base64.b64decode(event.get("awslogs", {}).get("data"))) + ) as decompress_stream: + data = b"".join(BufferedReader(decompress_stream)) + logs = json.loads(data) + log_group = logs.get("logGroup", "cloudwatch") + return "arn:{}:logs:{}:{}:log-group:{}".format( + aws_arn, region, account_id, log_group + ) + + # e.g. arn:aws:events:us-east-1:123456789012:rule/my-schedule + if source.event_type == EventTypes.CLOUDWATCH_EVENTS and event.get("resources"): + return event.get("resources")[0] + + +def get_event_source_arn(source: _EventSource, event: dict, context: Any) -> str: + event_source_arn = event.get("eventSourceARN") or event.get("eventSourceArn") + + event_record = get_first_record(event) + if event_record: + event_source_arn = event_record.get("eventSourceARN") or event_record.get( + "eventSourceArn" + ) + + if event_source_arn is None: + event_source_arn = parse_event_source_arn(source, event, context) + + return event_source_arn + + +def extract_http_tags(event): + """ + Extracts HTTP facet tags from the triggering event + """ + http_tags = {} + request_context = event.get("requestContext") + path = event.get("path") + method = event.get("httpMethod") + if request_context and request_context.get("stage"): + if request_context.get("domainName"): + http_tags["http.url"] = request_context.get("domainName") + + path = request_context.get("path") + method = request_context.get("httpMethod") + # Version 2.0 HTTP API Gateway + apigateway_v2_http = request_context.get("http") + if event.get("version") == "2.0" and apigateway_v2_http: + path = apigateway_v2_http.get("path") + method = apigateway_v2_http.get("method") + + if path: + http_tags["http.url_details.path"] = path + if method: + http_tags["http.method"] = method + + headers = event.get("headers") + if headers and headers.get("Referer"): + http_tags["http.referer"] = headers.get("Referer") + + return http_tags + + +def extract_trigger_tags(event: dict, context: Any) -> dict: + """ + Parses the trigger event object to get tags to be added to the span metadata + """ + trigger_tags = {} + event_source = parse_event_source(event) + if event_source.to_string() is not None and event_source.to_string() != "unknown": + trigger_tags["function_trigger.event_source"] = event_source.to_string() + + event_source_arn = get_event_source_arn(event_source, event, context) + if event_source_arn: + trigger_tags["function_trigger.event_source_arn"] = event_source_arn + + if event_source.event_type in [ + EventTypes.API_GATEWAY, + EventTypes.ALB, + EventTypes.LAMBDA_FUNCTION_URL, + ]: + trigger_tags.update(extract_http_tags(event)) + + return trigger_tags + + +def extract_http_status_code_tag(trigger_tags, response): + """ + If the Lambda was triggered by API Gateway, Lambda Function URL, or ALB, + add the returned status code as a tag to the function execution span. + """ + if trigger_tags is None: + return + str_event_source = trigger_tags.get("function_trigger.event_source") + # it would be cleaner if each event type was a constant object that + # knew some properties about itself like this. + str_http_triggers = [ + et.value + for et in [ + EventTypes.API_GATEWAY, + EventTypes.LAMBDA_FUNCTION_URL, + EventTypes.ALB, + ] + ] + if str_event_source not in str_http_triggers: + return + + status_code = "200" + if response is None: + # Return a 502 status if no response is found + status_code = "502" + elif hasattr(response, "get"): + status_code = response.get("statusCode") + elif hasattr(response, "status_code"): + status_code = response.status_code + + return str(status_code) diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/wrapper.py b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/wrapper.py new file mode 100644 index 0000000..73d1788 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/wrapper.py @@ -0,0 +1,395 @@ +# Unless explicitly stated otherwise all files in this repository are licensed +# under the Apache License Version 2.0. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2019 Datadog, Inc. +import base64 +import os +import logging +import traceback +from importlib import import_module +import json +from time import time_ns + +from datadog_lambda.extension import should_use_extension, flush_extension +from datadog_lambda.cold_start import ( + set_cold_start, + is_cold_start, + is_proactive_init, + is_new_sandbox, + ColdStartTracer, +) +from datadog_lambda.constants import ( + TraceContextSource, + XraySubsegment, + Headers, + TraceHeader, +) +from datadog_lambda.metric import ( + flush_stats, + submit_invocations_metric, + submit_errors_metric, +) +from datadog_lambda.module_name import modify_module_name +from datadog_lambda.patch import patch_all +from datadog_lambda.tracing import ( + extract_dd_trace_context, + create_dd_dummy_metadata_subsegment, + inject_correlation_ids, + dd_tracing_enabled, + mark_trace_as_error_for_5xx_responses, + set_correlation_ids, + set_dd_trace_py_root, + create_function_execution_span, + create_inferred_span, + InferredSpanInfo, + is_authorizer_response, + tracer, +) +from datadog_lambda.trigger import ( + extract_trigger_tags, + extract_http_status_code_tag, +) + +profiling_env_var = os.environ.get("DD_PROFILING_ENABLED", "false").lower() == "true" +if profiling_env_var: + from ddtrace.profiling import profiler + +logger = logging.getLogger(__name__) + +DD_FLUSH_TO_LOG = "DD_FLUSH_TO_LOG" +DD_LOGS_INJECTION = "DD_LOGS_INJECTION" +DD_MERGE_XRAY_TRACES = "DD_MERGE_XRAY_TRACES" +AWS_LAMBDA_FUNCTION_NAME = "AWS_LAMBDA_FUNCTION_NAME" +DD_LOCAL_TEST = "DD_LOCAL_TEST" +DD_TRACE_EXTRACTOR = "DD_TRACE_EXTRACTOR" +DD_TRACE_MANAGED_SERVICES = "DD_TRACE_MANAGED_SERVICES" +DD_ENCODE_AUTHORIZER_CONTEXT = "DD_ENCODE_AUTHORIZER_CONTEXT" +DD_DECODE_AUTHORIZER_CONTEXT = "DD_DECODE_AUTHORIZER_CONTEXT" +DD_COLD_START_TRACING = "DD_COLD_START_TRACING" +DD_MIN_COLD_START_DURATION = "DD_MIN_COLD_START_DURATION" +DD_COLD_START_TRACE_SKIP_LIB = "DD_COLD_START_TRACE_SKIP_LIB" +DD_CAPTURE_LAMBDA_PAYLOAD = "DD_CAPTURE_LAMBDA_PAYLOAD" +DD_CAPTURE_LAMBDA_PAYLOAD_MAX_DEPTH = "DD_CAPTURE_LAMBDA_PAYLOAD_MAX_DEPTH" +DD_REQUESTS_SERVICE_NAME = "DD_REQUESTS_SERVICE_NAME" +DD_SERVICE = "DD_SERVICE" +DD_ENV = "DD_ENV" + + +def get_env_as_int(env_key, default_value: int) -> int: + try: + return int(os.environ.get(env_key, default_value)) + except Exception as e: + logger.warn( + f"Failed to parse {env_key} as int. Using default value: {default_value}. Error: {e}" + ) + return default_value + + +dd_capture_lambda_payload_enabled = ( + os.environ.get(DD_CAPTURE_LAMBDA_PAYLOAD, "false").lower() == "true" +) + +if dd_capture_lambda_payload_enabled: + import datadog_lambda.tag_object as tag_object + + tag_object.max_depth = get_env_as_int( + DD_CAPTURE_LAMBDA_PAYLOAD_MAX_DEPTH, tag_object.max_depth + ) + +env_env_var = os.environ.get(DD_ENV, None) + +init_timestamp_ns = time_ns() + +""" +Usage: + +import requests +from datadog_lambda.wrapper import datadog_lambda_wrapper +from datadog_lambda.metric import lambda_metric + +@datadog_lambda_wrapper +def my_lambda_handle(event, context): + lambda_metric("my_metric", 10) + requests.get("https://www.datadoghq.com") +""" + + +class _NoopDecorator(object): + def __init__(self, func): + self.func = func + + def __call__(self, *args, **kwargs): + return self.func(*args, **kwargs) + + +class _LambdaDecorator(object): + """ + Decorator to automatically initialize Datadog API client, flush metrics, + and extracts/injects trace context. + """ + + _force_wrap = False + + def __new__(cls, func): + """ + If the decorator is accidentally applied to the same function multiple times, + wrap only once. + + If _force_wrap, always return a real decorator, useful for unit tests. + """ + try: + if cls._force_wrap or not isinstance(func, _LambdaDecorator): + wrapped = super(_LambdaDecorator, cls).__new__(cls) + logger.debug("datadog_lambda_wrapper wrapped") + return wrapped + else: + logger.debug("datadog_lambda_wrapper already wrapped") + return _NoopDecorator(func) + except Exception as e: + logger.error(format_err_with_traceback(e)) + return func + + def __init__(self, func): + """Executes when the wrapped function gets wrapped""" + try: + self.func = func + self.flush_to_log = os.environ.get(DD_FLUSH_TO_LOG, "").lower() == "true" + self.logs_injection = ( + os.environ.get(DD_LOGS_INJECTION, "true").lower() == "true" + ) + self.merge_xray_traces = ( + os.environ.get(DD_MERGE_XRAY_TRACES, "false").lower() == "true" + ) + self.function_name = os.environ.get(AWS_LAMBDA_FUNCTION_NAME, "function") + self.service = os.environ.get(DD_SERVICE, None) + self.extractor_env = os.environ.get(DD_TRACE_EXTRACTOR, None) + self.trace_extractor = None + self.span = None + self.inferred_span = None + depends_on_dd_tracing_enabled = ( + lambda original_boolean: dd_tracing_enabled and original_boolean + ) + self.make_inferred_span = depends_on_dd_tracing_enabled( + os.environ.get(DD_TRACE_MANAGED_SERVICES, "true").lower() == "true" + ) + self.encode_authorizer_context = depends_on_dd_tracing_enabled( + os.environ.get(DD_ENCODE_AUTHORIZER_CONTEXT, "true").lower() == "true" + ) + self.decode_authorizer_context = depends_on_dd_tracing_enabled( + os.environ.get(DD_DECODE_AUTHORIZER_CONTEXT, "true").lower() == "true" + ) + self.cold_start_tracing = depends_on_dd_tracing_enabled( + os.environ.get(DD_COLD_START_TRACING, "true").lower() == "true" + ) + self.min_cold_start_trace_duration = get_env_as_int( + DD_MIN_COLD_START_DURATION, 3 + ) + self.local_testing_mode = os.environ.get( + DD_LOCAL_TEST, "false" + ).lower() in ("true", "1") + self.cold_start_trace_skip_lib = [ + "ddtrace.internal.compat", + "ddtrace.filters", + ] + if DD_COLD_START_TRACE_SKIP_LIB in os.environ: + try: + self.cold_start_trace_skip_lib = os.environ[ + DD_COLD_START_TRACE_SKIP_LIB + ].split(",") + except Exception: + logger.debug(f"Malformatted for env {DD_COLD_START_TRACE_SKIP_LIB}") + self.response = None + if profiling_env_var: + self.prof = profiler.Profiler(env=env_env_var, service=self.service) + if self.extractor_env: + extractor_parts = self.extractor_env.rsplit(".", 1) + if len(extractor_parts) == 2: + (mod_name, extractor_name) = extractor_parts + modified_extractor_name = modify_module_name(mod_name) + extractor_module = import_module(modified_extractor_name) + self.trace_extractor = getattr(extractor_module, extractor_name) + + # Inject trace correlation ids to logs + if self.logs_injection: + inject_correlation_ids() + + # This prevents a breaking change in ddtrace v0.49 regarding the service name + # in requests-related spans + os.environ[DD_REQUESTS_SERVICE_NAME] = os.environ.get( + DD_SERVICE, "aws.lambda" + ) + # Patch third-party libraries for tracing + patch_all() + + logger.debug("datadog_lambda_wrapper initialized") + except Exception as e: + logger.error(format_err_with_traceback(e)) + + def __call__(self, event, context, **kwargs): + """Executes when the wrapped function gets called""" + self._before(event, context) + try: + self.response = self.func(event, context, **kwargs) + return self.response + except Exception: + submit_errors_metric(context) + if self.span: + self.span.set_traceback() + raise + finally: + self._after(event, context) + + def _inject_authorizer_span_headers(self, request_id): + reference_span = self.inferred_span if self.inferred_span else self.span + assert reference_span.finished + # the finish_time_ns should be set as the end of the inferred span if it exist + # or the end of the current span + finish_time_ns = ( + reference_span.start_ns + reference_span.duration_ns + if reference_span is not None + and hasattr(reference_span, "start_ns") + and hasattr(reference_span, "duration_ns") + else time_ns() + ) + injected_headers = {} + source_span = self.inferred_span if self.inferred_span else self.span + span_context = source_span.context + injected_headers[TraceHeader.TRACE_ID] = str(span_context.trace_id) + injected_headers[TraceHeader.PARENT_ID] = str(span_context.span_id) + sampling_priority = span_context.sampling_priority + if sampling_priority is not None: + injected_headers[TraceHeader.SAMPLING_PRIORITY] = str( + span_context.sampling_priority + ) + injected_headers[Headers.Parent_Span_Finish_Time] = finish_time_ns + if request_id is not None: + injected_headers[Headers.Authorizing_Request_Id] = request_id + datadog_data = base64.b64encode(json.dumps(injected_headers).encode()).decode() + self.response.setdefault("context", {}) + self.response["context"]["_datadog"] = datadog_data + + def _before(self, event, context): + try: + self.response = None + set_cold_start(init_timestamp_ns) + submit_invocations_metric(context) + self.trigger_tags = extract_trigger_tags(event, context) + # Extract Datadog trace context and source from incoming requests + dd_context, trace_context_source, event_source = extract_dd_trace_context( + event, + context, + extractor=self.trace_extractor, + decode_authorizer_context=self.decode_authorizer_context, + ) + self.event_source = event_source + # Create a Datadog X-Ray subsegment with the trace context + if dd_context and trace_context_source == TraceContextSource.EVENT: + create_dd_dummy_metadata_subsegment( + { + "trace-id": str(dd_context.trace_id), + "parent-id": str(dd_context.span_id), + "sampling-priority": str(dd_context.sampling_priority), + }, + XraySubsegment.TRACE_KEY, + ) + + if dd_tracing_enabled: + set_dd_trace_py_root(trace_context_source, self.merge_xray_traces) + if self.make_inferred_span: + self.inferred_span = create_inferred_span( + event, context, event_source, self.decode_authorizer_context + ) + self.span = create_function_execution_span( + context, + self.function_name, + is_cold_start(), + is_proactive_init(), + trace_context_source, + self.merge_xray_traces, + self.trigger_tags, + parent_span=self.inferred_span, + ) + else: + set_correlation_ids() + if profiling_env_var and is_new_sandbox(): + self.prof.start(stop_on_exit=False, profile_children=True) + logger.debug("datadog_lambda_wrapper _before() done") + except Exception as e: + logger.error(format_err_with_traceback(e)) + + def _after(self, event, context): + try: + status_code = extract_http_status_code_tag(self.trigger_tags, self.response) + if status_code: + self.trigger_tags["http.status_code"] = status_code + mark_trace_as_error_for_5xx_responses(context, status_code, self.span) + + # Create a new dummy Datadog subsegment for function trigger tags so we + # can attach them to X-Ray spans when hybrid tracing is used + if self.trigger_tags: + create_dd_dummy_metadata_subsegment( + self.trigger_tags, XraySubsegment.LAMBDA_FUNCTION_TAGS_KEY + ) + should_trace_cold_start = self.cold_start_tracing and is_new_sandbox() + if should_trace_cold_start: + trace_ctx = tracer.current_trace_context() + + if self.span: + if dd_capture_lambda_payload_enabled: + tag_object.tag_object(self.span, "function.request", event) + tag_object.tag_object(self.span, "function.response", self.response) + + if status_code: + self.span.set_tag("http.status_code", status_code) + self.span.finish() + + if self.inferred_span: + if status_code: + self.inferred_span.set_tag("http.status_code", status_code) + + if self.service: + self.inferred_span.set_tag("peer.service", self.service) + + if InferredSpanInfo.is_async(self.inferred_span) and self.span: + self.inferred_span.finish(finish_time=self.span.start) + else: + self.inferred_span.finish() + + if should_trace_cold_start: + try: + following_span = self.span or self.inferred_span + ColdStartTracer( + tracer, + self.function_name, + following_span.start_ns, + trace_ctx, + self.min_cold_start_trace_duration, + self.cold_start_trace_skip_lib, + ).trace() + except Exception as e: + logger.debug("Failed to create cold start spans. %s", e) + + if not self.flush_to_log or should_use_extension: + flush_stats() + if should_use_extension and self.local_testing_mode: + # when testing locally, the extension does not know when an + # invocation completes because it does not have access to the + # logs api + flush_extension() + + if self.encode_authorizer_context and is_authorizer_response(self.response): + self._inject_authorizer_span_headers( + event.get("requestContext", {}).get("requestId") + ) + logger.debug("datadog_lambda_wrapper _after() done") + except Exception as e: + logger.error(format_err_with_traceback(e)) + + +def format_err_with_traceback(e): + return "Error {}. Traceback: {}".format( + e, traceback.format_exc().replace("\n", "\r") + ) + + +datadog_lambda_wrapper = _LambdaDecorator diff --git a/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/xray.py b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/xray.py new file mode 100644 index 0000000..88d108f --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/datadog_lambda/xray.py @@ -0,0 +1,118 @@ +import os +import logging +import json +import binascii +import time +import socket + +from datadog_lambda.constants import XrayDaemon, XraySubsegment, TraceContextSource + +logger = logging.getLogger(__name__) + + +def get_xray_host_port(address): + if address == "": + logger.debug("X-Ray daemon env var not set, not sending sub-segment") + return None + parts = address.split(":") + if len(parts) <= 1: + logger.debug("X-Ray daemon env var not set, not sending sub-segment") + return None + port = int(parts[1]) + host = parts[0] + return (host, port) + + +def send(host_port_tuple, payload): + sock = None + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.setblocking(0) + sock.connect(host_port_tuple) + sock.send(payload.encode("utf-8")) + except Exception as e_send: + logger.error("Error occurred submitting to xray daemon: %s", str(e_send)) + try: + sock.close() + except Exception as e_close: + logger.error("Error while closing the socket: %s", str(e_close)) + + +def build_segment_payload(payload): + if payload is None: + return None + return '{"format": "json", "version": 1}' + "\n" + payload + + +def parse_xray_header(raw_trace_id): + # Example: + # Root=1-5e272390-8c398be037738dc042009320;Parent=94ae789b969f1cc5;Sampled=1;Lineage=c6c5b1b9:0 + logger.debug("Reading trace context from env var %s", raw_trace_id) + if len(raw_trace_id) == 0: + return None + parts = raw_trace_id.split(";") + if len(parts) < 3: + return None + root = parts[0].replace("Root=", "") + parent = parts[1].replace("Parent=", "") + sampled = parts[2].replace("Sampled=", "") + if ( + len(root) == len(parts[0]) + or len(parent) == len(parts[1]) + or len(sampled) == len(parts[2]) + ): + return None + return { + "parent_id": parent, + "trace_id": root, + "sampled": sampled, + "source": TraceContextSource.XRAY, + } + + +def generate_random_id(): + return binascii.b2a_hex(os.urandom(8)).decode("utf-8") + + +def build_segment(context, key, metadata): + segment = json.dumps( + { + "id": generate_random_id(), + "trace_id": context["trace_id"], + "parent_id": context["parent_id"], + "name": XraySubsegment.NAME, + "start_time": time.time(), + "end_time": time.time(), + "type": "subsegment", + "metadata": { + XraySubsegment.NAMESPACE: { + key: metadata, + } + }, + } + ) + return segment + + +def send_segment(key, metadata): + host_port_tuple = get_xray_host_port( + os.environ.get(XrayDaemon.XRAY_DAEMON_ADDRESS, "") + ) + if host_port_tuple is None: + return None + context = parse_xray_header( + os.environ.get(XrayDaemon.XRAY_TRACE_ID_HEADER_NAME, "") + ) + if context is None: + logger.debug( + "Failed to create segment since it was not possible to get trace context from header" + ) + return None + + # Skip adding segment, if the xray trace is going to be sampled away. + if context["sampled"] == "0": + logger.debug("Skipping sending metadata, x-ray trace was sampled out") + return None + segment = build_segment(context, key, metadata) + segment_payload = build_segment_payload(segment) + send(host_port_tuple, segment_payload) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/INSTALLER b/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/LICENSE b/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/LICENSE new file mode 100644 index 0000000..7e153db --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/LICENSE @@ -0,0 +1,13 @@ +Copyright 2020 DataDog, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/LICENSE-3rdparty.csv b/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/LICENSE-3rdparty.csv new file mode 100644 index 0000000..66c2263 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/LICENSE-3rdparty.csv @@ -0,0 +1,3 @@ +Component,Origin,License,Copyright +import,numpy,BSD-3-Clause,Copyright (c) 2005-2020 NumPy Developers.; All rights reserved. +import,setuptools,MIT,Copyright (c) 2016 Jason R Coombs diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/METADATA b/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/METADATA new file mode 100644 index 0000000..3716a35 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/METADATA @@ -0,0 +1,166 @@ +Metadata-Version: 2.1 +Name: ddsketch +Version: 2.0.4 +Summary: Distributed quantile sketches +Home-page: http://github.com/datadog/sketches-py +Download-URL: https://github.com/DataDog/sketches-py/archive/v1.0.tar.gz +Author: Jee Rim, Charles-Philippe Masson, Homin Lee +Author-email: jee.rim@datadoghq.com, charles.masson@datadoghq.com, homin@datadoghq.com +Keywords: ddsketch,quantile,sketch +Classifier: Programming Language :: Python :: 3 +Classifier: License :: OSI Approved :: Apache Software License +Requires-Python: >=2.7 +Description-Content-Type: text/markdown +License-File: LICENSE +License-File: LICENSE-3rdparty.csv +License-File: NOTICE +Requires-Dist: six +Requires-Dist: typing ; python_version < "3.5" +Requires-Dist: protobuf (<4.21.0,>=3.0.0) ; python_version < "3.7" +Requires-Dist: protobuf (>=3.0.0) ; python_version >= "3.7" + +# ddsketch + +This repo contains the Python implementation of the distributed quantile sketch +algorithm DDSketch [1]. DDSketch has relative-error guarantees for any quantile +q in [0, 1]. That is if the true value of the qth-quantile is `x` then DDSketch +returns a value `y` such that `|x-y| / x < e` where `e` is the relative error +parameter. (The default here is set to 0.01.) DDSketch is also fully mergeable, +meaning that multiple sketches from distributed systems can be combined in a +central node. + +Our default implementation, `DDSketch`, is guaranteed [1] to not grow too large +in size for any data that can be described by a distribution whose tails are +sub-exponential. + +We also provide implementations (`LogCollapsingLowestDenseDDSketch` and +`LogCollapsingHighestDenseDDSketch`) where the q-quantile will be accurate up to +the specified relative error for q that is not too small (or large). Concretely, +the q-quantile will be accurate up to the specified relative error as long as it +belongs to one of the `m` bins kept by the sketch. If the data is time in +seconds, the default of `m = 2048` covers 80 microseconds to 1 year. + +## Installation + +To install this package, run `pip install ddsketch`, or clone the repo and run +`python setup.py install`. This package depends on `numpy` and `protobuf`. (The +protobuf dependency can be removed if it's not applicable.) + +## Usage +``` +from ddsketch import DDSketch + +sketch = DDSketch() +``` +Add values to the sketch +``` +import numpy as np + +values = np.random.normal(size=500) +for v in values: + sketch.add(v) +``` +Find the quantiles of `values` to within the relative error. +``` +quantiles = [sketch.get_quantile_value(q) for q in [0.5, 0.75, 0.9, 1]] +``` +Merge another `DDSketch` into `sketch`. +``` +another_sketch = DDSketch() +other_values = np.random.normal(size=500) +for v in other_values: + another_sketch.add(v) +sketch.merge(another_sketch) +``` +The quantiles of `values` concatenated with `other_values` are still accurate to within the relative error. + +## Development + +To work on ddsketch a Python interpreter must be installed. It is recommended to use the provided development +container (requires [docker](https://www.docker.com/)) which includes all the required Python interpreters. + + docker-compose run dev + +Or, if developing outside of docker then it is recommended to use a virtual environment: + + pip install virtualenv + virtualenv --python=3 .venv + source .venv/bin/activate + + +### Testing + +To run the tests install `riot`: + + pip install riot + +Replace the Python version with the interpreter(s) available. + + # Run tests with Python 3.9 + riot run -p3.9 test + +### Release notes + +New features, bug fixes, deprecations and other breaking changes must have +release notes included. + +To generate a release note for the change: + + riot run reno new + +Edit the generated file to include notes on the changes made in the commit/PR +and add commit it. + + +### Formatting + +Format code with + + riot run fmt + + +### Type-checking + +Type checking is done with [mypy](http://mypy-lang.org/): + + riot run mypy + + +### Type-checking + +Lint the code with [flake8](https://flake8.pycqa.org/en/latest/): + + riot run flake8 + + +### Protobuf + +The protobuf is stored in the go repository: https://github.com/DataDog/sketches-go/blob/master/ddsketch/pb/ddsketch.proto + +Install the minimum required protoc and generate the Python code: + +```sh +docker run -v $PWD:/code -it ubuntu:18.04 /bin/bash +apt update && apt install protobuf-compiler # default is 3.0.0 +protoc --proto_path=ddsketch/pb/ --python_out=ddsketch/pb/ ddsketch/pb/ddsketch.proto +``` + + +### Releasing + +1. Generate the release notes and use [`pandoc`](https://pandoc.org/) to format +them for Github: +```bash + git checkout master && git pull + riot run -s reno report --no-show-source | pandoc -f rst -t gfm --wrap=none +``` + Copy the output into a new release: https://github.com/DataDog/sketches-py/releases/new. + +2. Enter a tag for the release (following [`semver`](https://semver.org)) (eg. `v1.1.3`, `v1.0.3`, `v1.2.0`). +3. Use the tag without the `v` as the title. +4. Save the release as a draft and pass the link to someone else to give a quick review. +5. If all looks good hit publish + + +## References +[1] Charles Masson and Jee E Rim and Homin K. Lee. DDSketch: A fast and fully-mergeable quantile sketch with relative-error guarantees. PVLDB, 12(12): 2195-2205, 2019. (The code referenced in the paper, including our implementation of the the Greenwald-Khanna (GK) algorithm, can be found at: https://github.com/DataDog/sketches-py/releases/tag/v0.1 ) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/NOTICE b/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/NOTICE new file mode 100644 index 0000000..035c9ad --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/NOTICE @@ -0,0 +1,4 @@ +Datadog sketches-py +Copyright 2020 Datadog, Inc. + +This product includes software developed at Datadog (https://www.datadoghq.com/). diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/RECORD b/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/RECORD new file mode 100644 index 0000000..d951ffa --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/RECORD @@ -0,0 +1,30 @@ +ddsketch-2.0.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +ddsketch-2.0.4.dist-info/LICENSE,sha256=T0-WFEYXjD5IYjlmlH0JAbfqTHa_YvAl875ydhaqdKA,554 +ddsketch-2.0.4.dist-info/LICENSE-3rdparty.csv,sha256=z16O1RgqAgDTZqLSzos-HZ1vqH1cqaR6Vm9qDz5Fhuc,201 +ddsketch-2.0.4.dist-info/METADATA,sha256=cMuoMMwqZ0i1m3iMwqDBdgsWokDVcY4BzUxZj0cZe68,5456 +ddsketch-2.0.4.dist-info/NOTICE,sha256=rVyH-sbkieAzCC_Ni4rDz0feqaG-7tWHwlj1aIfH33Q,132 +ddsketch-2.0.4.dist-info/RECORD,, +ddsketch-2.0.4.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddsketch-2.0.4.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92 +ddsketch-2.0.4.dist-info/top_level.txt,sha256=pPjwA4dRqDmExS6WSOHicanUpoHB-b9852iZwDUkNcI,9 +ddsketch/__init__.py,sha256=IYdg8QtcIE_l66Ze1TiC18XaVZwAR9k8YOSY_2rkMYs,717 +ddsketch/__pycache__/__init__.cpython-311.pyc,, +ddsketch/__pycache__/__version.cpython-311.pyc,, +ddsketch/__pycache__/_version.cpython-311.pyc,, +ddsketch/__pycache__/ddsketch.cpython-311.pyc,, +ddsketch/__pycache__/mapping.cpython-311.pyc,, +ddsketch/__pycache__/store.cpython-311.pyc,, +ddsketch/__version.py,sha256=O-_Pobu2r8B57xjWutMfz4LJDMMon23xJAyWJe3yPAQ,176 +ddsketch/_version.py,sha256=HUdEupMtRv7sb1QCczoihjq-kz5jF4rDbew15qDFB-g,504 +ddsketch/ddsketch.py,sha256=ha-eunudNhueaVJGDB9VF13QGhi1W1ncqAto5XDGtnI,11444 +ddsketch/mapping.py,sha256=FBs2PdhLAQB3F28GpWfpAGRpDcnt-FM-n8fIdtC0JYM,7759 +ddsketch/pb/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddsketch/pb/__pycache__/__init__.cpython-311.pyc,, +ddsketch/pb/__pycache__/ddsketch_pb2.cpython-311.pyc,, +ddsketch/pb/__pycache__/ddsketch_pre319_pb2.cpython-311.pyc,, +ddsketch/pb/__pycache__/proto.cpython-311.pyc,, +ddsketch/pb/ddsketch_pb2.py,sha256=y_5bB9hMZyDZQtNvSzRRxMua-BYBtarjSA6qLkuyA54,3580 +ddsketch/pb/ddsketch_pre319_pb2.py,sha256=lqYZ8DaWHl6Sgb5MW2rb4ztJQbE7Su-g5yqkClTmICA,10087 +ddsketch/pb/proto.py,sha256=S9PYWyGTpmB2XI_RJIdxLw9VA-b_OB1YQ-P7VXHcvNk,3315 +ddsketch/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddsketch/store.py,sha256=2yZvF78s65blAon-TuS_vRJiea4nD71P9Bo01rUuP7w,17515 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/REQUESTED b/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/WHEEL b/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/WHEEL new file mode 100644 index 0000000..becc9a6 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/top_level.txt b/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/top_level.txt new file mode 100644 index 0000000..292b4e0 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddsketch-2.0.4.dist-info/top_level.txt @@ -0,0 +1 @@ +ddsketch diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddsketch/.DS_Store b/lambdas/aws-dd-forwarder-3.127.0/ddsketch/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..c5e11fbc3b6d36f0a88f4197b2ae617ac8a4c045 GIT binary patch literal 6148 zcmeHKu};H447EdtqAnd7?-%+9p$a=vB_^amQq&BMl7)foK8=a*;`wZq9w7q@LX|D~ z-o=(QD$B*0jefwem3-7U;WKkg3CeLpB zfb)|5TP_F7#n!Tuv$g!3`?Ht!sWadVI0F~Q0BW|#a;4~_GvEw311ARL`w*alrD0S| zpAHPE1pqE!PJ+4g5)u;(OT(y$6$oo6P(#^T4AyYigZY()QBlK*t@&U(nXNfq|8Bnj&x8EQ8E^(JiUD3!yK0M9vf4U%IjOY)`UDjbzo>W$ j!6cPp_)00hfhK`H$OKp#Mnza4{v(iR@WC1QQwBZ(bMi+v literal 0 HcmV?d00001 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddsketch/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddsketch/__init__.py new file mode 100644 index 0000000..fcf1e10 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddsketch/__init__.py @@ -0,0 +1,24 @@ +from ._version import get_version +from .ddsketch import DDSketch +from .ddsketch import LogCollapsingHighestDenseDDSketch +from .ddsketch import LogCollapsingLowestDenseDDSketch +from .mapping import CubicallyInterpolatedMapping +from .mapping import LinearlyInterpolatedMapping +from .mapping import LogarithmicMapping +from .store import CollapsingHighestDenseStore +from .store import CollapsingLowestDenseStore + + +__version__ = get_version() + + +__all__ = [ + "DDSketch", + "LogCollapsingLowestDenseDDSketch", + "LogCollapsingHighestDenseDDSketch", + "CubicallyInterpolatedMapping", + "LinearlyInterpolatedMapping", + "LogarithmicMapping", + "CollapsingHighestDenseStore", + "CollapsingLowestDenseStore", +] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddsketch/__version.py b/lambdas/aws-dd-forwarder-3.127.0/ddsketch/__version.py new file mode 100644 index 0000000..51765be --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddsketch/__version.py @@ -0,0 +1,5 @@ +# coding: utf-8 +# file generated by setuptools_scm +# don't change, don't track in version control +__version__ = version = '2.0.4' +__version_tuple__ = version_tuple = (2, 0, 4) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddsketch/_version.py b/lambdas/aws-dd-forwarder-3.127.0/ddsketch/_version.py new file mode 100644 index 0000000..1c65a0d --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddsketch/_version.py @@ -0,0 +1,17 @@ +def get_version(): + # type: () -> str + """Return the package version. + + The write_to functionality of setuptools_scm is used (see setup.py) + to output the version to ddsketch/__version.py which we attempt to import. + + This is done to avoid the expensive overhead of importing pkg_resources. + """ + try: + from .__version import version + + return version + except ImportError: + import pkg_resources + + return pkg_resources.get_distribution(__name__).version diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddsketch/ddsketch.py b/lambdas/aws-dd-forwarder-3.127.0/ddsketch/ddsketch.py new file mode 100644 index 0000000..ba72562 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddsketch/ddsketch.py @@ -0,0 +1,316 @@ +# Unless explicitly stated otherwise all files in this repository are licensed +# under the Apache License 2.0. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2020 Datadog, Inc. + +"""A quantile sketch with relative-error guarantees. This sketch computes +quantile values with an approximation error that is relative to the actual +quantile value. It works on both negative and non-negative input values. + +For instance, using DDSketch with a relative accuracy guarantee set to 1%, if +the expected quantile value is 100, the computed quantile value is guaranteed to +be between 99 and 101. If the expected quantile value is 1000, the computed +quantile value is guaranteed to be between 990 and 1010. + +DDSketch works by mapping floating-point input values to bins and counting the +number of values for each bin. The underlying structure that keeps track of bin +counts is store. + +The memory size of the sketch depends on the range that is covered by the input +values: the larger that range, the more bins are needed to keep track of the +input values. As a rough estimate, if working on durations with a relative +accuracy of 2%, about 2kB (275 bins) are needed to cover values between 1 +millisecond and 1 minute, and about 6kB (802 bins) to cover values between 1 +nanosecond and 1 day. + +The size of the sketch can be have a fail-safe upper-bound by using collapsing +stores. As shown in +the DDSketch paper +the likelihood of a store collapsing when using the default bound is vanishingly +small for most data. + +DDSketch implementations are also available in: +Go +Python +JavaScript +""" +import typing + +from .mapping import LogarithmicMapping +from .store import CollapsingHighestDenseStore +from .store import CollapsingLowestDenseStore +from .store import DenseStore + + +if typing.TYPE_CHECKING: + from typing import Optional + + from .mapping import KeyMapping + from .store import Store + + +DEFAULT_REL_ACC = 0.01 # "alpha" in the paper +DEFAULT_BIN_LIMIT = 2048 + + +class BaseDDSketch(object): + """The base implementation of DDSketch with neither mapping nor storage specified. + + Args: + mapping (mapping.KeyMapping): map btw values and store bins + store (store.Store): storage for positive values + negative_store (store.Store): storage for negative values + zero_count (float): The count of zero values + + Attributes: + relative_accuracy (float): the accuracy guarantee; referred to as alpha + in the paper. (0. < alpha < 1.) + + count: the number of values seen by the sketch + min: the minimum value seen by the sketch + max: the maximum value seen by the sketch + sum: the sum of the values seen by the sketch + """ + + def __init__( + self, + mapping, + store, + negative_store, + zero_count, + ): + # type: (KeyMapping, Store, Store, float) -> None + self._mapping = mapping + self._store = store + self._negative_store = negative_store + self._zero_count = zero_count + + self._relative_accuracy = mapping.relative_accuracy + self._count = self._negative_store.count + self._zero_count + self._store.count + self._min = float("+inf") + self._max = float("-inf") + self._sum = 0.0 + + def __repr__(self): + # type: () -> str + return ( + "store: {}, negative_store: {}, " + "zero_count: {}, count: {}, " + "sum: {}, min: {}, max: {}" + ).format( + self._store, + self._negative_store, + self._zero_count, + self._count, + self._sum, + self._min, + self._max, + ) + + @property + def count(self): + return self._count + + @property + def name(self): + # type: () -> str + """str: name of the sketch""" + return "DDSketch" + + @property + def num_values(self): + # type: () -> float + """Return the number of values in the sketch.""" + return self._count + + @property + def avg(self): + # type: () -> float + """Return the exact average of the values added to the sketch.""" + return self._sum / self._count + + @property + def sum(self): # noqa: A003 + # type: () -> float + """Return the exact sum of the values added to the sketch.""" + return self._sum + + def add(self, val, weight=1.0): + # type: (float, float) -> None + """Add a value to the sketch.""" + if weight <= 0.0: + raise ValueError("weight must be a positive float, got %r" % weight) + + if val > self._mapping.min_possible: + self._store.add(self._mapping.key(val), weight) + elif val < -self._mapping.min_possible: + self._negative_store.add(self._mapping.key(-val), weight) + else: + self._zero_count += weight + + # Keep track of summary stats + self._count += weight + self._sum += val * weight + if val < self._min: + self._min = val + if val > self._max: + self._max = val + + def get_quantile_value(self, quantile): + # type: (float) -> Optional[float] + """Return the approximate value at the specified quantile. + + Args: + quantile (float): 0 <= q <=1 + + Returns: + the value at the specified quantile or None if the sketch is empty + """ + if quantile < 0 or quantile > 1 or self._count == 0: + return None + + rank = quantile * (self._count - 1) + if rank < self._negative_store.count: + reversed_rank = self._negative_store.count - rank - 1 + key = self._negative_store.key_at_rank(reversed_rank, lower=False) + quantile_value = -self._mapping.value(key) + elif rank < self._zero_count + self._negative_store.count: + return 0 + else: + key = self._store.key_at_rank( + rank - self._zero_count - self._negative_store.count + ) + quantile_value = self._mapping.value(key) + return quantile_value + + def merge(self, sketch): + # type: (BaseDDSketch) -> None + """Merge the given sketch into this one. After this operation, this sketch + encodes the values that were added to both this and the input sketch. + """ + if not self._mergeable(sketch): + raise ValueError( + "Cannot merge two DDSketches with different parameters, got %r and %r" + % (self._mapping.gamma, sketch._mapping.gamma) + ) + + if sketch.count == 0: + return + + if self._count == 0: + self._copy(sketch) + return + + # Merge the stores + self._store.merge(sketch._store) + self._negative_store.merge(sketch._negative_store) + self._zero_count += sketch._zero_count + + # Merge summary stats + self._count += sketch._count + self._sum += sketch._sum + if sketch._min < self._min: + self._min = sketch._min + if sketch._max > self._max: + self._max = sketch._max + + def _mergeable(self, other): + # type: (BaseDDSketch) -> bool + """Two sketches can be merged only if their gammas are equal.""" + return self._mapping.gamma == other._mapping.gamma + + def _copy(self, sketch): + # type: (BaseDDSketch) -> None + """Copy the input sketch into this one""" + self._store.copy(sketch._store) + self._negative_store.copy(sketch._negative_store) + self._zero_count = sketch._zero_count + self._min = sketch._min + self._max = sketch._max + self._count = sketch._count + self._sum = sketch._sum + + +class DDSketch(BaseDDSketch): + """The default implementation of BaseDDSketch, with optimized memory usage at + the cost of lower ingestion speed, using an unlimited number of bins. The + number of bins will not exceed a reasonable number unless the data is + distributed with tails heavier than any subexponential. + (cf. http://www.vldb.org/pvldb/vol12/p2195-masson.pdf) + """ + + def __init__(self, relative_accuracy=None): + # type: (Optional[float]) -> None + # Make sure the parameters are valid + if relative_accuracy is None: + relative_accuracy = DEFAULT_REL_ACC + + mapping = LogarithmicMapping(relative_accuracy) + store = DenseStore() + negative_store = DenseStore() + super(DDSketch, self).__init__( + mapping=mapping, + store=store, + negative_store=negative_store, + zero_count=0.0, + ) + + +class LogCollapsingLowestDenseDDSketch(BaseDDSketch): + """Implementation of BaseDDSketch with optimized memory usage at the cost of + lower ingestion speed, using a limited number of bins. When the maximum + number of bins is reached, bins with lowest indices are collapsed, which + causes the relative accuracy to be lost on the lowest quantiles. For the + default bin limit, collapsing is unlikely to occur unless the data is + distributed with tails heavier than any subexponential. + (cf. http://www.vldb.org/pvldb/vol12/p2195-masson.pdf) + """ + + def __init__(self, relative_accuracy=None, bin_limit=None): + # type: (Optional[float], Optional[int]) -> None + # Make sure the parameters are valid + if relative_accuracy is None: + relative_accuracy = DEFAULT_REL_ACC + + if bin_limit is None or bin_limit < 0: + bin_limit = DEFAULT_BIN_LIMIT + + mapping = LogarithmicMapping(relative_accuracy) + store = CollapsingLowestDenseStore(bin_limit) + negative_store = CollapsingLowestDenseStore(bin_limit) + super(LogCollapsingLowestDenseDDSketch, self).__init__( + mapping=mapping, + store=store, + negative_store=negative_store, + zero_count=0.0, + ) + + +class LogCollapsingHighestDenseDDSketch(BaseDDSketch): + """Implementation of BaseDDSketch with optimized memory usage at the cost of + lower ingestion speed, using a limited number of bins. When the maximum + number of bins is reached, bins with highest indices are collapsed, which + causes the relative accuracy to be lost on the highest quantiles. For the + default bin limit, collapsing is unlikely to occur unless the data is + distributed with tails heavier than any subexponential. + (cf. http://www.vldb.org/pvldb/vol12/p2195-masson.pdf) + """ + + def __init__(self, relative_accuracy=None, bin_limit=None): + # type: (Optional[float], Optional[int]) -> None + # Make sure the parameters are valid + if relative_accuracy is None: + relative_accuracy = DEFAULT_REL_ACC + + if bin_limit is None or bin_limit < 0: + bin_limit = DEFAULT_BIN_LIMIT + + mapping = LogarithmicMapping(relative_accuracy) + store = CollapsingHighestDenseStore(bin_limit) + negative_store = CollapsingHighestDenseStore(bin_limit) + super(LogCollapsingHighestDenseDDSketch, self).__init__( + mapping=mapping, + store=store, + negative_store=negative_store, + zero_count=0.0, + ) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddsketch/mapping.py b/lambdas/aws-dd-forwarder-3.127.0/ddsketch/mapping.py new file mode 100644 index 0000000..4599385 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddsketch/mapping.py @@ -0,0 +1,216 @@ +from __future__ import division + + +# Unless explicitly stated otherwise all files in this repository are licensed +# under the Apache License 2.0. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2020 Datadog, Inc. + +"""A mapping between values and integer indices that imposes relative accuracy +guarantees. Specifically, for any value `minIndexableValue() < value < +maxIndexableValue` implementations of `KeyMapping` must be such that +`value(key(v))` is close to `v` with a relative error that is less than +`relative_accuracy`. + +In implementations of KeyMapping, there is generally a trade-off between the +cost of computing the key and the number of keys that are required to cover a +given range of values (memory optimality). The most memory-optimal mapping is +the LogarithmicMapping, but it requires the costly evaluation of the logarithm +when computing the index. Other mappings can approximate the logarithmic +mapping, while being less computationally costly. +""" +from abc import ABCMeta +from abc import abstractmethod +import math +import sys + +import six + + +class KeyMapping(six.with_metaclass(ABCMeta)): + """ + Args: + relative_accuracy (float): the accuracy guarantee; referred to as alpha + in the paper. (0. < alpha < 1.) + offset (float): an offset that can be used to shift all bin keys + Attributes: + gamma (float): the base for the exponential buckets. gamma = (1 + alpha) / (1 - alpha) + min_possible: the smallest value the sketch can distinguish from 0 + max_possible: the largest value the sketch can handle + _multiplier (float): used for calculating log_gamma(value) initially, _multiplier = 1 / log(gamma) + """ + + def __init__(self, relative_accuracy, offset=0.0): + # type: (float, float) -> None + if relative_accuracy <= 0 or relative_accuracy >= 1: + raise ValueError( + "Relative accuracy must be between 0 and 1, got %r" % relative_accuracy + ) + self.relative_accuracy = relative_accuracy + self._offset = offset + + gamma_mantissa = 2 * relative_accuracy / (1 - relative_accuracy) + self.gamma = 1 + gamma_mantissa + self._multiplier = 1 / math.log1p(gamma_mantissa) + self.min_possible = sys.float_info.min * self.gamma + self.max_possible = sys.float_info.max / self.gamma + + @classmethod + def from_gamma_offset(cls, gamma, offset): + # type: (float, float) -> KeyMapping + """Constructor used by pb.proto""" + relative_accuracy = (gamma - 1.0) / (gamma + 1.0) + return cls(relative_accuracy, offset=offset) + + @abstractmethod + def _log_gamma(self, value): + # type: (float) -> float + """Return (an approximation of) the logarithm of the value base gamma""" + + @abstractmethod + def _pow_gamma(self, value): + # type: (float) -> float + """Return (an approximation of) gamma to the power value""" + + def key(self, value): + # type: (float) -> int + """ + Args: + value (float) + Returns: + int: the key specifying the bucket for value + """ + return int(math.ceil(self._log_gamma(value)) + self._offset) + + def value(self, key): + # type: (int) -> float + """ + Args: + key (int) + Returns: + float: the value represented by the bucket specified by the key + """ + return self._pow_gamma(key - self._offset) * (2.0 / (1 + self.gamma)) + + +class LogarithmicMapping(KeyMapping): + """A memory-optimal KeyMapping, i.e., given a targeted relative accuracy, it + requires the least number of keys to cover a given range of values. This is + done by logarithmically mapping floating-point values to integers. + """ + + def __init__(self, relative_accuracy, offset=0.0): + # type: (float, float) -> None + super(LogarithmicMapping, self).__init__(relative_accuracy, offset=offset) + self._multiplier *= math.log(2) + + def _log_gamma(self, value): + # type: (float) -> float + return math.log(value, 2) * self._multiplier + + def _pow_gamma(self, value): + # type: (float) -> float + return math.pow(2.0, value / self._multiplier) + + +def _cbrt(x): + # type: (float) -> float + y = float(abs(x) ** (1.0 / 3.0)) + if x < 0: + return -y + return y + + +class LinearlyInterpolatedMapping(KeyMapping): + """A fast KeyMapping that approximates the memory-optimal + LogarithmicMapping by extracting the floor value of the logarithm to the + base 2 from the binary representations of floating-point values and + linearly interpolating the logarithm in-between. + """ + + def _log2_approx(self, value): + # type: (float) -> float + """Approximates log2 by s + f + where v = (s+1) * 2 ** f for s in [0, 1) + + frexp(v) returns m and e s.t. + v = m * 2 ** e ; (m in [0.5, 1) or 0.0) + so we adjust m and e accordingly + """ + mantissa, exponent = math.frexp(value) + significand = 2 * mantissa - 1 + return significand + (exponent - 1) + + def _exp2_approx(self, value): + # type: (float) -> float + """Inverse of _log2_approx""" + exponent = int(math.floor(value) + 1) + mantissa = (value - exponent + 2) / 2.0 + return math.ldexp(mantissa, exponent) + + def _log_gamma(self, value): + # type: (float) -> float + return self._log2_approx(value) * self._multiplier + + def _pow_gamma(self, value): + # type: (float) -> float + return self._exp2_approx(value / self._multiplier) + + +class CubicallyInterpolatedMapping(KeyMapping): + """A fast KeyMapping that approximates the memory-optimal LogarithmicMapping by + extracting the floor value of the logarithm to the base 2 from the binary + representations of floating-point values and cubically interpolating the + logarithm in-between. + + More detailed documentation of this method can be found in: + sketches-java + """ + + A = 6.0 / 35.0 + B = -3.0 / 5.0 + C = 10.0 / 7.0 + + def __init__(self, relative_accuracy, offset=0.0): + # type: (float, float) -> None + super(CubicallyInterpolatedMapping, self).__init__( + relative_accuracy, offset=offset + ) + self._multiplier /= self.C + + def _cubic_log2_approx(self, value): + # type: (float) -> float + """Approximates log2 using a cubic polynomial""" + mantissa, exponent = math.frexp(value) + significand = 2 * mantissa - 1 + return ( + (self.A * significand + self.B) * significand + self.C + ) * significand + (exponent - 1) + + def _cubic_exp2_approx(self, value): + # type: (float) -> float + # Derived from Cardano's formula + exponent = int(math.floor(value)) + delta_0 = self.B * self.B - 3 * self.A * self.C + delta_1 = ( + 2.0 * self.B * self.B * self.B + - 9.0 * self.A * self.B * self.C + - 27.0 * self.A * self.A * (value - exponent) + ) + cardano = _cbrt( + (delta_1 - ((delta_1 * delta_1 - 4 * delta_0 * delta_0 * delta_0) ** 0.5)) + / 2.0 + ) + significand_plus_one = ( + -(self.B + cardano + delta_0 / cardano) / (3.0 * self.A) + 1.0 + ) + mantissa = significand_plus_one / 2 + return math.ldexp(mantissa, exponent + 1) + + def _log_gamma(self, value): + # type: (float) -> float + return self._cubic_log2_approx(value) * self._multiplier + + def _pow_gamma(self, value): + # type: (float) -> float + return self._cubic_exp2_approx(value / self._multiplier) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddsketch/pb/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddsketch/pb/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddsketch/pb/ddsketch_pb2.py b/lambdas/aws-dd-forwarder-3.127.0/ddsketch/pb/ddsketch_pb2.py new file mode 100644 index 0000000..81525b2 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddsketch/pb/ddsketch_pb2.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: ddsketch.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0e\x64\x64sketch.proto\"}\n\x08\x44\x44Sketch\x12\x1e\n\x07mapping\x18\x01 \x01(\x0b\x32\r.IndexMapping\x12\x1e\n\x0epositiveValues\x18\x02 \x01(\x0b\x32\x06.Store\x12\x1e\n\x0enegativeValues\x18\x03 \x01(\x0b\x32\x06.Store\x12\x11\n\tzeroCount\x18\x04 \x01(\x01\"\xa7\x01\n\x0cIndexMapping\x12\r\n\x05gamma\x18\x01 \x01(\x01\x12\x13\n\x0bindexOffset\x18\x02 \x01(\x01\x12\x32\n\rinterpolation\x18\x03 \x01(\x0e\x32\x1b.IndexMapping.Interpolation\"?\n\rInterpolation\x12\x08\n\x04NONE\x10\x00\x12\n\n\x06LINEAR\x10\x01\x12\r\n\tQUADRATIC\x10\x02\x12\t\n\x05\x43UBIC\x10\x03\"\xa6\x01\n\x05Store\x12(\n\tbinCounts\x18\x01 \x03(\x0b\x32\x15.Store.BinCountsEntry\x12\x1f\n\x13\x63ontiguousBinCounts\x18\x02 \x03(\x01\x42\x02\x10\x01\x12 \n\x18\x63ontiguousBinIndexOffset\x18\x03 \x01(\x11\x1a\x30\n\x0e\x42inCountsEntry\x12\x0b\n\x03key\x18\x01 \x01(\x11\x12\r\n\x05value\x18\x02 \x01(\x01:\x02\x38\x01\x62\x06proto3') + + + +_DDSKETCH = DESCRIPTOR.message_types_by_name['DDSketch'] +_INDEXMAPPING = DESCRIPTOR.message_types_by_name['IndexMapping'] +_STORE = DESCRIPTOR.message_types_by_name['Store'] +_STORE_BINCOUNTSENTRY = _STORE.nested_types_by_name['BinCountsEntry'] +_INDEXMAPPING_INTERPOLATION = _INDEXMAPPING.enum_types_by_name['Interpolation'] +DDSketch = _reflection.GeneratedProtocolMessageType('DDSketch', (_message.Message,), { + 'DESCRIPTOR' : _DDSKETCH, + '__module__' : 'ddsketch_pb2' + # @@protoc_insertion_point(class_scope:DDSketch) + }) +_sym_db.RegisterMessage(DDSketch) + +IndexMapping = _reflection.GeneratedProtocolMessageType('IndexMapping', (_message.Message,), { + 'DESCRIPTOR' : _INDEXMAPPING, + '__module__' : 'ddsketch_pb2' + # @@protoc_insertion_point(class_scope:IndexMapping) + }) +_sym_db.RegisterMessage(IndexMapping) + +Store = _reflection.GeneratedProtocolMessageType('Store', (_message.Message,), { + + 'BinCountsEntry' : _reflection.GeneratedProtocolMessageType('BinCountsEntry', (_message.Message,), { + 'DESCRIPTOR' : _STORE_BINCOUNTSENTRY, + '__module__' : 'ddsketch_pb2' + # @@protoc_insertion_point(class_scope:Store.BinCountsEntry) + }) + , + 'DESCRIPTOR' : _STORE, + '__module__' : 'ddsketch_pb2' + # @@protoc_insertion_point(class_scope:Store) + }) +_sym_db.RegisterMessage(Store) +_sym_db.RegisterMessage(Store.BinCountsEntry) + +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + _STORE_BINCOUNTSENTRY._options = None + _STORE_BINCOUNTSENTRY._serialized_options = b'8\001' + _STORE.fields_by_name['contiguousBinCounts']._options = None + _STORE.fields_by_name['contiguousBinCounts']._serialized_options = b'\020\001' + _DDSKETCH._serialized_start=18 + _DDSKETCH._serialized_end=143 + _INDEXMAPPING._serialized_start=146 + _INDEXMAPPING._serialized_end=313 + _INDEXMAPPING_INTERPOLATION._serialized_start=250 + _INDEXMAPPING_INTERPOLATION._serialized_end=313 + _STORE._serialized_start=316 + _STORE._serialized_end=482 + _STORE_BINCOUNTSENTRY._serialized_start=434 + _STORE_BINCOUNTSENTRY._serialized_end=482 +# @@protoc_insertion_point(module_scope) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddsketch/pb/ddsketch_pre319_pb2.py b/lambdas/aws-dd-forwarder-3.127.0/ddsketch/pb/ddsketch_pre319_pb2.py new file mode 100644 index 0000000..4a6d3ef --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddsketch/pb/ddsketch_pre319_pb2.py @@ -0,0 +1,283 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: ddsketch.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='ddsketch.proto', + package='', + syntax='proto3', + serialized_pb=_b('\n\x0e\x64\x64sketch.proto\"}\n\x08\x44\x44Sketch\x12\x1e\n\x07mapping\x18\x01 \x01(\x0b\x32\r.IndexMapping\x12\x1e\n\x0epositiveValues\x18\x02 \x01(\x0b\x32\x06.Store\x12\x1e\n\x0enegativeValues\x18\x03 \x01(\x0b\x32\x06.Store\x12\x11\n\tzeroCount\x18\x04 \x01(\x01\"\xa7\x01\n\x0cIndexMapping\x12\r\n\x05gamma\x18\x01 \x01(\x01\x12\x13\n\x0bindexOffset\x18\x02 \x01(\x01\x12\x32\n\rinterpolation\x18\x03 \x01(\x0e\x32\x1b.IndexMapping.Interpolation\"?\n\rInterpolation\x12\x08\n\x04NONE\x10\x00\x12\n\n\x06LINEAR\x10\x01\x12\r\n\tQUADRATIC\x10\x02\x12\t\n\x05\x43UBIC\x10\x03\"\xa6\x01\n\x05Store\x12(\n\tbinCounts\x18\x01 \x03(\x0b\x32\x15.Store.BinCountsEntry\x12\x1f\n\x13\x63ontiguousBinCounts\x18\x02 \x03(\x01\x42\x02\x10\x01\x12 \n\x18\x63ontiguousBinIndexOffset\x18\x03 \x01(\x11\x1a\x30\n\x0e\x42inCountsEntry\x12\x0b\n\x03key\x18\x01 \x01(\x11\x12\r\n\x05value\x18\x02 \x01(\x01:\x02\x38\x01\x62\x06proto3') +) +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + + + +_INDEXMAPPING_INTERPOLATION = _descriptor.EnumDescriptor( + name='Interpolation', + full_name='IndexMapping.Interpolation', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='NONE', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='LINEAR', index=1, number=1, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='QUADRATIC', index=2, number=2, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='CUBIC', index=3, number=3, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=250, + serialized_end=313, +) +_sym_db.RegisterEnumDescriptor(_INDEXMAPPING_INTERPOLATION) + + +_DDSKETCH = _descriptor.Descriptor( + name='DDSketch', + full_name='DDSketch', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='mapping', full_name='DDSketch.mapping', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='positiveValues', full_name='DDSketch.positiveValues', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='negativeValues', full_name='DDSketch.negativeValues', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='zeroCount', full_name='DDSketch.zeroCount', index=3, + number=4, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=18, + serialized_end=143, +) + + +_INDEXMAPPING = _descriptor.Descriptor( + name='IndexMapping', + full_name='IndexMapping', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='gamma', full_name='IndexMapping.gamma', index=0, + number=1, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='indexOffset', full_name='IndexMapping.indexOffset', index=1, + number=2, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='interpolation', full_name='IndexMapping.interpolation', index=2, + number=3, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _INDEXMAPPING_INTERPOLATION, + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=146, + serialized_end=313, +) + + +_STORE_BINCOUNTSENTRY = _descriptor.Descriptor( + name='BinCountsEntry', + full_name='Store.BinCountsEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='Store.BinCountsEntry.key', index=0, + number=1, type=17, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='value', full_name='Store.BinCountsEntry.value', index=1, + number=2, type=1, cpp_type=5, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=_descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')), + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=434, + serialized_end=482, +) + +_STORE = _descriptor.Descriptor( + name='Store', + full_name='Store', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='binCounts', full_name='Store.binCounts', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='contiguousBinCounts', full_name='Store.contiguousBinCounts', index=1, + number=2, type=1, cpp_type=5, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=_descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001'))), + _descriptor.FieldDescriptor( + name='contiguousBinIndexOffset', full_name='Store.contiguousBinIndexOffset', index=2, + number=3, type=17, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[_STORE_BINCOUNTSENTRY, ], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=316, + serialized_end=482, +) + +_DDSKETCH.fields_by_name['mapping'].message_type = _INDEXMAPPING +_DDSKETCH.fields_by_name['positiveValues'].message_type = _STORE +_DDSKETCH.fields_by_name['negativeValues'].message_type = _STORE +_INDEXMAPPING.fields_by_name['interpolation'].enum_type = _INDEXMAPPING_INTERPOLATION +_INDEXMAPPING_INTERPOLATION.containing_type = _INDEXMAPPING +_STORE_BINCOUNTSENTRY.containing_type = _STORE +_STORE.fields_by_name['binCounts'].message_type = _STORE_BINCOUNTSENTRY +DESCRIPTOR.message_types_by_name['DDSketch'] = _DDSKETCH +DESCRIPTOR.message_types_by_name['IndexMapping'] = _INDEXMAPPING +DESCRIPTOR.message_types_by_name['Store'] = _STORE + +DDSketch = _reflection.GeneratedProtocolMessageType('DDSketch', (_message.Message,), dict( + DESCRIPTOR = _DDSKETCH, + __module__ = 'ddsketch_pb2' + # @@protoc_insertion_point(class_scope:DDSketch) + )) +_sym_db.RegisterMessage(DDSketch) + +IndexMapping = _reflection.GeneratedProtocolMessageType('IndexMapping', (_message.Message,), dict( + DESCRIPTOR = _INDEXMAPPING, + __module__ = 'ddsketch_pb2' + # @@protoc_insertion_point(class_scope:IndexMapping) + )) +_sym_db.RegisterMessage(IndexMapping) + +Store = _reflection.GeneratedProtocolMessageType('Store', (_message.Message,), dict( + + BinCountsEntry = _reflection.GeneratedProtocolMessageType('BinCountsEntry', (_message.Message,), dict( + DESCRIPTOR = _STORE_BINCOUNTSENTRY, + __module__ = 'ddsketch_pb2' + # @@protoc_insertion_point(class_scope:Store.BinCountsEntry) + )) + , + DESCRIPTOR = _STORE, + __module__ = 'ddsketch_pb2' + # @@protoc_insertion_point(class_scope:Store) + )) +_sym_db.RegisterMessage(Store) +_sym_db.RegisterMessage(Store.BinCountsEntry) + + +_STORE_BINCOUNTSENTRY.has_options = True +_STORE_BINCOUNTSENTRY._options = _descriptor._ParseOptions(descriptor_pb2.MessageOptions(), _b('8\001')) +_STORE.fields_by_name['contiguousBinCounts'].has_options = True +_STORE.fields_by_name['contiguousBinCounts']._options = _descriptor._ParseOptions(descriptor_pb2.FieldOptions(), _b('\020\001')) +# @@protoc_insertion_point(module_scope) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddsketch/pb/proto.py b/lambdas/aws-dd-forwarder-3.127.0/ddsketch/pb/proto.py new file mode 100644 index 0000000..ebccfd6 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddsketch/pb/proto.py @@ -0,0 +1,104 @@ +from ddsketch.ddsketch import BaseDDSketch +from ..mapping import ( + CubicallyInterpolatedMapping, + LinearlyInterpolatedMapping, + LogarithmicMapping, +) +from ..store import DenseStore + +import google.protobuf + + +pb_version = tuple(map(int, google.protobuf.__version__.split(".")[0:2])) + +if pb_version >= (3, 19, 0): + import ddsketch.pb.ddsketch_pb2 as pb +else: + import ddsketch.pb.ddsketch_pre319_pb2 as pb + + +class KeyMappingProto: + @classmethod + def _proto_interpolation(cls, mapping): + if type(mapping) is LogarithmicMapping: + return pb.IndexMapping.NONE + if type(mapping) is LinearlyInterpolatedMapping: + return pb.IndexMapping.LINEAR + if type(mapping) is CubicallyInterpolatedMapping: + return pb.IndexMapping.CUBIC + + @classmethod + def to_proto(cls, mapping): + """serialize to protobuf""" + return pb.IndexMapping( + gamma=mapping.gamma, + indexOffset=mapping._offset, + interpolation=cls._proto_interpolation(mapping), + ) + + @classmethod + def from_proto(cls, proto): + """deserialize from protobuf""" + if proto.interpolation == pb.IndexMapping.NONE: + return LogarithmicMapping.from_gamma_offset(proto.gamma, proto.indexOffset) + elif proto.interpolation == pb.IndexMapping.LINEAR: + return LinearlyInterpolatedMapping.from_gamma_offset( + proto.gamma, proto.indexOffset + ) + elif proto.interpolation == pb.IndexMapping.CUBIC: + return CubicallyInterpolatedMapping.from_gamma_offset( + proto.gamma, proto.indexOffset + ) + else: + raise ValueError("Unrecognized interpolation %r" % proto.interpolation) + + +class StoreProto: + """Currently only supports DenseStore""" + + @classmethod + def to_proto(cls, store): + """serialize to protobuf""" + return pb.Store( + contiguousBinCounts=store.bins, contiguousBinIndexOffset=store.offset + ) + + @classmethod + def from_proto(cls, proto): + """deserialize from protobuf""" + store = DenseStore() + index = proto.contiguousBinIndexOffset + store.offset = index + for count in proto.contiguousBinCounts: + store.add(index, count) + index += 1 + return store + + +class DDSketchProto: + @classmethod + def to_proto(self, ddsketch): + """serialize to protobuf""" + return pb.DDSketch( + mapping=KeyMappingProto.to_proto(ddsketch._mapping), + positiveValues=StoreProto.to_proto(ddsketch._store), + negativeValues=StoreProto.to_proto(ddsketch._negative_store), + zeroCount=ddsketch._zero_count, + ) + + @classmethod + def from_proto(cls, proto): + """deserialize from protobuf + + N.B., The current protobuf loses any min/max/sum/avg information. + """ + mapping = KeyMappingProto.from_proto(proto.mapping) + negative_store = StoreProto.from_proto(proto.negativeValues) + store = StoreProto.from_proto(proto.positiveValues) + zero_count = proto.zeroCount + return BaseDDSketch( + mapping=mapping, + store=store, + negative_store=negative_store, + zero_count=zero_count, + ) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddsketch/py.typed b/lambdas/aws-dd-forwarder-3.127.0/ddsketch/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddsketch/store.py b/lambdas/aws-dd-forwarder-3.127.0/ddsketch/store.py new file mode 100644 index 0000000..b9fbb48 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddsketch/store.py @@ -0,0 +1,504 @@ +from __future__ import division + + +# Unless explicitly stated otherwise all files in this repository are licensed +# under the Apache License 2.0. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2020 Datadog, Inc. + +""" +Stores map integers to counters. They can be seen as a collection of bins. +We start with 128 bins and grow the store in chunks of 128 unless specified +otherwise. +""" + +import abc +import math +import typing + + +if typing.TYPE_CHECKING: + from typing import List + from typing import Optional + +import six + + +CHUNK_SIZE = 128 + + +class _NegativeIntInfinity(int): + def __ge__(self, x): + return False + + __gt__ = __ge__ + + def __lt__(self, x): + return True + + __le__ = __lt__ + + +class _PositiveIntInfinity(int): + def __ge__(self, x): + return True + + __gt__ = __ge__ + + def __lt__(self, x): + return False + + __le__ = __lt__ + + +_neg_infinity = _NegativeIntInfinity() +_pos_infinity = _PositiveIntInfinity() + + +class Store(six.with_metaclass(abc.ABCMeta)): + """The basic specification of a store + + Attributes: + count (float): the sum of the counts for the bins + min_key (int): the minimum key bin + max_key (int): the maximum key bin + """ + + def __init__(self): + # type: () -> None + self.count = 0 # type: float + self.min_key = _pos_infinity # type: int + self.max_key = _neg_infinity # type: int + + @abc.abstractmethod + def copy(self, store): + """Copies the input store into this one.""" + + @abc.abstractmethod + def length(self): + # type: () -> int + """Return the number of bins.""" + + @abc.abstractmethod + def add(self, key, weight=1.0): + # type: (int, float) -> None + """Updates the counter at the specified index key, growing the number of bins if + necessary. + """ + + @abc.abstractmethod + def key_at_rank(self, rank, lower=True): + # type: (float, bool) -> int + """Return the key for the value at given rank. + + E.g., if the non-zero bins are [1, 1] for keys a, b with no offset + + if lower = True: + key_at_rank(x) = a for x in [0, 1) + key_at_rank(x) = b for x in [1, 2) + + if lower = False: + key_at_rank(x) = a for x in (-1, 0] + key_at_rank(x) = b for x in (0, 1] + """ + + @abc.abstractmethod + def merge(self, store): + # type: (Store) -> None + """Merge another store into this one. This should be equivalent as running the + add operations that have been run on the other store on this one. + """ + + +class DenseStore(Store): + """A dense store that keeps all the bins between the bin for the min_key and the + bin for the max_key. + + Args: + chunk_size (int, optional): the number of bins to grow by + + Attributes: + count (int): the sum of the counts for the bins + min_key (int): the minimum key bin + max_key (int): the maximum key bin + offset (int): the difference btw the keys and the index in which they are stored + bins (List[float]): the bins + """ + + def __init__(self, chunk_size=CHUNK_SIZE): + # type: (int) -> None + super(DenseStore, self).__init__() + + self.chunk_size = chunk_size # type: int + self.offset = 0 # type: int + self.bins = [] # type: List[float] + + def __repr__(self): + # type: () -> str + repr_str = "{" + for i, sbin in enumerate(self.bins): + repr_str += "%s: %s, " % (i + self.offset, sbin) + repr_str += "}}, min_key:%s, max_key:%s, offset:%s" % ( + self.min_key, + self.max_key, + self.offset, + ) + return repr_str + + def copy(self, store): + # type: (DenseStore) -> None + self.bins = store.bins[:] + self.count = store.count + self.min_key = store.min_key + self.max_key = store.max_key + self.offset = store.offset + + def length(self): + # type: () -> int + """Return the number of bins.""" + return len(self.bins) + + def add(self, key, weight=1.0): + # type: (int, float) -> None + idx = self._get_index(key) + self.bins[idx] += weight + self.count += weight + + def _get_index(self, key): + # type: (int) -> int + """Calculate the bin index for the key, extending the range if necessary.""" + if key < self.min_key: + self._extend_range(key) + elif key > self.max_key: + self._extend_range(key) + + return key - self.offset + + def _get_new_length(self, new_min_key, new_max_key): + # type: (int, int) -> int + desired_length = new_max_key - new_min_key + 1 + return self.chunk_size * int(math.ceil(desired_length / self.chunk_size)) + + def _extend_range(self, key, second_key=None): + # type: (int, Optional[int]) -> None + """Grow the bins as necessary and call _adjust""" + if second_key is None: + second_key = key + new_min_key = min(key, second_key, self.min_key) + new_max_key = max(key, second_key, self.max_key) + + if self.length() == 0: + # initialize bins + self.bins = [0.0] * self._get_new_length(new_min_key, new_max_key) + self.offset = new_min_key + self._adjust(new_min_key, new_max_key) + + elif new_min_key >= self.min_key and new_max_key < self.offset + self.length(): + # no need to change the range; just update min/max keys + self.min_key = new_min_key + self.max_key = new_max_key + + else: + # grow the bins + new_length = self._get_new_length(new_min_key, new_max_key) + if new_length > self.length(): + self.bins.extend([0.0] * (new_length - self.length())) + self._adjust(new_min_key, new_max_key) + + def _adjust(self, new_min_key, new_max_key): + # type: (int, int) -> None + """Adjust the bins, the offset, the min_key, and max_key, without resizing the + bins, in order to try making it fit the specified range. + """ + self._center_bins(new_min_key, new_max_key) + self.min_key = new_min_key + self.max_key = new_max_key + + def _shift_bins(self, shift): + # type: (int) -> None + """Shift the bins; this changes the offset.""" + if shift > 0: + self.bins = self.bins[:-shift] + self.bins[:0] = [0.0] * shift + else: + self.bins = self.bins[abs(shift) :] + self.bins.extend([0.0] * abs(shift)) + self.offset -= shift + + def _center_bins(self, new_min_key, new_max_key): + # type: (int, int) -> None + """Center the bins; this changes the offset.""" + middle_key = new_min_key + (new_max_key - new_min_key + 1) // 2 + self._shift_bins(self.offset + self.length() // 2 - middle_key) + + def key_at_rank(self, rank, lower=True): + # type: (float, bool) -> int + running_ct = 0.0 + for i, bin_ct in enumerate(self.bins): + running_ct += bin_ct + if (lower and running_ct > rank) or (not lower and running_ct >= rank + 1): + return i + self.offset + + return self.max_key + + def merge(self, store): # type: ignore[override] + # type: (DenseStore) -> None + if store.count == 0: + return + + if self.count == 0: + self.copy(store) + return + + if store.min_key < self.min_key or store.max_key > self.max_key: + self._extend_range(store.min_key, store.max_key) + + for key in range(store.min_key, store.max_key + 1): + self.bins[key - self.offset] += store.bins[key - store.offset] + + self.count += store.count + + +class CollapsingLowestDenseStore(DenseStore): + """A dense store that keeps all the bins between the bin for the min_key and the + bin for the max_key, but collapsing the left-most bins if the number of bins + exceeds the bin_limit + + Args: + bin_limit (int): the maximum number of bins + chunk_size (int, optional): the number of bins to grow by + + Attributes: + count (int): the sum of the counts for the bins + min_key (int): the minimum key bin + max_key (int): the maximum key bin + offset (int): the difference btw the keys and the index in which they are stored + bins (List[int]): the bins + """ + + def __init__(self, bin_limit, chunk_size=CHUNK_SIZE): + # type: (int, int) -> None + super(CollapsingLowestDenseStore, self).__init__() + self.bin_limit = bin_limit + self.is_collapsed = False + + def copy(self, store): # type: ignore[override] + # type: (CollapsingLowestDenseStore) -> None + self.bin_limit = store.bin_limit + self.is_collapsed = store.is_collapsed + super(CollapsingLowestDenseStore, self).copy(store) + + def _get_new_length(self, new_min_key, new_max_key): + # type: (int, int) -> int + desired_length = new_max_key - new_min_key + 1 + return min( + self.chunk_size * int(math.ceil(desired_length / self.chunk_size)), + self.bin_limit, + ) + + def _get_index(self, key): + # type: (int) -> int + """Calculate the bin index for the key, extending the range if necessary.""" + if key < self.min_key: + if self.is_collapsed: + return 0 + + self._extend_range(key) + if self.is_collapsed: + return 0 + elif key > self.max_key: + self._extend_range(key) + + return key - self.offset + + def _adjust(self, new_min_key, new_max_key): + # type: (int, int) -> None + """Override. Adjust the bins, the offset, the min_key, and max_key, without + resizing the bins, in order to try making it fit the specified + range. Collapse to the left if necessary. + """ + if new_max_key - new_min_key + 1 > self.length(): + # The range of keys is too wide, the lowest bins need to be collapsed. + new_min_key = new_max_key - self.length() + 1 + + if new_min_key >= self.max_key: + # put everything in the first bin + self.offset = new_min_key + self.min_key = new_min_key + self.bins[:] = [0.0] * self.length() + self.bins[0] = self.count + else: + shift = self.offset - new_min_key + if shift < 0: + collapse_start_index = self.min_key - self.offset + collapse_end_index = new_min_key - self.offset + collapsed_count = sum( + self.bins[collapse_start_index:collapse_end_index] + ) + self.bins[collapse_start_index:collapse_end_index] = [0.0] * ( + new_min_key - self.min_key + ) + self.bins[collapse_end_index] += collapsed_count + self.min_key = new_min_key + # shift the buckets to make room for new_max_key + self._shift_bins(shift) + else: + self.min_key = new_min_key + # shift the buckets to make room for new_min_key + self._shift_bins(shift) + + self.max_key = new_max_key + self.is_collapsed = True + else: + self._center_bins(new_min_key, new_max_key) + self.min_key = new_min_key + self.max_key = new_max_key + + def merge(self, store): # type: ignore[override] + # type: (CollapsingLowestDenseStore) -> None # type: ignore[override] + """Override.""" + if store.count == 0: + return + + if self.count == 0: + self.copy(store) + return + + if store.min_key < self.min_key or store.max_key > self.max_key: + self._extend_range(store.min_key, store.max_key) + + collapse_start_idx = store.min_key - store.offset + collapse_end_idx = min(self.min_key, store.max_key + 1) - store.offset + if collapse_end_idx > collapse_start_idx: + collapse_count = sum(store.bins[collapse_start_idx:collapse_end_idx]) + self.bins[0] += collapse_count + else: + collapse_end_idx = collapse_start_idx + + for key in range(collapse_end_idx + store.offset, store.max_key + 1): + self.bins[key - self.offset] += store.bins[key - store.offset] + + self.count += store.count + + +class CollapsingHighestDenseStore(DenseStore): + """A dense store that keeps all the bins between the bin for the min_key and the + bin for the max_key, but collapsing the right-most bins if the number of bins + exceeds the bin_limit + + Args: + bin_limit (int): the maximum number of bins + chunk_size (int, optional): the number of bins to grow by + + Attributes: + count (int): the sum of the counts for the bins + min_key (int): the minimum key bin + max_key (int): the maximum key bin + offset (int): the difference btw the keys and the index in which they are stored + bins (List[int]): the bins + """ + + def __init__(self, bin_limit, chunk_size=CHUNK_SIZE): + super(CollapsingHighestDenseStore, self).__init__() + self.bin_limit = bin_limit + self.is_collapsed = False + + def copy(self, store): # type: ignore[override] + # type: (CollapsingHighestDenseStore) -> None + self.bin_limit = store.bin_limit + self.is_collapsed = store.is_collapsed + super(CollapsingHighestDenseStore, self).copy(store) + + def _get_new_length(self, new_min_key, new_max_key): + # type: (int, int) -> int + desired_length = new_max_key - new_min_key + 1 + # For some reason mypy can't infer that min(int, int) is an int, so cast it. + return int( + min( + self.chunk_size * int(math.ceil(desired_length / self.chunk_size)), + self.bin_limit, + ) + ) + + def _get_index(self, key): + # type: (int) -> int + """Calculate the bin index for the key, extending the range if necessary""" + if key > self.max_key: + if self.is_collapsed: + return self.length() - 1 + + self._extend_range(key) + if self.is_collapsed: + return self.length() - 1 + elif key < self.min_key: + self._extend_range(key) + return key - self.offset + + def _adjust(self, new_min_key, new_max_key): + # type: (int, int) -> None + """Override. Adjust the bins, the offset, the min_key, and max_key, without + resizing the bins, in order to try making it fit the specified + range. Collapse to the left if necessary. + """ + if new_max_key - new_min_key + 1 > self.length(): + # The range of keys is too wide, the lowest bins need to be collapsed. + new_max_key = new_min_key + self.length() - 1 + + if new_max_key <= self.min_key: + # put everything in the last bin + self.offset = new_min_key + self.max_key = new_max_key + self.bins[:] = [0.0] * self.length() + self.bins[-1] = self.count + else: + shift = self.offset - new_min_key + if shift > 0: + collapse_start_index = new_max_key - self.offset + 1 + collapse_end_index = self.max_key - self.offset + 1 + collapsed_count = sum( + self.bins[collapse_start_index:collapse_end_index] + ) + self.bins[collapse_start_index:collapse_end_index] = [0.0] * ( + self.max_key - new_max_key + ) + self.bins[collapse_start_index - 1] += collapsed_count + self.max_key = new_max_key + # shift the buckets to make room for new_max_key + self._shift_bins(shift) + else: + self.max_key = new_max_key + # shift the buckets to make room for new_min_key + self._shift_bins(shift) + + self.min_key = new_min_key + self.is_collapsed = True + else: + self._center_bins(new_min_key, new_max_key) + self.min_key = new_min_key + self.max_key = new_max_key + + def merge(self, store): # type: ignore[override] + # type: (CollapsingHighestDenseStore) -> None # type: ignore[override] + """Override.""" + if store.count == 0: + return + + if self.count == 0: + self.copy(store) + return + + if store.min_key < self.min_key or store.max_key > self.max_key: + self._extend_range(store.min_key, store.max_key) + + collapse_end_idx = store.max_key - store.offset + 1 + collapse_start_idx = max(self.max_key + 1, store.min_key) - store.offset + if collapse_end_idx > collapse_start_idx: + collapse_count = sum(store.bins[collapse_start_idx:collapse_end_idx]) + self.bins[-1] += collapse_count + else: + collapse_start_idx = collapse_end_idx + + for key in range(store.min_key, collapse_start_idx + store.offset): + self.bins[key - self.offset] += store.bins[key - store.offset] + + self.count += store.count diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/INSTALLER b/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/LICENSE b/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/LICENSE new file mode 100644 index 0000000..5f8fd63 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/LICENSE @@ -0,0 +1,6 @@ +## License + +This work is dual-licensed under Apache 2.0 or BSD3. +You may select, at your option, one of the above-listed licenses. + +`SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause` diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/LICENSE.Apache b/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/LICENSE.Apache new file mode 100644 index 0000000..bff56b5 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/LICENSE.Apache @@ -0,0 +1,200 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016 Datadog, Inc. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/LICENSE.BSD3 b/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/LICENSE.BSD3 new file mode 100644 index 0000000..e8f3a81 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/LICENSE.BSD3 @@ -0,0 +1,24 @@ +Copyright (c) 2016, Datadog +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Datadog nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL DATADOG BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/METADATA b/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/METADATA new file mode 100644 index 0000000..fd46eae --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/METADATA @@ -0,0 +1,68 @@ +Metadata-Version: 2.1 +Name: ddtrace +Version: 2.6.5 +Summary: Datadog APM client library +Home-page: https://github.com/DataDog/dd-trace-py +Author: Datadog, Inc. +Author-email: "Datadog, Inc." +License: LICENSE.BSD3 +Project-URL: Bug Tracker, https://github.com/DataDog/dd-trace-py/issues +Project-URL: Changelog, https://github.com/DataDog/dd-trace-py/releases +Project-URL: Documentation, https://ddtrace.readthedocs.io/en/stable/ +Project-URL: Homepage, https://github.com/DataDog/dd-trace-py +Project-URL: Source Code, https://github.com/DataDog/dd-trace-py/ +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Requires-Python: >=3.7 +Description-Content-Type: text/markdown +License-File: LICENSE +License-File: LICENSE.Apache +License-File: LICENSE.BSD3 +License-File: NOTICE +Requires-Dist: attrs >=20 +Requires-Dist: cattrs +Requires-Dist: ddsketch >=2.0.1 +Requires-Dist: envier +Requires-Dist: opentelemetry-api >=1 +Requires-Dist: protobuf >=3 +Requires-Dist: six >=1.12.0 +Requires-Dist: typing-extensions +Requires-Dist: xmltodict >=0.12 +Requires-Dist: importlib-metadata <=6.5.0 ; python_version < "3.8" +Requires-Dist: bytecode ~=0.13.0 ; python_version == "3.7" +Requires-Dist: setuptools ; python_version >= "3.12" +Requires-Dist: bytecode ; python_version >= "3.8" +Provides-Extra: opentracing +Requires-Dist: opentracing >=2.0.0 ; extra == 'opentracing' + +# `ddtrace` + +[![CircleCI](https://circleci.com/gh/DataDog/dd-trace-py/tree/main.svg?style=svg)](https://circleci.com/gh/DataDog/dd-trace-py/tree/main) +[![PypiVersions](https://img.shields.io/pypi/v/ddtrace.svg)](https://pypi.org/project/ddtrace/) +[![Pyversions](https://img.shields.io/pypi/pyversions/ddtrace.svg?style=flat)](https://pypi.org/project/ddtrace/) + +bits python + +This library powers [Distributed Tracing](https://docs.datadoghq.com/tracing/), + [Continuous Profiling](https://docs.datadoghq.com/tracing/profiler/), + [Error Tracking](https://docs.datadoghq.com/tracing/error_tracking/), + [Continuous Integration Visibility](https://docs.datadoghq.com/continuous_integration/), + [Deployment Tracking](https://docs.datadoghq.com/tracing/deployment_tracking/), + [Code Hotspots](https://docs.datadoghq.com/tracing/profiler/connect_traces_and_profiles/), + [Dynamic Instrumentation](https://docs.datadoghq.com/dynamic_instrumentation/), + and more. + +To get started with tracing, check out the [product documentation][setup docs] or the [glossary][visualization docs]. + +For advanced usage and configuration information, check out the [library documentation][api docs]. + +To get started as a contributor, see [the contributing docs](https://ddtrace.readthedocs.io/en/stable/contributing.html) first. + +[setup docs]: https://docs.datadoghq.com/tracing/setup/python/ +[api docs]: https://ddtrace.readthedocs.io/ +[visualization docs]: https://docs.datadoghq.com/tracing/visualization/ diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/NOTICE b/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/NOTICE new file mode 100644 index 0000000..732c748 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/NOTICE @@ -0,0 +1,4 @@ +Datadog dd-trace-py +Copyright 2016-Present Datadog, Inc. + +This product includes software developed at Datadog, Inc. (https://www.datadoghq.com/). diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/RECORD b/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/RECORD new file mode 100644 index 0000000..57017c5 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/RECORD @@ -0,0 +1,1391 @@ +../../bin/ddtrace-run,sha256=-2hcsI92sxn_Du4qKKiIz_MEFpezi6TBsB7AVMn47U8,236 +ddtrace-2.6.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +ddtrace-2.6.5.dist-info/LICENSE,sha256=OZvn-IQ0kjk6HmSP8Yi2WQqZBzGRJQmvBO5w9KBdD3o,186 +ddtrace-2.6.5.dist-info/LICENSE.Apache,sha256=5V2RruBHZQIcPyceiv51DjjvdvhgsgS4pnXAOHDuZkQ,11342 +ddtrace-2.6.5.dist-info/LICENSE.BSD3,sha256=J9S_Tq-hhvteDV2W8R0rqht5DZHkmvgdx3gnLZg4j6Q,1493 +ddtrace-2.6.5.dist-info/METADATA,sha256=tGHBBqBIyOkA1eJ3mPq8FY6G12qN2-hl-Ef-axiZK0w,3287 +ddtrace-2.6.5.dist-info/NOTICE,sha256=Wmf6iXVNfb58zWLK5pIkcbqMflb7pl38JhxjMwmjtyc,146 +ddtrace-2.6.5.dist-info/RECORD,, +ddtrace-2.6.5.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace-2.6.5.dist-info/WHEEL,sha256=AI1yqBLEPcVKWn5Ls2uPawjbqPXPFTYdQLSdN8WFCJw,152 +ddtrace-2.6.5.dist-info/entry_points.txt,sha256=1t-yacpd7hsx2aKKB7_O34U414M5pPnqp-tu0XmflB8,337 +ddtrace-2.6.5.dist-info/top_level.txt,sha256=jPd7qTCAnWevz7DZiI0jdVlnFB3cautvluLsO-iMgQY,8 +ddtrace/__init__.py,sha256=bBRAg-TKVYa9IaNrop8whGGNAGY4PSofcN_FxgrIeHY,1754 +ddtrace/__pycache__/__init__.cpython-311.pyc,, +ddtrace/__pycache__/_hooks.cpython-311.pyc,, +ddtrace/__pycache__/_logger.cpython-311.pyc,, +ddtrace/__pycache__/_monkey.cpython-311.pyc,, +ddtrace/__pycache__/_version.cpython-311.pyc,, +ddtrace/__pycache__/auto.cpython-311.pyc,, +ddtrace/__pycache__/constants.cpython-311.pyc,, +ddtrace/__pycache__/context.cpython-311.pyc,, +ddtrace/__pycache__/data_streams.cpython-311.pyc,, +ddtrace/__pycache__/filters.cpython-311.pyc,, +ddtrace/__pycache__/pin.cpython-311.pyc,, +ddtrace/__pycache__/provider.cpython-311.pyc,, +ddtrace/__pycache__/sampler.cpython-311.pyc,, +ddtrace/__pycache__/sampling_rule.cpython-311.pyc,, +ddtrace/__pycache__/span.cpython-311.pyc,, +ddtrace/__pycache__/tracer.cpython-311.pyc,, +ddtrace/__pycache__/version.cpython-311.pyc,, +ddtrace/_hooks.py,sha256=VW8lblk-yD8rBtvU7dPetjAfaeEtUVDuJCrGbPEnwMI,3865 +ddtrace/_logger.py,sha256=T55nFe_TYEfQK0NmTb513ux6yurm5s1qAWc3j-ZIA8g,3658 +ddtrace/_monkey.py,sha256=AC5dmFiLBVrA2Hl3ZJmfhSNOcGQMsX7aUFKfYBv_o3M,8905 +ddtrace/_trace/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/_trace/__pycache__/__init__.cpython-311.pyc,, +ddtrace/_trace/__pycache__/_limits.cpython-311.pyc,, +ddtrace/_trace/_limits.py,sha256=L2N27bqVnpOn2T53HDn-0rD43ek7GNuyS6piZgP-2tM,92 +ddtrace/_version.py,sha256=D_wLNXauKIfiJpn1EENjWxSq66DO-yHjqpgfDoH3wzY,411 +ddtrace/appsec/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/appsec/__pycache__/__init__.cpython-311.pyc,, +ddtrace/appsec/__pycache__/_asm_request_context.cpython-311.pyc,, +ddtrace/appsec/__pycache__/_capabilities.cpython-311.pyc,, +ddtrace/appsec/__pycache__/_constants.cpython-311.pyc,, +ddtrace/appsec/__pycache__/_deduplications.cpython-311.pyc,, +ddtrace/appsec/__pycache__/_handlers.cpython-311.pyc,, +ddtrace/appsec/__pycache__/_metrics.cpython-311.pyc,, +ddtrace/appsec/__pycache__/_processor.cpython-311.pyc,, +ddtrace/appsec/__pycache__/_remoteconfiguration.cpython-311.pyc,, +ddtrace/appsec/__pycache__/_trace_utils.cpython-311.pyc,, +ddtrace/appsec/__pycache__/_utils.cpython-311.pyc,, +ddtrace/appsec/_api_security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/appsec/_api_security/__pycache__/__init__.cpython-311.pyc,, +ddtrace/appsec/_api_security/__pycache__/api_manager.cpython-311.pyc,, +ddtrace/appsec/_api_security/api_manager.py,sha256=g8L3zogrDhJQGgdxULXdigviGnmzD1pEF2Oc9yVLQqQ,6217 +ddtrace/appsec/_asm_request_context.py,sha256=K4MArRjpn9AI0g5563dd2tQyu2V-FhkdELspeTtm7Wo,18228 +ddtrace/appsec/_capabilities.py,sha256=NL1qG7JMitdrEF53kkUKiXLMyvDOCIQ0Jp9s3KsUo38,2513 +ddtrace/appsec/_constants.py,sha256=pUaW4ys3NpzgbZa_GB8gOBD8Zl6lUlpNHMA8DzrLQ8s,8237 +ddtrace/appsec/_ddwaf/__init__.py,sha256=zQKZHtgw2QQv3SQ_5gGgV2z79T4C1KSqOFkCgojS6vQ,7685 +ddtrace/appsec/_ddwaf/__pycache__/__init__.cpython-311.pyc,, +ddtrace/appsec/_ddwaf/__pycache__/ddwaf_types.cpython-311.pyc,, +ddtrace/appsec/_ddwaf/ddwaf_types.py,sha256=rCTezCO2D7dskFQ14_2Q7VodGBgjaOEWjGY8jZSkrY0,16417 +ddtrace/appsec/_ddwaf/libddwaf/x86_64/lib/libddwaf.so,sha256=N_HdHd9oTea4Ep9fyse9AUgfHoddwt9OKJfo5iir2Oc,2195976 +ddtrace/appsec/_deduplications.py,sha256=balBc-3vYEsb9qsajcMTqZ0TYM8eaAyygDymuUTQJTA,1096 +ddtrace/appsec/_handlers.py,sha256=Z9y5pNYejwxbAEwHA1cmRwONHKgoUzJEJvmfdIk4bNU,14559 +ddtrace/appsec/_iast/__init__.py,sha256=115d3innxqsf_N85LwpM47F_rBlBENHipfV4Rh1u_DM,2528 +ddtrace/appsec/_iast/__pycache__/__init__.cpython-311.pyc,, +ddtrace/appsec/_iast/__pycache__/_input_info.cpython-311.pyc,, +ddtrace/appsec/_iast/__pycache__/_loader.cpython-311.pyc,, +ddtrace/appsec/_iast/__pycache__/_metrics.cpython-311.pyc,, +ddtrace/appsec/_iast/__pycache__/_overhead_control_engine.cpython-311.pyc,, +ddtrace/appsec/_iast/__pycache__/_patch.cpython-311.pyc,, +ddtrace/appsec/_iast/__pycache__/_patch_modules.cpython-311.pyc,, +ddtrace/appsec/_iast/__pycache__/_taint_dict.cpython-311.pyc,, +ddtrace/appsec/_iast/__pycache__/_taint_utils.cpython-311.pyc,, +ddtrace/appsec/_iast/__pycache__/_utils.cpython-311.pyc,, +ddtrace/appsec/_iast/__pycache__/constants.cpython-311.pyc,, +ddtrace/appsec/_iast/__pycache__/processor.cpython-311.pyc,, +ddtrace/appsec/_iast/__pycache__/reporter.cpython-311.pyc,, +ddtrace/appsec/_iast/_ast/__init__.py,sha256=1oLL20yLB1GL9IbFiZD8OReDqiCpFr-yetIR6x1cNkI,23 +ddtrace/appsec/_iast/_ast/__pycache__/__init__.cpython-311.pyc,, +ddtrace/appsec/_iast/_ast/__pycache__/ast_patching.cpython-311.pyc,, +ddtrace/appsec/_iast/_ast/__pycache__/visitor.cpython-311.pyc,, +ddtrace/appsec/_iast/_ast/ast_patching.py,sha256=EiFFgzy-ICs9EWYDPRRUrYezp_X-HNpwyijhq0kNQxk,5319 +ddtrace/appsec/_iast/_ast/visitor.py,sha256=i4rF8ijsM2Rp57zkBkcG-fB0aEH_GRuRuRtZyUgd_9g,31555 +ddtrace/appsec/_iast/_input_info.py,sha256=fd2GRmY1bsjrm3jFXu_dgwerOlKWO3B785FG-XhAgxQ,447 +ddtrace/appsec/_iast/_loader.py,sha256=48dE2QjNYEwYPXiy4BTtm83ucDuDYAkklFTGs55ZyCw,863 +ddtrace/appsec/_iast/_metrics.py,sha256=B6gK0mDidUAk3jfOC0CuI2Apk2yuIh-r9eI_FlC3qhA,5336 +ddtrace/appsec/_iast/_overhead_control_engine.py,sha256=BI0oXr3XLRWW4Pds5B57LmXiOOKpiM6y_Y89l4H6ZLs,4451 +ddtrace/appsec/_iast/_patch.py,sha256=oVKBEfsTpPSIb0sLEalQppf4ccXQmPNKQCiko713INQ,5764 +ddtrace/appsec/_iast/_patch_modules.py,sha256=v-C0yqWrEpr_NpRry8jufU2ZCD2Kyqa8F0SYgQV_Ff8,789 +ddtrace/appsec/_iast/_patches/__pycache__/json_tainting.cpython-311.pyc,, +ddtrace/appsec/_iast/_patches/json_tainting.py,sha256=BS4KA9M5q5gozp0A3nRwQkbh6cM09PAgWvxP4flPmWA,2881 +ddtrace/appsec/_iast/_stacktrace.c,sha256=c-iYHfUnsbJQhIvpYVMJP1sJYOlGgbW1gicnjyYI0Tg,4745 +ddtrace/appsec/_iast/_stacktrace.cpython-311-x86_64-linux-gnu.so,sha256=fxKX5mOHeE5nSZyInZXStAuTrO-PuH_Zzn9f_CwPq6I,16976 +ddtrace/appsec/_iast/_taint_dict.py,sha256=aX-Orr9g0HkASLmEBg57z8oLP9NBzo6C6FEpkHSfVHE,522 +ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectExtend.cpp,sha256=lTYdMHMeKdke5vadPDMg9dtGMLwcwnb2r1F1P1dorxs,1310 +ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectExtend.h,sha256=SiBtZrE70Jl2Slk4rPyyxQC7OgPDU5SRNrbNDIs_iwQ,157 +ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectFormat.cpp,sha256=ALl3sNgE9DWte8NAWMBTMObUEOM_6xJO4wGKl12Jhkk,2095 +ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectFormat.h,sha256=BWDsHh9McH3GdvzjA8ljNDXb-uHQ7a3EdsPpGlBNjGw,448 +ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectIndex.cpp,sha256=AS1_-KYkTbSceURhDJNKy1AOvsZ_n0HAsHsy4Fasw90,1282 +ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectIndex.h,sha256=g58F_438ciqqhKtAY4yqEheDZJgv93lBCCZ1tCgpgFo,281 +ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectJoin.cpp,sha256=NyoC_13HIJtS9ERrg9wCwCtL7SmDzvQBk_EmtthoSHA,7175 +ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectJoin.h,sha256=Nm7Zqs_LhHyttysjV9wu86UME3a_dgEMa6le6k-xRFs,240 +ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectOperatorAdd.cpp,sha256=Q11TfgGUnOO5D3n7W5AzED7cru1BmfeYbvSAcUiesgM,4129 +ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectOperatorAdd.h,sha256=bmkqJlXgpphoOqBTOh098MWmjkp-kDDPRDMjyJwWtbg,213 +ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSlice.cpp,sha256=oC7zPY2n_3Nmz8GRglXY27Pz543FRXvhCb5oNvyjsZY,3853 +ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSlice.h,sha256=xBy5qunF4kNnBh1yQEJUNkHPZGx3q3yHC55OP1QKClc,163 +ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.cpp,sha256=7158WBwsJioeb52e_InpKzkYhYd07MgYQSm4OYO7Hy4,12777 +ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.h,sha256=PxvMWJPKAv52IhrgY2fNjWDd2n6NmHV7pdfcVaU7FZM,1834 +ddtrace/appsec/_iast/_taint_tracking/Aspects/_aspects_exports.h,sha256=W9eoyYf-sNInZU1bVn23UcQ0E-Ghut8KMTANmkXH0Bk,412 +ddtrace/appsec/_iast/_taint_tracking/CMakeLists.txt,sha256=SlR7CmiJ9ui_KtFvVLRBHg4pCrf7_6IGPxiHFNoOJjY,2305 +ddtrace/appsec/_iast/_taint_tracking/Constants.h,sha256=8pRUHCymtbzoIFKGdLnmCKDPzLY9BM6ombe0p0LLRGo,134 +ddtrace/appsec/_iast/_taint_tracking/Initializer/Initializer.cpp,sha256=81vx8MXRYX2XM3OLEuaiaCSHiXjbq0YxEpyy46s0wGM,6471 +ddtrace/appsec/_iast/_taint_tracking/Initializer/Initializer.h,sha256=M1TmaVeUl0ZCH3pQ8SEI69q7K-6vwHZi811TQLxWIxs,4548 +ddtrace/appsec/_iast/_taint_tracking/Initializer/_initializer.h,sha256=LwQAUIDDAx-Yk7_k1O6hoRGek6VoAqnMPjzYM-bV_8E,307 +ddtrace/appsec/_iast/_taint_tracking/README.txt,sha256=TLWoDrzE3qfLe88EvUQkJthIRrWYN6JG9oXTw7YDZqs,821 +ddtrace/appsec/_iast/_taint_tracking/TaintTracking/Source.cpp,sha256=rC9Eg4StydtdxwAJNaNs1CQAwi8M_WHxzlZ_3yd1sXM,2847 +ddtrace/appsec/_iast/_taint_tracking/TaintTracking/Source.h,sha256=A5BgphieMkFVAn3G8QBv5E7sV5kJ5CMI2OZbRuSuiFk,3347 +ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintRange.cpp,sha256=3k88GA3ONPvp3cR40ARkkprmqX-b3TdrLOSEjMit6Tw,13939 +ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintRange.h,sha256=78w6fhKoIvLf4sak3UGgTEMvCHNFBM4eG7H_3182Ewg,3983 +ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintedObject.cpp,sha256=V_u-Z3mFcwLR2nZJRElVjZMomGjJjf01ZICQNI9H-JQ,4014 +ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintedObject.h,sha256=yMKiLV9RupcamBpHbePvGRmpO8lGMYwBbGzsjMasPRU,1615 +ddtrace/appsec/_iast/_taint_tracking/TaintTracking/_taint_tracking.h,sha256=EVO6qgu6MyZtxsTWiomXsOpl_NaxLTT-299jwo9lpxE,474 +ddtrace/appsec/_iast/_taint_tracking/TaintedOps/TaintedOps.cpp,sha256=_1E1VqgfvXXoTrpw__J6uch8na5RuRTJvpZf9f6aZq0,1152 +ddtrace/appsec/_iast/_taint_tracking/TaintedOps/TaintedOps.h,sha256=UsV-E2mlzpGNELnbgJ1SY07CJz3BbziufhC_p2JtVOk,1032 +ddtrace/appsec/_iast/_taint_tracking/Utils/StringUtils.cpp,sha256=sKtcQp_RI8Ayi8U4m6JX-UlDFGeShvV8cprvSPVtO94,4290 +ddtrace/appsec/_iast/_taint_tracking/Utils/StringUtils.h,sha256=jPiwMj7Lo3T4vnaTlQL8Y8WESVaHvbno0lxQ5LP9flk,684 +ddtrace/appsec/_iast/_taint_tracking/__init__.py,sha256=V2yjQL_pdVkwyQOmRFJk3U-5stEKWy_YKJCMGSbMzzY,5048 +ddtrace/appsec/_iast/_taint_tracking/__pycache__/__init__.cpython-311.pyc,, +ddtrace/appsec/_iast/_taint_tracking/__pycache__/aspects.cpython-311.pyc,, +ddtrace/appsec/_iast/_taint_tracking/_native.cpp,sha256=7gFaQS6h_dw53_AGvbmnwJWDhq5R33DohTzPfNT5WCA,3611 +ddtrace/appsec/_iast/_taint_tracking/_native.cpython-311-x86_64-linux-gnu.so,sha256=-miouZYVKFSgF-pfMLVgoFGT9c26WLvUTmX081jgY-Y,633224 +ddtrace/appsec/_iast/_taint_tracking/_vendor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/appsec/_iast/_taint_tracking/_vendor/__pycache__/__init__.cpython-311.pyc,, +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/CMakeLists.txt,sha256=X4_B7XO6EzkqC94TVFELCWz_CS0kxMJIo31_ohjS_gY,12067 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/__pycache__/__init__.cpython-311.pyc,, +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/attr.h,sha256=QPjH7BfhL8QFwHHkrDak8gNOLMlb1itAO5fobjdoLp8,24334 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/buffer_info.h,sha256=m_VE_hfWPKl-KgUZy9aVQdPg1xtoaDaBgkurIX7aGig,7750 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/cast.h,sha256=Rcq-l1HCfMIyBxbJSM041wpu3EpZBVChfgVdEpdnqC0,67312 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/chrono.h,sha256=A23naeloqn-1NKVAABOsJtHU9Vz8lfvrAICuLk-7qBM,8458 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/common.h,sha256=ATg9Bt1pwF8qnNuI086fprM4CUTdrZdk_g2HXE1Sf6A,120 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/complex.h,sha256=AaDZ-rEmK4tFaue-K9P5y3TxxnaQF6JwZ_6LAzkdLQI,2096 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/detail/class.h,sha256=Y2IzTplhE5KiMiBlzWSAovCQaI_1M0dlsoDYCTpB5Hg,28518 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/detail/common.h,sha256=rACKWPmqUkdizSQMftuCkLCZBXGbVSiuRv_h0uaY-k4,53480 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/detail/descr.h,sha256=k1nvytx1zhMh8ERL2xS8Unbxcio5fa7eZIqnTsZ0orE,5962 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/detail/init.h,sha256=vYO2nScstnYiCn4Kh57IKrOwpNkQ9gqME4foF03JU6A,17859 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/detail/internals.h,sha256=Az8HDKl3QU-KEOapdwHYifjWpwbej4Js5wKvSspAjQk,28221 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/detail/type_caster_base.h,sha256=LC91ejtvXjGQ0DaUFrYN3ChE1agf9Y2hHDs7byTbZa8,48364 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/detail/typeid.h,sha256=jw5pr9m72vkDsloT8vxl9wj17VJGcEdXDyziBlt89Js,1625 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/eigen.h,sha256=-HmSA1kgwCQ-GHUt7PHtTEc-vxqw9xARpF8PHWJip28,316 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/eigen/common.h,sha256=dIeqmK7IzW5K4k2larPnA1A863rDp38U9YbNIwiIyYk,378 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/eigen/matrix.h,sha256=CS8NpkZI8Y8ty0NFQC7GZcUlM5o8_1Abv1GbGltsbkA,32135 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/eigen/tensor.h,sha256=jLtNZKXr7MWFplt9x3qnHdO73jNZlAqT40Hb4FPabnk,18442 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/embed.h,sha256=xD-oEg56PadTig9a8FOcMgbsL64jaie7hwG3y6DWPEI,13459 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/eval.h,sha256=7re-O2Eor1yD0Q_KgFkHIjKD17ejzII687Yszl9_KfE,4731 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/functional.h,sha256=cXDJUS0Y_1GBbOK4Nn13exhkZsAQWx408HZ-PFBmbJo,5002 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/gil.h,sha256=RZkkMm0E9PQlHXW6xkBIhM7VBeCvmyJlPVQNaSJMUQQ,8262 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/iostream.h,sha256=K5rPXoCYN325r1PptcJCIhPhgtRtTJQjMr7bvUIOwxk,8862 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/numpy.h,sha256=G-hxJJom5roJ7s_hTiG1Mq9NxpZ6BOzK03fLXUQuH30,79725 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/operators.h,sha256=224RoAXcv1la4NNY9rQ3aD_AeC8S9ZKx3HVK1O8B4MU,9103 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/options.h,sha256=qXvmnj--9fZSp56NYefnB3W5V17ppHlY1Srgo3DNBpw,2734 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/pybind11.h,sha256=V1zKPVpON-t2yGVQigySYMALadMx-ux7eZ_xC0ILg9c,126706 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/pytypes.h,sha256=mshHowCgq91Dt06atf5C6DFhRSWbUwYBgIl21-2usco,98455 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/stl.h,sha256=dajIEYNnTimX5uYYLm0TzYesxq87JakWZ5KWCzbET2I,15477 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/stl/filesystem.h,sha256=refLexmdcqOM6Qjo9QMB6heA5bQ7GZrP6DCvVBv0R1M,4185 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/stl_bind.h,sha256=835YP_4OkcKTkNOaY-GUUXIDf86GSpN65lVivP0M4TY,29897 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/include/pybind11/type_caster_pyobject_ptr.h,sha256=H7pKBYTvUlibiJQEcKmeAkygSQwoCkuIyukNSDmVq-U,1929 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/__init__.py,sha256=4-WhH9Ac6P8D_FqnflpOch8XlaZrkXbe95FspbMvwu0,429 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/__main__.py,sha256=ATLlhFlhBxDXxxXEfnf2F1RcRhuWN1ziMwbmrGuhif0,1544 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/__pycache__/__init__.cpython-311.pyc,, +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/__pycache__/__main__.cpython-311.pyc,, +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/__pycache__/_version.cpython-311.pyc,, +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/__pycache__/commands.cpython-311.pyc,, +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/__pycache__/setup_helpers.cpython-311.pyc,, +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/_version.py,sha256=wUJj-zKbescYMsNA17iZHUMgzy99ahCucWDV4KMhPCg,228 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/commands.py,sha256=iJBFWhXHqlC_JMAgMjMIn6H_hizvorS572upGU1roGA,1207 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/setup_helpers.py,sha256=CSDjuh2T5zlcZHqII8tO7HZcz8-qmMlUfCo23_WESaw,17475 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/FindCatch.cmake,sha256=7D4GwE3lgw_0y-NMGqkGS9aTEXFteGbj3ZgXlXr3g2A,2449 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/FindEigen3.cmake,sha256=liSnYcMw1gAxI-AZGVS0CJJsOQ2bGcDcG3LbCR5sta8,3105 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/FindPythonLibsNew.cmake,sha256=1dEMOUQxj-xTQzlQLBBXMT4DTScNJFquOQGAWiJawCA,11190 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/JoinPaths.cmake,sha256=eUsNj6YqO3mMffEtUBFFgNGkeiNL-2tNgwkutkam7MQ,817 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/__pycache__/__init__.cpython-311.pyc,, +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/__pycache__/codespell_ignore_lines_from_errors.cpython-311.pyc,, +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/__pycache__/libsize.cpython-311.pyc,, +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/__pycache__/make_changelog.cpython-311.pyc,, +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/check-style.sh,sha256=TigulaRClaWcR-fjZLt0PtH9oncUdvsDnOxFyp9CjX8,1423 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/cmake_uninstall.cmake.in,sha256=39wpRXJHm-hEzGGkil1HbBFPITu9JOzV5xVt46JLcBE,952 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/codespell_ignore_lines_from_errors.py,sha256=bTs7QS1-reWL04cS6C-Fh4F-TTXBgLIhemO4gfRaIgo,1117 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/libsize.py,sha256=3MBZDCi0-kdKei_6RcTbmVJgtmT4udB-WIF-mOPLBD4,1031 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/make_changelog.py,sha256=W1WAntnxxI8vWp6JPikaiY6FToN4vpYcXFBSJhP7ZdM,1311 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pybind11.pc.in,sha256=O_qrWegwZzC4WaSJ5pCnoeTCRt6-z1KRrb0gElWoBYo,196 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pybind11Common.cmake,sha256=JXy2aActygFERWyazURxTk0jW1MTcYtvaOL_8KFjNWw,14449 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pybind11Config.cmake.in,sha256=AkLEzM2gv7T49m5w32CnB1ez6uxX9P2_fUdypNc_MPo,7101 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pybind11NewTools.cmake,sha256=2mZ2ZUkFMsDk2JQU5hRwIjamCsljarTQu8CM8mY7P8A,8960 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pybind11Tools.cmake,sha256=kRoc1SvQl6NRZEHHuM-NDrfyMJF1HbgL-TiTu__Dkw8,8361 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pyproject.toml,sha256=JPALGLInEk7BiomriJ9xCKQW6vzO82rAvFSn1125GMA,94 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/setup_global.py.in,sha256=01t6jThpKlPyD3SJGWPj1aiejGAzGtzlm_XjRY4fsHM,2104 +ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/setup_main.py.in,sha256=XNB-0uhzvP6MP0mePkM0oWcIPpjBdwgVCeseEhTvX34,1234 +ddtrace/appsec/_iast/_taint_tracking/aspects.py,sha256=JCVHjDKVFmlnkOYOhPj6ijzDUhr299kUlF4eCiO6bjA,34809 +ddtrace/appsec/_iast/_taint_tracking/clean.sh,sha256=RF6OT4iCwc19nMNWVTK_Ka6GweEqc4Cucz_2BLsZ7-k,231 +ddtrace/appsec/_iast/_taint_utils.py,sha256=98Zd6pFR-bpWkTntbBOS_fph_EjJJY55C6KO36ldzO8,18317 +ddtrace/appsec/_iast/_utils.py,sha256=2r3bykNHgeooPO1rWL5at0K38QFq0UysF_pb5x_WOoc,3478 +ddtrace/appsec/_iast/constants.py,sha256=Z8nH-Y1aUd7OB633ehFGfESr5mcoLZnGv2mwb0jcCw4,1907 +ddtrace/appsec/_iast/processor.py,sha256=MVPSNBuE0eKZnCMNPLaDbxcMNe26Vzeh4WcwyruDaoo,2921 +ddtrace/appsec/_iast/reporter.py,sha256=ERwuOWSP8wWrauRgSbtoerRxmM-IVqoh6je3fB1nloc,2811 +ddtrace/appsec/_iast/taint_sinks/__init__.py,sha256=xDTH7yfOCdm84VRsHDvmCJSTFwY-FXxbFVs1pPjxXeQ,147 +ddtrace/appsec/_iast/taint_sinks/__pycache__/__init__.cpython-311.pyc,, +ddtrace/appsec/_iast/taint_sinks/__pycache__/_base.cpython-311.pyc,, +ddtrace/appsec/_iast/taint_sinks/__pycache__/ast_taint.cpython-311.pyc,, +ddtrace/appsec/_iast/taint_sinks/__pycache__/command_injection.cpython-311.pyc,, +ddtrace/appsec/_iast/taint_sinks/__pycache__/insecure_cookie.cpython-311.pyc,, +ddtrace/appsec/_iast/taint_sinks/__pycache__/path_traversal.cpython-311.pyc,, +ddtrace/appsec/_iast/taint_sinks/__pycache__/sql_injection.cpython-311.pyc,, +ddtrace/appsec/_iast/taint_sinks/__pycache__/ssrf.cpython-311.pyc,, +ddtrace/appsec/_iast/taint_sinks/__pycache__/weak_cipher.cpython-311.pyc,, +ddtrace/appsec/_iast/taint_sinks/__pycache__/weak_hash.cpython-311.pyc,, +ddtrace/appsec/_iast/taint_sinks/__pycache__/weak_randomness.cpython-311.pyc,, +ddtrace/appsec/_iast/taint_sinks/_base.py,sha256=EDZseIRIo_Or3YhVw6ACs42r8Ro-QtKLQh1tiTLvcoI,12126 +ddtrace/appsec/_iast/taint_sinks/ast_taint.py,sha256=amvb3i-w_Wln0c_jLICoS7GRTxegunYFFGyIaN98uTY,1723 +ddtrace/appsec/_iast/taint_sinks/command_injection.py,sha256=v0W6TbctjKz_0Rru3j1ROvT8IiSR9fjC5Z14R81YPA8,9623 +ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py,sha256=Rg2lp-JQUMT7QwDrrcScBph4Upj-MsS_JcE9Yajx59g,2545 +ddtrace/appsec/_iast/taint_sinks/path_traversal.py,sha256=eODEuVCuA0koBJynvQSxXTWxYXPlB8a2taeqlaCFzKo,2456 +ddtrace/appsec/_iast/taint_sinks/sql_injection.py,sha256=EZIl8U4DeWEYL9PpO76mQzjIAmTpR_Th4FumjXyvesE,1503 +ddtrace/appsec/_iast/taint_sinks/ssrf.py,sha256=FoQivFkvcHGCUHEdlnMpUIjitGIAq1_2vPaoh1ds6Wg,7558 +ddtrace/appsec/_iast/taint_sinks/weak_cipher.py,sha256=XpCBpZOGKNdls7fNS3N684rWQ5Q9SEESv3pTQu-0JYw,6302 +ddtrace/appsec/_iast/taint_sinks/weak_hash.py,sha256=CnxlkN_Yar52lZyJtvpjrFFsyFzjaJfveSoi3zf-B3c,6694 +ddtrace/appsec/_iast/taint_sinks/weak_randomness.py,sha256=28UXtKlDYspbXMlj4raZF-l_oFoTn5YKSMqjTfCXQ8E,445 +ddtrace/appsec/_metrics.py,sha256=clpTcUMi8rYlwmCfa4c0d6qzEwYKTeTKI8i7T_Xeaek,4374 +ddtrace/appsec/_processor.py,sha256=PYnoaPb6lHGVIEkud5WZQ9HADJhI5F88jb6wP-Mg_RE,17693 +ddtrace/appsec/_python_info/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/appsec/_python_info/__pycache__/__init__.cpython-311.pyc,, +ddtrace/appsec/_python_info/stdlib/__init__.py,sha256=KMAkWzD1EKBveQs9pDbEQrbCLk-TVqTo2lSzYL2yWW0,634 +ddtrace/appsec/_python_info/stdlib/__pycache__/__init__.cpython-311.pyc,, +ddtrace/appsec/_python_info/stdlib/__pycache__/module_names_py310.cpython-311.pyc,, +ddtrace/appsec/_python_info/stdlib/__pycache__/module_names_py311.cpython-311.pyc,, +ddtrace/appsec/_python_info/stdlib/__pycache__/module_names_py36.cpython-311.pyc,, +ddtrace/appsec/_python_info/stdlib/__pycache__/module_names_py37.cpython-311.pyc,, +ddtrace/appsec/_python_info/stdlib/__pycache__/module_names_py38.cpython-311.pyc,, +ddtrace/appsec/_python_info/stdlib/__pycache__/module_names_py39.cpython-311.pyc,, +ddtrace/appsec/_python_info/stdlib/module_names_py310.py,sha256=ngWzLCBwy9ByT-cyVIjyBSLc4WDYCRYu1vvP5Ke9fRg,3176 +ddtrace/appsec/_python_info/stdlib/module_names_py311.py,sha256=2IiNW7Hgew3aaUMD9my6yRB9ufexn2C8wJXGXbP_RTk,3177 +ddtrace/appsec/_python_info/stdlib/module_names_py36.py,sha256=4LjKXzxt67jF6chR7if5s1UQYgoqYEkUlHbqeeBUldc,3209 +ddtrace/appsec/_python_info/stdlib/module_names_py37.py,sha256=szgt5Nxtx2yWlaeepsgROy1V7LT3nUHXFiM9da9bMmY,3233 +ddtrace/appsec/_python_info/stdlib/module_names_py38.py,sha256=3MCI6Um6QWjdS1Vorn4WQxyAbZaaGYf8eYVdpH720Ho,3218 +ddtrace/appsec/_python_info/stdlib/module_names_py39.py,sha256=jvuaSW0_7wtPnmIP6BdPSCwc1SKcw5EcBmnndAhnQ0A,3206 +ddtrace/appsec/_remoteconfiguration.py,sha256=4oFpe_6LloYsA2aRVGqLX818wPio4wfXhuDPQRH-fuQ,10647 +ddtrace/appsec/_trace_utils.py,sha256=UDUADGGe7_DFLbTMgyqU-BSPpJg9DZUv76UMKwFKeRk,12733 +ddtrace/appsec/_utils.py,sha256=Srs9dBgdSZubMfOBCCVuWQtSFPixADzt_MteL459GEs,5478 +ddtrace/appsec/iast/__init__.py,sha256=CPD2rJbZh290-1clJzq3J1E0d9dpNjvR3cWyL6nLuGc,72 +ddtrace/appsec/iast/__pycache__/__init__.cpython-311.pyc,, +ddtrace/appsec/rules.json,sha256=8KQsRDdq1ouTNZfTXt1LFdmXH45ZHAPUpjhRRTOShc4,278159 +ddtrace/appsec/trace_utils/__init__.py,sha256=oWT5uFME10wKUiQX0GDnWWAXOvmPOrPl7otH7DlUlhQ,666 +ddtrace/appsec/trace_utils/__pycache__/__init__.cpython-311.pyc,, +ddtrace/auto.py,sha256=PrVqH550rIK7CLn3O8eUpOM6ufWI8EaFVu45li8KR_k,601 +ddtrace/bootstrap/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/bootstrap/__pycache__/__init__.cpython-311.pyc,, +ddtrace/bootstrap/__pycache__/preload.cpython-311.pyc,, +ddtrace/bootstrap/__pycache__/sitecustomize.cpython-311.pyc,, +ddtrace/bootstrap/preload.py,sha256=8pkqcDru6SeC6Y4ZOa6k9wp-zS9dJzcoCD-roiOmuL0,4111 +ddtrace/bootstrap/sitecustomize.py,sha256=YSiZiOMB7VOPbmPO8F9AhZmljf8VmblNI-vERyHQMGg,6790 +ddtrace/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/commands/__pycache__/__init__.cpython-311.pyc,, +ddtrace/commands/__pycache__/ddtrace_run.cpython-311.pyc,, +ddtrace/commands/ddtrace_run.py,sha256=4snfPPvjISyj9238hkf1dic7MSoac7CIOIDtmYNEJK4,4585 +ddtrace/constants.py,sha256=PUmBvEkUlKfxKeuVqBrGDNbq21V0iB6hl9pyfmqLZaM,1708 +ddtrace/context.py,sha256=TAaybV1PrIr0MOUYvnsAoSg59C1gM44ahI1mD_eLhKk,9543 +ddtrace/contrib/__init__.py,sha256=qFyfkycuuYfhxPcJIHW0Q5hBRwsfUaabDanAKm7fVqU,244 +ddtrace/contrib/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/__pycache__/trace_utils.cpython-311.pyc,, +ddtrace/contrib/__pycache__/trace_utils_async.cpython-311.pyc,, +ddtrace/contrib/__pycache__/trace_utils_redis.cpython-311.pyc,, +ddtrace/contrib/aiobotocore/__init__.py,sha256=9bV_yu3Eff-SRRAvPykek5LpkJtq7zV36lMfCBdXuJo,1023 +ddtrace/contrib/aiobotocore/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/aiobotocore/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/aiobotocore/patch.py,sha256=vjWEkkS5ql98QRi9CTB2QPoqZUPM9u1yy7--t3DosF0,6347 +ddtrace/contrib/aiohttp/__init__.py,sha256=UmZ0ex1doI_bvV2MXui2PagPEZhJancBv4k4nETUHfg,3061 +ddtrace/contrib/aiohttp/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/aiohttp/__pycache__/middlewares.cpython-311.pyc,, +ddtrace/contrib/aiohttp/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/aiohttp/middlewares.py,sha256=RqDs9dWPJjMqyqqDT2eTP5YMi73lGcBwWvD-K1DgnUQ,6769 +ddtrace/contrib/aiohttp/patch.py,sha256=2uWWp6vFJYQ5gay5nM2u448dsTi6Hiyu9K8JCzny1eY,5107 +ddtrace/contrib/aiohttp_jinja2/__init__.py,sha256=ZvNEC7eSdy2UWiSJhCYoc8STU2OTgw6lHIJZMM1f0Rs,711 +ddtrace/contrib/aiohttp_jinja2/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/aiohttp_jinja2/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/aiohttp_jinja2/patch.py,sha256=rxsbERf6uyDmKu3JLZb7WPwzDcAO2-_8zWsRgi9rWq4,2023 +ddtrace/contrib/aiomysql/__init__.py,sha256=3zK56C7PWr5uYGl5oMSYrEwlKZSICy4Hl0A3ggZ__Os,1338 +ddtrace/contrib/aiomysql/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/aiomysql/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/aiomysql/patch.py,sha256=alWBpZ6ZBs4xy2i8MHzg7qOdjw98P6BSABTHgGbecs0,5849 +ddtrace/contrib/aiopg/__init__.py,sha256=Of3jYJ4jsNnNUDLNkstkRYWnSJ29hpOdj-GwagxZ8Hg,827 +ddtrace/contrib/aiopg/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/aiopg/__pycache__/connection.cpython-311.pyc,, +ddtrace/contrib/aiopg/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/aiopg/connection.py,sha256=90sl-eDM5QpbIIuM0SkmXDTKHnFRyU0D6E2L8Kr_-Do,4393 +ddtrace/contrib/aiopg/patch.py,sha256=2P8XOpJXmQsc0nhAAzjue_ddAF0x13_L0mDq_JjExDo,1887 +ddtrace/contrib/aioredis/__init__.py,sha256=C5EeJsQPTJHIILnPqRU-q28nHZZaByptetMmHcG6Fys,2222 +ddtrace/contrib/aioredis/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/aioredis/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/aioredis/patch.py,sha256=tvDMaHqJglMm2_9N4tQxNOYgHCzSe8zyWWeMAYDmOxI,8603 +ddtrace/contrib/algoliasearch/__init__.py,sha256=Hw78TlIYgBQEA_YyfYMMwIrorhSLIcW1A0iE5bA9rOo,974 +ddtrace/contrib/algoliasearch/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/algoliasearch/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/algoliasearch/patch.py,sha256=NoZcMQP-QNrf5oaGgafwNlINaptyvI9GmCMuLQKqJso,5696 +ddtrace/contrib/aredis/__init__.py,sha256=AF8GAoHfbKiChvZtDd8A6yKjLV39CvEbVK_yhWCX92o,2030 +ddtrace/contrib/aredis/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/aredis/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/aredis/patch.py,sha256=D2mggX9PFLqcwnZq4s7g4MxLLZcAWN3xPb2zXhy0Frc,2783 +ddtrace/contrib/asgi/__init__.py,sha256=Wrd3MJD8DOzh3ntszQ7BckhPedB8EAjHTf9CuxUFt-c,2145 +ddtrace/contrib/asgi/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/asgi/__pycache__/middleware.cpython-311.pyc,, +ddtrace/contrib/asgi/__pycache__/utils.cpython-311.pyc,, +ddtrace/contrib/asgi/middleware.py,sha256=p0NMvCMzbMgten6AC5Q3mVHYvAaJAiUQLCjMAAH7wCc,11437 +ddtrace/contrib/asgi/utils.py,sha256=A0YA0ZMhryqiM4jXkeWenF320SCJmXIHy4GQzl-fhXQ,3287 +ddtrace/contrib/asyncio/__init__.py,sha256=rPEAg2IyIY8Ar2O3s1p9X-n3KA-Prx1bbK_LKRgsWs4,2553 +ddtrace/contrib/asyncio/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/asyncio/__pycache__/compat.cpython-311.pyc,, +ddtrace/contrib/asyncio/__pycache__/helpers.cpython-311.pyc,, +ddtrace/contrib/asyncio/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/asyncio/__pycache__/provider.cpython-311.pyc,, +ddtrace/contrib/asyncio/__pycache__/wrappers.cpython-311.pyc,, +ddtrace/contrib/asyncio/compat.py,sha256=HgAfoCMH2TTyooC4siI5pH2472-mFzC8-yrAExm0m7U,278 +ddtrace/contrib/asyncio/helpers.py,sha256=G1L7FBnpFVBkn5m9dEFHdXLG1ECY2LANpJAN2H3_nEU,3102 +ddtrace/contrib/asyncio/patch.py,sha256=QcFgpIKsiNB5kRO2mSXTT_bdTP44zhzKF-Js_3GPHYo,463 +ddtrace/contrib/asyncio/provider.py,sha256=CIZMUeR3Glm-WrO7PQH0OUMUSUXjI-YMMv73zueWFpA,2855 +ddtrace/contrib/asyncio/wrappers.py,sha256=svG3-7JQ9kM1pFcKYCji-LRGGqe5ZDUTFx4h3qkDpsM,953 +ddtrace/contrib/asyncpg/__init__.py,sha256=cGQezxZJ2UYMlK1ypBzQ3WuVksl9l1UQbBIc1RngNJM,1322 +ddtrace/contrib/asyncpg/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/asyncpg/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/asyncpg/patch.py,sha256=JIkh5ZdxjWjoDjYypfWNIuvPhOQbDX5zslLjil8AYVo,4664 +ddtrace/contrib/aws_lambda/__init__.py,sha256=h0Wsf5SvBEy9bJVhVSNBPICTFBhtgHsGnzJUU8m8TQI,1434 +ddtrace/contrib/aws_lambda/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/aws_lambda/__pycache__/_cold_start.cpython-311.pyc,, +ddtrace/contrib/aws_lambda/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/aws_lambda/_cold_start.py,sha256=nOvLzOrLto40M_JlV2NY_7qfI-HbhcaMiMwDr1MHTXI,482 +ddtrace/contrib/aws_lambda/patch.py,sha256=EitsmJ9HAlA2e414488WIpNJ0xtEDcCHl5mD8KJ4bzY,9218 +ddtrace/contrib/boto/__init__.py,sha256=IfPRmsVDN_OiiLPZAQg3oXgTrRGC9Jw1E2BcYNtU3vs,1019 +ddtrace/contrib/boto/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/boto/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/boto/patch.py,sha256=rAWuYvUvCpVk67pnM68uA6KhpVL7yQdksYmCH91KEO8,6881 +ddtrace/contrib/botocore/__init__.py,sha256=f-8_Ra8c1IUM3mK63D300YYUjeZY3dvQfovBYkjOeFM,3870 +ddtrace/contrib/botocore/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/botocore/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/botocore/__pycache__/utils.cpython-311.pyc,, +ddtrace/contrib/botocore/patch.py,sha256=w8cSKtZLuZxEtNDRmyDCmKBhMLGumGjWBN7L5FWulE4,11389 +ddtrace/contrib/botocore/services/__pycache__/bedrock.cpython-311.pyc,, +ddtrace/contrib/botocore/services/__pycache__/kinesis.cpython-311.pyc,, +ddtrace/contrib/botocore/services/__pycache__/sqs.cpython-311.pyc,, +ddtrace/contrib/botocore/services/__pycache__/stepfunctions.cpython-311.pyc,, +ddtrace/contrib/botocore/services/bedrock.py,sha256=w7xujld-YvXFXYNH-gWmJAec5-ar397OvDj7e-BPOhw,15713 +ddtrace/contrib/botocore/services/kinesis.py,sha256=879NrScMaMaZ90q3FQNYQVnkmL-T5PX_qlIfHw6xXek,7430 +ddtrace/contrib/botocore/services/sqs.py,sha256=2pN--HSpNZl6eB_W1hINRBaetKe1YMx1V0vkMlnoY4Q,10521 +ddtrace/contrib/botocore/services/stepfunctions.py,sha256=NOjLyVlJqb0qKEh0ze1KsOMLHI1Ef63SBMc2zaGx8HI,4122 +ddtrace/contrib/botocore/utils.py,sha256=iUUVH1O_UWBrlK-7pUz4EQM61KYb1FhnQgac7ghEcTo,9177 +ddtrace/contrib/bottle/__init__.py,sha256=wy6alj22Uo4lNzk8BQUEnA__lGx7P7Beqxp2yiAF-Ug,1140 +ddtrace/contrib/bottle/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/bottle/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/bottle/__pycache__/trace.cpython-311.pyc,, +ddtrace/contrib/bottle/patch.py,sha256=9OzWL40cv559g1gAyN0RctZH_yiRteJUqhvYYjd4y7E,861 +ddtrace/contrib/bottle/trace.py,sha256=wXEhP6piR2oEemPFXsRE4bOR48Pz6V3JZoyc7Yblh5M,4246 +ddtrace/contrib/cassandra/__init__.py,sha256=LeXodkO4NBuOndLu-iZ0oPT4XZsG71-keSw7w5wux0A,1190 +ddtrace/contrib/cassandra/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/cassandra/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/cassandra/__pycache__/session.cpython-311.pyc,, +ddtrace/contrib/cassandra/patch.py,sha256=gTEULNQXWMvEH4VQHcvBthVH3XGWytnHLMCum2XcjeQ,89 +ddtrace/contrib/cassandra/session.py,sha256=KkpujlPSpcev1pvdaU6iVQR4e-qheqqL3ve5eDiCI0M,10272 +ddtrace/contrib/celery/__init__.py,sha256=Kgp_FgcEiQF4VOVE0TqIRo5OqwssjvNePtF34ujRz6Q,1583 +ddtrace/contrib/celery/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/celery/__pycache__/app.cpython-311.pyc,, +ddtrace/contrib/celery/__pycache__/constants.cpython-311.pyc,, +ddtrace/contrib/celery/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/celery/__pycache__/signals.cpython-311.pyc,, +ddtrace/contrib/celery/__pycache__/utils.cpython-311.pyc,, +ddtrace/contrib/celery/app.py,sha256=t4tRJ3o2ZN8s-q3_Fyp9e3W95ugxL65WfTTcj_ARmfk,3340 +ddtrace/contrib/celery/constants.py,sha256=2ULjjEu1VmKSb26zHXWcWhScx3uGP_nc7SoxRdo_jkk,470 +ddtrace/contrib/celery/patch.py,sha256=5Jc37i_k0w1juOz6TFyVnyAty9n8hEhQ1zeZTsUBSIQ,1138 +ddtrace/contrib/celery/signals.py,sha256=YyM7Oex2_1aDfx1sIahMh7a4sC3umEDaSnvKaNp8QoI,8211 +ddtrace/contrib/celery/utils.py,sha256=ICRnxclfoa-OqWypL-YivweF02MvGvHGX9csKnAJ_GU,4545 +ddtrace/contrib/cherrypy/__init__.py,sha256=GU1w6levb2eE_O4_L8F5KXSNeKSYrD_tzsVS1sXJEXQ,1642 +ddtrace/contrib/cherrypy/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/cherrypy/__pycache__/middleware.cpython-311.pyc,, +ddtrace/contrib/cherrypy/middleware.py,sha256=5P_rk9yVIZ_8ccUtXAfsCK4BTHTHrPfaiALM0ZTj4lA,5743 +ddtrace/contrib/consul/__init__.py,sha256=0i1qNV6XNYcA1Se0_X0h5y3e60lPzorE_-d0cDQexlE,907 +ddtrace/contrib/consul/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/consul/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/consul/patch.py,sha256=Y3gY9ku8OBveB4o2O2y14BhZnHBUYrPgnA_thM6Jdtw,2685 +ddtrace/contrib/coverage/__init__.py,sha256=94_a-BjnFq_5-89Q293HWZirZsklo-dnmmXAIItW9a4,914 +ddtrace/contrib/coverage/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/coverage/__pycache__/constants.cpython-311.pyc,, +ddtrace/contrib/coverage/__pycache__/data.cpython-311.pyc,, +ddtrace/contrib/coverage/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/coverage/__pycache__/utils.cpython-311.pyc,, +ddtrace/contrib/coverage/constants.py,sha256=6llF4KSvJb8GfOYvQUdRlMjZN23Y8n3YNcECnkWcpjM,33 +ddtrace/contrib/coverage/data.py,sha256=l-JnkQjfIw_xakUxD6xeohdndOPXLf-7WWuTsX90wCw,100 +ddtrace/contrib/coverage/patch.py,sha256=3EIgmvgmpa-jP87J7XtNK9NjCXr5sWlVIBPHEqD4-fo,1604 +ddtrace/contrib/coverage/utils.py,sha256=g_O_N-Fef9859SpUxS64voJh01Pad48t2iD1WeQjWOg,664 +ddtrace/contrib/dbapi/__init__.py,sha256=fcSUo-w_COulEPTpZ0O2B1uEo1uV8YW3C8OOWvFipAY,14002 +ddtrace/contrib/dbapi/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/dbapi_async/__init__.py,sha256=igsTeNZG69SzZe09FvjnVxwYG5K5PFfDTECxF6noF1c,11190 +ddtrace/contrib/dbapi_async/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/django/__init__.py,sha256=JWv5O5uYYbZlagVDmEBhyvozo5Jm9EMpxywp45_jk7o,5488 +ddtrace/contrib/django/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/django/__pycache__/_asgi.cpython-311.pyc,, +ddtrace/contrib/django/__pycache__/compat.cpython-311.pyc,, +ddtrace/contrib/django/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/django/__pycache__/restframework.cpython-311.pyc,, +ddtrace/contrib/django/__pycache__/utils.cpython-311.pyc,, +ddtrace/contrib/django/_asgi.py,sha256=qzP0RaYXUaEhMa0QfF2hU7hdMt6XSQU76ZzozeZJLKE,1390 +ddtrace/contrib/django/compat.py,sha256=AAjJn7NXuQMNFsyrcSc_B7B1NyugPTB-wEUSvz3knOY,908 +ddtrace/contrib/django/patch.py,sha256=TTJSayW6SpRGgQBgFYSfRsOfEIWYZTRtL3HipiYNPoY,34624 +ddtrace/contrib/django/restframework.py,sha256=pZ0I_FEwZA2L0yg3aUAw1yWRUXpQi-ccCRy3PPxlFvs,1155 +ddtrace/contrib/django/utils.py,sha256=K61VSWZA6ooTxFS0NGE9UsbnVWFoCgMN05NzUsbEN1M,16558 +ddtrace/contrib/dogpile_cache/__init__.py,sha256=rnl4KhUBRUcZC7FNnu1g9gcJZu78FOgNsZfuN2di_CU,1653 +ddtrace/contrib/dogpile_cache/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/dogpile_cache/__pycache__/lock.cpython-311.pyc,, +ddtrace/contrib/dogpile_cache/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/dogpile_cache/__pycache__/region.cpython-311.pyc,, +ddtrace/contrib/dogpile_cache/lock.py,sha256=zmEG-OVH_veGeupY4UdxAZ6kERgjmpHZLjUMupFT6vc,1636 +ddtrace/contrib/dogpile_cache/patch.py,sha256=e8wiG1obluHLLPObyb9l9t_TajaRy7FnhqGeksv_mHk,1875 +ddtrace/contrib/dogpile_cache/region.py,sha256=DGO_uSOTEM3lAyug5bTFo1Pf0_NgNPliRW9vhkrwUWw,1990 +ddtrace/contrib/elasticsearch/__init__.py,sha256=dVviFwaMSxlUctu7yARm2yezD2W4bzirnLu2U551lds,1526 +ddtrace/contrib/elasticsearch/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/elasticsearch/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/elasticsearch/__pycache__/quantize.cpython-311.pyc,, +ddtrace/contrib/elasticsearch/patch.py,sha256=D2UuaxvwsTahHGUphybiB8urrGUjRMUf-qpoIvrlvfE,8926 +ddtrace/contrib/elasticsearch/quantize.py,sha256=wdiGa9N2CcPXw614lwb6-kCTBrxvkubtmb4KI2S9H5c,1052 +ddtrace/contrib/falcon/__init__.py,sha256=XbjcXYfnw7lLbgJh9u-2ylkut7UtQ2O29z6v-TzYCBs,1512 +ddtrace/contrib/falcon/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/falcon/__pycache__/middleware.cpython-311.pyc,, +ddtrace/contrib/falcon/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/falcon/middleware.py,sha256=qIOvgkBJLsYaJMW60NQsrokNL91UFDe4lx1zamkU6dA,4444 +ddtrace/contrib/falcon/patch.py,sha256=tM-0kfBFRW4F0DlkD46Lz_KGqK6wD0jyh_61ZIrvLjw,1217 +ddtrace/contrib/fastapi/__init__.py,sha256=7j0YDwboKR_ij9DP-qjB99mpUC9lkCUf3Yyyf1SDtac,1978 +ddtrace/contrib/fastapi/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/fastapi/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/fastapi/patch.py,sha256=uaii_6XeyBizbCT7VFN8lMBi2ixSBFsqsx7Ls_HFr9w,3401 +ddtrace/contrib/flask/__init__.py,sha256=3w7QicHrmgLmQD7GkCXX56a_-UnBquxDzW3TiweLvE0,2864 +ddtrace/contrib/flask/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/flask/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/flask/__pycache__/wrappers.cpython-311.pyc,, +ddtrace/contrib/flask/patch.py,sha256=n0Sxkz5t4T1g82lfafUWwWXdKM1V9wyeUaHs3aeTEcw,19694 +ddtrace/contrib/flask/wrappers.py,sha256=6jphZzUVDvfJuc_adDTZ7o3ih_y7u1A7LK2BeoIgg8M,3187 +ddtrace/contrib/flask_cache/__init__.py,sha256=MfOIBgrD0uPsa0b2lfoG-G3Mnzti5S8PY51P0V1aW2g,1702 +ddtrace/contrib/flask_cache/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/flask_cache/__pycache__/tracers.cpython-311.pyc,, +ddtrace/contrib/flask_cache/__pycache__/utils.cpython-311.pyc,, +ddtrace/contrib/flask_cache/tracers.py,sha256=NXbnigTR_MXnVpzvDvaST0Kx7HzgLZELvQBTvNDFDyQ,6752 +ddtrace/contrib/flask_cache/utils.py,sha256=V6nc8kZKp_dhQFyco_gx3ez0FCZYfF6bcYLf_hgw5eE,2186 +ddtrace/contrib/flask_login/__init__.py,sha256=29ErfIxFnpOOKktBl-Ag9keQWFTlZTT-tPivqncowBc,1919 +ddtrace/contrib/flask_login/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/flask_login/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/flask_login/patch.py,sha256=Gfp2jwW-4gBvd38Qs_Sb2vdW1RRS6cN-ZfwgUj6Txwk,3626 +ddtrace/contrib/futures/__init__.py,sha256=RBln83ltDdVksGsSFM8rsDv9m9RsECNAQify_Rrkf3s,979 +ddtrace/contrib/futures/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/futures/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/futures/__pycache__/threading.cpython-311.pyc,, +ddtrace/contrib/futures/patch.py,sha256=_CgJp1Zf5pxHzqdQuYaxfDc1DpEvm1GZz9jQS-VMPhI,1106 +ddtrace/contrib/futures/threading.py,sha256=KsSEBnoKn6Qy_2T8AGN8JneN7RBoQrmWGWxN6Cep9iE,1822 +ddtrace/contrib/gevent/__init__.py,sha256=lwZFVcW4MMkAu6YiD8hOBj1qqxSLwVUy8ni6ENaRpJ0,1962 +ddtrace/contrib/gevent/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/gevent/__pycache__/greenlet.cpython-311.pyc,, +ddtrace/contrib/gevent/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/gevent/__pycache__/provider.cpython-311.pyc,, +ddtrace/contrib/gevent/greenlet.py,sha256=rEzvw7pibHIXNe8tOK4Jm9J7Cb4pK6_nt-me8p6lNVE,2241 +ddtrace/contrib/gevent/patch.py,sha256=XiU7qr-im4NYZmwTUAPL4221Ii7-Fc-I6XN_zQDDp8c,2538 +ddtrace/contrib/gevent/provider.py,sha256=wWh_EnT4xdpekgdJhiqGWnTj2CXyUfLlYJAVYX2WrjA,1468 +ddtrace/contrib/graphql/__init__.py,sha256=oCrbJUcgUTbC-Evfg4Xb5d8RtHQEBs_xlt1v38JY3-8,1482 +ddtrace/contrib/graphql/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/graphql/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/graphql/patch.py,sha256=DIJXmXJUJPJRlQ1x7DZhqktcNcbSVKy9sprc3aB91UA,11437 +ddtrace/contrib/grpc/__init__.py,sha256=g5ckIPN9aK0aTIzWi624l9t-5mtxKLKyENjALrTm_iQ,2248 +ddtrace/contrib/grpc/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/grpc/__pycache__/aio_client_interceptor.cpython-311.pyc,, +ddtrace/contrib/grpc/__pycache__/aio_server_interceptor.cpython-311.pyc,, +ddtrace/contrib/grpc/__pycache__/client_interceptor.cpython-311.pyc,, +ddtrace/contrib/grpc/__pycache__/constants.cpython-311.pyc,, +ddtrace/contrib/grpc/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/grpc/__pycache__/server_interceptor.cpython-311.pyc,, +ddtrace/contrib/grpc/__pycache__/utils.cpython-311.pyc,, +ddtrace/contrib/grpc/aio_client_interceptor.py,sha256=JuAd5dzJMYDqj3Y6kbZVvZLj5YU4WofAA0GvrtK_K1w,9950 +ddtrace/contrib/grpc/aio_server_interceptor.py,sha256=QxFDDHUpplTcDMyAuCMsHXWKe4uNzcK2ILWU1TMZgXw,12388 +ddtrace/contrib/grpc/client_interceptor.py,sha256=paMEhofuD2CPb8yQH2ZgYK5JKbH92jIQ7E1if6VvW4k,10747 +ddtrace/contrib/grpc/constants.py,sha256=g4pTnEmFqAabsQBLDCvKoB5lKZCtZc-ah2qs6VEVOPw,1016 +ddtrace/contrib/grpc/patch.py,sha256=HqfgTPXP2pBiUpnCn2UtYxL57nZy5rJwWlbppnMUUJk,7526 +ddtrace/contrib/grpc/server_interceptor.py,sha256=NWQmtbk8NjZ1qYWJEqrCpb5UokaX4ZV8uhPTkxGw15I,5161 +ddtrace/contrib/grpc/utils.py,sha256=FAfO5UyMg2nkLZ_NxVzR3_z8v3Z6tqIytNsn6L6tsdQ,2970 +ddtrace/contrib/gunicorn/__init__.py,sha256=tEinb0tU3N8cW111sI9gRlL5dNpL_hkhWwd8lzX5s6A,421 +ddtrace/contrib/gunicorn/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/httplib/__init__.py,sha256=wol5d0NIeR9jhdqC0CXkMx42wkAh989LypeaUOHPKmU,1604 +ddtrace/contrib/httplib/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/httplib/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/httplib/patch.py,sha256=m-ooHqTSl1aL5RUZpVO8n7O3RhEqRdwndzIILROZAto,8130 +ddtrace/contrib/httpx/__init__.py,sha256=sDbsKCk_Yv_ZsJaKWU7nYDh5uCW2tVCZkOUlyjihrfU,2556 +ddtrace/contrib/httpx/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/httpx/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/httpx/patch.py,sha256=-cLHcKUWnpLy9hxRnc94yxtiTLpBjwWquvgupMT6n_w,6951 +ddtrace/contrib/jinja2/__init__.py,sha256=sbeozQZfnw1pyXNKhwefKO-X8Le1cI6fEawccPuLyDo,1273 +ddtrace/contrib/jinja2/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/jinja2/__pycache__/constants.cpython-311.pyc,, +ddtrace/contrib/jinja2/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/jinja2/constants.py,sha256=BpRqO-bQIWzrHFO7DtJAZ4v7lH7HpYWjYri7yCOG-Ts,35 +ddtrace/contrib/jinja2/patch.py,sha256=bFKs5VUF1LICVwCJkSJ6EOkFi3pZNSUJyA8CCf3EAnY,3483 +ddtrace/contrib/kafka/__init__.py,sha256=d2GeIQqFVrPhkO_JAKvgPx73iW2RjqOAzAggyydnb4s,1375 +ddtrace/contrib/kafka/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/kafka/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/kafka/patch.py,sha256=m2BTr7FrWEAU_LbkXafddxsCZiKqEhw-HL3Z_affW9k,11518 +ddtrace/contrib/kombu/__init__.py,sha256=SWh0sC8gHZ71qvDd5j2TiOZQ1h2ECnSCONntoSOhgbo,1671 +ddtrace/contrib/kombu/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/kombu/__pycache__/constants.cpython-311.pyc,, +ddtrace/contrib/kombu/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/kombu/__pycache__/utils.cpython-311.pyc,, +ddtrace/contrib/kombu/constants.py,sha256=RhhLOyKsbyFLnko4wvuyDQvBv_wy0CMN27k4akMSlak,26 +ddtrace/contrib/kombu/patch.py,sha256=QEz5P8WCVjVo0SjV1e9cLOFq2kpoPQ1ZPN5X3nublnU,5818 +ddtrace/contrib/kombu/utils.py,sha256=xjAzg2zr7o74Iy7Bc2vIQvfvm_W82ZzvbiBHLNtCa7E,1101 +ddtrace/contrib/langchain/__init__.py,sha256=iy5GzZMp1ApEevdyPrtQBqZ9Z81RWmlLCBlZD7FE8Zk,6459 +ddtrace/contrib/langchain/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/langchain/__pycache__/constants.cpython-311.pyc,, +ddtrace/contrib/langchain/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/langchain/constants.py,sha256=hgeiUEiR5fjYj7w2uxO1osAesux0172juwSES-gE2uY,2478 +ddtrace/contrib/langchain/patch.py,sha256=zAhRvOnX0vXC0hgr-lToG8sti4NF92Z6VeYwOmtdk2I,36845 +ddtrace/contrib/logbook/__init__.py,sha256=lm-ge5wJYMIzmW7Vm_uu0IgElav1tgiSGruvCCvKZRo,2578 +ddtrace/contrib/logbook/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/logbook/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/logbook/patch.py,sha256=Np-C41AcLIDyDBgbqlSgX75vb2cz3k6jUnxCP0Ms5sI,2327 +ddtrace/contrib/logging/__init__.py,sha256=Q1NLFHbcjRFBLg05AQ96Qm-vsA-o3cPYTAOYS3b-G7I,2145 +ddtrace/contrib/logging/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/logging/__pycache__/constants.cpython-311.pyc,, +ddtrace/contrib/logging/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/logging/constants.py,sha256=kOOXgHFz3wb4Im9UuARht1TPZaOpVA_QvzM0j0bHJs4,227 +ddtrace/contrib/logging/patch.py,sha256=zGfI465HqorDyuV5tfBna-sCyHavpHRzaCnaoOyPW9k,5077 +ddtrace/contrib/loguru/__init__.py,sha256=gs8t4FTGLbWDfsafRbWkBxNQsCPKc1G9b0X6B94xgsU,2671 +ddtrace/contrib/loguru/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/loguru/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/loguru/patch.py,sha256=2OaCdYyumFeRlUjhN7pFfLDbBXdhjeFeR8A40nJXRww,2835 +ddtrace/contrib/mako/__init__.py,sha256=hXN21PGRMpV9RkGA3-ws5j_YaXXrBaKJp2Kcwze9LpQ,616 +ddtrace/contrib/mako/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/mako/__pycache__/constants.cpython-311.pyc,, +ddtrace/contrib/mako/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/mako/constants.py,sha256=BpRqO-bQIWzrHFO7DtJAZ4v7lH7HpYWjYri7yCOG-Ts,35 +ddtrace/contrib/mako/patch.py,sha256=rUg6jdkaDxLobRFyHnFNfeziSP2coaojPd4QfMgrSIM,2298 +ddtrace/contrib/mariadb/__init__.py,sha256=yV-9X6B-EnmFd8CbqcfZWu6s-WPML5vtJJ9mE4YTcOw,1716 +ddtrace/contrib/mariadb/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/mariadb/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/mariadb/patch.py,sha256=tQnNB0Zo9xhxmPV6XFmRRJ12eoKO3gq8TwJpCKOVY3U,1501 +ddtrace/contrib/molten/__init__.py,sha256=QsJLSom7Lphx2S4uofnr7XN_7Ge8SGPljCO312AqqGo,1271 +ddtrace/contrib/molten/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/molten/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/molten/__pycache__/wrappers.cpython-311.pyc,, +ddtrace/contrib/molten/patch.py,sha256=_x1b6d6kG9FOePSj-CuHTZ9iE9NV947Ut_WkEVluQCE,5838 +ddtrace/contrib/molten/wrappers.py,sha256=xpd3ygO-4rnuQWhtXEdJYq1IJa5V6qv0zPK10xCafmQ,4018 +ddtrace/contrib/mongoengine/__init__.py,sha256=1Buh90LANDKZiIREQFKm__Ai5LBx92XSk72nkkPrgnI,897 +ddtrace/contrib/mongoengine/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/mongoengine/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/mongoengine/__pycache__/trace.cpython-311.pyc,, +ddtrace/contrib/mongoengine/patch.py,sha256=Zg18ToqJFeGIASS52ZbF7N4nMDXc2G4GRf2Q89YSCnk,327 +ddtrace/contrib/mongoengine/trace.py,sha256=pweTTRGYAKhUoyIqyZEJjobCc2CmSwDfj5T4NZvikzk,1269 +ddtrace/contrib/mysql/__init__.py,sha256=4LqJc0VntMxCQzxWJBSy7WJYUq5U9ihZRe6fx25olWc,2070 +ddtrace/contrib/mysql/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/mysql/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/mysql/patch.py,sha256=9O1RUsLCN-KTeOgGm5lJfVZvh_nKehb5iU-yOMoiLic,1895 +ddtrace/contrib/mysqldb/__init__.py,sha256=-PtsHQA_acRQSd1msdEEh09RKpB5U-Zf0Zd4qXaMM-4,2418 +ddtrace/contrib/mysqldb/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/mysqldb/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/mysqldb/patch.py,sha256=_p8AaBY4tbcPekQEp1rsSFgbP0OswmzMLynzeMQzz9E,3286 +ddtrace/contrib/openai/__init__.py,sha256=gqjPSjrQfizPnu2MHoSzpgAA1WPTth8XBpTAlx6Qe4I,8349 +ddtrace/contrib/openai/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/openai/__pycache__/_endpoint_hooks.cpython-311.pyc,, +ddtrace/contrib/openai/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/openai/__pycache__/utils.cpython-311.pyc,, +ddtrace/contrib/openai/_endpoint_hooks.py,sha256=Nd45jIy0DYhr4DxJBcO929eDeAERuI0VxHznjE9hLJ8,38541 +ddtrace/contrib/openai/patch.py,sha256=gOQ8a3uyjPdqCH-z3nUS43lModo60gzUrmRhn0wMyuU,15797 +ddtrace/contrib/openai/utils.py,sha256=Sc37PAWkqwwlBzqCvCTTGWF4wRwTjIUf6FGN79iegWs,4308 +ddtrace/contrib/psycopg/__init__.py,sha256=TpbJmEjFAg2f44h6tJNNRHqG5NtTk6jVP3wv-HzVZvU,1646 +ddtrace/contrib/psycopg/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/psycopg/__pycache__/async_connection.cpython-311.pyc,, +ddtrace/contrib/psycopg/__pycache__/async_cursor.cpython-311.pyc,, +ddtrace/contrib/psycopg/__pycache__/connection.cpython-311.pyc,, +ddtrace/contrib/psycopg/__pycache__/cursor.cpython-311.pyc,, +ddtrace/contrib/psycopg/__pycache__/extensions.cpython-311.pyc,, +ddtrace/contrib/psycopg/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/psycopg/async_connection.py,sha256=C5J8oNkn8qDnxcofPHC3DsX_pt_f0tX9R_s_m0kF8Qw,2784 +ddtrace/contrib/psycopg/async_cursor.py,sha256=5_b6hgUMogq_k0Pw9UqyWY8A0qh4pny4ntuBLb7gXrg,1147 +ddtrace/contrib/psycopg/connection.py,sha256=hWCZ5FA8qOrpv9S1J8iwIwqUnQMtINeyOR50h0JUyi0,4271 +ddtrace/contrib/psycopg/cursor.py,sha256=8yQREYRPy97Z4wTsj1hhP8d0eAMXONss3SDmpGE4Nb0,1041 +ddtrace/contrib/psycopg/extensions.py,sha256=6jnUb4otunC0A57CmdO48XutTaCW7YfQxlqP8XDNR0Y,6777 +ddtrace/contrib/psycopg/patch.py,sha256=oPHIT22RhYAz6jzNOjazgFoHOfMF96sDMLGKFSRqvOs,8132 +ddtrace/contrib/pylibmc/__init__.py,sha256=peMKPM5-dPINxamo8OM0AaXtRMNRcD1HUjZVsz2Mo0Q,1017 +ddtrace/contrib/pylibmc/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/pylibmc/__pycache__/addrs.cpython-311.pyc,, +ddtrace/contrib/pylibmc/__pycache__/client.cpython-311.pyc,, +ddtrace/contrib/pylibmc/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/pylibmc/addrs.py,sha256=S1wHEMy_T_zZWom_p6_u9eyC1W39nn_sXE9gC9nsTGg,365 +ddtrace/contrib/pylibmc/client.py,sha256=aNZuTtUMYfMYwE3EqHDIl4RzOXBPM9_oQ0wXogZb76E,7087 +ddtrace/contrib/pylibmc/patch.py,sha256=ui1_WRVbZ8Pyt3i5R8hugd4mmnV7PwDfgWDNYEQn3Lg,285 +ddtrace/contrib/pymemcache/__init__.py,sha256=TJltXVZEH9LxVdztLjJkRclsqv3ciFcAFy2oDtimfSg,1433 +ddtrace/contrib/pymemcache/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/pymemcache/__pycache__/client.cpython-311.pyc,, +ddtrace/contrib/pymemcache/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/pymemcache/client.py,sha256=4rtTVAoReUOZPqdf200BxrDn9__GvhGDhmroku1ogiU,12267 +ddtrace/contrib/pymemcache/patch.py,sha256=zX8yxueKVhU5m-LuZ9rtb7NtFf67evCu8VNf2GY9eNc,1528 +ddtrace/contrib/pymongo/__init__.py,sha256=2efOGg1MJ1JvlBZMqa0wBa0plAZn3SlmYNCpYVL9jaE,1497 +ddtrace/contrib/pymongo/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/pymongo/__pycache__/client.cpython-311.pyc,, +ddtrace/contrib/pymongo/__pycache__/parse.cpython-311.pyc,, +ddtrace/contrib/pymongo/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/pymongo/client.py,sha256=Lks6ebPx9frA7I8UPiLBUyTBgNDug4xUuLgRNOQNz8Y,13064 +ddtrace/contrib/pymongo/parse.py,sha256=kJccHF7TKlWnfrN75duPAkRQeKph75h3LyzON0y_cBs,6292 +ddtrace/contrib/pymongo/patch.py,sha256=r7_VAeSxRcExucmo3IxRrn9SFBNmyFoPTAfKKytHLu4,2611 +ddtrace/contrib/pymysql/__init__.py,sha256=-aqmsgZRHaYRNMzUeCKZntkuVWxdmxIRwXm_S_zdR5Y,1674 +ddtrace/contrib/pymysql/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/pymysql/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/pymysql/patch.py,sha256=dszSvQTkBVdX726O4eC4xOLCxPeqMqbcpsPstWZEpOg,1552 +ddtrace/contrib/pynamodb/__init__.py,sha256=vz_iCDyV1Q8n2hltDBwY1D4X99CJVGhRxfvBd4Mcruo,1019 +ddtrace/contrib/pynamodb/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/pynamodb/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/pynamodb/patch.py,sha256=B9Xn4Z5H013kUD2zAHuylodiVMYkWjNXlGYlZJbpW_g,3395 +ddtrace/contrib/pyodbc/__init__.py,sha256=LwxVnw4ji9JZyqY0i_KPUdQDm34BL6FWqKrMcLn3Vtc,1604 +ddtrace/contrib/pyodbc/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/pyodbc/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/pyodbc/patch.py,sha256=DpRKnDFDx6RQkXjRlkRyvFcKTjmid2FrxPrCrY5tsc4,1689 +ddtrace/contrib/pyramid/__init__.py,sha256=H30eVR2iOoWisvpFupS7VTMXiOqEyC5eQ3O3LakUhnI,1710 +ddtrace/contrib/pyramid/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/pyramid/__pycache__/constants.cpython-311.pyc,, +ddtrace/contrib/pyramid/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/pyramid/__pycache__/trace.cpython-311.pyc,, +ddtrace/contrib/pyramid/constants.py,sha256=69TDS3jZ0kf6M7WlNT60Nv4qOWIiGEcbsCeG8_Qp9vw,310 +ddtrace/contrib/pyramid/patch.py,sha256=oqYCKckucQOeVkpLMo0lKa6gW6EuMewMraD_Ls2NECU,3773 +ddtrace/contrib/pyramid/trace.py,sha256=lAhsmH5Mp7XyaJq9scjNWt9fAwP61pV8WzAkS9pvp2I,5774 +ddtrace/contrib/pytest/__init__.py,sha256=zVzjTq1hiBAEQc-WBmsKFe8yfiITAmr5sfd84hDH9DU,2123 +ddtrace/contrib/pytest/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/pytest/__pycache__/constants.cpython-311.pyc,, +ddtrace/contrib/pytest/__pycache__/newhooks.cpython-311.pyc,, +ddtrace/contrib/pytest/__pycache__/plugin.cpython-311.pyc,, +ddtrace/contrib/pytest/constants.py,sha256=pqR9gHBrmyU5CyU7My2zeB9KR5BPOQOQv87KJA0FGEw,294 +ddtrace/contrib/pytest/newhooks.py,sha256=kVTKsDqYKrw_VM7MffQAJYKiuHnqUbPa7Qwrk8Wq8pE,1021 +ddtrace/contrib/pytest/plugin.py,sha256=VfR_r106w9p_Tsfkp8AxENRQrHDuQjl6bmNlnbVVHcc,38415 +ddtrace/contrib/pytest_bdd/__init__.py,sha256=4z2Vsify_2RmMeBl-tEBVhxDvOiYm9egr6mh_YKJMM4,1088 +ddtrace/contrib/pytest_bdd/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/pytest_bdd/__pycache__/constants.cpython-311.pyc,, +ddtrace/contrib/pytest_bdd/__pycache__/plugin.cpython-311.pyc,, +ddtrace/contrib/pytest_bdd/constants.py,sha256=hJUsSNAs87r1DIS41T39abbDG3pJpTNC52e2ZPtkvOk,55 +ddtrace/contrib/pytest_bdd/plugin.py,sha256=1tC3dk5ThGqLJk7JhVsvHgrL7yl_CZyWDG5bo0x9wBE,5191 +ddtrace/contrib/pytest_benchmark/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/contrib/pytest_benchmark/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/pytest_benchmark/__pycache__/constants.cpython-311.pyc,, +ddtrace/contrib/pytest_benchmark/__pycache__/plugin.cpython-311.pyc,, +ddtrace/contrib/pytest_benchmark/constants.py,sha256=Qldd-8g3tRjpdgVTMMwL4i49_ROoJ3t0VB9eJ4FeiJo,2134 +ddtrace/contrib/pytest_benchmark/plugin.py,sha256=DkAtkQoQps8AOLNC7NzcsTn2heto0ywkGkE5rxVsVXE,1302 +ddtrace/contrib/redis/__init__.py,sha256=eVvzamumY2gjIPmKfiNk9-ZglLg59Aq6KFkhukVQV3o,1981 +ddtrace/contrib/redis/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/redis/__pycache__/asyncio_patch.cpython-311.pyc,, +ddtrace/contrib/redis/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/redis/asyncio_patch.py,sha256=tK-gFeBzd3ohwNKf_0Ha3xnC_eeI2XNhGFavGKNR6uU,1595 +ddtrace/contrib/redis/patch.py,sha256=OJ53h-fbNY4bBHIpvm-n1pL4-QDdFB5IIi513J4pmWU,6933 +ddtrace/contrib/rediscluster/__init__.py,sha256=vAhdTYKt8Cet0L1GkSZbPsEyepRT1sw1uQ6rlMAMSjk,1837 +ddtrace/contrib/rediscluster/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/rediscluster/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/rediscluster/patch.py,sha256=5dKj2-Qi91yvHtDo1CcUErojELrWaW_yLRSbmQkehBo,4191 +ddtrace/contrib/requests/__init__.py,sha256=hSf34AnfweSRwnPd5vSuyAFGfxgXz7MvSfNdLX85PsA,2280 +ddtrace/contrib/requests/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/requests/__pycache__/connection.cpython-311.pyc,, +ddtrace/contrib/requests/__pycache__/constants.cpython-311.pyc,, +ddtrace/contrib/requests/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/requests/__pycache__/session.cpython-311.pyc,, +ddtrace/contrib/requests/connection.py,sha256=14QyMr_458cXPmQBFqpFn1hJJ6eLqao05_IBU5lvPmA,5288 +ddtrace/contrib/requests/constants.py,sha256=HY71FxxxJKkCVoCNspOczEf9Ji6QcVDTeDCgYyuA0qU,29 +ddtrace/contrib/requests/patch.py,sha256=prHMxIef4MsTO2bAHUWH7E4sMAQ-nwoZJIb5tDg912w,1448 +ddtrace/contrib/requests/session.py,sha256=_vtXsnkadx3J6cJXjDcz39VKPQIzMVLWf0XyB-hOXm8,512 +ddtrace/contrib/rq/__init__.py,sha256=J9hChEpXvRKNvcUsIe4HdXbRTtqGMcnSn5-iSGFlv24,8596 +ddtrace/contrib/rq/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/sanic/__init__.py,sha256=yzXGvBedAy-_llonmMe6mroKNE29J0AmZMbo5Kanw2k,1830 +ddtrace/contrib/sanic/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/sanic/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/sanic/patch.py,sha256=BbFMt18dpIbq3OEnr1W5NHtSo204HERGnZfcx7ZKjGg,9868 +ddtrace/contrib/snowflake/__init__.py,sha256=BD1iYTVozgcwANvNG5keqz4HXNV9ez9_dTUbef7bNko,1916 +ddtrace/contrib/snowflake/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/snowflake/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/snowflake/patch.py,sha256=YhjNOrjyW4s18SaoFpHDrJ6kk_fIpgQ62w_xZDZvcWs,2715 +ddtrace/contrib/sqlalchemy/__init__.py,sha256=RovvKRxQQuWTjEnRAqw4QZ8tKZtpd5kLyOoJDsZoD_0,1159 +ddtrace/contrib/sqlalchemy/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/sqlalchemy/__pycache__/engine.cpython-311.pyc,, +ddtrace/contrib/sqlalchemy/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/sqlalchemy/engine.py,sha256=bsULXVgNUp7mZmDcDTGSkZbmA2y2DIL-4opqd3z-OVo,5475 +ddtrace/contrib/sqlalchemy/patch.py,sha256=sahiUKP2g9PazD2cCvD4tzowi2EkyqrBtGklcPqTJ_E,798 +ddtrace/contrib/sqlite3/__init__.py,sha256=vP1mIts57RrXXdpdveCnCTWxb54ldQRxBMZ2vy9jODc,1604 +ddtrace/contrib/sqlite3/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/sqlite3/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/sqlite3/patch.py,sha256=UPRCErN3pdWiZaBM2lZSWEfPJt9Xd9PoV11BDVRcA2A,2999 +ddtrace/contrib/starlette/__init__.py,sha256=ohVJF2DVGOi-hWv71b-xUmifIsjrbpQjEZbUDM-i7yc,2317 +ddtrace/contrib/starlette/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/starlette/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/starlette/patch.py,sha256=iOM0W857ZkHcugmEqwtVPirmijq_DzGVFLeCyAdqR5E,7074 +ddtrace/contrib/structlog/__init__.py,sha256=JJpgY9efpqxiV7D1YqtQL_THivsg1vAJDPgde2_rZ78,1721 +ddtrace/contrib/structlog/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/structlog/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/structlog/patch.py,sha256=ql3QPna9bCZVQ-_OloNMafW_JJxQOJ5jWcLdnZbTNhU,3133 +ddtrace/contrib/subprocess/__init__.py,sha256=mzY7mir2dYoaumFAWtOG-qTlnc2dbB1gtNDwmeDR81s,1012 +ddtrace/contrib/subprocess/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/subprocess/__pycache__/constants.cpython-311.pyc,, +ddtrace/contrib/subprocess/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/subprocess/constants.py,sha256=4f8Nsp5Q7RoYe1jOzh_4o-1gNsZ2mgP4bJgM75ILnwI,585 +ddtrace/contrib/subprocess/patch.py,sha256=bGbFSp1ZSr4d_OEFb3V97yS6RnhDAz-yU0xvl9v4Sd0,14442 +ddtrace/contrib/tornado/__init__.py,sha256=eLRgK_bPQrsB9aMLxEMIDvYplUB4Shoxjx2zFF39Kp4,4605 +ddtrace/contrib/tornado/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/tornado/__pycache__/application.cpython-311.pyc,, +ddtrace/contrib/tornado/__pycache__/constants.cpython-311.pyc,, +ddtrace/contrib/tornado/__pycache__/decorators.cpython-311.pyc,, +ddtrace/contrib/tornado/__pycache__/handlers.cpython-311.pyc,, +ddtrace/contrib/tornado/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/tornado/__pycache__/stack_context.cpython-311.pyc,, +ddtrace/contrib/tornado/__pycache__/template.cpython-311.pyc,, +ddtrace/contrib/tornado/application.py,sha256=Ivn9_sQC6mM9bhkbyx9yBeUSf_on90Jchwwxfd_Rvlc,1843 +ddtrace/contrib/tornado/constants.py,sha256=PsZUkpzC0uUfshu3QrhvPMj5b669K9w1pXGHgitaK00,205 +ddtrace/contrib/tornado/decorators.py,sha256=qc8t-_uSVKmUERUQOLuNb7j1C5Q1TyGeoy-RK1PNaYA,3368 +ddtrace/contrib/tornado/handlers.py,sha256=jspe5TDJ4vP_p1S6_r4sa1gTSdgsntAQmpW3MbR1CQo,5569 +ddtrace/contrib/tornado/patch.py,sha256=7KGmMwD2AS-4BcBbYa6uiekZZ4_sD0jhxqAkl4-m7U0,1998 +ddtrace/contrib/tornado/stack_context.py,sha256=eKJjaSAlLu78-OvFK2tt8CXAskIyL2LBcaetmLI8tyo,6067 +ddtrace/contrib/tornado/template.py,sha256=7fLfkYziu9djuCl8F0hw_qC3fUzLoLFKkMuSvDXgGSs,1166 +ddtrace/contrib/trace_utils.py,sha256=urNH62swJDJca9ivwGqXmFuRsvAb3_mrXhVLFRNP3Ak,25317 +ddtrace/contrib/trace_utils_async.py,sha256=1b0AQ-_qk97P3Y-mLYLMzl2yo32SNjkhaKiSJ0WdmUw,1059 +ddtrace/contrib/trace_utils_redis.py,sha256=Zr_SiSVXUge57Nb5huQ0o3htyHLdP3zOIRIl2heW9aU,7256 +ddtrace/contrib/unittest/__init__.py,sha256=ItDIm3eH7jHfSeDZ8c4yguf8_WtFt2flkiJ2gVHhO9c,1250 +ddtrace/contrib/unittest/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/unittest/__pycache__/constants.cpython-311.pyc,, +ddtrace/contrib/unittest/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/unittest/constants.py,sha256=FqTAdWU1ZqPhTMuZ19Dx1a0XW6FP1jwHylw9ZhRzwPo,246 +ddtrace/contrib/unittest/patch.py,sha256=3nQGZfz-FadYwTj6Tv8MlH4DfbKPiN6X84OIP6dnAWw,35366 +ddtrace/contrib/urllib3/__init__.py,sha256=AMtocBCa7f3H5MgtZTjg1YFX5Wrw2R9fYF6IeE-8ypg,1597 +ddtrace/contrib/urllib3/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/urllib3/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/urllib3/patch.py,sha256=Y8N4R0GrMuqfSoB9uq3NopEBEslKrfhzMa41hzNS7u0,5290 +ddtrace/contrib/vertica/__init__.py,sha256=H1U-TD0OD6KmZYitew-MvYuMowAGzkXtnV9-chGZW0I,1420 +ddtrace/contrib/vertica/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/vertica/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/vertica/patch.py,sha256=tKpfdxR4eQ9TZfIZ_yw7KtGQiZe6JOExcEieoiQZqGA,8760 +ddtrace/contrib/wsgi/__init__.py,sha256=1-5ThMEpTx-Jt55Ni1DM4M06j8wTf0zuBB9NjTMd_gQ,868 +ddtrace/contrib/wsgi/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/wsgi/__pycache__/wsgi.cpython-311.pyc,, +ddtrace/contrib/wsgi/wsgi.py,sha256=Q8LFKYBoCATkvMQdXNsYdZ5JPof1-iSDrR4T44IhiPU,10523 +ddtrace/contrib/yaaredis/__init__.py,sha256=UiSo4UQXYrsP3Pcc9Yo4IuwHKwD6myL2Eh2BF160kug,2060 +ddtrace/contrib/yaaredis/__pycache__/__init__.cpython-311.pyc,, +ddtrace/contrib/yaaredis/__pycache__/patch.cpython-311.pyc,, +ddtrace/contrib/yaaredis/patch.py,sha256=DJFQivzCYqO7QdjzNQ8CP2N9qOSF0DqC6h8CStU0_bQ,2787 +ddtrace/data_streams.py,sha256=y5qpNXHaOgd2wLl5nVImreZW0nfHeLtfQS-osStQZh0,1663 +ddtrace/debugging/__init__.py,sha256=6q2f6Z5_A_PhEu8k70s4mTFy1DWlGAWx3zWEi2x9DVY,806 +ddtrace/debugging/__pycache__/__init__.cpython-311.pyc,, +ddtrace/debugging/__pycache__/_async.cpython-311.pyc,, +ddtrace/debugging/__pycache__/_config.cpython-311.pyc,, +ddtrace/debugging/__pycache__/_debugger.cpython-311.pyc,, +ddtrace/debugging/__pycache__/_encoding.cpython-311.pyc,, +ddtrace/debugging/__pycache__/_expressions.cpython-311.pyc,, +ddtrace/debugging/__pycache__/_metrics.cpython-311.pyc,, +ddtrace/debugging/__pycache__/_redaction.cpython-311.pyc,, +ddtrace/debugging/__pycache__/_safety.cpython-311.pyc,, +ddtrace/debugging/__pycache__/_uploader.cpython-311.pyc,, +ddtrace/debugging/_async.py,sha256=oAs-UL8NMhrI6GhgecrfRF2fRKG9eko1AMotJ_ZvlvM,758 +ddtrace/debugging/_config.py,sha256=OdbGrspuwbOl9x7u3LbVK_U0u88IfICUIVigJHxqcYY,246 +ddtrace/debugging/_debugger.py,sha256=XEq2UcOqptVL2-CdwNJlLCgDtT4lsSNpEp4HrjU0JS4,31180 +ddtrace/debugging/_encoding.py,sha256=K5ZH75NHm7Qq0ddwG2uKZKqdAPQmqLxNW_kzW51GADo,9307 +ddtrace/debugging/_exception/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/debugging/_exception/__pycache__/__init__.cpython-311.pyc,, +ddtrace/debugging/_exception/__pycache__/auto_instrument.cpython-311.pyc,, +ddtrace/debugging/_exception/auto_instrument.py,sha256=dKHEb1JCwJ1c1wd5ZIR3EFkSYuLLSM2sCBdR007t1wc,7011 +ddtrace/debugging/_expressions.py,sha256=T0MLsnLqsggS12C0M36lpnIRp4Gb5MApfsOZ2bcTSFI,13171 +ddtrace/debugging/_function/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/debugging/_function/__pycache__/__init__.cpython-311.pyc,, +ddtrace/debugging/_function/__pycache__/discovery.cpython-311.pyc,, +ddtrace/debugging/_function/__pycache__/store.cpython-311.pyc,, +ddtrace/debugging/_function/discovery.py,sha256=EsAUjo3WIgL9wOgHjnSyExn4opKM_S-w6O05YoYOWMs,8740 +ddtrace/debugging/_function/store.py,sha256=3EKl9AxFDWwI308InM5H-tmgX61AtgagWM08srKJFsU,4340 +ddtrace/debugging/_metrics.py,sha256=Q8MKihuNSIxLLhYy0s3jJs6r7S8owsao7eQBhxC4LZs,223 +ddtrace/debugging/_probe/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/debugging/_probe/__pycache__/__init__.cpython-311.pyc,, +ddtrace/debugging/_probe/__pycache__/model.cpython-311.pyc,, +ddtrace/debugging/_probe/__pycache__/registry.cpython-311.pyc,, +ddtrace/debugging/_probe/__pycache__/remoteconfig.cpython-311.pyc,, +ddtrace/debugging/_probe/__pycache__/status.cpython-311.pyc,, +ddtrace/debugging/_probe/model.py,sha256=GOKZjdNH3XD4uPpE9GK6io0XgH043ajqZlG1FoRQ02Y,7585 +ddtrace/debugging/_probe/registry.py,sha256=GB8xaPo-Oqis411ThJQ6hjNrVUeYnX181xM3TZOo7QE,6877 +ddtrace/debugging/_probe/remoteconfig.py,sha256=CoPjHvVL3FR9_FBvFUxBw8ntA68kHQP4FXGao7jD1m0,13092 +ddtrace/debugging/_probe/status.py,sha256=O8vXceuim7R1BEKLctJ9d1eW28qx_2JvvRa2-IbQBfs,5317 +ddtrace/debugging/_redaction.py,sha256=X4qtpuhAfSNvBMJISO8hdS0bNB2T280je8_lDTlLFxs,4182 +ddtrace/debugging/_safety.py,sha256=aBd-fyAs7wqf30LiCfmFG5CzR-xbXb7osnh2nf8W61A,2152 +ddtrace/debugging/_signal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/debugging/_signal/__pycache__/__init__.cpython-311.pyc,, +ddtrace/debugging/_signal/__pycache__/collector.cpython-311.pyc,, +ddtrace/debugging/_signal/__pycache__/metric_sample.cpython-311.pyc,, +ddtrace/debugging/_signal/__pycache__/model.cpython-311.pyc,, +ddtrace/debugging/_signal/__pycache__/snapshot.cpython-311.pyc,, +ddtrace/debugging/_signal/__pycache__/tracing.cpython-311.pyc,, +ddtrace/debugging/_signal/__pycache__/utils.cpython-311.pyc,, +ddtrace/debugging/_signal/collector.py,sha256=6esIpqf4jkRVMX32Gi6jQXre2VaspOLV2orByKxjH8w,4323 +ddtrace/debugging/_signal/metric_sample.py,sha256=Pj49TDe3iqFv1eNM-jsFoFVGtc0GaYjor7-1ZkGmuXA,2773 +ddtrace/debugging/_signal/model.py,sha256=7D5rOwtxZtIjB-qR__hZs7JKIJsL9hehwE5jX39Kvrk,4942 +ddtrace/debugging/_signal/snapshot.py,sha256=IRl3zyqv2-HCPzQSV4u7WRc6x_DI61SFGJT5uWI5ags,8547 +ddtrace/debugging/_signal/tracing.py,sha256=rHXS4w6V_AGGbXIJzmSUR8BMWjfqxl3wTA8xaXwRlic,5677 +ddtrace/debugging/_signal/utils.py,sha256=48H65Um7jj7qfK0INw8RvTpbfPMz_yfaK7y2BG3cCXI,9759 +ddtrace/debugging/_uploader.py,sha256=tadYtxcBizkxsXL37FT1Ft5fb8QQYrKao8VIf9COyxE,3546 +ddtrace/ext/__init__.py,sha256=0Ax8C94xQ7C80xKs2_xYzP6RVESKZH2DB3rM1MjyLKQ,473 +ddtrace/ext/__pycache__/__init__.cpython-311.pyc,, +ddtrace/ext/__pycache__/aws.cpython-311.pyc,, +ddtrace/ext/__pycache__/cassandra.cpython-311.pyc,, +ddtrace/ext/__pycache__/ci.cpython-311.pyc,, +ddtrace/ext/__pycache__/consul.cpython-311.pyc,, +ddtrace/ext/__pycache__/db.cpython-311.pyc,, +ddtrace/ext/__pycache__/elasticsearch.cpython-311.pyc,, +ddtrace/ext/__pycache__/git.cpython-311.pyc,, +ddtrace/ext/__pycache__/http.cpython-311.pyc,, +ddtrace/ext/__pycache__/kafka.cpython-311.pyc,, +ddtrace/ext/__pycache__/kombu.cpython-311.pyc,, +ddtrace/ext/__pycache__/memcached.cpython-311.pyc,, +ddtrace/ext/__pycache__/mongo.cpython-311.pyc,, +ddtrace/ext/__pycache__/net.cpython-311.pyc,, +ddtrace/ext/__pycache__/redis.cpython-311.pyc,, +ddtrace/ext/__pycache__/sql.cpython-311.pyc,, +ddtrace/ext/__pycache__/test.cpython-311.pyc,, +ddtrace/ext/__pycache__/user.cpython-311.pyc,, +ddtrace/ext/aws.py,sha256=0t5-ftzdd4eCkpG4udnbvONnSe648nfsuH_D914oGCg,3538 +ddtrace/ext/cassandra.py,sha256=nT1jltcvXRSDddqWHWhFD3cxInDhhKd0ouGghNV3zgQ,191 +ddtrace/ext/ci.py,sha256=4VuQNSLToF0QSM8zt-GrX0BMjy6qxVIyODXT-I-IyhE,21950 +ddtrace/ext/consul.py,sha256=6Vq9cGCCOimwFMyQRR1rx0mnEWYKWn3JSYVihLnkPhk,76 +ddtrace/ext/db.py,sha256=dW0pOWQYQlAri4goGdlVIcDzmx6GjpURPXAMEoOvzt4,325 +ddtrace/ext/elasticsearch.py,sha256=1NbDiLzJYpzs0KVC7W2q8MsHkcayjFjZwasFP6mRSkI,211 +ddtrace/ext/git.py,sha256=BibJ42uXDj9Wnkg2JchffvUgJRiM-OOytHvQS-1GGaQ,14225 +ddtrace/ext/http.py,sha256=TegstAKHfmYMdxQBDdTKd8wHGGNK44xWq2JlECo7evg,447 +ddtrace/ext/kafka.py,sha256=Fwb5SfjCSAS5Q1cunqzTxpGD8MgwskrTJr_ofTYedmg,349 +ddtrace/ext/kombu.py,sha256=JqzezXNOH_wlcrQzNAOeM87v4CnJPFXrNeK9hX-UDTk,228 +ddtrace/ext/memcached.py,sha256=9OhpONkQtGpcQBAcX6AxdMmvWk7J7A8aJfknfemGZ4M,98 +ddtrace/ext/mongo.py,sha256=xLojJ7vdDhEpYaFLcYASCC2iUM0pPcZ83o-7JSgnmgc,96 +ddtrace/ext/net.py,sha256=jkuYQt8qHAceRflb0QNud6ramJXrrLUoSYgrf-7dOo0,215 +ddtrace/ext/redis.py,sha256=MCy19md0SCMkAXohiBxHmeye3f-3WXwbqpfyp9W9DoA,296 +ddtrace/ext/sql.py,sha256=PWbg2AmP3SFey-knezYm71HbFh76KV9K_JKhi0HaRN8,2297 +ddtrace/ext/test.py,sha256=Jlg8nZCsz2PKQWzEM7ZAAMeBypS9yn-bqW4XAlErp0s,2082 +ddtrace/ext/user.py,sha256=v3K0RLPK3xmQFS-jAq2oYclb09TKEsFc61fAJyZQ3DA,149 +ddtrace/filters.py,sha256=YfvhPWtyirFGQibO9Zp7q3GRci9T17CEPqJMbdm3H8A,2602 +ddtrace/internal/README.md,sha256=1uhyNSgCgy-mhrKDfkBxHD76hPffLFZFRGH7ma6JRdk,323 +ddtrace/internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/internal/__pycache__/__init__.cpython-311.pyc,, +ddtrace/internal/__pycache__/agent.cpython-311.pyc,, +ddtrace/internal/__pycache__/assembly.cpython-311.pyc,, +ddtrace/internal/__pycache__/atexit.cpython-311.pyc,, +ddtrace/internal/__pycache__/codeowners.cpython-311.pyc,, +ddtrace/internal/__pycache__/compat.cpython-311.pyc,, +ddtrace/internal/__pycache__/constants.cpython-311.pyc,, +ddtrace/internal/__pycache__/debug.cpython-311.pyc,, +ddtrace/internal/__pycache__/dogstatsd.cpython-311.pyc,, +ddtrace/internal/__pycache__/encoding.cpython-311.pyc,, +ddtrace/internal/__pycache__/forksafe.cpython-311.pyc,, +ddtrace/internal/__pycache__/gitmetadata.cpython-311.pyc,, +ddtrace/internal/__pycache__/glob_matching.cpython-311.pyc,, +ddtrace/internal/__pycache__/hostname.cpython-311.pyc,, +ddtrace/internal/__pycache__/http.cpython-311.pyc,, +ddtrace/internal/__pycache__/injection.cpython-311.pyc,, +ddtrace/internal/__pycache__/log_writer.cpython-311.pyc,, +ddtrace/internal/__pycache__/logger.cpython-311.pyc,, +ddtrace/internal/__pycache__/metrics.cpython-311.pyc,, +ddtrace/internal/__pycache__/module.cpython-311.pyc,, +ddtrace/internal/__pycache__/packages.cpython-311.pyc,, +ddtrace/internal/__pycache__/periodic.cpython-311.pyc,, +ddtrace/internal/__pycache__/rate_limiter.cpython-311.pyc,, +ddtrace/internal/__pycache__/safety.cpython-311.pyc,, +ddtrace/internal/__pycache__/sampling.cpython-311.pyc,, +ddtrace/internal/__pycache__/service.cpython-311.pyc,, +ddtrace/internal/__pycache__/sma.cpython-311.pyc,, +ddtrace/internal/__pycache__/tracemethods.cpython-311.pyc,, +ddtrace/internal/__pycache__/uds.cpython-311.pyc,, +ddtrace/internal/__pycache__/uwsgi.cpython-311.pyc,, +ddtrace/internal/_encoding.c,sha256=A7JX7UkPMykG0eNxozk2RM2JVtv-Zda0zvJT7x9FYq0,1637460 +ddtrace/internal/_encoding.cpython-311-x86_64-linux-gnu.so,sha256=k8xvHXdyGo8wTGqkavFJtqdiGV9j-7zMWzojzTg2lgs,357568 +ddtrace/internal/_encoding.pyi,sha256=JNUuxuoZz2D3nKo5lGDUsxA9u1fS6MSffYD8he_hnz8,1063 +ddtrace/internal/_encoding.pyx,sha256=8J2uckAmCWnfzxmqdHx6Lh5mJItwbmGdmM0E29BASGA,36236 +ddtrace/internal/_rand.c,sha256=4UADi5JunmqpTSh5HHjAHiyJLz9mk3ZyYygHrf8XYPQ,296384 +ddtrace/internal/_rand.cpython-311-x86_64-linux-gnu.so,sha256=tTEd4T3xjRJAnmmH9Px71Pqo9olFkujGslzCFyIs38I,64144 +ddtrace/internal/_rand.pyi,sha256=VQEVI10a3YVrb7Bg5adUaUkI0J70QWdt6E0Bt5dH_MY,127 +ddtrace/internal/_rand.pyx,sha256=BXiqrnuT7h3wRsrWN4jhC1bFxfwyHMrDq4Bbxs1yZ5w,5107 +ddtrace/internal/_stdint.h,sha256=DY0jy2g-deZsm-PfT3vWeiGgMoldI8AZX-nQ2qnLSVo,109 +ddtrace/internal/_tagset.c,sha256=xf0v8CcBZcRVul9rgwB7l4y4Qgu4S5E6LNgphbTyox8,434000 +ddtrace/internal/_tagset.cpython-311-x86_64-linux-gnu.so,sha256=GnY8j-hJdHDzv8XllxAb6--gvDcn0RsIEPnuGKXKr9s,118400 +ddtrace/internal/_tagset.pyi,sha256=J-05rpWA3TH8ex_nckcYq8mY0yO1enHCKsm8f_XmL7Y,624 +ddtrace/internal/_tagset.pyx,sha256=6UeR66ZK3PbiCWb_-Sj0srNuxOy8HQmOfztaTDLJGD4,7483 +ddtrace/internal/_utils.h,sha256=eX_-G0pyK96mZ60uiUPeLsNqX5r_9PfMmy9y8fq3hSs,191 +ddtrace/internal/_utils.pxd,sha256=XmIW76IjCJ-yipWMi97KuUYu3LRU_LjHN4YxuEN-W4g,77 +ddtrace/internal/agent.py,sha256=pbfCcLDqpFUbbxtRgGJORAf0SzyiO83s4Z0bxELvvao,3304 +ddtrace/internal/assembly.py,sha256=mcRrXrHGk_caBpsGugJ-pj3EgY4M67AMCPFGjenP7Vw,9198 +ddtrace/internal/atexit.py,sha256=EBzMjEW4_gZ32QkrSQjkDmt9nY_MMspYaUFUVrRVtGA,2607 +ddtrace/internal/buff_converter.h,sha256=lQM0fFqpHtXpaprDrIE9bDXqIlCFbpt_h6nzJS1eRjk,658 +ddtrace/internal/ci_visibility/__init__.py,sha256=C5YraQmpFIH2KMYneZNA2FsBOFTtUKWtCN_xYi-5ODQ,353 +ddtrace/internal/ci_visibility/__pycache__/__init__.cpython-311.pyc,, +ddtrace/internal/ci_visibility/__pycache__/constants.cpython-311.pyc,, +ddtrace/internal/ci_visibility/__pycache__/coverage.cpython-311.pyc,, +ddtrace/internal/ci_visibility/__pycache__/encoder.cpython-311.pyc,, +ddtrace/internal/ci_visibility/__pycache__/filters.cpython-311.pyc,, +ddtrace/internal/ci_visibility/__pycache__/git_client.cpython-311.pyc,, +ddtrace/internal/ci_visibility/__pycache__/recorder.cpython-311.pyc,, +ddtrace/internal/ci_visibility/__pycache__/utils.cpython-311.pyc,, +ddtrace/internal/ci_visibility/__pycache__/writer.cpython-311.pyc,, +ddtrace/internal/ci_visibility/constants.py,sha256=YjMp_YtDGMr776Hi00vexvde0Mep_EJ1GfvPL36Re-s,1967 +ddtrace/internal/ci_visibility/coverage.py,sha256=syHQAPVkWaS5jn_8SSDFcYSmP5PFVz64MpOFSjkJjHQ,5744 +ddtrace/internal/ci_visibility/encoder.py,sha256=9z2RQIen80ly3uqJAUxnHmpKElwtOa_WTsWDzUHtJbo,8005 +ddtrace/internal/ci_visibility/filters.py,sha256=1ayfP0sPlKGweR51MrNKY78b8hoqniGZpxLybYuEXQ4,1211 +ddtrace/internal/ci_visibility/git_client.py,sha256=8gCcNM5XkD6FlCcxH5JF1f14G94pdtHl2pTxYB294RU,21375 +ddtrace/internal/ci_visibility/recorder.py,sha256=kfdA8pDlHf3nONOxWRodKeE8jbpS3a8zZAmouaE4jAk,25338 +ddtrace/internal/ci_visibility/telemetry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/internal/ci_visibility/telemetry/__pycache__/__init__.cpython-311.pyc,, +ddtrace/internal/ci_visibility/telemetry/__pycache__/constants.cpython-311.pyc,, +ddtrace/internal/ci_visibility/telemetry/__pycache__/git.cpython-311.pyc,, +ddtrace/internal/ci_visibility/telemetry/__pycache__/utils.cpython-311.pyc,, +ddtrace/internal/ci_visibility/telemetry/constants.py,sha256=NKfiMnsYyUE2UuFGy7GG6jCd1Ur3PiLocjAWh_l8HlE,1384 +ddtrace/internal/ci_visibility/telemetry/git.py,sha256=ic7wWqiPGNiDe9QhpXFhZwUXj0KNsVF_W1D6j8VkP4A,4137 +ddtrace/internal/ci_visibility/telemetry/utils.py,sha256=DEVzVw2llci3NqBRam5lbixKWsiL94l5MUBXJ7tZviY,491 +ddtrace/internal/ci_visibility/utils.py,sha256=wbefZtB9Z_YD3cyTGA4mbWnvHSbBVyTj3DP49DKOyjg,5257 +ddtrace/internal/ci_visibility/writer.py,sha256=1YDHO_UEOh5zXlAb4FmbFZMqhY4IZmENc7Hdm0wU3do,5401 +ddtrace/internal/codeowners.py,sha256=9uses9up7Jq24yGi_qulebiwn2M9Cfx6riQgS5E8pvA,7361 +ddtrace/internal/compat.py,sha256=nRCBVmjlDgxO5ea7bntNuE39heegORK6jph2I5c2dXE,13558 +ddtrace/internal/constants.py,sha256=lBoJ_oZQPv6Vs6izrvOjeZ4XJ5kY3f8ZbaxlBjoL7qo,4521 +ddtrace/internal/core/__init__.py,sha256=8CmrXGXXXHtCJS8LiS83jM1hoj759nai4JQZPgxrXEU,12372 +ddtrace/internal/core/__pycache__/__init__.cpython-311.pyc,, +ddtrace/internal/core/__pycache__/event_hub.cpython-311.pyc,, +ddtrace/internal/core/event_hub.py,sha256=fIXb8T_h1VBOoF0LAmWe8AQeNmnt5x8GB0Ndj9T7PvI,3530 +ddtrace/internal/datadog/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/internal/datadog/__pycache__/__init__.cpython-311.pyc,, +ddtrace/internal/datadog/profiling/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/internal/datadog/profiling/__pycache__/__init__.cpython-311.pyc,, +ddtrace/internal/datadog/profiling/__pycache__/ddup.cpython-311.pyc,, +ddtrace/internal/datadog/profiling/__pycache__/utils.cpython-311.pyc,, +ddtrace/internal/datadog/profiling/_ddup.cpp,sha256=OBhXOKGg_eigy87R85OhkILx1Z6D-SQS7szgV_IlpUE,555023 +ddtrace/internal/datadog/profiling/_ddup.cpython-311-x86_64-linux-gnu.so,sha256=TMkHk1LIW2XuFhFDMcsuOaOOqPLZr92s94xOXjidKv0,3667048 +ddtrace/internal/datadog/profiling/_ddup.pyi,sha256=5BDwwZ0wFSK1OA6jyt_3N-Xq62FP0gP0l4yEP0J_dGo,1181 +ddtrace/internal/datadog/profiling/_ddup.pyx,sha256=oBPnQND4FKaFK2_6H-2KCbcxVpZEMKC1kCkEsY6iY5c,6914 +ddtrace/internal/datadog/profiling/ddup.py,sha256=r-5tknAytd52G7r-zhy6VaJFv4yy_FwrEX1hO8XWn4o,2383 +ddtrace/internal/datadog/profiling/include/exporter.hpp,sha256=a9O2sqkmbcKe6L0doPcwM3hLzx_D0PABi81Jaz775N4,8438 +ddtrace/internal/datadog/profiling/include/interface.hpp,sha256=2fIXtkft9g-gO91qScunLEPshfMzJFoiNi7Dr45NJjQ,2117 +ddtrace/internal/datadog/profiling/src/exporter.cpp,sha256=v0_wQ0JEDl0esx92ySC0WSf6kh1yDWb_4GNuIOYyN20,19149 +ddtrace/internal/datadog/profiling/src/interface.cpp,sha256=I05bqNo4gaCN_kSKhHCd1p5b77uUzTa7pNaH7BiSv-s,6736 +ddtrace/internal/datadog/profiling/utils.py,sha256=ibJDCIZyJAK2qiUFIQzJkHSj-hSJMoWM18uJy7iOYBY,819 +ddtrace/internal/datastreams/__init__.py,sha256=Fb7HvVbejdmBFjPMQyPmdGY1Pho2kRozukRBKD5KE9k,817 +ddtrace/internal/datastreams/__pycache__/__init__.cpython-311.pyc,, +ddtrace/internal/datastreams/__pycache__/botocore.cpython-311.pyc,, +ddtrace/internal/datastreams/__pycache__/encoding.cpython-311.pyc,, +ddtrace/internal/datastreams/__pycache__/fnv.cpython-311.pyc,, +ddtrace/internal/datastreams/__pycache__/kafka.cpython-311.pyc,, +ddtrace/internal/datastreams/__pycache__/kombu.cpython-311.pyc,, +ddtrace/internal/datastreams/__pycache__/processor.cpython-311.pyc,, +ddtrace/internal/datastreams/__pycache__/utils.cpython-311.pyc,, +ddtrace/internal/datastreams/botocore.py,sha256=Q1iyGAwVSstVF-Kprl8O4KRbPzThxxL9YPOX5dhsoSo,6761 +ddtrace/internal/datastreams/encoding.py,sha256=SSLbXfaLgxR-jVPx6NnPYfbHbAafqEtHna66VEhzrPA,969 +ddtrace/internal/datastreams/fnv.py,sha256=EfOCo9miVh3ohlsQpqPMa7PvFeG124cwSv1aaP4R4_c,779 +ddtrace/internal/datastreams/kafka.py,sha256=fvMTF7t0TnEjZkDoZtCnVGKe5phZuGce7WR73-bmfbE,4737 +ddtrace/internal/datastreams/kombu.py,sha256=kgFGfXfbp6NSUPRuQaT7g2pJY0xrgLQT44NZl6-HHn0,2050 +ddtrace/internal/datastreams/processor.py,sha256=1ycLrFk-fsHqR__w6TgemUY9QVBC4dkZlBfrT8sNTio,18767 +ddtrace/internal/datastreams/utils.py,sha256=0AU8Wwly06bjP5lSCixR2SZpgTvi7sybYItEsjQk0cM,612 +ddtrace/internal/debug.py,sha256=FeWWWKI7g4xHlMbngqNaYFqsU6c4lE2Dxo18Fpx067A,10650 +ddtrace/internal/dogstatsd.py,sha256=efN0ySeNqX07_oFtQpJiKPmX6QDFHWWUrF2ywXV5zSc,1038 +ddtrace/internal/encoding.py,sha256=h5aSO2sNajjq5XeR-XzQj69lcrPmS_zCZDq85otwHRk,4632 +ddtrace/internal/forksafe.py,sha256=jwvxSkHG5ZfEml47mtIXhxOcqMcQ0HDkteTWriQpQrI,3896 +ddtrace/internal/gitmetadata.py,sha256=pXd1Mv-olBtaCt93LVYaN9Oy0h9i5_rTt4M1H1CxNmU,5197 +ddtrace/internal/glob_matching.py,sha256=BRhphDAbDGKvKqfMd-Z2m84ByljJbJX1nDhz-N_UBiI,1625 +ddtrace/internal/hostname.py,sha256=JxcUHAgbWLtnOAjghUb0MkQ4gHiFka15PwsDSDYJexk,285 +ddtrace/internal/http.py,sha256=F8XLr90sCfPG_KuAPwppsOdJoSwYzWjJB97Yaon3egw,1103 +ddtrace/internal/injection.py,sha256=FqCQipq4LnE_terTZomcnFDdhtnKHmc6Jl4DJY3P_k0,6546 +ddtrace/internal/llmobs/__init__.py,sha256=yVpxe-9lyZ-SOjbgkP2kvsOOmXTwUMna6QpQHYimBOU,62 +ddtrace/internal/llmobs/__pycache__/__init__.cpython-311.pyc,, +ddtrace/internal/llmobs/__pycache__/writer.cpython-311.pyc,, +ddtrace/internal/llmobs/integrations/__init__.py,sha256=pptHo2prPaGFp2wFjWJmd-s3s0Zt5nbsTd-5nWOXBk8,261 +ddtrace/internal/llmobs/integrations/__pycache__/__init__.cpython-311.pyc,, +ddtrace/internal/llmobs/integrations/__pycache__/base.cpython-311.pyc,, +ddtrace/internal/llmobs/integrations/__pycache__/bedrock.cpython-311.pyc,, +ddtrace/internal/llmobs/integrations/__pycache__/langchain.cpython-311.pyc,, +ddtrace/internal/llmobs/integrations/__pycache__/openai.cpython-311.pyc,, +ddtrace/internal/llmobs/integrations/base.py,sha256=_S42QhGOAlBrSNx2u5RBfJx_UpO0BcHUWGJKx4nToIs,10029 +ddtrace/internal/llmobs/integrations/bedrock.py,sha256=MCSeBcazo9Z_kSeao_Ifn9DeaK1Iq7TUOMpY1F6HOSM,2997 +ddtrace/internal/llmobs/integrations/langchain.py,sha256=tZ7UaWCLU7ZFqRxPoDCxdMcSA83TJRjHkOi63pM6VnA,3091 +ddtrace/internal/llmobs/integrations/openai.py,sha256=0kzXxufGC0pZ1N-a1FQ0uQyNaGZQirSFcZumxkmxzD4,10146 +ddtrace/internal/llmobs/writer.py,sha256=w4Cvh8BfI7wiTgzl8dvU_fORzKddirjaXwnf_opX5Co,4858 +ddtrace/internal/log_writer.py,sha256=rp2JATtkdLWmINqPd_PkUT58qGH5v5BROrMhiLNRmtA,3675 +ddtrace/internal/logger.py,sha256=fpAztptlezDIA80oyoZ-N1ZMZjoUj_nLgRk8m9dA9FM,7597 +ddtrace/internal/metrics.py,sha256=qpPVykItcuEs_Y6GGxFyxf10FQPlYYDgIVDW1Fe3QiU,3151 +ddtrace/internal/module.py,sha256=n6LUvLreMXwkjkXUDJdiemGMKv77-8eB0Ump0fnKE-E,19554 +ddtrace/internal/pack.h,sha256=VnFr2XJ8XJHaYh62dJSsrNhmgZPJBgOxSkoIuNgfoQg,2534 +ddtrace/internal/pack_template.h,sha256=dIcX-OWgFLwJBeVRxKmShwhs4nvGfYUrqDewJAFyvjw,39017 +ddtrace/internal/packages.py,sha256=Mvl9Grw00vXEXFdetAYmEeYn_qH025XDmfDiuvavLEQ,4343 +ddtrace/internal/periodic.py,sha256=BYrTDM5fA8t7CPQcmtlkfnCMfT41ecvuUk2cC0mi1wY,5180 +ddtrace/internal/processor/__init__.py,sha256=P6T_QmsAqT9rtTOOkgtDGQtzMmLmhR_uI9kGpclZrJE,2342 +ddtrace/internal/processor/__pycache__/__init__.cpython-311.pyc,, +ddtrace/internal/processor/__pycache__/endpoint_call_counter.cpython-311.pyc,, +ddtrace/internal/processor/__pycache__/stats.cpython-311.pyc,, +ddtrace/internal/processor/__pycache__/trace.cpython-311.pyc,, +ddtrace/internal/processor/endpoint_call_counter.py,sha256=pIAEWYdt0CERXV-9SBPlEqTfA7uyF_8oE1FoYw4Zd8E,1394 +ddtrace/internal/processor/stats.py,sha256=SP3aWerJD-47MeUFG7PgxU8ZIpGwJRvgdLwUBUgAU-4,9324 +ddtrace/internal/processor/trace.py,sha256=xfJDag2Bbv0v4KY9KCj7WK41X0USCIYvvaXKJGcQ2a0,15789 +ddtrace/internal/rate_limiter.py,sha256=Da2oKLfGF7RD-NvMBm3WzTMEETqha5ANP1fEqeaELHw,8974 +ddtrace/internal/remoteconfig/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/internal/remoteconfig/__pycache__/__init__.cpython-311.pyc,, +ddtrace/internal/remoteconfig/__pycache__/_connectors.cpython-311.pyc,, +ddtrace/internal/remoteconfig/__pycache__/_publishers.cpython-311.pyc,, +ddtrace/internal/remoteconfig/__pycache__/_pubsub.cpython-311.pyc,, +ddtrace/internal/remoteconfig/__pycache__/_subscribers.cpython-311.pyc,, +ddtrace/internal/remoteconfig/__pycache__/client.cpython-311.pyc,, +ddtrace/internal/remoteconfig/__pycache__/constants.cpython-311.pyc,, +ddtrace/internal/remoteconfig/__pycache__/utils.cpython-311.pyc,, +ddtrace/internal/remoteconfig/__pycache__/worker.cpython-311.pyc,, +ddtrace/internal/remoteconfig/_connectors.py,sha256=JorVJOVB6Z7_IzZmj_ILq_UYwX4PUCdXG3Z5cOR5CXU,3386 +ddtrace/internal/remoteconfig/_publishers.py,sha256=E2wv4iRGD16Ky9M-d3Tx5iFXMkl781QeamPiR0uXD40,5479 +ddtrace/internal/remoteconfig/_pubsub.py,sha256=R29hzWRueVHSLrhfzqrPWZDIPqqMAkhNgLdlL3plUW4,5456 +ddtrace/internal/remoteconfig/_subscribers.py,sha256=kMWhI_qN41Y4HGou4y3oFGH92sj0PqV4g8n_RAQ9ceM,2212 +ddtrace/internal/remoteconfig/client.py,sha256=uG9HoFfjq45gu3I3uwnH1Umt89-Hbpb_CZMtyOmzvFU,22235 +ddtrace/internal/remoteconfig/constants.py,sha256=cUe9H0q6O1wMYA4fyM54CbfgtqAmTJDeRGp6qSN9vyU,83 +ddtrace/internal/remoteconfig/utils.py,sha256=x6lFRbYU9baAU4wpD8EApw8ZLTk5eo9HIur9oMYh010,132 +ddtrace/internal/remoteconfig/worker.py,sha256=LzVerzeL6N3lU1qhJHvPxJZkjjZNDt3-d8ltLb3U554,6498 +ddtrace/internal/runtime/__init__.py,sha256=mtD3VXezLQmvYO1gpGVwPVDQjSIfh27fnCI-wkq45_Y,1049 +ddtrace/internal/runtime/__pycache__/__init__.cpython-311.pyc,, +ddtrace/internal/runtime/__pycache__/collector.cpython-311.pyc,, +ddtrace/internal/runtime/__pycache__/constants.cpython-311.pyc,, +ddtrace/internal/runtime/__pycache__/container.cpython-311.pyc,, +ddtrace/internal/runtime/__pycache__/metric_collectors.cpython-311.pyc,, +ddtrace/internal/runtime/__pycache__/runtime_metrics.cpython-311.pyc,, +ddtrace/internal/runtime/__pycache__/tag_collectors.cpython-311.pyc,, +ddtrace/internal/runtime/collector.py,sha256=ZqeQ2lVLgMXrmisQPE4Nm-g0fbjfdVP1vsDeEMICzDM,3117 +ddtrace/internal/runtime/constants.py,sha256=tm1iOlpgtGM0a0uXubjnoDWUCMqB1DzjoNTFm0Z5ZaY,1036 +ddtrace/internal/runtime/container.py,sha256=fCd_Ge1SB0chqPaQptciMs-RvrCd2dUnX4qxC1q6O90,3838 +ddtrace/internal/runtime/metric_collectors.py,sha256=YxTwAQVJJlnjp543RMLGN9vxK202q6Zz3EXERNOx-VQ,2930 +ddtrace/internal/runtime/runtime_metrics.py,sha256=Bouu18-AyB6DEef-qzTK1y3RNY6wQPnsyiVJSrs05J8,5738 +ddtrace/internal/runtime/tag_collectors.py,sha256=7syAU56F5Y8snWyY3vDxo4fxswysiH11Tu7_59nHNR4,2338 +ddtrace/internal/safety.py,sha256=R9SVrTeahuaPcpKUvCuXVr9DB5x9MipRCqGfa_ydj6c,4099 +ddtrace/internal/sampling.py,sha256=rs5MVlbcuiZ29YSXthjrTOIf9WLdrVPlUzNTuIG0qBs,10577 +ddtrace/internal/schema/__init__.py,sha256=X8UqZKBuI8aRpQdJ6JjynG0oqMjKay78P5SCOkZLbfo,2558 +ddtrace/internal/schema/__pycache__/__init__.cpython-311.pyc,, +ddtrace/internal/schema/__pycache__/span_attribute_schema.cpython-311.pyc,, +ddtrace/internal/schema/span_attribute_schema.py,sha256=TH6cLDqAhG9SLUsjJZTW0TdpFc1Un8ghgCX7TRYSOZU,3693 +ddtrace/internal/serverless/__init__.py,sha256=3y6rA68TpzSYSK4T96vrPK3l5jiEIETFYJyaqNifhmo,1791 +ddtrace/internal/serverless/__pycache__/__init__.cpython-311.pyc,, +ddtrace/internal/serverless/__pycache__/mini_agent.cpython-311.pyc,, +ddtrace/internal/serverless/mini_agent.py,sha256=NPyY8h-mgtf_HUN1U6lTxIBVVa1lQxKGPXYensUADW4,1507 +ddtrace/internal/service.py,sha256=47l3CMtCbGkVjKQcd41MNKRVyikEa0tm-aFOTghacC0,2957 +ddtrace/internal/sma.py,sha256=CZJTxxoBnFeMN0_xgn-cms3y3BQfMCBTX0Z_H4N1WVY,1601 +ddtrace/internal/sysdep.h,sha256=d2ZWwMOPyhrZctSVL52Ctvrg4jaOGiPjZKOCe8NJLgU,7597 +ddtrace/internal/telemetry/__init__.py,sha256=VpcHpiqW0mZmK2WNc6PhoMn9IHE1DCTzUjgQS6-j1sw,2596 +ddtrace/internal/telemetry/__pycache__/__init__.cpython-311.pyc,, +ddtrace/internal/telemetry/__pycache__/constants.cpython-311.pyc,, +ddtrace/internal/telemetry/__pycache__/data.cpython-311.pyc,, +ddtrace/internal/telemetry/__pycache__/metrics.cpython-311.pyc,, +ddtrace/internal/telemetry/__pycache__/metrics_namespaces.cpython-311.pyc,, +ddtrace/internal/telemetry/__pycache__/writer.cpython-311.pyc,, +ddtrace/internal/telemetry/constants.py,sha256=AaJz5TFGQ0HP6d_FheUPkAxcUI-WtnIf0KRnHTg5Erw,3868 +ddtrace/internal/telemetry/data.py,sha256=mrM-D2nZrDKne7C25g13nIXBcwH-s2poZhG6yXYiIw4,3992 +ddtrace/internal/telemetry/metrics.py,sha256=FZPRofKBa7MYmZ6aIFG_GcCa9RurYKsvK_AEEB7Au9w,5074 +ddtrace/internal/telemetry/metrics_namespaces.py,sha256=ZdofJKRQwo4khl3IM_v6xMxyfYx77gLbCTXVEREy_Ak,2495 +ddtrace/internal/telemetry/writer.py,sha256=m0j9-glfvqquDwbFMUiXsPVppBKyZg9bZBut9w5OUac,33941 +ddtrace/internal/tracemethods.py,sha256=tt9_-ghcywLVpDvs38B-epdvZaJLcftt2bn5ME8sVkk,4229 +ddtrace/internal/uds.py,sha256=Pcf8sWhGzh-MCcEMwlyaDsq7M-OQqH4DryRpHIP1D6c,848 +ddtrace/internal/utils/__init__.py,sha256=pAW2h7Vl9oJGDGEl9mvk_mjTuAwbUxP7Nmluf6UNLpk,2822 +ddtrace/internal/utils/__pycache__/__init__.cpython-311.pyc,, +ddtrace/internal/utils/__pycache__/attrdict.cpython-311.pyc,, +ddtrace/internal/utils/__pycache__/cache.cpython-311.pyc,, +ddtrace/internal/utils/__pycache__/config.cpython-311.pyc,, +ddtrace/internal/utils/__pycache__/deprecations.cpython-311.pyc,, +ddtrace/internal/utils/__pycache__/formats.cpython-311.pyc,, +ddtrace/internal/utils/__pycache__/http.cpython-311.pyc,, +ddtrace/internal/utils/__pycache__/importlib.cpython-311.pyc,, +ddtrace/internal/utils/__pycache__/inspection.cpython-311.pyc,, +ddtrace/internal/utils/__pycache__/retry.cpython-311.pyc,, +ddtrace/internal/utils/__pycache__/signals.cpython-311.pyc,, +ddtrace/internal/utils/__pycache__/time.cpython-311.pyc,, +ddtrace/internal/utils/__pycache__/version.cpython-311.pyc,, +ddtrace/internal/utils/__pycache__/wrappers.cpython-311.pyc,, +ddtrace/internal/utils/attrdict.py,sha256=-zaPkaVMhBzwWABgVs1MM6R5At53aCEDzebuWMeeD1M,1181 +ddtrace/internal/utils/cache.py,sha256=hupOr0tIsga9X4hNE6eIKSy40wP7rU_tm8lgzg4C3I4,4189 +ddtrace/internal/utils/config.py,sha256=T-t1Wj8qUOmQ98TBaZNb9o10X9zaLMEYphwNniNLBCw,446 +ddtrace/internal/utils/deprecations.py,sha256=l2a0vrVpTd9lm1DI9KFf8WdD0_YxzNWk9uvUnokefbs,369 +ddtrace/internal/utils/formats.py,sha256=7k6w76X8oGmnsSircnNcyyejrHDjT980LA5xI5N3ycA,5180 +ddtrace/internal/utils/http.py,sha256=wqctaZP4cvwCDGi3sbGQLITFAgpbZX7uYaMVnvlu1Qc,16031 +ddtrace/internal/utils/importlib.py,sha256=nfCdh0p50JqvpBjTI093rMgSzAaJQNWBmXRU05tiuJg,1404 +ddtrace/internal/utils/inspection.py,sha256=C2pLZr6GBHmlk1PAIQrNOdp_NVUTcErjyzJWD29cq3k,4450 +ddtrace/internal/utils/retry.py,sha256=3FxbFjM1C23tcbAzsYq0Z7Qzdqi3mqhAvumTUl6V7-U,1657 +ddtrace/internal/utils/signals.py,sha256=PZSn4_YNMALZXLhpo333n2LijDcegNYrAQJibXPvnQw,866 +ddtrace/internal/utils/time.py,sha256=lmK2CUxarFj4IMF-18QspZ08iAIb48FoTx-Jjj-xj28,4113 +ddtrace/internal/utils/version.py,sha256=HATPlVAyNyIHXip-0_-8uBhYnomBzYm1Tr0XYsGcLts,2937 +ddtrace/internal/utils/wrappers.py,sha256=9sF0cdQQb7NTZ5fAbiXNgs-Z57mFkK6iv5u4Q04mBOw,632 +ddtrace/internal/uwsgi.py,sha256=ZpAZEcrP9F32dM5uAO2xgXDW8Rzo3iv1j5Ih6BMojco,2770 +ddtrace/internal/wrapping/__init__.py,sha256=24xDiCnr0Atilv8Dj16ORl78O6Jw1X2nZiMFSecISec,9840 +ddtrace/internal/wrapping/__pycache__/__init__.cpython-311.pyc,, +ddtrace/internal/wrapping/__pycache__/asyncs.cpython-311.pyc,, +ddtrace/internal/wrapping/__pycache__/generators.cpython-311.pyc,, +ddtrace/internal/wrapping/asyncs.py,sha256=vD66Tf836TbXCYE2SxxoD7U-fXICgJVYQcAaTVX93f4,19872 +ddtrace/internal/wrapping/generators.py,sha256=q7TrbVGf4Pl3kEqrjCbzmLC1NiL3GGCUeihFBTjvGqE,13579 +ddtrace/internal/writer/__init__.py,sha256=rOoVAxiRLPg-F6K_7AatS8iwWlQr8Fqtxi-AWE2adYY,431 +ddtrace/internal/writer/__pycache__/__init__.cpython-311.pyc,, +ddtrace/internal/writer/__pycache__/writer.cpython-311.pyc,, +ddtrace/internal/writer/__pycache__/writer_client.cpython-311.pyc,, +ddtrace/internal/writer/writer.py,sha256=fdW961Rmt_OoljdMme-o31SK_OP9ArLmaNCNIR4l7C8,24201 +ddtrace/internal/writer/writer_client.py,sha256=yY0T5VbCzRF_xWHVyKxlTuOrmK9oXYPUQSfGiRhYeb0,1198 +ddtrace/opentelemetry/__init__.py,sha256=SHqVDpXH5Od-0tffHvOX41YI5eBc16I043MXgUaWXEw,3455 +ddtrace/opentelemetry/__pycache__/__init__.cpython-311.pyc,, +ddtrace/opentelemetry/__pycache__/_context.cpython-311.pyc,, +ddtrace/opentelemetry/__pycache__/_span.cpython-311.pyc,, +ddtrace/opentelemetry/__pycache__/_trace.cpython-311.pyc,, +ddtrace/opentelemetry/_context.py,sha256=7ofcRQ8jMlpfEXsOrGTr70_pet_wQLL7XaYR44DLb9Q,3881 +ddtrace/opentelemetry/_span.py,sha256=Iu3WTVLh0DcZao5GOVjiO3TWICo98QBM-mMpjhF-fLE,11663 +ddtrace/opentelemetry/_trace.py,sha256=HEBJ3g-Sv7N5167v0NcHmpx56mqF5xLzUbiHkcIY5JM,6039 +ddtrace/opentracer/__init__.py,sha256=Z2NreXCsKituj_xkrBrxox6dJME-HY5dAQdoPrT6P4A,121 +ddtrace/opentracer/__pycache__/__init__.cpython-311.pyc,, +ddtrace/opentracer/__pycache__/helpers.cpython-311.pyc,, +ddtrace/opentracer/__pycache__/settings.cpython-311.pyc,, +ddtrace/opentracer/__pycache__/span.cpython-311.pyc,, +ddtrace/opentracer/__pycache__/span_context.cpython-311.pyc,, +ddtrace/opentracer/__pycache__/tags.cpython-311.pyc,, +ddtrace/opentracer/__pycache__/tracer.cpython-311.pyc,, +ddtrace/opentracer/__pycache__/utils.cpython-311.pyc,, +ddtrace/opentracer/helpers.py,sha256=3Ayxc9IgQPRxgSwEFb2ChKJmfVpn_syv1crODBUYFcg,491 +ddtrace/opentracer/propagation/__init__.py,sha256=dM61Rvq_oyksFQii8bRAUFjm2CZXksIsSf_5fSCjoQw,71 +ddtrace/opentracer/propagation/__pycache__/__init__.cpython-311.pyc,, +ddtrace/opentracer/propagation/__pycache__/binary.cpython-311.pyc,, +ddtrace/opentracer/propagation/__pycache__/http.cpython-311.pyc,, +ddtrace/opentracer/propagation/__pycache__/propagator.cpython-311.pyc,, +ddtrace/opentracer/propagation/__pycache__/text.cpython-311.pyc,, +ddtrace/opentracer/propagation/binary.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/opentracer/propagation/http.py,sha256=arRf_w6-nP5gi_bJKWpeC-8G8rXTxalUF1eazBwJveo,2562 +ddtrace/opentracer/propagation/propagator.py,sha256=It4jZZ0bf2yEwngyHLyXofoFd6WxVz1HqNmUTwziAdY,230 +ddtrace/opentracer/propagation/text.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/opentracer/settings.py,sha256=yinHnUraYQvARuRbs-CqpBKwg3pncm6umSdk8vT33VQ,1005 +ddtrace/opentracer/span.py,sha256=5RZDaDrNA6Gp_oCOBNA13cJsjKFVHirjnUVdQ-5UIuI,6494 +ddtrace/opentracer/span_context.py,sha256=RIsesab64GauFgWnolw9E4lDP9I6HTVsI2veg8hqbIw,2262 +ddtrace/opentracer/tags.py,sha256=J7h3lOXHloO69vDaMlERZCUxuEDnb8pz36jSKImPslE,466 +ddtrace/opentracer/tracer.py,sha256=SXwuAE6_4t1dM2bYp5rTYBJupBGh7EkWhq0jPdOajhE,16164 +ddtrace/opentracer/utils.py,sha256=oNiXe2G49awvP4CsYngbzmCpfVycF_u0LSv4RXFa8OQ,2171 +ddtrace/pin.py,sha256=cAXUJEMZdqWXyR9_oRqn7RmswXNv4Z-Oxm-mK3OMsdQ,7267 +ddtrace/profiling/__init__.py,sha256=55iYcZRG6WJMVRH5cF_iocmCVlPa3HyIVayeqimh9-Y,583 +ddtrace/profiling/__pycache__/__init__.cpython-311.pyc,, +ddtrace/profiling/__pycache__/_asyncio.cpython-311.pyc,, +ddtrace/profiling/__pycache__/_traceback.cpython-311.pyc,, +ddtrace/profiling/__pycache__/auto.cpython-311.pyc,, +ddtrace/profiling/__pycache__/event.cpython-311.pyc,, +ddtrace/profiling/__pycache__/profiler.cpython-311.pyc,, +ddtrace/profiling/__pycache__/recorder.cpython-311.pyc,, +ddtrace/profiling/__pycache__/scheduler.cpython-311.pyc,, +ddtrace/profiling/_asyncio.py,sha256=uQn3gCMVjTEAuZuzr6VUxz4aAp2pdLAz9olRXtL6m_U,1852 +ddtrace/profiling/_build.c,sha256=-IPUlnGO13bHG4wHBSnixKctNjQHPy8sB3JQd4SfyeY,166988 +ddtrace/profiling/_build.cpython-311-x86_64-linux-gnu.so,sha256=ALhe1kK1EOtef-zXPnx9TkQb_cxdKLR4oKJE610ywtw,28336 +ddtrace/profiling/_build.pyi,sha256=qScjYCL2Bcn8kdVuBq80FWiOFoWnLAnUTER93amencA,58 +ddtrace/profiling/_build.pyx,sha256=3ScNzOvIqQswf00e3gswCDAJ2_vcvXzEAxKYTXiFuM0,146 +ddtrace/profiling/_threading.c,sha256=UEgDY9-nf_9GKN50BrzVp0_UWv6kJWPAx2L8L1eiBZg,477347 +ddtrace/profiling/_threading.cpython-311-x86_64-linux-gnu.so,sha256=WStL_UjyA8AeoFa2_6i9g1creGOC0n9gDNoF2ve4kPQ,120872 +ddtrace/profiling/_threading.pyi,sha256=KiFDR9TLKLjmCuQN6V26nBThaF6nGuyOlzGC8IKa4Vg,379 +ddtrace/profiling/_threading.pyx,sha256=4rW6W_5qQ10ycisDqr3cUXh8vUvkJNHNDPKQn3G1fAw,4884 +ddtrace/profiling/_traceback.py,sha256=gtTV5xW0HKO2azPwCNlx2PdJ0KnM4TcKo7EeJm8cSrU,111 +ddtrace/profiling/auto.py,sha256=9FKbtu11hUCOsN4PrtRkDTVXRGBSIh2REzpaOhBW0tA,294 +ddtrace/profiling/bootstrap/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/profiling/bootstrap/__pycache__/__init__.cpython-311.pyc,, +ddtrace/profiling/bootstrap/__pycache__/sitecustomize.cpython-311.pyc,, +ddtrace/profiling/bootstrap/sitecustomize.py,sha256=zSmUmKEOjy4hq1Jiw3yIrhGUQRCl7W3LAHLlLbRbvU0,431 +ddtrace/profiling/collector/__init__.py,sha256=vRaJ6PmEuxDlatggriSI4DoiWR_oRH-oN8YKZ_6s2iU,2091 +ddtrace/profiling/collector/__pycache__/__init__.cpython-311.pyc,, +ddtrace/profiling/collector/__pycache__/_lock.cpython-311.pyc,, +ddtrace/profiling/collector/__pycache__/asyncio.cpython-311.pyc,, +ddtrace/profiling/collector/__pycache__/memalloc.cpython-311.pyc,, +ddtrace/profiling/collector/__pycache__/stack_event.cpython-311.pyc,, +ddtrace/profiling/collector/__pycache__/threading.cpython-311.pyc,, +ddtrace/profiling/collector/_lock.py,sha256=JGnZlXdf6sJrlxImsblKGsNBFkwdtiagsK7CbGRB64Q,7993 +ddtrace/profiling/collector/_memalloc.c,sha256=Ml4NnFRwLG94t38ohmCz6gzoY-JkniR0w2c_0wbCl_E,16129 +ddtrace/profiling/collector/_memalloc.cpython-311-x86_64-linux-gnu.so,sha256=eeYgPdCBc-HqNT-ipxO18TfL3a7muo9CQmobrpuL1ZU,28688 +ddtrace/profiling/collector/_memalloc.pyi,sha256=ENw5uCdruLqV36nO1QabiG-od5pEpelbFQSeOIMdlbA,463 +ddtrace/profiling/collector/_memalloc_heap.c,sha256=RZzQo-eQBrof43OUgV7-BsGbpVXgtgeNGDyCIvPkZxg,7257 +ddtrace/profiling/collector/_memalloc_heap.h,sha256=6u-TDLfnzpWonCYBxNBuP-m3cvYXIcFhciQlnh7sTVw,765 +ddtrace/profiling/collector/_memalloc_reentrant.h,sha256=0aoD1DdLXUSl81Bhp2Su5sIy1xVz8uQOvrj9Mmi1uIQ,1055 +ddtrace/profiling/collector/_memalloc_tb.c,sha256=Dl5TeSDTDxqLtFHlMV-nHzsuCRiSDBmTYnc9UTHD0C8,6574 +ddtrace/profiling/collector/_memalloc_tb.h,sha256=vOjplniALMGIaJa1w7pXyY1Tja-9dfc0UgcXZF5IIyg,1517 +ddtrace/profiling/collector/_pymacro.h,sha256=qpyla0EzoHaikKmLVUG4uRYHf0Qop3r1RuWC_kwKQ-w,344 +ddtrace/profiling/collector/_task.c,sha256=jTkmFu0scXUaptjL2s1ztWsOYmKIbD8_YgSk3j8DcAM,471728 +ddtrace/profiling/collector/_task.cpython-311-x86_64-linux-gnu.so,sha256=MX8pEldhkEUgbBzxW3oG0CA6zBS-S6Ijj9vU-gusPZY,120712 +ddtrace/profiling/collector/_task.pyi,sha256=S3p21udMqXIsTNSVTxdKjoxXR_nDXQK3jnk478lB14U,241 +ddtrace/profiling/collector/_task.pyx,sha256=l-gF6mqMeW1VDkBCTX37m4_jjSpAATvkXBxM9OPlxF0,4655 +ddtrace/profiling/collector/_traceback.c,sha256=I8oYytqC0EncJMwytbNfYJJW1p_d7aoq5F8QUHpgcnE,361265 +ddtrace/profiling/collector/_traceback.cpython-311-x86_64-linux-gnu.so,sha256=oQ77Q3v1v0CVlgAb90hcoPoZvmb6Xsqd2nr5KDoOWIE,87768 +ddtrace/profiling/collector/_traceback.pyi,sha256=pFYKACbj-50H5U2OUNYdiQt-N9rWBE3lKJ4la_7kCN8,302 +ddtrace/profiling/collector/_traceback.pyx,sha256=72dvyn_meGBX09H9DoOpWJkU82Yzq5ylzWnIX8kl1XE,3591 +ddtrace/profiling/collector/_utils.h,sha256=zVoIoSfw9BSin0AO9OnkfyNKG_VGCU5hV4KWlCXatvM,10273 +ddtrace/profiling/collector/asyncio.py,sha256=IRhMR3xsYY8JTThQFOXaRNIFTTwCWznF3bAGObJYDyo,1250 +ddtrace/profiling/collector/memalloc.py,sha256=TUAO6s1BEVW0rJAjs7TEuRlskQSqF87NFnFieSs-6m8,8544 +ddtrace/profiling/collector/stack.c,sha256=96rpcYisDuZWJi6OdNW8ijCRDPixQYlRDNIf1MVhd_8,970390 +ddtrace/profiling/collector/stack.cpython-311-x86_64-linux-gnu.so,sha256=oNCm9K-aMulWmO6xsw4ui2z2JV_fV8A4fdBem5vcDzw,300672 +ddtrace/profiling/collector/stack.pyi,sha256=S1UO1iCbwIrI3V5GYplF1dXsxUDqW7R40T0tw-m37Rk,166 +ddtrace/profiling/collector/stack.pyx,sha256=MuBGNubfA3Mqfx0Lgc7cg_-WVfO3aWoSD7Z5QHl-NDs,20601 +ddtrace/profiling/collector/stack_event.py,sha256=2NzWb6KzjCZLVAhZq_LmJbs1OWSGKraBJXMKVvz1Q8E,547 +ddtrace/profiling/collector/threading.py,sha256=b9TK0j_nBX71GePtrtWSUDGYtXJvQq9Eg5gAPEcHJTo,944 +ddtrace/profiling/event.py,sha256=sRnYKPC15VEDnjcmqFPumMuosVfO-deOv7z1NZ_0jas,2272 +ddtrace/profiling/exporter/__init__.py,sha256=eXAfV1Fa6YIpsJLI23wZP_3CQOlKsj-qFPK6jtZvSmI,950 +ddtrace/profiling/exporter/__pycache__/__init__.cpython-311.pyc,, +ddtrace/profiling/exporter/__pycache__/file.cpython-311.pyc,, +ddtrace/profiling/exporter/__pycache__/http.cpython-311.pyc,, +ddtrace/profiling/exporter/__pycache__/pprof_312_pb2.cpython-311.pyc,, +ddtrace/profiling/exporter/__pycache__/pprof_319_pb2.cpython-311.pyc,, +ddtrace/profiling/exporter/__pycache__/pprof_3_pb2.cpython-311.pyc,, +ddtrace/profiling/exporter/__pycache__/pprof_421_pb2.cpython-311.pyc,, +ddtrace/profiling/exporter/file.py,sha256=09gHkRknzieU6r4TXMD0eb2oJ93pl0W00cvK068bwvY,1292 +ddtrace/profiling/exporter/http.py,sha256=uUaOuXVJo5YGRRQxh3lrH_gAz3FdlFpbGRRVB-ss66A,9387 +ddtrace/profiling/exporter/pprof.c,sha256=ynWy8C3UZ9Fq0gWSpSSBzZ2sI_4a8wZRB3-aYEVtuGE,1467390 +ddtrace/profiling/exporter/pprof.cpython-311-x86_64-linux-gnu.so,sha256=PbajgOQxi__H25pvJmEKAWUJbQN1ZVLwILT_hCPtcbE,516968 +ddtrace/profiling/exporter/pprof.proto,sha256=iWP1lCz5SeZQVec025rSgjwR6pX8WReAtRv3Eho0lJ4,8841 +ddtrace/profiling/exporter/pprof.pyi,sha256=eEfLtOjXlwXwGOJ1nS75_C4TTFfSbinYL6r9D_iDgW8,3917 +ddtrace/profiling/exporter/pprof.pyx,sha256=tHuHWOdNTPZiluBHTYybuIwyfIGH_s_8TogpEN6C1Ow,28222 +ddtrace/profiling/exporter/pprof_312_pb2.py,sha256=HuWbIP1UWvcAU5EhOYmXt42aW-5UfoD0oyQ-CPongs4,28539 +ddtrace/profiling/exporter/pprof_319_pb2.py,sha256=NDbRjoKkKw25uVofgPBYEvhbdSGKLUA_7zEemvrZXfE,6062 +ddtrace/profiling/exporter/pprof_3_pb2.py,sha256=lj4Su40oadwotRGrDGW8lG774Vs7d-Pu7AsEt_hzSuI,24821 +ddtrace/profiling/exporter/pprof_421_pb2.py,sha256=jgpfHGCEvbfvXF3ChGF5Iw-S6XDQ3yu3_2FB7LVGQCI,3736 +ddtrace/profiling/profiler.py,sha256=KN7irjGLDWjSRfzPmMfK1F5mBilivG7wsSue74r2hF0,14943 +ddtrace/profiling/recorder.py,sha256=XTHoRz1y6hNa25jkY3QZ2dU3nQMQsEwbXJCqhYutUUo,3127 +ddtrace/profiling/scheduler.py,sha256=IcaSJ86hm45ItRJczmY3lBCsl7OsGtqd0m68Epm3ehM,4089 +ddtrace/propagation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/propagation/__pycache__/__init__.cpython-311.pyc,, +ddtrace/propagation/__pycache__/_database_monitoring.cpython-311.pyc,, +ddtrace/propagation/__pycache__/_utils.cpython-311.pyc,, +ddtrace/propagation/__pycache__/http.cpython-311.pyc,, +ddtrace/propagation/_database_monitoring.py,sha256=OlGXOdBCzyAcipWY3w0UpOsdR5R1FMqxYNNql6uiT4w,3768 +ddtrace/propagation/_utils.py,sha256=EdttZRhZ85wNtZJ5nOKz50JWTW9RNqbKck_rGF3cNts,976 +ddtrace/propagation/http.py,sha256=IYjOXcKdRkcsYc9Uijub9OkF_GVf_2wFRfnFIxYIfn4,40548 +ddtrace/provider.py,sha256=g-sJdm0Sa52I5cuiBZOemFNXxW816f6TscVZmYG0m7Q,5764 +ddtrace/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/runtime/__init__.py,sha256=Aif4-abbGD3jRhKav4seFbLglncJhqtwLMyCK7mWnSw,1795 +ddtrace/runtime/__pycache__/__init__.cpython-311.pyc,, +ddtrace/sampler.py,sha256=5aVQ4zc54HjOSc0BoHXyAL5kNx9MyM3AutzhqqEo-l4,12314 +ddtrace/sampling_rule.py,sha256=WXoE3fRi10-E1_IN63UxlNL1P_lpbvLFH2ljCQbtEp8,8074 +ddtrace/settings/__init__.py,sha256=K7w4Jo7H8WFNSUlCP_cTwhHHG4C-oO8LlC7yBpYb4x4,319 +ddtrace/settings/__pycache__/__init__.cpython-311.pyc,, +ddtrace/settings/__pycache__/_database_monitoring.cpython-311.pyc,, +ddtrace/settings/__pycache__/asm.cpython-311.pyc,, +ddtrace/settings/__pycache__/config.cpython-311.pyc,, +ddtrace/settings/__pycache__/dynamic_instrumentation.cpython-311.pyc,, +ddtrace/settings/__pycache__/exception_debugging.cpython-311.pyc,, +ddtrace/settings/__pycache__/exceptions.cpython-311.pyc,, +ddtrace/settings/__pycache__/http.cpython-311.pyc,, +ddtrace/settings/__pycache__/integration.cpython-311.pyc,, +ddtrace/settings/__pycache__/peer_service.cpython-311.pyc,, +ddtrace/settings/__pycache__/profiling.cpython-311.pyc,, +ddtrace/settings/_database_monitoring.py,sha256=PdUCzgpCEsOmJVv2hjLQ3_lePN_SuTu-Cxbdg-oHKFo,400 +ddtrace/settings/asm.py,sha256=fRgtjQ0PjrwlGdqeb1UVylQnuTyU4y08QWqCkUFala0,3913 +ddtrace/settings/config.py,sha256=2b2phe_FII2UTPFyTw1-3h9Ey4tadaVivN0YNjjdEWo,33175 +ddtrace/settings/dynamic_instrumentation.py,sha256=zQJhp0GRBjV18X2d7reZHV1xqkQobtqFxYx1P_XdwoM,3853 +ddtrace/settings/exception_debugging.py,sha256=pOSY-q8hsIiGwuSphZJOnUPayhQWu0acdRKGFVn0Dnw,330 +ddtrace/settings/exceptions.py,sha256=w0BejP6qaqnGpwCBsn8oJ65gEmTg1ESDmvDrKS_YRDo,163 +ddtrace/settings/http.py,sha256=5zdHBrGd21h048yGjXwzM-KcmBMV46u1YH7b7no-Q9c,2976 +ddtrace/settings/integration.py,sha256=S-OknNhNTnkwth-Vz3MR-p7nvINrBTg65yZKnDgY0iY,5956 +ddtrace/settings/peer_service.py,sha256=vIEu-APjj66OUfurtBIdyYabsHfY6qkgZjoOTOZA2fE,1587 +ddtrace/settings/profiling.py,sha256=2PlfUgC4qxeanCENiNSD7ouC0O8LhyQl4Z6lheKfNfU,6084 +ddtrace/sourcecode/__init__.py,sha256=u-FB5twfJUJiYiPbptsm-QkAmJfKXIqxb-6P20wzNOQ,1055 +ddtrace/sourcecode/__pycache__/__init__.cpython-311.pyc,, +ddtrace/sourcecode/__pycache__/_utils.cpython-311.pyc,, +ddtrace/sourcecode/__pycache__/setuptools_auto.cpython-311.pyc,, +ddtrace/sourcecode/_utils.py,sha256=W3czbBD5ZOm_pkaGJMzZJiVr2KlMqRswMrU50kB9Q-4,1777 +ddtrace/sourcecode/setuptools_auto.py,sha256=5Y-U4pYFylJ5OtKXUpH7wbPUxZsHITVqEamZnrc4c-I,722 +ddtrace/span.py,sha256=6Br4M9muWOHqYzxOGbiVbaYRskId6mbBwouNuGMz0HI,21471 +ddtrace/tracer.py,sha256=usToTIk7INNewSGttvrV2jYYJwcrfAvxapmwPodGz7w,43015 +ddtrace/tracing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/tracing/__pycache__/__init__.cpython-311.pyc,, +ddtrace/tracing/__pycache__/_span_link.cpython-311.pyc,, +ddtrace/tracing/__pycache__/trace_handlers.cpython-311.pyc,, +ddtrace/tracing/_span_link.py,sha256=2XIDqE0d5wW1CQBJTo8mMavb0kFfsAvl5SqiEcvSR_c,3411 +ddtrace/tracing/trace_handlers.py,sha256=ZyFyz98IR3pTQVas7Ah2NqlJ9arBHRYi5bxOK9OLD28,23324 +ddtrace/vendor/__init__.py,sha256=zqXumNo12FW-Pe66cv93BkDDTnSO3Se_ZOCgKguxIKE,2962 +ddtrace/vendor/__pycache__/__init__.cpython-311.pyc,, +ddtrace/vendor/contextvars/__init__.py,sha256=TRvgsEausfPDhHwEBJ-z2tLSMWRC6E_088v4ODkcnXg,3898 +ddtrace/vendor/contextvars/__pycache__/__init__.cpython-311.pyc,, +ddtrace/vendor/debtcollector/__init__.py,sha256=Q8OE09M7ZpXatj2Ft47FVL_ClA5KY5upavDWXg1DKtE,2175 +ddtrace/vendor/debtcollector/__pycache__/__init__.cpython-311.pyc,, +ddtrace/vendor/debtcollector/__pycache__/_utils.cpython-311.pyc,, +ddtrace/vendor/debtcollector/__pycache__/moves.cpython-311.pyc,, +ddtrace/vendor/debtcollector/__pycache__/removals.cpython-311.pyc,, +ddtrace/vendor/debtcollector/__pycache__/renames.cpython-311.pyc,, +ddtrace/vendor/debtcollector/__pycache__/updating.cpython-311.pyc,, +ddtrace/vendor/debtcollector/_utils.py,sha256=OXhJruEi9X2H0EaVIcNkByltmf2VKkczf2IAfOB71SE,6346 +ddtrace/vendor/debtcollector/moves.py,sha256=tapV2utvk2OtYcM_joe_l3Yg3XuTtEZkbeVT-tfKcCQ,8421 +ddtrace/vendor/debtcollector/removals.py,sha256=0q5MvpcXKP_W1Jx6Qp4URBwYqYc3q_rsJRgKT3VW-Kw,13890 +ddtrace/vendor/debtcollector/renames.py,sha256=Lok-0KYHlVCCho9vkFsFPFI6VtgUzk0z5NdUyEUH9wA,1715 +ddtrace/vendor/debtcollector/updating.py,sha256=2x54f0_UKhs_SsGHWzt0zpEFq6M0_RzmC7O42ZXaFLo,2426 +ddtrace/vendor/dogstatsd/__init__.py,sha256=IEA4PcepGTcsZQUetyoN10380qLjmtou7rX-CPURmW4,312 +ddtrace/vendor/dogstatsd/__pycache__/__init__.cpython-311.pyc,, +ddtrace/vendor/dogstatsd/__pycache__/base.cpython-311.pyc,, +ddtrace/vendor/dogstatsd/__pycache__/compat.cpython-311.pyc,, +ddtrace/vendor/dogstatsd/__pycache__/container.cpython-311.pyc,, +ddtrace/vendor/dogstatsd/__pycache__/context.cpython-311.pyc,, +ddtrace/vendor/dogstatsd/__pycache__/context_async.cpython-311.pyc,, +ddtrace/vendor/dogstatsd/__pycache__/format.cpython-311.pyc,, +ddtrace/vendor/dogstatsd/__pycache__/route.cpython-311.pyc,, +ddtrace/vendor/dogstatsd/base.py,sha256=AwYw0ZKoF-BzGj1LugWxvvUHgg-RK3zujKPyyJ_88FM,46735 +ddtrace/vendor/dogstatsd/compat.py,sha256=BVmqw2TtupHEis35I9Cl_FVcb2wd1mGEiEADVWA-eJk,996 +ddtrace/vendor/dogstatsd/container.py,sha256=0doQtobT4ID8GKDwa-jUjUFr_NTsf0jgc2joaUT0y7o,2052 +ddtrace/vendor/dogstatsd/context.py,sha256=FU8kb8meKfgzSB6igyEM6iByqq2u0Hpr98FBqbt8Wco,2844 +ddtrace/vendor/dogstatsd/context_async.py,sha256=wJgbf9n_pHaN95I0I1RoxycjoK18L0ZBGUVrzcVsW4M,1543 +ddtrace/vendor/dogstatsd/format.py,sha256=maACZlLz8DuSv1sNyhQQQtgswyLPiR98HiHU6cwhxRE,1025 +ddtrace/vendor/dogstatsd/route.py,sha256=VOoCuD5XD9PPtEydVjpbz_FldgGEOd8Yazpt2YoVD-U,1253 +ddtrace/vendor/monotonic/__init__.py,sha256=1wJOetpAPQUteaP7IxAelyChpkITsxZf-eV4V2bTHrA,7117 +ddtrace/vendor/monotonic/__pycache__/__init__.cpython-311.pyc,, +ddtrace/vendor/packaging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +ddtrace/vendor/packaging/__pycache__/__init__.cpython-311.pyc,, +ddtrace/vendor/packaging/__pycache__/_structures.cpython-311.pyc,, +ddtrace/vendor/packaging/__pycache__/version.cpython-311.pyc,, +ddtrace/vendor/packaging/_structures.py,sha256=DCpKtb7u94_oqgVsIJQTrTyZcb3Gz7sSGbk9vYDMME0,1418 +ddtrace/vendor/packaging/version.py,sha256=MKL8nbKLPLGPouIwFvwSVnYRzNpkMo5AIcsa6LGqDF8,12219 +ddtrace/vendor/psutil/__init__.py,sha256=zLnjfXMB3aOXBkgwVbiaTzFYKvDpY3bRBS12BryUMn8,90581 +ddtrace/vendor/psutil/__pycache__/__init__.cpython-311.pyc,, +ddtrace/vendor/psutil/__pycache__/_common.cpython-311.pyc,, +ddtrace/vendor/psutil/__pycache__/_compat.cpython-311.pyc,, +ddtrace/vendor/psutil/__pycache__/_psaix.cpython-311.pyc,, +ddtrace/vendor/psutil/__pycache__/_psbsd.cpython-311.pyc,, +ddtrace/vendor/psutil/__pycache__/_pslinux.cpython-311.pyc,, +ddtrace/vendor/psutil/__pycache__/_psosx.cpython-311.pyc,, +ddtrace/vendor/psutil/__pycache__/_psposix.cpython-311.pyc,, +ddtrace/vendor/psutil/__pycache__/_pssunos.cpython-311.pyc,, +ddtrace/vendor/psutil/__pycache__/_pswindows.cpython-311.pyc,, +ddtrace/vendor/psutil/__pycache__/setup.cpython-311.pyc,, +ddtrace/vendor/psutil/_common.py,sha256=eA2kZtQPvlmMpKw_Fz3lswXSg35tthoM6OAb2UhdNNQ,20224 +ddtrace/vendor/psutil/_compat.py,sha256=c9jBW_7ZcDyl562gIeIh53drZ-oUVLPobOnuAJRhY2w,11191 +ddtrace/vendor/psutil/_psaix.py,sha256=IDY57Ybv5k4eSKH50lD7EJ9slfRoPJ-SLKyniXQFvkw,18564 +ddtrace/vendor/psutil/_psbsd.py,sha256=kvDbgjD38KtIZDhsTNbmMowvjSmKs701D-aYr_5wPQE,30566 +ddtrace/vendor/psutil/_pslinux.py,sha256=N-p3vkd-QG9132CIihCIZ47mPa_vwBWkHZf6x3pI6Xg,79839 +ddtrace/vendor/psutil/_psosx.py,sha256=JbNktzY5i5xQJTWNWdWbFSRPBZneb4_34Pm6GKyDiZs,17196 +ddtrace/vendor/psutil/_psposix.py,sha256=sQajYsNSDFV0HqN3GFf7Rvh8vu9eQLbzMpD2eqgakVk,6159 +ddtrace/vendor/psutil/_pssunos.py,sha256=ZayYw299DPsmA8TzA7UpuFuigW49OC0KrdrU4A1hOlY,25109 +ddtrace/vendor/psutil/_psutil_aix.c,sha256=fEoiu23Up7TxlsiUfeSWSubKRAO8te5we63MBZ6vOWI,31034 +ddtrace/vendor/psutil/_psutil_bsd.c,sha256=neGG1AtSb2f2TOSQ8w99XDn51yvjWjOZENvs5HsDv40,34969 +ddtrace/vendor/psutil/_psutil_common.c,sha256=HecdlMru0pRZAhuORp9xDelN4RaVK4muio4qS0Hfx-M,3136 +ddtrace/vendor/psutil/_psutil_common.h,sha256=mJvKu0yDQYOTpzbfkXnLOk3mFPBxAF56qsyyck29e9o,870 +ddtrace/vendor/psutil/_psutil_linux.c,sha256=PvXPmGMPnxWkqtnHwkOAZhIdfkISpPyklpGQc0la-kI,18930 +ddtrace/vendor/psutil/_psutil_linux.cpython-311-x86_64-linux-gnu.so,sha256=IyZF2O26ibj5PviSho1j16ljMmCgLvMkWyNsJFPO0v8,29376 +ddtrace/vendor/psutil/_psutil_osx.c,sha256=2HZi7AKgRu7lDVu6WYiJZ0Yc0c5Z0zffOzsbeuw1z6o,59036 +ddtrace/vendor/psutil/_psutil_posix.c,sha256=sHiCGhhXyF7t7AYpHvjsDXjVvc3NY3poNZpvNBa3CDo,18620 +ddtrace/vendor/psutil/_psutil_posix.cpython-311-x86_64-linux-gnu.so,sha256=E5JUP3XQTr8MqqYT125bNBaD5KnOZrZycT_DDGNWxxE,22696 +ddtrace/vendor/psutil/_psutil_posix.h,sha256=uWkIEoeQUPIFOuiMzvMo5XzPYkM6JtXsFVPNzyntlu0,256 +ddtrace/vendor/psutil/_psutil_sunos.c,sha256=-LKjVI0TP_n676lCoA-Wrh3u0GvplkrH1Oqc49uuO84,51184 +ddtrace/vendor/psutil/_psutil_windows.c,sha256=ACXdZpmW6Z8onahjDuHRYaBwnX9f0ddgxTsGLP7RPms,114213 +ddtrace/vendor/psutil/_pswindows.py,sha256=A2mlRzUvPxPrj8LETN25MAn6fRcLp12L-RP7bwP2TKM,37400 +ddtrace/vendor/psutil/arch/aix/common.c,sha256=zwuWitDFSTJzYjZVWWqGhrpyhueVxK5Yf0YU1KtCRzc,2284 +ddtrace/vendor/psutil/arch/aix/common.h,sha256=eLKtDGdfcV1kC_h2Wl4dc0kg2wr-qKs5WKIAoKiUx_o,894 +ddtrace/vendor/psutil/arch/aix/ifaddrs.c,sha256=RLyTuG2zstr53zze2KaS2qy0cnvnZ7GaB8ZGI7QD7vA,3839 +ddtrace/vendor/psutil/arch/aix/ifaddrs.h,sha256=Bq9yYVe8ggnStKukX9g7yhSv9teB9aLf3Ms_YSzwNaA,767 +ddtrace/vendor/psutil/arch/aix/net_connections.c,sha256=dw2HRw8Z9XCNeiWmcXk1mj_AukSrG2P_7zGwqNtfj20,8134 +ddtrace/vendor/psutil/arch/aix/net_connections.h,sha256=FWI-ALW2lInyvex3FPEB4Gnb_veTCtaxZah0g0jwyh4,355 +ddtrace/vendor/psutil/arch/aix/net_kernel_structs.h,sha256=3qhQr5WOzoQNjfCTatC6f1m0uJvYQKhvpA3kbW_O5N4,4059 +ddtrace/vendor/psutil/arch/freebsd/proc_socks.c,sha256=UcKuLJ3V5crqeB6z7KfQYWiNBzFaIBODDESMZ0iZwrI,10925 +ddtrace/vendor/psutil/arch/freebsd/proc_socks.h,sha256=8zCu73GZa_RUeX7DZVy--EDPAE2yzvMmW0L7KqrM1fk,263 +ddtrace/vendor/psutil/arch/freebsd/specific.c,sha256=l_QUghmeq9EFP-iamfFF1dRbkf8F-JM3lNtyXgV4ZSk,31802 +ddtrace/vendor/psutil/arch/freebsd/specific.h,sha256=2w0cLpvd91ZhLaFoRF436ag19e7JMqEtrN3QK62jlbY,1563 +ddtrace/vendor/psutil/arch/freebsd/sys_socks.c,sha256=PGWKfERuBDcIX1xEFT6TMotaFfRENknsUjNnpO6zsi0,9986 +ddtrace/vendor/psutil/arch/freebsd/sys_socks.h,sha256=_VQx5Cch72yHa5a73s7-6XVbY15kO_DOZ6vJb6Wj7Yg,265 +ddtrace/vendor/psutil/arch/netbsd/socks.c,sha256=NI4eN62mpbqgbxm2uJnMdUrqSPf9cL8k53wGFxNvYzk,12227 +ddtrace/vendor/psutil/arch/netbsd/socks.h,sha256=N8aNAVjqpoywUwhlRSXxe0dxQMOaBL2JU-y7vQuOmNs,331 +ddtrace/vendor/psutil/arch/netbsd/specific.c,sha256=cwyxa9_JWAf5m3x3Wvl-JA63F8HbXeu8AEvIiLImkeI,17351 +ddtrace/vendor/psutil/arch/netbsd/specific.h,sha256=DiMug_IhzJMkSwJKRcLHWrJJlc3XhPLLFFvfdDyT_qI,1220 +ddtrace/vendor/psutil/arch/openbsd/specific.c,sha256=vPG1fdC8EePuBbg5BodoOB1CHUOKKlmJsYx1_QjNwJ0,22349 +ddtrace/vendor/psutil/arch/openbsd/specific.h,sha256=c0_IddDQN7zplGtruCD_4-oPfgjQZxS7S9o2KG6DG5k,1086 +ddtrace/vendor/psutil/arch/osx/process_info.c,sha256=sLR5Vb4TYvdcoER5RxtVlTns-9FuhnU2GPYAT_gl4_c,9875 +ddtrace/vendor/psutil/arch/osx/process_info.h,sha256=dPBFqGTCs34kI8Mw1w-Ot3QhS6PrnDVkGKAkRM5C-lg,602 +ddtrace/vendor/psutil/arch/solaris/environ.c,sha256=UDkpvJMkAjPPUAYhcg7OOmuW_hxbz7W2HOa-a9vbwOU,10188 +ddtrace/vendor/psutil/arch/solaris/environ.h,sha256=aQIDTp6uPYLPCyd7qPK1go_BQAthD7IyG2kH2zwjcZM,511 +ddtrace/vendor/psutil/arch/solaris/v10/ifaddrs.c,sha256=73ckKxa0IQjH38Wawr-G5_Tb922goQQZ4eXqDbQKsZg,3253 +ddtrace/vendor/psutil/arch/solaris/v10/ifaddrs.h,sha256=VpaMZuVkenSRVoCBbqaAaaZnC9S6JI-VFiShSv747Go,567 +ddtrace/vendor/psutil/arch/windows/global.c,sha256=FgYmyGBKIP0HrngAra05m_kkbWGiscb2FQ4PQ7wwTMY,6654 +ddtrace/vendor/psutil/arch/windows/global.h,sha256=ROqv8VNajiYBUJ2ltJrsX9Ic57590BB8hyDZqMVV4Hc,1841 +ddtrace/vendor/psutil/arch/windows/inet_ntop.c,sha256=0Iqg1WKrC5mZ84xBVuqeBKmFm4U2iZCBr3jalGNct5s,1407 +ddtrace/vendor/psutil/arch/windows/inet_ntop.h,sha256=REJAYcwsuTpXo5KDh7Fd37EiTM882S0xL9LPjsqC9v8,575 +ddtrace/vendor/psutil/arch/windows/ntextapi.h,sha256=SF141Jz80K5EWZgKilLMtt3UcsW7cet3P8xzoW5Sd1k,13760 +ddtrace/vendor/psutil/arch/windows/process_handles.c,sha256=OYyUdPT2Dh924Mj4MPWsDIg3hDz-Gp4NJv7gH8OWpcc,14681 +ddtrace/vendor/psutil/arch/windows/process_handles.h,sha256=3XlBjAsF8Fw77dnaBiMUR6rpkIhF30h0yb_-vogZ9Jc,293 +ddtrace/vendor/psutil/arch/windows/process_info.c,sha256=Aursf0zYxWuktaFmtUjOqrnxXgCxW-X13chE28bhA0Y,29326 +ddtrace/vendor/psutil/arch/windows/process_info.h,sha256=29okjO4nZXHhag1M8ZEy2tYUdSZNdV9RoVFNh4zmo_I,1000 +ddtrace/vendor/psutil/arch/windows/security.c,sha256=nwkIAw66q-D9TrMrrmmp6529w3PE5YeGZ2IgyzXKOoc,3672 +ddtrace/vendor/psutil/arch/windows/security.h,sha256=OEwyZUlqk6Ipsmdg6ho7E1XULgJlciOteIr4Kxf-9nc,365 +ddtrace/vendor/psutil/arch/windows/services.c,sha256=rW4LUZZiFfLdNI6I5lgEAobbJesEvrSJBrYeWEs41q8,13168 +ddtrace/vendor/psutil/arch/windows/services.h,sha256=4Wo7Tn_AgeXd6UQ68uGphuFxxxeXc0u734skNmzHG1s,730 +ddtrace/vendor/psutil/arch/windows/wmi.c,sha256=TWJJmrLfNLZ-8Lj0SBMPMe2J5gUNmF3TF-O3uNsFLEI,3408 +ddtrace/vendor/psutil/arch/windows/wmi.h,sha256=E-fBKzcyeLaj3e2b_26jxtchpm0X7YVvKkHp-ix3BPA,282 +ddtrace/vendor/psutil/setup.py,sha256=Ze5fZ_gj3Y6e4i3Wb446CgVdiICXpfLYkZVvlllusqo,7931 +ddtrace/vendor/sqlcommenter/__init__.py,sha256=w33CHxf0qVmKrWygWIRc_bw_vK6ukSXvEli5p22XQL0,1723 +ddtrace/vendor/sqlcommenter/__pycache__/__init__.cpython-311.pyc,, +ddtrace/vendor/wrapt/LICENSE,sha256=-ORXVVFm6-jW4-VBEREdObh8EdASF-dmJ7gpCSKYLUQ,1303 +ddtrace/vendor/wrapt/__init__.py,sha256=Bh0h33Iapc_qaoLWsWfaXK5xJz9KJExF7gQKIWYdSsg,1200 +ddtrace/vendor/wrapt/__pycache__/__init__.cpython-311.pyc,, +ddtrace/vendor/wrapt/__pycache__/arguments.cpython-311.pyc,, +ddtrace/vendor/wrapt/__pycache__/decorators.cpython-311.pyc,, +ddtrace/vendor/wrapt/__pycache__/importer.cpython-311.pyc,, +ddtrace/vendor/wrapt/__pycache__/setup.cpython-311.pyc,, +ddtrace/vendor/wrapt/__pycache__/wrappers.cpython-311.pyc,, +ddtrace/vendor/wrapt/_wrappers.c,sha256=X3MBBnD_-XcLbVoRW73-9vQ5Qbl5J5SCUH-unjzjBRk,95815 +ddtrace/vendor/wrapt/_wrappers.cpython-311-x86_64-linux-gnu.so,sha256=f9xbe8ViWi0KK0yz3fZjQOsS4tqXCZ1P8Jg6BxSa9mQ,65368 +ddtrace/vendor/wrapt/arguments.py,sha256=RF0nTEdPzPIewJ-jnSY42i4JSzK3ctjPABV1SJxLymg,1746 +ddtrace/vendor/wrapt/decorators.py,sha256=gNy1PVq9NNVDAB9tujaAVhb0xtVKSSzqT-hdGFeWM34,21332 +ddtrace/vendor/wrapt/importer.py,sha256=yxFgVg6-lRTbSVJ2oZbw1TPCtB98fIF4A_qi_Dh2JRc,9981 +ddtrace/vendor/wrapt/setup.py,sha256=CF2p_6VhgEGASbK2JH_MARGMt3GHe6uADKRqu573QY0,191 +ddtrace/vendor/wrapt/wrappers.py,sha256=ofd5HIVcZ8-YCcMH1SCeUcxsueYhRLcBDByrP_366yQ,35222 +ddtrace/version.py,sha256=uvdArJQL_C2QEfxXrcI1hs28bkcgLWfgwzpLUj50HI0,527 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/REQUESTED b/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/WHEEL b/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/WHEEL new file mode 100644 index 0000000..4497ba5 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.42.0) +Root-Is-Purelib: false +Tag: cp311-cp311-manylinux_2_17_x86_64 +Tag: cp311-cp311-manylinux2014_x86_64 + diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/entry_points.txt b/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/entry_points.txt new file mode 100644 index 0000000..a2ad0bb --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/entry_points.txt @@ -0,0 +1,10 @@ +[console_scripts] +ddtrace-run = ddtrace.commands.ddtrace_run:main + +[opentelemetry_context] +ddcontextvars_context = ddtrace.opentelemetry._context:DDRuntimeContext + +[pytest11] +ddtrace = ddtrace.contrib.pytest.plugin +ddtrace.pytest_bdd = ddtrace.contrib.pytest_bdd.plugin +ddtrace.pytest_benchmark = ddtrace.contrib.pytest_benchmark.plugin diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/top_level.txt b/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/top_level.txt new file mode 100644 index 0000000..749bf29 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace-2.6.5.dist-info/top_level.txt @@ -0,0 +1 @@ +ddtrace diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/.DS_Store b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..78912b33c642e5ebd5fb6abc3e9844fa46c76fc9 GIT binary patch literal 10244 zcmeI1F=!M)6o%g>A-T&LOrc=87NS_0M$l>vCnzWcQW{9^lJhQbce`9J!63pG)?#CC zW1mv2QdwGC3w9RvHvVsB68G)QJ4h#C_LzN_-T%#e_wU?HW{!xG^~1e2qLhdlC>&R> z;1oFcJ{Rwpb61`qCden+r4Gt~dbI7SWD4-O4cJOYXU*$Byt3zYjqXS9D9|8TQrjcyr*>el%s-EdO$z|GHGHs5H zXZzF@R9C+rK~qymrgIlORh39n37#A88K?U|#?%)~b1%MxRSH+2KJ__z zEepQ+4d$B%-7A_3Ci?vD-yMa+~=__FnDz- zG@edyJwIWt74+r;RNQlVG(;Jp@Hg*R5K`ycY_-BVqke^YMuy-AHjd>AzZ*qqBFW#} z(sT-Quug?GsFj?*nX?Jp30PmE9}ENEhik8IhE)n-y8`3CDUR?^6rxRSs26HWgD~D4P%nz8^@gAe2bZg~@rc?NWbt;TOt>C14 z%(-O79R9_{pDSpP`wE4vR*yneQ%RnJc^2sie(?;S*T$3PpHKDs3X7;iArTtE(b{2- zcti)H1JQx#z-$M2#>bKJ{{LhB_kSabr|3X*;Bq>UEVMV<>-cPLZ{7QpbGVYC?4a<$ zy3$6If)h*ly4jA$AG{rZ$6Yi3th)D^i=~Yw9eE}l(WE1CI@h=AV(Mb%VripIzxa;< a*h^6Uw~Xt5<7{02*LNPoI^z Optional[Callable[..., Any]] + """ + Function used to register a hook for the provided name. + + Example:: + + def on_request(span, request, response): + pass + + config.falcon.hooks.register('request', on_request) + + + If no function is provided then a decorator is returned:: + + @config.falcon.hooks.register('request') + def on_request(span, request, response): + pass + + :param hook: The name of the hook to register the function for + :type hook: object + :param func: The function to register, or ``None`` if a decorator should be returned + :type func: function, None + :returns: Either a function decorator if ``func is None``, otherwise ``None`` + :rtype: function, None + """ + # If they didn't provide a function, then return a decorator + if not func: + + def wrapper(func): + self.register(hook, func) + return func + + return wrapper + self._hooks[hook].add(func) + return None + + # Provide shorthand `on` method for `register` + # >>> @config.falcon.hooks.on('request') + # def on_request(span, request, response): + # pass + on = register + + def deregister( + self, + hook, # type: Any + func, # type: Callable + ): + # type: (...) -> None + """ + Function to deregister a function from a hook it was registered under + + Example:: + + @config.falcon.hooks.on('request') + def on_request(span, request, response): + pass + + config.falcon.hooks.deregister('request', on_request) + + :param hook: The name of the hook to register the function for + :type hook: object + :param func: Function hook to register + :type func: function + """ + if hook in self._hooks: + try: + self._hooks[hook].remove(func) + except KeyError: + pass + + def emit( + self, + hook, # type: Any + *args, # type: Any + **kwargs, # type: Any + ): + # type: (...) -> None + """ + Function used to call registered hook functions. + + :param hook: The hook to call functions for + :type hook: str + :param args: Positional arguments to pass to the hook functions + :type args: list + :param kwargs: Keyword arguments to pass to the hook functions + :type kwargs: dict + """ + # Call registered hooks + for func in self._hooks.get(hook, ()): + try: + func(*args, **kwargs) + except Exception: + log.error("Failed to run hook %s function %s", hook, func, exc_info=True) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/_logger.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/_logger.py new file mode 100644 index 0000000..f7989f8 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/_logger.py @@ -0,0 +1,88 @@ +import logging +from logging.handlers import RotatingFileHandler +import os + +from ddtrace.internal.utils.formats import asbool + + +DD_LOG_FORMAT = "%(asctime)s %(levelname)s [%(name)s] [%(filename)s:%(lineno)d] {}- %(message)s".format( + "[dd.service=%(dd.service)s dd.env=%(dd.env)s dd.version=%(dd.version)s" + " dd.trace_id=%(dd.trace_id)s dd.span_id=%(dd.span_id)s] " +) + +DEFAULT_FILE_SIZE_BYTES = 15 << 20 # 15 MB + + +def configure_ddtrace_logger(): + # type: () -> None + """Configures ddtrace log levels and file paths. + + Customization is possible with the environment variables: + ``DD_TRACE_DEBUG``, ``DD_TRACE_LOG_FILE_LEVEL``, and ``DD_TRACE_LOG_FILE`` + + By default, when none of the settings have been changed, ddtrace loggers + inherit from the root logger in the logging module and no logs are written to a file. + + When DD_TRACE_DEBUG has been enabled: + - Logs are propagated up so that they appear in the application logs if a file path wasn't provided + - Logs are routed to a file when DD_TRACE_LOG_FILE is specified, using the log level in DD_TRACE_LOG_FILE_LEVEL. + - Child loggers inherit from the parent ddtrace logger + + Note(s): + 1) The ddtrace-run logs under commands/ddtrace_run do not follow DD_TRACE_LOG_FILE if DD_TRACE_DEBUG is enabled. + This is because ddtrace-run calls ``logging.basicConfig()`` when DD_TRACE_DEBUG is enabled, so + this configuration is not applied. + 2) Python 2: If the application is using DD_TRACE_DEBUG=true, logging will need to be configured, + ie: ``logging.basicConfig()``. + + """ + ddtrace_logger = logging.getLogger("ddtrace") + if asbool(os.environ.get("DD_TRACE_LOG_STREAM_HANDLER", "true")): + ddtrace_logger.addHandler(logging.StreamHandler()) + + _configure_ddtrace_debug_logger(ddtrace_logger) + _configure_ddtrace_file_logger(ddtrace_logger) + + +def _configure_ddtrace_debug_logger(logger): + if asbool(os.environ.get("DD_TRACE_DEBUG", "false")): + logger.setLevel(logging.DEBUG) + logger.debug("debug mode has been enabled for the ddtrace logger") + + +def _configure_ddtrace_file_logger(logger): + log_file_level = os.environ.get("DD_TRACE_LOG_FILE_LEVEL", "DEBUG").upper() + try: + file_log_level_value = getattr(logging, log_file_level) + except AttributeError: + raise ValueError( + "DD_TRACE_LOG_FILE_LEVEL is invalid. Log level must be CRITICAL/ERROR/WARNING/INFO/DEBUG.", + log_file_level, + ) + + log_path = os.environ.get("DD_TRACE_LOG_FILE") + if log_path is not None: + log_path = os.path.abspath(log_path) + max_file_bytes = int(os.environ.get("DD_TRACE_LOG_FILE_SIZE_BYTES", DEFAULT_FILE_SIZE_BYTES)) + num_backup = 1 + ddtrace_file_handler = RotatingFileHandler( + filename=log_path, mode="a", maxBytes=max_file_bytes, backupCount=num_backup + ) + log_format = "%(asctime)s %(levelname)s [%(name)s] [%(filename)s:%(lineno)d] - %(message)s" + log_formatter = logging.Formatter(log_format) + ddtrace_file_handler.setLevel(file_log_level_value) + ddtrace_file_handler.setFormatter(log_formatter) + logger.addHandler(ddtrace_file_handler) + logger.debug("ddtrace logs will be routed to %s", log_path) + + +def _configure_log_injection(): + """ + Ensures that logging is patched before we inject trace information into logs. + """ + from ddtrace import patch + + patch(logging=True) + ddtrace_logger = logging.getLogger("ddtrace") + for handler in ddtrace_logger.handlers: + handler.setFormatter(logging.Formatter(DD_LOG_FORMAT)) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/_monkey.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/_monkey.py new file mode 100644 index 0000000..6090e9e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/_monkey.py @@ -0,0 +1,273 @@ +import importlib +import os +import threading +from typing import TYPE_CHECKING # noqa:F401 + +from ddtrace.vendor.wrapt.importer import when_imported + +from .internal.logger import get_logger +from .internal.utils import formats +from .settings import _config as config +from .settings.asm import config as asm_config + + +if TYPE_CHECKING: # pragma: no cover + from typing import Any # noqa:F401 + from typing import Callable # noqa:F401 + from typing import List # noqa:F401 + from typing import Union # noqa:F401 + + +log = get_logger(__name__) + +# Default set of modules to automatically patch or not +PATCH_MODULES = { + "aioredis": True, + "aiomysql": True, + "aredis": True, + "asyncio": True, + "boto": True, + "botocore": True, + "bottle": True, + "cassandra": True, + "celery": True, + "consul": True, + "django": True, + "elasticsearch": True, + "algoliasearch": True, + "futures": True, + "gevent": True, + "graphql": True, + "grpc": True, + "httpx": True, + "kafka": True, + "mongoengine": True, + "mysql": True, + "mysqldb": True, + "pymysql": True, + "mariadb": True, + "psycopg": True, + "pylibmc": True, + "pymemcache": True, + "pymongo": True, + "redis": True, + "rediscluster": True, + "requests": True, + "rq": True, + "sanic": True, + "snowflake": False, + "sqlalchemy": False, # Prefer DB client instrumentation + "sqlite3": True, + "aiohttp": True, # requires asyncio (Python 3.4+) + "aiohttp_jinja2": True, + "aiopg": True, + "aiobotocore": False, + "httplib": False, + "urllib3": False, + "vertica": True, + "molten": True, + "jinja2": True, + "mako": True, + "flask": True, + "flask_login": True, + "kombu": False, + "starlette": True, + # Ignore some web framework integrations that might be configured explicitly in code + "falcon": True, + "pyramid": True, + # Auto-enable logging if the environment variable DD_LOGS_INJECTION is true + "logbook": config.logs_injection, + "logging": config.logs_injection, + "loguru": config.logs_injection, + "structlog": config.logs_injection, + "pynamodb": True, + "pyodbc": True, + "fastapi": True, + "dogpile_cache": True, + "yaaredis": True, + "asyncpg": True, + "aws_lambda": True, # patch only in AWS Lambda environments + "tornado": False, + "openai": True, + "langchain": True, + "subprocess": True, + "unittest": True, + "coverage": False, +} + + +# this information would make sense to live in the contrib modules, +# but that would mean getting it would require importing those modules, +# which we need to avoid until as late as possible. +CONTRIB_DEPENDENCIES = { + "tornado": ("futures",), +} + + +_LOCK = threading.Lock() +_PATCHED_MODULES = set() + +# Module names that need to be patched for a given integration. If the module +# name coincides with the integration name, then there is no need to add an +# entry here. +_MODULES_FOR_CONTRIB = { + "elasticsearch": ( + "elasticsearch", + "elasticsearch1", + "elasticsearch2", + "elasticsearch5", + "elasticsearch6", + "elasticsearch7", + # Starting with version 8, the default transport which is what we + # actually patch is found in the separate elastic_transport package + "elastic_transport", + "opensearchpy", + ), + "psycopg": ( + "psycopg", + "psycopg2", + ), + "snowflake": ("snowflake.connector",), + "cassandra": ("cassandra.cluster",), + "dogpile_cache": ("dogpile.cache",), + "mysqldb": ("MySQLdb",), + "futures": ("concurrent.futures.thread",), + "vertica": ("vertica_python",), + "aws_lambda": ("datadog_lambda",), + "httplib": ("http.client",), + "kafka": ("confluent_kafka",), +} + + +DEFAULT_MODULES_PREFIX = "ddtrace.contrib" + + +class PatchException(Exception): + """Wraps regular `Exception` class when patching modules""" + + pass + + +class ModuleNotFoundException(PatchException): + pass + + +def _on_import_factory(module, prefix="ddtrace.contrib", raise_errors=True, patch_indicator=True): + # type: (str, str, bool, Union[bool, List[str]]) -> Callable[[Any], None] + """Factory to create an import hook for the provided module name""" + + def on_import(hook): + if config._telemetry_enabled: + from .internal import telemetry + # Import and patch module + path = "%s.%s" % (prefix, module) + try: + imported_module = importlib.import_module(path) + except Exception as e: + if raise_errors: + raise + error_msg = "failed to import ddtrace module %r when patching on import" % (path,) + log.error(error_msg, exc_info=True) + if config._telemetry_enabled: + telemetry.telemetry_writer.add_integration(module, False, PATCH_MODULES.get(module) is True, error_msg) + telemetry.telemetry_writer.add_count_metric( + "tracers", "integration_errors", 1, (("integration_name", module), ("error_type", type(e).__name__)) + ) + else: + imported_module.patch() + if config._telemetry_enabled: + if hasattr(imported_module, "get_versions"): + versions = imported_module.get_versions() + for name, v in versions.items(): + telemetry.telemetry_writer.add_integration( + name, True, PATCH_MODULES.get(module) is True, "", version=v + ) + else: + version = imported_module.get_version() + telemetry.telemetry_writer.add_integration( + module, True, PATCH_MODULES.get(module) is True, "", version=version + ) + + if hasattr(imported_module, "patch_submodules"): + imported_module.patch_submodules(patch_indicator) + + return on_import + + +def patch_all(**patch_modules): + # type: (bool) -> None + """Automatically patches all available modules. + + In addition to ``patch_modules``, an override can be specified via an + environment variable, ``DD_TRACE__ENABLED`` for each module. + + ``patch_modules`` have the highest precedence for overriding. + + :param dict patch_modules: Override whether particular modules are patched or not. + + >>> patch_all(redis=False, cassandra=False) + """ + modules = PATCH_MODULES.copy() + + # The enabled setting can be overridden by environment variables + for module, _enabled in modules.items(): + env_var = "DD_TRACE_%s_ENABLED" % module.upper() + if env_var in os.environ: + modules[module] = formats.asbool(os.environ[env_var]) + + # Enable all dependencies for the module + if modules[module]: + for dep in CONTRIB_DEPENDENCIES.get(module, ()): + modules[dep] = True + + # Arguments take precedence over the environment and the defaults. + modules.update(patch_modules) + + patch(raise_errors=False, **modules) + if asm_config._iast_enabled: + from ddtrace.appsec._iast._patch_modules import patch_iast + + patch_iast() + + +def patch(raise_errors=True, patch_modules_prefix=DEFAULT_MODULES_PREFIX, **patch_modules): + # type: (bool, str, Union[List[str], bool]) -> None + """Patch only a set of given modules. + + :param bool raise_errors: Raise error if one patch fail. + :param dict patch_modules: List of modules to patch. + + >>> patch(psycopg=True, elasticsearch=True) + """ + contribs = {c: patch_indicator for c, patch_indicator in patch_modules.items() if patch_indicator} + for contrib, patch_indicator in contribs.items(): + # Check if we have the requested contrib. + if not os.path.isfile(os.path.join(os.path.dirname(__file__), "contrib", contrib, "__init__.py")): + if raise_errors: + raise ModuleNotFoundException( + "integration module ddtrace.contrib.%s does not exist, " + "module will not have tracing available" % contrib + ) + modules_to_patch = _MODULES_FOR_CONTRIB.get(contrib, (contrib,)) + for module in modules_to_patch: + # Use factory to create handler to close over `module` and `raise_errors` values from this loop + when_imported(module)( + _on_import_factory(contrib, raise_errors=raise_errors, patch_indicator=patch_indicator) + ) + + # manually add module to patched modules + with _LOCK: + _PATCHED_MODULES.add(contrib) + + log.info( + "Configured ddtrace instrumentation for %s integration(s). The following modules have been patched: %s", + len(contribs), + ",".join(contribs), + ) + + +def _get_patched_modules(): + # type: () -> List[str] + """Get the list of patched modules""" + with _LOCK: + return sorted(_PATCHED_MODULES) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/_trace/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/_trace/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/_trace/_limits.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/_trace/_limits.py new file mode 100644 index 0000000..2d773d0 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/_trace/_limits.py @@ -0,0 +1,6 @@ +""" +Limits for trace data. +""" + +MAX_SPAN_META_KEY_LEN = 200 +MAX_SPAN_META_VALUE_LEN = 25000 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/_version.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/_version.py new file mode 100644 index 0000000..9c520e5 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/_version.py @@ -0,0 +1,16 @@ +# file generated by setuptools_scm +# don't change, don't track in version control +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import Tuple, Union + VERSION_TUPLE = Tuple[Union[int, str], ...] +else: + VERSION_TUPLE = object + +version: str +__version__: str +__version_tuple__: VERSION_TUPLE +version_tuple: VERSION_TUPLE + +__version__ = version = '2.6.5' +__version_tuple__ = version_tuple = (2, 6, 5) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_api_security/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_api_security/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_api_security/api_manager.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_api_security/api_manager.py new file mode 100644 index 0000000..60e3b14 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_api_security/api_manager.py @@ -0,0 +1,163 @@ +import base64 +import gzip +import json +import sys +from typing import TYPE_CHECKING # noqa:F401 + +from ddtrace._trace._limits import MAX_SPAN_META_VALUE_LEN +from ddtrace.appsec import _processor as appsec_processor +from ddtrace.appsec._asm_request_context import add_context_callback +from ddtrace.appsec._asm_request_context import call_waf_callback +from ddtrace.appsec._asm_request_context import remove_context_callback +from ddtrace.appsec._constants import API_SECURITY +from ddtrace.appsec._constants import SPAN_DATA_NAMES +from ddtrace.internal.logger import get_logger +from ddtrace.internal.metrics import Metrics +from ddtrace.internal.service import Service +from ddtrace.settings.asm import config as asm_config + + +if TYPE_CHECKING: + from typing import Optional # noqa:F401 + + +log = get_logger(__name__) +metrics = Metrics(namespace="datadog.api_security") +_sentinel = object() + + +class TooLargeSchemaException(Exception): + pass + + +class APIManager(Service): + COLLECTED = [ + ("REQUEST_HEADERS_NO_COOKIES", API_SECURITY.REQUEST_HEADERS_NO_COOKIES, dict), + ("REQUEST_COOKIES", API_SECURITY.REQUEST_COOKIES, dict), + ("REQUEST_QUERY", API_SECURITY.REQUEST_QUERY, dict), + ("REQUEST_PATH_PARAMS", API_SECURITY.REQUEST_PATH_PARAMS, dict), + ("REQUEST_BODY", API_SECURITY.REQUEST_BODY, None), + ("RESPONSE_HEADERS_NO_COOKIES", API_SECURITY.RESPONSE_HEADERS_NO_COOKIES, dict), + ("RESPONSE_BODY", API_SECURITY.RESPONSE_BODY, None), + ] + + _instance = None # type: Optional[APIManager] + + SAMPLE_START_VALUE = 1.0 - sys.float_info.epsilon + + @classmethod + def enable(cls): + # type: () -> None + if cls._instance is not None: + log.debug("%s already enabled", cls.__name__) + return + + log.debug("Enabling %s", cls.__name__) + metrics.enable() + cls._instance = cls() + cls._instance.start() + log.debug("%s enabled", cls.__name__) + + @classmethod + def disable(cls): + # type: () -> None + if cls._instance is None: + log.debug("%s not enabled", cls.__name__) + return + + log.debug("Disabling %s", cls.__name__) + cls._instance.stop() + cls._instance = None + metrics.disable() + log.debug("%s disabled", cls.__name__) + + def __init__(self): + # type: () -> None + super(APIManager, self).__init__() + + self.current_sampling_value = self.SAMPLE_START_VALUE + self._schema_meter = metrics.get_meter("schema") + log.debug("%s initialized", self.__class__.__name__) + + def _stop_service(self): + # type: () -> None + remove_context_callback(self._schema_callback, global_callback=True) + + def _start_service(self): + # type: () -> None + add_context_callback(self._schema_callback, global_callback=True) + + def _should_collect_schema(self, env): + method = env.waf_addresses.get(SPAN_DATA_NAMES.REQUEST_METHOD) + route = env.waf_addresses.get(SPAN_DATA_NAMES.REQUEST_ROUTE) + sample_rate = asm_config._api_security_sample_rate + # Framework is not fully supported + if not method or not route: + log.debug("unsupported groupkey for api security [method %s] [route %s]", bool(method), bool(route)) + return False + # Rate limit per route + self.current_sampling_value += sample_rate + if self.current_sampling_value >= 1.0: + self.current_sampling_value -= 1.0 + return True + return False + + def _schema_callback(self, env): + from ddtrace.appsec._utils import _appsec_apisec_features_is_active + + if env.span is None or not _appsec_apisec_features_is_active(): + return + root = env.span._local_root or env.span + if not root or any(meta_name in root._meta for _, meta_name, _ in self.COLLECTED): + return + + try: + if not self._should_collect_schema(env): + return + except Exception: + log.warning("Failed to sample request for schema generation", exc_info=True) + + # we need the request content type on the span + try: + headers = env.waf_addresses.get(SPAN_DATA_NAMES.REQUEST_HEADERS_NO_COOKIES, _sentinel) + if headers is not _sentinel: + appsec_processor._set_headers(root, headers, kind="request") + except Exception: + log.debug("Failed to enrich request span with headers", exc_info=True) + + waf_payload = {} + for address, _, transform in self.COLLECTED: + if not asm_config._api_security_parse_response_body and address == "RESPONSE_BODY": + continue + value = env.waf_addresses.get(SPAN_DATA_NAMES[address], _sentinel) + if value is _sentinel: + log.debug("no value for %s", address) + continue + if transform is not None: + value = transform(value) + waf_payload[address] = value + if waf_payload: + waf_payload["PROCESSOR_SETTINGS"] = {"extract-schema": True} + result = call_waf_callback(waf_payload) + if result is None: + return + for meta, schema in result.items(): + b64_gzip_content = b"" + try: + b64_gzip_content = base64.b64encode( + gzip.compress(json.dumps(schema, separators=",:").encode()) + ).decode() + if len(b64_gzip_content) >= MAX_SPAN_META_VALUE_LEN: + raise TooLargeSchemaException + root._meta[meta] = b64_gzip_content + except Exception as e: + self._schema_meter.increment("errors", tags={"exc": e.__class__.__name__, "address": address}) + self._log_limiter.limit( + log.warning, + "Failed to get schema from %r [schema length=%d]:\n%s", + address, + len(b64_gzip_content), + repr(value)[:256], + exc_info=True, + ) + self._schema_meter.increment("spans") diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_asm_request_context.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_asm_request_context.py new file mode 100644 index 0000000..1e1f28e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_asm_request_context.py @@ -0,0 +1,549 @@ +import contextlib +import functools +import json +from typing import Any +from typing import Callable +from typing import Dict +from typing import Generator +from typing import List +from typing import Optional +from typing import Set +from typing import Tuple +from urllib import parse + +from ddtrace.appsec import _handlers +from ddtrace.appsec._constants import APPSEC +from ddtrace.appsec._constants import SPAN_DATA_NAMES +from ddtrace.appsec._constants import WAF_CONTEXT_NAMES +from ddtrace.appsec._iast._utils import _is_iast_enabled +from ddtrace.internal import core +from ddtrace.internal.constants import REQUEST_PATH_PARAMS +from ddtrace.internal.logger import get_logger +from ddtrace.settings.asm import config as asm_config +from ddtrace.span import Span + + +log = get_logger(__name__) + +# Stopgap module for providing ASM context for the blocking features wrapping some contextvars. + +_WAF_ADDRESSES = "waf_addresses" +_CALLBACKS = "callbacks" +_TELEMETRY = "telemetry" +_CONTEXT_CALL = "context" +_WAF_CALL = "waf_run" +_BLOCK_CALL = "block" +_WAF_RESULTS = "waf_results" + + +GLOBAL_CALLBACKS: Dict[str, List[Callable]] = {} + + +class ASM_Environment: + """ + an object of this class contains all asm data (waf and telemetry) + for a single request. It is bound to a single asm request context. + It is contained into a ContextVar. + """ + + def __init__(self, active: bool = False): + self.active: bool = active + self.span: Optional[Span] = None + self.span_asm_context: Optional[contextlib.AbstractContextManager] = None + self.waf_addresses: Dict[str, Any] = {} + self.callbacks: Dict[str, Any] = {} + self.telemetry: Dict[str, Any] = {} + self.addresses_sent: Set[str] = set() + self.must_call_globals: bool = True + self.waf_triggers: List[Dict[str, Any]] = [] + + +def _get_asm_context() -> ASM_Environment: + env = core.get_item("asm_env") + if env is None: + env = ASM_Environment() + core.set_item("asm_env", env) + return env + + +def free_context_available() -> bool: + env = _get_asm_context() + return env.active and env.span is None + + +def in_context() -> bool: + env = _get_asm_context() + return env.active + + +def is_blocked() -> bool: + try: + env = _get_asm_context() + if not env.active or env.span is None: + return False + return bool(core.get_item(WAF_CONTEXT_NAMES.BLOCKED, span=env.span)) + except BaseException: + return False + + +def register(span: Span, span_asm_context=None) -> None: + env = _get_asm_context() + if not env.active: + log.debug("registering a span with no active asm context") + return + env.span = span + env.span_asm_context = span_asm_context + + +def unregister(span: Span) -> None: + env = _get_asm_context() + if env.span_asm_context is not None and env.span is span: + env.span_asm_context.__exit__(None, None, None) + elif env.span is span and env.must_call_globals: + # needed for api security flushing information before end of the span + for function in GLOBAL_CALLBACKS.get(_CONTEXT_CALL, []): + function(env) + env.must_call_globals = False + + +def flush_waf_triggers(env: ASM_Environment) -> None: + if env.waf_triggers and env.span: + root_span = env.span._local_root or env.span + old_tags = root_span.get_tag(APPSEC.JSON) + if old_tags is not None: + try: + new_json = json.loads(old_tags) + if "triggers" not in new_json: + new_json["triggers"] = [] + new_json["triggers"].extend(env.waf_triggers) + except BaseException: + new_json = {"triggers": env.waf_triggers} + else: + new_json = {"triggers": env.waf_triggers} + root_span.set_tag_str(APPSEC.JSON, json.dumps(new_json, separators=(",", ":"))) + + env.waf_triggers = [] + + +GLOBAL_CALLBACKS[_CONTEXT_CALL] = [flush_waf_triggers] + + +class _DataHandler: + """ + An object of this class is created by each asm request context. + It handles the creation and destruction of ASM_Environment object. + It allows the ASM context to be reentrant. + """ + + main_id = 0 + + def __init__(self): + _DataHandler.main_id += 1 + env = ASM_Environment(True) + + self._id = _DataHandler.main_id + self.active = True + self.execution_context = core.ExecutionContext(__name__, **{"asm_env": env}) + + env.telemetry[_WAF_RESULTS] = [], [], [] + env.callbacks[_CONTEXT_CALL] = [] + + def finalise(self): + if self.active: + env = self.execution_context.get_item("asm_env") + callbacks = GLOBAL_CALLBACKS.get(_CONTEXT_CALL, []) if env.must_call_globals else [] + env.must_call_globals = False + if env is not None and env.callbacks is not None and env.callbacks.get(_CONTEXT_CALL): + callbacks += env.callbacks.get(_CONTEXT_CALL) + if callbacks: + if env is not None: + for function in callbacks: + function(env) + self.execution_context.end() + self.active = False + + +def set_value(category: str, address: str, value: Any) -> None: + env = _get_asm_context() + if not env.active: + log.debug("setting %s address %s with no active asm context", category, address) + return + asm_context_attr = getattr(env, category, None) + if asm_context_attr is not None: + asm_context_attr[address] = value + + +def set_headers_response(headers: Any) -> None: + if headers is not None: + set_waf_address(SPAN_DATA_NAMES.RESPONSE_HEADERS_NO_COOKIES, headers, _get_asm_context().span) + + +def set_body_response(body_response): + # local import to avoid circular import + from ddtrace.appsec._utils import parse_response_body + + parsed_body = parse_response_body(body_response) + + if parse_response_body is not None: + set_waf_address(SPAN_DATA_NAMES.RESPONSE_BODY, parsed_body) + + +def set_waf_address(address: str, value: Any, span: Optional[Span] = None) -> None: + if address == SPAN_DATA_NAMES.REQUEST_URI_RAW: + parse_address = parse.urlparse(value) + no_scheme = parse.ParseResult("", "", *parse_address[2:]) + waf_value = parse.urlunparse(no_scheme) + set_value(_WAF_ADDRESSES, address, waf_value) + else: + set_value(_WAF_ADDRESSES, address, value) + if span is None: + span = _get_asm_context().span + if span: + core.set_item(address, value, span=span) + + +def get_value(category: str, address: str, default: Any = None) -> Any: + env = _get_asm_context() + if not env.active: + log.debug("getting %s address %s with no active asm context", category, address) + return default + asm_context_attr = getattr(env, category, None) + if asm_context_attr is not None: + return asm_context_attr.get(address, default) + return default + + +def get_waf_address(address: str, default: Any = None) -> Any: + return get_value(_WAF_ADDRESSES, address, default=default) + + +def get_waf_addresses(default: Any = None) -> Any: + env = _get_asm_context() + if not env.active: + log.debug("getting WAF addresses with no active asm context") + return default + return env.waf_addresses + + +def add_context_callback(function, global_callback: bool = False) -> None: + if global_callback: + callbacks = GLOBAL_CALLBACKS.setdefault(_CONTEXT_CALL, []) + else: + callbacks = get_value(_CALLBACKS, _CONTEXT_CALL) + if callbacks is not None: + callbacks.append(function) + + +def remove_context_callback(function, global_callback: bool = False) -> None: + if global_callback: + callbacks = GLOBAL_CALLBACKS.get(_CONTEXT_CALL) + else: + callbacks = get_value(_CALLBACKS, _CONTEXT_CALL) + if callbacks: + callbacks[:] = list([cb for cb in callbacks if cb != function]) + + +def set_waf_callback(value) -> None: + set_value(_CALLBACKS, _WAF_CALL, value) + + +def call_waf_callback(custom_data: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, str]]: + if not asm_config._asm_enabled: + return None + callback = get_value(_CALLBACKS, _WAF_CALL) + if callback: + return callback(custom_data) + else: + log.warning("WAF callback called but not set") + return None + + +def set_ip(ip: Optional[str]) -> None: + if ip is not None: + set_waf_address(SPAN_DATA_NAMES.REQUEST_HTTP_IP, ip, _get_asm_context().span) + + +def get_ip() -> Optional[str]: + return get_value(_WAF_ADDRESSES, SPAN_DATA_NAMES.REQUEST_HTTP_IP) + + +# Note: get/set headers use Any since we just carry the headers here without changing or using them +# and different frameworks use different types that we don't want to force it into a Mapping at the +# early point set_headers is usually called + + +def set_headers(headers: Any) -> None: + if headers is not None: + set_waf_address(SPAN_DATA_NAMES.REQUEST_HEADERS_NO_COOKIES, headers, _get_asm_context().span) + + +def get_headers() -> Optional[Any]: + return get_value(_WAF_ADDRESSES, SPAN_DATA_NAMES.REQUEST_HEADERS_NO_COOKIES, {}) + + +def set_headers_case_sensitive(case_sensitive: bool) -> None: + set_waf_address(SPAN_DATA_NAMES.REQUEST_HEADERS_NO_COOKIES_CASE, case_sensitive, _get_asm_context().span) + + +def get_headers_case_sensitive() -> bool: + return get_value(_WAF_ADDRESSES, SPAN_DATA_NAMES.REQUEST_HEADERS_NO_COOKIES_CASE, False) # type : ignore + + +def set_block_request_callable(_callable: Optional[Callable], *_) -> None: + """ + Sets a callable that could be use to do a best-effort to block the request. If + the callable need any params, like headers, they should be curried with + functools.partial. + """ + if _callable: + set_value(_CALLBACKS, _BLOCK_CALL, _callable) + + +def block_request() -> None: + """ + Calls or returns the stored block request callable, if set. + """ + _callable = get_value(_CALLBACKS, _BLOCK_CALL) + if _callable: + _callable() + else: + log.debug("Block request called but block callable not set by framework") + + +def get_data_sent() -> Set[str]: + env = _get_asm_context() + if not env.active: + log.debug("getting addresses sent with no active asm context") + return set() + return env.addresses_sent + + +def asm_request_context_set( + remote_ip: Optional[str] = None, + headers: Any = None, + headers_case_sensitive: bool = False, + block_request_callable: Optional[Callable] = None, +) -> None: + set_ip(remote_ip) + set_headers(headers) + set_headers_case_sensitive(headers_case_sensitive) + set_block_request_callable(block_request_callable) + + +def set_waf_results(result_data, result_info, is_blocked) -> None: + three_lists = get_waf_results() + if three_lists is not None: + list_results_data, list_result_info, list_is_blocked = three_lists + list_results_data.append(result_data) + list_result_info.append(result_info) + list_is_blocked.append(is_blocked) + + +def get_waf_results() -> Optional[Tuple[List[Any], List[Any], List[bool]]]: + return get_value(_TELEMETRY, _WAF_RESULTS) + + +def reset_waf_results() -> None: + set_value(_TELEMETRY, _WAF_RESULTS, ([], [], [])) + + +def store_waf_results_data(data) -> None: + if not data: + return + env = _get_asm_context() + if not env.active: + log.debug("storing waf results data with no active asm context") + return + if not env.span: + log.debug("storing waf results data with no active span") + return + for d in data: + d["span_id"] = env.span.span_id + env.waf_triggers.extend(data) + + +@contextlib.contextmanager +def asm_request_context_manager( + remote_ip: Optional[str] = None, + headers: Any = None, + headers_case_sensitive: bool = False, + block_request_callable: Optional[Callable] = None, +) -> Generator[Optional[_DataHandler], None, None]: + """ + The ASM context manager + """ + resources = _start_context(remote_ip, headers, headers_case_sensitive, block_request_callable) + if resources is not None: + try: + yield resources + finally: + _end_context(resources) + else: + yield None + + +def _start_context( + remote_ip: Optional[str], headers: Any, headers_case_sensitive: bool, block_request_callable: Optional[Callable] +) -> Optional[_DataHandler]: + if asm_config._asm_enabled or asm_config._iast_enabled: + resources = _DataHandler() + if asm_config._asm_enabled: + asm_request_context_set(remote_ip, headers, headers_case_sensitive, block_request_callable) + _handlers.listen() + listen_context_handlers() + return resources + return None + + +def _on_context_started(ctx): + resources = _start_context( + ctx.get_item("remote_addr"), + ctx.get_item("headers"), + ctx.get_item("headers_case_sensitive"), + ctx.get_item("block_request_callable"), + ) + ctx.set_item("resources", resources) + + +def _end_context(resources): + resources.finalise() + core.set_item("asm_env", None) + + +def _on_context_ended(ctx): + resources = ctx.get_item("resources") + if resources is not None: + _end_context(resources) + + +core.on("context.started.wsgi.__call__", _on_context_started) +core.on("context.ended.wsgi.__call__", _on_context_ended) +core.on("context.started.django.traced_get_response", _on_context_started) +core.on("context.ended.django.traced_get_response", _on_context_ended) +core.on("django.traced_get_response.pre", set_block_request_callable) + + +def _on_wrapped_view(kwargs): + return_value = [None, None] + # if Appsec is enabled, we can try to block as we have the path parameters at that point + if asm_config._asm_enabled and in_context(): + log.debug("Flask WAF call for Suspicious Request Blocking on request") + if kwargs: + set_waf_address(REQUEST_PATH_PARAMS, kwargs) + call_waf_callback() + if is_blocked(): + callback_block = get_value(_CALLBACKS, "flask_block") + return_value[0] = callback_block + + # If IAST is enabled, taint the Flask function kwargs (path parameters) + if _is_iast_enabled() and kwargs: + from ddtrace.appsec._iast._taint_tracking import OriginType + from ddtrace.appsec._iast._taint_tracking import taint_pyobject + from ddtrace.appsec._iast.processor import AppSecIastSpanProcessor + + if not AppSecIastSpanProcessor.is_span_analyzed(): + return return_value + + _kwargs = {} + for k, v in kwargs.items(): + _kwargs[k] = taint_pyobject( + pyobject=v, source_name=k, source_value=v, source_origin=OriginType.PATH_PARAMETER + ) + return_value[1] = _kwargs + return return_value + + +def _on_set_request_tags(request, span, flask_config): + if _is_iast_enabled(): + from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_source + from ddtrace.appsec._iast._taint_tracking import OriginType + from ddtrace.appsec._iast._taint_utils import taint_structure + from ddtrace.appsec._iast.processor import AppSecIastSpanProcessor + + _set_metric_iast_instrumented_source(OriginType.COOKIE_NAME) + _set_metric_iast_instrumented_source(OriginType.COOKIE) + + if not AppSecIastSpanProcessor.is_span_analyzed(span._local_root or span): + return + + request.cookies = taint_structure( + request.cookies, + OriginType.COOKIE_NAME, + OriginType.COOKIE, + override_pyobject_tainted=True, + ) + + +def _on_pre_tracedrequest(ctx): + _on_set_request_tags(ctx.get_item("flask_request"), ctx["current_span"], ctx.get_item("flask_config")) + block_request_callable = ctx.get_item("block_request_callable") + current_span = ctx["current_span"] + if asm_config._asm_enabled: + set_block_request_callable(functools.partial(block_request_callable, current_span)) + if core.get_item(WAF_CONTEXT_NAMES.BLOCKED): + block_request() + + +def _set_headers_and_response(response, headers, *_): + if not asm_config._asm_enabled: + return + + from ddtrace.appsec._utils import _appsec_apisec_features_is_active + + if _appsec_apisec_features_is_active(): + if headers: + # start_response was not called yet, set the HTTP response headers earlier + if isinstance(headers, dict): + list_headers = list(headers.items()) + else: + list_headers = list(headers) + set_headers_response(list_headers) + if response and asm_config._api_security_parse_response_body: + set_body_response(response) + + +def _call_waf_first(integration, *_): + if not asm_config._asm_enabled: + return + + log.debug("%s WAF call for Suspicious Request Blocking on request", integration) + return call_waf_callback() + + +def _call_waf(integration, *_): + if not asm_config._asm_enabled: + return + + log.debug("%s WAF call for Suspicious Request Blocking on response", integration) + return call_waf_callback() + + +def _on_block_decided(callback): + if not asm_config._asm_enabled: + return + + set_value(_CALLBACKS, "flask_block", callback) + + +def _get_headers_if_appsec(): + if asm_config._asm_enabled: + return get_headers() + + +def listen_context_handlers(): + core.on("flask.finalize_request.post", _set_headers_and_response) + core.on("flask.wrapped_view", _on_wrapped_view, "callback_and_args") + core.on("flask._patched_request", _on_pre_tracedrequest) + core.on("wsgi.block_decided", _on_block_decided) + core.on("flask.start_response", _call_waf_first, "waf") + + core.on("django.start_response.post", _call_waf_first) + core.on("django.finalize_response", _call_waf) + core.on("django.after_request_headers", _get_headers_if_appsec, "headers") + core.on("django.extract_body", _get_headers_if_appsec, "headers") + core.on("django.after_request_headers.finalize", _set_headers_and_response) + core.on("flask.set_request_tags", _on_set_request_tags) + + core.on("asgi.start_request", _call_waf_first) + core.on("asgi.start_response", _call_waf) + core.on("asgi.finalize_response", _set_headers_and_response) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_capabilities.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_capabilities.py new file mode 100644 index 0000000..b5ac759 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_capabilities.py @@ -0,0 +1,77 @@ +import base64 +import enum +import os +from typing import Optional + +import ddtrace +from ddtrace.appsec._utils import _appsec_rc_features_is_enabled +from ddtrace.settings.asm import config as asm_config + + +def _appsec_rc_file_is_not_static(): + return "DD_APPSEC_RULES" not in os.environ + + +def _asm_feature_is_required(): + flags = _rc_capabilities() + return Flags.ASM_ACTIVATION in flags or Flags.ASM_API_SECURITY_SAMPLE_RATE in flags + + +class Flags(enum.IntFlag): + ASM_ACTIVATION = 1 << 1 + ASM_IP_BLOCKING = 1 << 2 + ASM_DD_RULES = 1 << 3 + ASM_EXCLUSIONS = 1 << 4 + ASM_REQUEST_BLOCKING = 1 << 5 + ASM_ASM_RESPONSE_BLOCKING = 1 << 6 + ASM_USER_BLOCKING = 1 << 7 + ASM_CUSTOM_RULES = 1 << 8 + ASM_CUSTOM_BLOCKING_RESPONSE = 1 << 9 + ASM_TRUSTED_IPS = 1 << 10 + ASM_API_SECURITY_SAMPLE_RATE = 1 << 11 + + +_ALL_ASM_BLOCKING = ( + Flags.ASM_IP_BLOCKING + | Flags.ASM_DD_RULES + | Flags.ASM_EXCLUSIONS + | Flags.ASM_REQUEST_BLOCKING + | Flags.ASM_ASM_RESPONSE_BLOCKING + | Flags.ASM_USER_BLOCKING + | Flags.ASM_CUSTOM_RULES + | Flags.ASM_CUSTOM_RULES + | Flags.ASM_CUSTOM_BLOCKING_RESPONSE +) + + +def _rc_capabilities(test_tracer: Optional[ddtrace.Tracer] = None) -> Flags: + tracer = ddtrace.tracer if test_tracer is None else test_tracer + value = Flags(0) + if ddtrace.config._remote_config_enabled: + if _appsec_rc_features_is_enabled(): + value |= Flags.ASM_ACTIVATION + if tracer._appsec_processor and _appsec_rc_file_is_not_static(): + value |= _ALL_ASM_BLOCKING + if asm_config._api_security_enabled: + value |= Flags.ASM_API_SECURITY_SAMPLE_RATE + return value + + +def _appsec_rc_capabilities(test_tracer: Optional[ddtrace.Tracer] = None) -> str: + r"""return the bit representation of the composed capabilities in base64 + bit 0: Reserved + bit 1: ASM 1-click Activation + bit 2: ASM Ip blocking + + Int Number -> binary number -> bytes representation -> base64 representation + ASM Activation: + 2 -> 10 -> b'\x02' -> "Ag==" + ASM Ip blocking: + 4 -> 100 -> b'\x04' -> "BA==" + ASM Activation and ASM Ip blocking: + 6 -> 110 -> b'\x06' -> "Bg==" + ... + 256 -> 100000000 -> b'\x01\x00' -> b'AQA=' + """ + value = _rc_capabilities(test_tracer=test_tracer) + return base64.b64encode(value.to_bytes((value.bit_length() + 7) // 8, "big")).decode() diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_constants.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_constants.py new file mode 100644 index 0000000..fc0f4a4 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_constants.py @@ -0,0 +1,216 @@ +import os +from typing import Any +from typing import Iterator + +from ddtrace.internal.constants import HTTP_REQUEST_BLOCKED +from ddtrace.internal.constants import REQUEST_PATH_PARAMS +from ddtrace.internal.constants import RESPONSE_HEADERS +from ddtrace.internal.constants import STATUS_403_TYPE_AUTO + + +class Constant_Class(type): + """ + metaclass for Constant Classes + - You can access constants with APPSEC.ENV or APPSEC["ENV"] + - Direct assignment will fail: APPSEC.ENV = "something" raise TypeError, like other immutable types + - Constant Classes can be iterated: + for constant_name, constant_value in APPSEC: ... + """ + + def __setattr__(self, __name: str, __value: Any) -> None: + raise TypeError("Constant class does not support item assignment: %s.%s" % (self.__name__, __name)) + + def __iter__(self) -> Iterator[str]: + def aux(): + for t in self.__dict__.items(): + if not t[0].startswith("_"): + yield t + + return aux() + + def get(self, k: str, default: Any = None) -> Any: + return self.__dict__.get(k, default) + + def __contains__(self, k: str) -> bool: + return k in self.__dict__ + + def __getitem__(self, k: str) -> Any: + return self.__dict__[k] + + +class APPSEC(metaclass=Constant_Class): + """Specific constants for AppSec""" + + ENV = "DD_APPSEC_ENABLED" + ENABLED = "_dd.appsec.enabled" + JSON = "_dd.appsec.json" + EVENT_RULE_VERSION = "_dd.appsec.event_rules.version" + EVENT_RULE_ERRORS = "_dd.appsec.event_rules.errors" + EVENT_RULE_LOADED = "_dd.appsec.event_rules.loaded" + EVENT_RULE_ERROR_COUNT = "_dd.appsec.event_rules.error_count" + WAF_DURATION = "_dd.appsec.waf.duration" + WAF_DURATION_EXT = "_dd.appsec.waf.duration_ext" + WAF_TIMEOUTS = "_dd.appsec.waf.timeouts" + WAF_VERSION = "_dd.appsec.waf.version" + ORIGIN_VALUE = "appsec" + CUSTOM_EVENT_PREFIX = "appsec.events" + USER_LOGIN_EVENT_PREFIX = "_dd.appsec.events.users.login" + USER_LOGIN_EVENT_PREFIX_PUBLIC = "appsec.events.users.login" + USER_LOGIN_EVENT_SUCCESS_TRACK = "appsec.events.users.login.success.track" + USER_LOGIN_EVENT_FAILURE_TRACK = "appsec.events.users.login.failure.track" + USER_SIGNUP_EVENT = "appsec.events.users.signup.track" + AUTO_LOGIN_EVENTS_SUCCESS_MODE = "_dd.appsec.events.users.login.success.auto.mode" + AUTO_LOGIN_EVENTS_FAILURE_MODE = "_dd.appsec.events.users.login.failure.auto.mode" + BLOCKED = "appsec.blocked" + EVENT = "appsec.event" + AUTOMATIC_USER_EVENTS_TRACKING = "DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING" + USER_MODEL_LOGIN_FIELD = "DD_USER_MODEL_LOGIN_FIELD" + USER_MODEL_EMAIL_FIELD = "DD_USER_MODEL_EMAIL_FIELD" + USER_MODEL_NAME_FIELD = "DD_USER_MODEL_NAME_FIELD" + + +class IAST(metaclass=Constant_Class): + """Specific constants for IAST""" + + ENV = "DD_IAST_ENABLED" + ENV_DEBUG = "_DD_IAST_DEBUG" + TELEMETRY_REPORT_LVL = "DD_IAST_TELEMETRY_VERBOSITY" + LAZY_TAINT = "_DD_IAST_LAZY_TAINT" + JSON = "_dd.iast.json" + ENABLED = "_dd.iast.enabled" + CONTEXT_KEY = "_iast_data" + PATCH_MODULES = "_DD_IAST_PATCH_MODULES" + DENY_MODULES = "_DD_IAST_DENY_MODULES" + SEP_MODULES = "," + REQUEST_IAST_ENABLED = "_dd.iast.request_enabled" + + +class IAST_SPAN_TAGS(metaclass=Constant_Class): + """Specific constants for IAST span tags""" + + TELEMETRY_REQUEST_TAINTED = "_dd.iast.telemetry.request.tainted" + TELEMETRY_EXECUTED_SINK = "_dd.iast.telemetry.executed.sink" + + +class WAF_DATA_NAMES(metaclass=Constant_Class): + """string names used by the waf library for requesting data from requests""" + + REQUEST_BODY = "server.request.body" + REQUEST_QUERY = "server.request.query" + REQUEST_HEADERS_NO_COOKIES = "server.request.headers.no_cookies" + REQUEST_URI_RAW = "server.request.uri.raw" + REQUEST_METHOD = "server.request.method" + REQUEST_PATH_PARAMS = "server.request.path_params" + REQUEST_COOKIES = "server.request.cookies" + REQUEST_HTTP_IP = "http.client_ip" + REQUEST_USER_ID = "usr.id" + RESPONSE_STATUS = "server.response.status" + RESPONSE_HEADERS_NO_COOKIES = "server.response.headers.no_cookies" + RESPONSE_BODY = "server.response.body" + PROCESSOR_SETTINGS = "waf.context.processor" + + +class SPAN_DATA_NAMES(metaclass=Constant_Class): + """string names used by the library for tagging data from requests in context or span""" + + REQUEST_BODY = "http.request.body" + REQUEST_QUERY = "http.request.query" + REQUEST_HEADERS_NO_COOKIES = "http.request.headers" + REQUEST_HEADERS_NO_COOKIES_CASE = "http.request.headers_case_sensitive" + REQUEST_URI_RAW = "http.request.uri" + REQUEST_ROUTE = "http.request.route" + REQUEST_METHOD = "http.request.method" + REQUEST_PATH_PARAMS = REQUEST_PATH_PARAMS + REQUEST_COOKIES = "http.request.cookies" + REQUEST_HTTP_IP = "http.request.remote_ip" + REQUEST_USER_ID = "usr.id" + RESPONSE_STATUS = "http.response.status" + RESPONSE_HEADERS_NO_COOKIES = RESPONSE_HEADERS + RESPONSE_BODY = "http.response.body" + + +class API_SECURITY(metaclass=Constant_Class): + """constants related to API Security""" + + ENABLED = "_dd.appsec.api_security.enabled" + ENV_VAR_ENABLED = "DD_API_SECURITY_ENABLED" + PARSE_RESPONSE_BODY = "DD_API_SECURITY_PARSE_RESPONSE_BODY" + REQUEST_HEADERS_NO_COOKIES = "_dd.appsec.s.req.headers" + REQUEST_COOKIES = "_dd.appsec.s.req.cookies" + REQUEST_QUERY = "_dd.appsec.s.req.query" + REQUEST_PATH_PARAMS = "_dd.appsec.s.req.params" + REQUEST_BODY = "_dd.appsec.s.req.body" + RESPONSE_HEADERS_NO_COOKIES = "_dd.appsec.s.res.headers" + RESPONSE_BODY = "_dd.appsec.s.res.body" + SAMPLE_RATE = "DD_API_SECURITY_REQUEST_SAMPLE_RATE" + MAX_PAYLOAD_SIZE = 0x1000000 # 16MB maximum size + + +class WAF_CONTEXT_NAMES(metaclass=Constant_Class): + """string names used by the library for tagging data from requests in context""" + + RESULTS = "http.request.waf.results" + BLOCKED = HTTP_REQUEST_BLOCKED + CALLBACK = "http.request.waf.callback" + + +class WAF_ACTIONS(metaclass=Constant_Class): + """string identifier for actions returned by the waf""" + + BLOCK = "block" + PARAMETERS = "parameters" + TYPE = "type" + ID = "id" + DEFAULT_PARAMETERS = STATUS_403_TYPE_AUTO + BLOCK_ACTION = "block_request" + REDIRECT_ACTION = "redirect_request" + DEFAULT_ACTIONS = { + BLOCK: { + ID: BLOCK, + TYPE: BLOCK_ACTION, + PARAMETERS: DEFAULT_PARAMETERS, + } + } + + +class PRODUCTS(metaclass=Constant_Class): + """string identifier for remote config products""" + + ASM = "ASM" + ASM_DATA = "ASM_DATA" + ASM_DD = "ASM_DD" + ASM_FEATURES = "ASM_FEATURES" + + +class LOGIN_EVENTS_MODE(metaclass=Constant_Class): + """ + string identifier for the mode of the user login events. Can be: + DISABLED: automatic login events are disabled. + SAFE: automatic login events are enabled but will only store non-PII fields (id, pk uid...) + EXTENDED: automatic login events are enabled and will store potentially PII fields (username, + email, ...). + SDK: manually issued login events using the SDK. + """ + + DISABLED = "disabled" + SAFE = "safe" + EXTENDED = "extended" + SDK = "sdk" + + +class DEFAULT(metaclass=Constant_Class): + ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) + RULES = os.path.join(ROOT_DIR, "rules.json") + TRACE_RATE_LIMIT = 100 + WAF_TIMEOUT = 5.0 # float (milliseconds) + APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP = ( + rb"(?i)(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?)key)|token|consumer_?" + rb"(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization" + ) + APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP = ( + rb"(?i)(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)" + rb"key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)" + rb'(?:\s*=[^;]|"\s*:\s*"[^"]+")|bearer\s+[a-z0-9\._\-]+|token:[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}' + rb"|ey[I-L][\w=-]+\.ey[I-L][\w=-]+(?:\.[\w.+\/=-]+)?|[\-]{5}BEGIN[a-z\s]+PRIVATE\sKEY[\-]{5}[^\-]+[\-]" + rb"{5}END[a-z\s]+PRIVATE\sKEY|ssh-rsa\s*[a-z0-9\/\.+]{100,}" + ) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_ddwaf/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_ddwaf/__init__.py new file mode 100644 index 0000000..cee6eda --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_ddwaf/__init__.py @@ -0,0 +1,214 @@ +import ctypes +import time +from typing import Any +from typing import Dict +from typing import List +from typing import Optional + +from ddtrace.appsec._constants import DEFAULT +from ddtrace.internal.logger import get_logger +from ddtrace.settings.asm import config as asm_config + + +LOGGER = get_logger(__name__) + +if asm_config._asm_libddwaf_available: + try: + from .ddwaf_types import DDWafRulesType + from .ddwaf_types import _observator + from .ddwaf_types import ddwaf_config + from .ddwaf_types import ddwaf_context_capsule + from .ddwaf_types import ddwaf_get_version + from .ddwaf_types import ddwaf_object + from .ddwaf_types import ddwaf_object_free + from .ddwaf_types import ddwaf_result + from .ddwaf_types import ddwaf_run + from .ddwaf_types import py_ddwaf_context_init + from .ddwaf_types import py_ddwaf_init + from .ddwaf_types import py_ddwaf_known_addresses + from .ddwaf_types import py_ddwaf_update + + _DDWAF_LOADED = True + except BaseException: + _DDWAF_LOADED = False + LOGGER.warning("DDWaf features disabled. WARNING: Dynamic Library not loaded", exc_info=True) +else: + _DDWAF_LOADED = False + + +class DDWaf_result(object): + __slots__ = ["data", "actions", "runtime", "total_runtime", "timeout", "truncation", "derivatives"] + + def __init__( + self, + data: Optional[str], + actions: List[str], + runtime: float, + total_runtime: float, + timeout: bool, + truncation: int, + derivatives: Dict[str, Any], + ): + self.data = data + self.actions = actions + self.runtime = runtime + self.total_runtime = total_runtime + self.timeout = timeout + self.truncation = truncation + self.derivatives = derivatives + + +class DDWaf_info(object): + __slots__ = ["loaded", "failed", "errors", "version"] + + def __init__(self, loaded: int, failed: int, errors: Dict[str, Any], version: str): + self.loaded = loaded + self.failed = failed + self.errors = errors + self.version = version + + def __repr__(self): + return "{loaded: %d, failed: %d, errors: %s, version: %s}" % ( + self.loaded, + self.failed, + str(self.errors), + self.version, + ) + + +if _DDWAF_LOADED: + + class DDWaf(object): + def __init__( + self, + ruleset_map: Dict[str, Any], + obfuscation_parameter_key_regexp: bytes, + obfuscation_parameter_value_regexp: bytes, + ): + config = ddwaf_config( + key_regex=obfuscation_parameter_key_regexp, value_regex=obfuscation_parameter_value_regexp + ) + diagnostics = ddwaf_object() + ruleset_map_object = ddwaf_object.create_without_limits(ruleset_map) + self._handle = py_ddwaf_init(ruleset_map_object, ctypes.byref(config), ctypes.byref(diagnostics)) + self._set_info(diagnostics) + info = self.info + if not self._handle or info.failed: + # We keep the handle alive in case of errors, as some valid rules can be loaded + # at the same time some invalid ones are rejected + LOGGER.debug( + "DDWAF.__init__: invalid rules\n ruleset: %s\nloaded:%s\nerrors:%s\n", + ruleset_map_object.struct, + info.failed, + info.errors, + ) + ddwaf_object_free(ctypes.byref(ruleset_map_object)) + + @property + def required_data(self) -> List[str]: + return py_ddwaf_known_addresses(self._handle) if self._handle else [] + + def _set_info(self, diagnostics: ddwaf_object) -> None: + info_struct = diagnostics.struct + rules = info_struct.get("rules", {}) if info_struct else {} # type: ignore + errors_result = rules.get("errors", {}) + version = info_struct.get("ruleset_version", "") if info_struct else "" # type: ignore + self._info = DDWaf_info(len(rules.get("loaded", [])), len(rules.get("failed", [])), errors_result, version) + ddwaf_object_free(diagnostics) + + @property + def info(self) -> DDWaf_info: + return self._info + + def update_rules(self, new_rules: Dict[str, DDWafRulesType]) -> bool: + """update the rules of the WAF instance. return True if an error occurs.""" + rules = ddwaf_object.create_without_limits(new_rules) + diagnostics = ddwaf_object() + result = py_ddwaf_update(self._handle, rules, diagnostics) + self._set_info(diagnostics) + ddwaf_object_free(rules) + if result: + LOGGER.debug("DDWAF.update_rules success.\ninfo %s", self.info) + self._handle = result + return True + else: + LOGGER.debug("DDWAF.update_rules: keeping the previous handle.") + return False + + def _at_request_start(self) -> Optional[ddwaf_context_capsule]: + ctx = None + if self._handle: + ctx = py_ddwaf_context_init(self._handle) + if not ctx: + LOGGER.debug("DDWaf._at_request_start: failure to create the context.") + return ctx + + def _at_request_end(self) -> None: + pass + + def run( + self, + ctx: ddwaf_context_capsule, + data: DDWafRulesType, + timeout_ms: float = DEFAULT.WAF_TIMEOUT, + ) -> DDWaf_result: + start = time.time() + if not ctx: + LOGGER.debug("DDWaf.run: dry run. no context created.") + return DDWaf_result(None, [], 0, (time.time() - start) * 1e6, False, 0, {}) + + result = ddwaf_result() + observator = _observator() + wrapper = ddwaf_object(data, observator=observator) + error = ddwaf_run(ctx.ctx, wrapper, None, ctypes.byref(result), int(timeout_ms * 1000)) + if error < 0: + LOGGER.debug("run DDWAF error: %d\ninput %s\nerror %s", error, wrapper.struct, self.info.errors) + return DDWaf_result( + result.events.struct, + result.actions.struct, + result.total_runtime / 1e3, + (time.time() - start) * 1e6, + result.timeout, + observator.truncation, + result.derivatives.struct, + ) + + def version() -> str: + return ddwaf_get_version().decode("UTF-8") + +else: + # Mockup of the DDWaf class doing nothing + class DDWaf(object): # type: ignore + required_data: List[str] = [] + info: DDWaf_info = DDWaf_info(0, 0, {}, "") + + def __init__( + self, + rules: Dict[str, Any], + obfuscation_parameter_key_regexp: bytes, + obfuscation_parameter_value_regexp: bytes, + ): + self._handle = None + + def run( + self, + ctx: Any, + data: Any, + timeout_ms: float = DEFAULT.WAF_TIMEOUT, + ) -> DDWaf_result: + LOGGER.debug("DDWaf features disabled. dry run") + return DDWaf_result(None, [], 0.0, 0.0, False, 0, {}) + + def update_rules(self, _: Dict[str, Any]) -> bool: + LOGGER.debug("DDWaf features disabled. dry update") + return False + + def _at_request_start(self) -> None: + return None + + def _at_request_end(self) -> None: + pass + + def version() -> str: + LOGGER.debug("DDWaf features disabled. null version") + return "0.0.0" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_ddwaf/ddwaf_types.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_ddwaf/ddwaf_types.py new file mode 100644 index 0000000..aecfd79 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_ddwaf/ddwaf_types.py @@ -0,0 +1,555 @@ +import ctypes +import ctypes.util +from enum import IntEnum +from platform import machine +from platform import system +from typing import Any +from typing import Dict +from typing import List +from typing import Union + +from ddtrace.internal.logger import get_logger +from ddtrace.settings.asm import config as asm_config + + +DDWafRulesType = Union[None, int, str, List[Any], Dict[str, Any]] + +log = get_logger(__name__) + +# +# Dynamic loading of libddwaf. For now it requires the file or a link to be in current directory +# + +if system() == "Linux": + try: + ctypes.CDLL(ctypes.util.find_library("rt"), mode=ctypes.RTLD_GLOBAL) + except BaseException: # nosec + pass + +ARCHI = machine().lower() + +# 32-bit-Python on 64-bit-Windows + +ddwaf = ctypes.CDLL(asm_config._asm_libddwaf) +# +# Constants +# + +DDWAF_MAX_STRING_LENGTH = 4096 +DDWAF_MAX_CONTAINER_DEPTH = 20 +DDWAF_MAX_CONTAINER_SIZE = 256 +DDWAF_NO_LIMIT = 1 << 31 +DDWAF_DEPTH_NO_LIMIT = 1000 +_TRUNC_STRING_LENGTH = 1 +_TRUNC_CONTAINER_DEPTH = 4 +_TRUNC_CONTAINER_SIZE = 2 + + +class DDWAF_OBJ_TYPE(IntEnum): + DDWAF_OBJ_INVALID = 0 + # Value shall be decoded as a int64_t (or int32_t on 32bits platforms). + DDWAF_OBJ_SIGNED = 1 << 0 + # Value shall be decoded as a uint64_t (or uint32_t on 32bits platforms). + DDWAF_OBJ_UNSIGNED = 1 << 1 + # Value shall be decoded as a UTF-8 string of length nbEntries. + DDWAF_OBJ_STRING = 1 << 2 + # Value shall be decoded as an array of ddwaf_object of length nbEntries, each item having no parameterName. + DDWAF_OBJ_ARRAY = 1 << 3 + # Value shall be decoded as an array of ddwaf_object of length nbEntries, each item having a parameterName. + DDWAF_OBJ_MAP = 1 << 4 + # Value shall be decode as bool + DDWAF_OBJ_BOOL = 1 << 5 + # 64-bit float (or double) type + DDWAF_OBJ_FLOAT = 1 << 6 + # Null type, only used for its semantical value + DDWAF_OBJ_NULL = 1 << 7 + + +class DDWAF_RET_CODE(IntEnum): + DDWAF_ERR_INTERNAL = -3 + DDWAF_ERR_INVALID_OBJECT = -2 + DDWAF_ERR_INVALID_ARGUMENT = -1 + DDWAF_OK = 0 + DDWAF_MATCH = 1 + + +class DDWAF_LOG_LEVEL(IntEnum): + DDWAF_LOG_TRACE = 0 + DDWAF_LOG_DEBUG = 1 + DDWAF_LOG_INFO = 2 + DDWAF_LOG_WARN = 3 + DDWAF_LOG_ERROR = 4 + DDWAF_LOG_OFF = 5 + + +# +# Objects Definitions +# + +# obj_struct = DDWafRulesType + + +class _observator: + def __init__(self): + self.truncation = 0 + + +# to allow cyclic references, ddwaf_object fields are defined later +class ddwaf_object(ctypes.Structure): + # "type" define how to read the "value" union field + # defined in ddwaf.h + # 1 is intValue + # 2 is uintValue + # 4 is stringValue as UTF-8 encoded + # 8 is array of length "nbEntries" without parameterName + # 16 is a map : array of length "nbEntries" with parameterName + # 32 is boolean + + def __init__( + self, + struct: DDWafRulesType = None, + observator: _observator = _observator(), # noqa : B008 + max_objects: int = DDWAF_MAX_CONTAINER_SIZE, + max_depth: int = DDWAF_MAX_CONTAINER_DEPTH, + max_string_length: int = DDWAF_MAX_STRING_LENGTH, + ) -> None: + def truncate_string(string: bytes) -> bytes: + if len(string) > max_string_length - 1: + observator.truncation |= _TRUNC_STRING_LENGTH + # difference of 1 to take null char at the end on the C side into account + return string[: max_string_length - 1] + return string + + if isinstance(struct, bool): + ddwaf_object_bool(self, struct) + elif isinstance(struct, int): + ddwaf_object_signed(self, struct) + elif isinstance(struct, str): + ddwaf_object_string(self, truncate_string(struct.encode("UTF-8", errors="ignore"))) + elif isinstance(struct, bytes): + ddwaf_object_string(self, truncate_string(struct)) + elif isinstance(struct, float): + ddwaf_object_float(self, struct) + elif isinstance(struct, list): + if max_depth <= 0: + observator.truncation |= _TRUNC_CONTAINER_DEPTH + max_objects = 0 + array = ddwaf_object_array(self) + for counter_object, elt in enumerate(struct): + if counter_object >= max_objects: + observator.truncation |= _TRUNC_CONTAINER_SIZE + break + obj = ddwaf_object( + elt, + observator=observator, + max_objects=max_objects, + max_depth=max_depth - 1, + max_string_length=max_string_length, + ) + ddwaf_object_array_add(array, obj) + elif isinstance(struct, dict): + if max_depth <= 0: + observator.truncation |= _TRUNC_CONTAINER_DEPTH + max_objects = 0 + map_o = ddwaf_object_map(self) + # order is unspecified and could lead to problems if max_objects is reached + for counter_object, (key, val) in enumerate(struct.items()): + if not isinstance(key, (bytes, str)): # discards non string keys + continue + if counter_object >= max_objects: + observator.truncation |= _TRUNC_CONTAINER_SIZE + break + res_key = truncate_string(key.encode("UTF-8", errors="ignore") if isinstance(key, str) else key) + obj = ddwaf_object( + val, + observator=observator, + max_objects=max_objects, + max_depth=max_depth - 1, + max_string_length=max_string_length, + ) + ddwaf_object_map_add(map_o, res_key, obj) + elif struct is not None: + ddwaf_object_string(self, truncate_string(str(struct).encode("UTF-8", errors="ignore"))) + else: + ddwaf_object_null(self) + + @classmethod + def create_without_limits(cls, struct: DDWafRulesType) -> "ddwaf_object": + return cls(struct, max_objects=DDWAF_NO_LIMIT, max_depth=DDWAF_DEPTH_NO_LIMIT, max_string_length=DDWAF_NO_LIMIT) + + @property + def struct(self) -> DDWafRulesType: + """Generate a python structure from ddwaf_object""" + if self.type == DDWAF_OBJ_TYPE.DDWAF_OBJ_STRING: + return self.value.stringValue.decode("UTF-8", errors="ignore") + if self.type == DDWAF_OBJ_TYPE.DDWAF_OBJ_MAP: + return { + self.value.array[i].parameterName.decode("UTF-8", errors="ignore"): self.value.array[i].struct + for i in range(self.nbEntries) + } + if self.type == DDWAF_OBJ_TYPE.DDWAF_OBJ_ARRAY: + return [self.value.array[i].struct for i in range(self.nbEntries)] + if self.type == DDWAF_OBJ_TYPE.DDWAF_OBJ_SIGNED: + return self.value.intValue + if self.type == DDWAF_OBJ_TYPE.DDWAF_OBJ_UNSIGNED: + return self.value.uintValue + if self.type == DDWAF_OBJ_TYPE.DDWAF_OBJ_BOOL: + return self.value.boolean + if self.type == DDWAF_OBJ_TYPE.DDWAF_OBJ_FLOAT: + return self.value.f64 + if self.type == DDWAF_OBJ_TYPE.DDWAF_OBJ_NULL or self.type == DDWAF_OBJ_TYPE.DDWAF_OBJ_INVALID: + return None + log.debug("ddwaf_object struct: unknown object type: %s", repr(type(self.type))) + return None + + def __repr__(self): + return repr(self.struct) + + +ddwaf_object_p = ctypes.POINTER(ddwaf_object) + + +class ddwaf_value(ctypes.Union): + _fields_ = [ + ("stringValue", ctypes.c_char_p), + ("uintValue", ctypes.c_ulonglong), + ("intValue", ctypes.c_longlong), + ("array", ddwaf_object_p), + ("boolean", ctypes.c_bool), + ("f64", ctypes.c_double), + ] + + +ddwaf_object._fields_ = [ + ("parameterName", ctypes.c_char_p), + ("parameterNameLength", ctypes.c_uint64), + ("value", ddwaf_value), + ("nbEntries", ctypes.c_uint64), + ("type", ctypes.c_int), +] + + +class ddwaf_result(ctypes.Structure): + _fields_ = [ + ("timeout", ctypes.c_bool), + ("events", ddwaf_object), + ("actions", ddwaf_object), + ("derivatives", ddwaf_object), + ("total_runtime", ctypes.c_uint64), + ] + + def __repr__(self): + return "total_runtime=%r, events=%r, timeout=%r, action=[%r]" % ( + self.total_runtime, + self.events.struct, + self.timeout.struct, + self.actions, + ) + + def __del__(self): + try: + ddwaf_result_free(self) + except TypeError: + pass + + +ddwaf_result_p = ctypes.POINTER(ddwaf_result) + + +class ddwaf_config_limits(ctypes.Structure): + _fields_ = [ + ("max_container_size", ctypes.c_uint32), + ("max_container_depth", ctypes.c_uint32), + ("max_string_length", ctypes.c_uint32), + ] + + +class ddwaf_config_obfuscator(ctypes.Structure): + _fields_ = [ + ("key_regex", ctypes.c_char_p), + ("value_regex", ctypes.c_char_p), + ] + + +ddwaf_object_free_fn = ctypes.CFUNCTYPE(None, ddwaf_object_p) +ddwaf_object_free = ddwaf_object_free_fn( + ("ddwaf_object_free", ddwaf), + ((1, "object"),), +) + + +class ddwaf_config(ctypes.Structure): + _fields_ = [ + ("limits", ddwaf_config_limits), + ("obfuscator", ddwaf_config_obfuscator), + ("free_fn", ddwaf_object_free_fn), + ] + # TODO : initial value of free_fn + + def __init__( + self, + max_container_size: int = 0, + max_container_depth: int = 0, + max_string_length: int = 0, + key_regex: bytes = b"", + value_regex: bytes = b"", + free_fn=ddwaf_object_free, + ) -> None: + self.limits.max_container_size = max_container_size + self.limits.max_container_depth = max_container_depth + self.limits.max_string_length = max_string_length + self.obfuscator.key_regex = key_regex + self.obfuscator.value_regex = value_regex + self.free_fn = free_fn + + +ddwaf_config_p = ctypes.POINTER(ddwaf_config) + + +ddwaf_handle = ctypes.c_void_p # may stay as this because it's mainly an abstract type in the interface +ddwaf_context = ctypes.c_void_p # may stay as this because it's mainly an abstract type in the interface + + +class ddwaf_handle_capsule: + def __init__(self, handle: ddwaf_handle) -> None: + self.handle = handle + self.free_fn = ddwaf_destroy + + def __del__(self): + if self.handle: + try: + self.free_fn(self.handle) + except TypeError: + pass + self.handle = None + + def __bool__(self): + return bool(self.handle) + + +class ddwaf_context_capsule: + def __init__(self, ctx: ddwaf_context) -> None: + self.ctx = ctx + self.free_fn = ddwaf_context_destroy + + def __del__(self): + if self.ctx: + try: + self.free_fn(self.ctx) + except TypeError: + pass + self.ctx = None + + def __bool__(self): + return bool(self.ctx) + + +ddwaf_log_cb = ctypes.POINTER( + ctypes.CFUNCTYPE( + None, ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_uint, ctypes.c_char_p, ctypes.c_uint64 + ) +) + + +# +# Functions Prototypes (creating python counterpart function from C function with ) +# + +ddwaf_init = ctypes.CFUNCTYPE(ddwaf_handle, ddwaf_object_p, ddwaf_config_p, ddwaf_object_p)( + ("ddwaf_init", ddwaf), + ( + (1, "ruleset_map"), + (1, "config", None), + (1, "diagnostics", None), + ), +) + + +def py_ddwaf_init(ruleset_map: ddwaf_object, config, info) -> ddwaf_handle_capsule: + return ddwaf_handle_capsule(ddwaf_init(ruleset_map, config, info)) + + +ddwaf_update = ctypes.CFUNCTYPE(ddwaf_handle, ddwaf_handle, ddwaf_object_p, ddwaf_object_p)( + ("ddwaf_update", ddwaf), + ( + (1, "handle"), + (1, "ruleset_map"), + (1, "diagnostics", None), + ), +) + + +def py_ddwaf_update(handle: ddwaf_handle_capsule, ruleset_map: ddwaf_object, info) -> ddwaf_handle_capsule: + return ddwaf_handle_capsule(ddwaf_update(handle.handle, ruleset_map, ctypes.byref(info))) + + +ddwaf_destroy = ctypes.CFUNCTYPE(None, ddwaf_handle)( + ("ddwaf_destroy", ddwaf), + ((1, "handle"),), +) + +ddwaf_known_addresses = ctypes.CFUNCTYPE( + ctypes.POINTER(ctypes.c_char_p), ddwaf_handle, ctypes.POINTER(ctypes.c_uint32) +)( + ("ddwaf_known_addresses", ddwaf), + ( + (1, "handle"), + (1, "size"), + ), +) + + +def py_ddwaf_known_addresses(handle: ddwaf_handle_capsule) -> List[str]: + size = ctypes.c_uint32() + obj = ddwaf_known_addresses(handle.handle, ctypes.byref(size)) + return [obj[i].decode("UTF-8") for i in range(size.value)] + + +ddwaf_context_init = ctypes.CFUNCTYPE(ddwaf_context, ddwaf_handle)( + ("ddwaf_context_init", ddwaf), + ((1, "handle"),), +) + + +def py_ddwaf_context_init(handle: ddwaf_handle_capsule) -> ddwaf_context_capsule: + return ddwaf_context_capsule(ddwaf_context_init(handle.handle)) + + +ddwaf_run = ctypes.CFUNCTYPE( + ctypes.c_int, ddwaf_context, ddwaf_object_p, ddwaf_object_p, ddwaf_result_p, ctypes.c_uint64 +)(("ddwaf_run", ddwaf), ((1, "context"), (1, "persistent_data"), (1, "ephemeral_data"), (1, "result"), (1, "timeout"))) + +ddwaf_context_destroy = ctypes.CFUNCTYPE(None, ddwaf_context)( + ("ddwaf_context_destroy", ddwaf), + ((1, "context"),), +) + +ddwaf_result_free = ctypes.CFUNCTYPE(None, ddwaf_result_p)( + ("ddwaf_result_free", ddwaf), + ((1, "result"),), +) + +ddwaf_object_invalid = ctypes.CFUNCTYPE(ddwaf_object_p, ddwaf_object_p)( + ("ddwaf_object_invalid", ddwaf), + ((3, "object"),), +) + +ddwaf_object_string = ctypes.CFUNCTYPE(ddwaf_object_p, ddwaf_object_p, ctypes.c_char_p)( + ("ddwaf_object_string", ddwaf), + ( + (3, "object"), + (1, "string"), + ), +) + +# object_string variants not used + +ddwaf_object_string_from_unsigned = ctypes.CFUNCTYPE(ddwaf_object_p, ddwaf_object_p, ctypes.c_uint64)( + ("ddwaf_object_string_from_unsigned", ddwaf), + ( + (3, "object"), + (1, "value"), + ), +) + +ddwaf_object_string_from_signed = ctypes.CFUNCTYPE(ddwaf_object_p, ddwaf_object_p, ctypes.c_int64)( + ("ddwaf_object_string_from_signed", ddwaf), + ( + (3, "object"), + (1, "value"), + ), +) + +ddwaf_object_unsigned = ctypes.CFUNCTYPE(ddwaf_object_p, ddwaf_object_p, ctypes.c_uint64)( + ("ddwaf_object_unsigned", ddwaf), + ( + (3, "object"), + (1, "value"), + ), +) + +ddwaf_object_signed = ctypes.CFUNCTYPE(ddwaf_object_p, ddwaf_object_p, ctypes.c_int64)( + ("ddwaf_object_signed", ddwaf), + ( + (3, "object"), + (1, "value"), + ), +) + +# object_(un)signed_forced : not used ? + +ddwaf_object_bool = ctypes.CFUNCTYPE(ddwaf_object_p, ddwaf_object_p, ctypes.c_bool)( + ("ddwaf_object_bool", ddwaf), + ( + (3, "object"), + (1, "value"), + ), +) + + +ddwaf_object_float = ctypes.CFUNCTYPE(ddwaf_object_p, ddwaf_object_p, ctypes.c_double)( + ("ddwaf_object_float", ddwaf), + ( + (3, "object"), + (1, "value"), + ), +) + +ddwaf_object_null = ctypes.CFUNCTYPE(ddwaf_object_p, ddwaf_object_p)( + ("ddwaf_object_null", ddwaf), + ((3, "object"),), +) + +ddwaf_object_array = ctypes.CFUNCTYPE(ddwaf_object_p, ddwaf_object_p)( + ("ddwaf_object_array", ddwaf), + ((3, "object"),), +) + +ddwaf_object_map = ctypes.CFUNCTYPE(ddwaf_object_p, ddwaf_object_p)( + ("ddwaf_object_map", ddwaf), + ((3, "object"),), +) + +ddwaf_object_array_add = ctypes.CFUNCTYPE(ctypes.c_bool, ddwaf_object_p, ddwaf_object_p)( + ("ddwaf_object_array_add", ddwaf), + ( + (1, "array"), + (1, "object"), + ), +) + +ddwaf_object_map_add = ctypes.CFUNCTYPE(ctypes.c_bool, ddwaf_object_p, ctypes.c_char_p, ddwaf_object_p)( + ("ddwaf_object_map_add", ddwaf), + ( + (1, "map"), + (1, "key"), + (1, "object"), + ), +) + +# unused because accessible from python part +# ddwaf_object_type +# ddwaf_object_size +# ddwaf_object_length +# ddwaf_object_get_key +# ddwaf_object_get_string +# ddwaf_object_get_unsigned +# ddwaf_object_get_signed +# ddwaf_object_get_index +# ddwaf_object_get_bool https://github.com/DataDog/libddwaf/commit/7dc68dacd972ae2e2a3c03a69116909c98dbd9cb +# ddwaf_object_get_float + + +ddwaf_get_version = ctypes.CFUNCTYPE(ctypes.c_char_p)( + ("ddwaf_get_version", ddwaf), + (), +) + + +ddwaf_set_log_cb = ctypes.CFUNCTYPE(ctypes.c_bool, ddwaf_log_cb, ctypes.c_int)( + ("ddwaf_set_log_cb", ddwaf), + ( + (1, "cb"), + (1, "min_level"), + ), +) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_ddwaf/libddwaf/x86_64/lib/libddwaf.so b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_ddwaf/libddwaf/x86_64/lib/libddwaf.so new file mode 100644 index 0000000000000000000000000000000000000000..ac482e11a9f613064ed824ccc33659cf1dab6a70 GIT binary patch literal 2195976 zcmb5%d%Rm!o&W#MMLi;DxQG!&GG5~ai8l=12#6R|Br0MQu_$BJ1NlN>+7rpH89e3Cv@vohd7bO3Clq3axqj<(Yxyjt}$%E$q{}IVP$^GZL^Za)~ z-{sfpj^P{rx}wwr&&u+ymx^E8p6FzH@XweO2ou zt^eU%Rz|Th&e3L_=VOasYdeG=@K^ubD{tDmcHw-H`4#>g=BaLyl78-zEg?64J^Q)g zeE#Y+d&T_cR>t}N_t(nLul(++F8KAa>d;?T+OGUs`MTHq_fGTQFMj!}ldE_5-P0cZ z^BoTRunK#Z^SMT!e%(2Lh}-7h3I6cF$&KnK zqxKp3i`7r--$Hx){%SwEB>0Ebe&zXirT>)1^WXCK>Aa0^FZS~bc?RQojru7)D70UB zu3GuEi^elLKlpb8C;NH6{~mmm{F(9u^ZJy$b3thTGqt})KKXj^*UG=Ian3aU$H~_@ z2aWTk@(;^%4-Wmb=JmAl>w)U0c46?x22S1}uPD#s+Vqhvu}m=Yt{c2_20v~@2h?~IPbgFex&v*&xI?$4p94%+B>&@lMghW z$Asp|gXb@#R_dpMdU%|?A%CRWzeM97Ers!Ko$v1}zs`_nb`Aajd0Rf%J@}(T^W=Pu zzkzlCjOVLy`g(b;#?#Swa_Z-S^RFwlxAHn|pKV_=hs}}UzlVX zOPM73r^e2OWNkB9c|_T*I4_MsBh%PyBQ1{DyVmd-wgFRcq?2SDmnazm8`f;tv}Ij#^6{IuZaQtv%C|PIIk}ds z-?Dl`t-5-9KU>yq-BMe(X8j54x3$^2>4fXjr`ZP~hd%}J})R8LxU!s_+^+u_NlY+YAh zHE(rNG9UYf`Ts~(AHQky)@04P^&7U0f9s|V8cn}YlaF7u=9JB=PF`JKnehDQw@+pM z-R<+7k8$P9Cv0Bl0nZz6ShsN=PJQkA6W4D|Hm*Bu`&3qzZa(Syoaf`3=e5%H{Qu^| zShsoe#!V~JS-o}rd~_?ro<}?nW}}kXy6Kc!Z9apOkKeWk>$hy{exC5DTQ+LVoOOui zl#TP=PTD@|HJdiBU3L8CO{>?gS-oYR((0|7YV&cPP&;Mon)ztwgPiB{4-2-f{b{Si zP*=)jzWgf_J6Tz8SbyTib!%6Jqj@CjHlCVn*|KK!#uMhtylvF;!LOVq6pzQYc_odL zR%visw$3krmFaKYuw`57+g3m1z4jjlqo3NmcH5M;egBmC;+}BY=6M|@^E1r5oR49p zU{_ALvO+7-&imf9dDZ6mT5p@{Tvlq~1jV&}%eF-4r`dA)eBkr`PSpf3*wZ#IB&ory zT{rJ<(>#}TC!f6O)OFieeBR&jD{HrMwry7llhx0!-#E{9-e~Klm6^^nTS@m+4{rOS zu9Q}?cI|1aPgu3-_AVJiFVz**yk77 zw%+CoJTJwq^ZjBzjzF7FnO_$R-(2iyp6jYp*KOXiQlX*UyoTpRd*Z4!^NV`h56(|< z%7%q8YSbsMuBplY!pfND`^!I0ZXU?`jVA)O`BH2;dDWI}S3>;zr)<2h7Wb|9)$;gF^UY^rq$h0HwAyR7 znC7bLx(zG4#6ok$_kU-xve_**-Li61S@_|3Ri3!Dx@aqPFu!a3Ukt1FfBn8EyVA{l zO?bucV~Q*Mf3sN8^&8i&t0xD{FXPRdHf=qio1|5T5&W@DaQucYhw|Samf)`(s^) z@XWud4&l9>^a@JfcmE^#65Khb;LbS>cg{Y)`*+S+v|q3B=itt{0I%t|2(LUS%&P=% z$jflgs{;4Ds&LQC=jXiYXn%?3)qs0mE%-pkZFsV0m{$j$k$2&qR}b!a_2HhE&(C=c z(LVLFuwRYfp4SAP>V-O-(jOV-HG}8m%kYXkQGXt%=d}d)ynKGQcO9nD{us?G1NXeL zaQBmge_H+I;oesZaQ9P$e^vdI;NJf$aQ9nHjy-nU!u+S;&Q?TB~at@nlY zU3f>{gV)|4+V|mJ-vPX&eSQdU$w%;!d<-vrFr0S+AIPWhk$eW9$d}=b4~6~{?cc7$ z!bgIq;8h)`;m$t;cb#Y9xpC+}4{zwW0C)dIxce``OX}axJD!*CQx&u?PQt$7`MJI8 zzlQc3)xHk*`8*Hy(?t7MtDhF!<7mS@o(}vB_0xr?z8?0s9^C!(;b*I#0o>yl!QJl| zUQzxNcuhW~>-wEVd>P(SKZ%}yopW+isH-J-=2xm`c#YFq=(q^?eXtbqa>Og}&QrptfK_-;UW5C- zP=~kn5B)UYZr_CaKGK3a=QccjbI93wFy}7XPj3qCdvKp`08d>Q_U$3uIgcVfj`#%b zoTqf)rG+yW6_DgW*?D@I9>obk^1?8N9JGUIXq2oNAccWzDina>RSVH^JDOXnXL&wq8{M;#N| z_u(%)BeWkzd<_5asiB`K{1itHODQ@F%@D^fQJZuKuTRpLZF4`@zb;znK5; zUH|ZdblxodUiFiQ|5@WN!jDuwW%x(6E>-wxT9-Qf80`;Dxch0tPt0qxSiT7vYtcg#ODB zufqE}uEG8MP>*;c;!U`pA6oFqN5Xxu4flPm1NVKf3wQe--1kAxkN3d=+V63Tu1C1< zgJXF1-^032;LdFt@ma)|;m$45c|88iSs}M2xN}Isom(32_8GWy^Za~XKR@Kq{t)Gs zhdZ|-yshIB+_{w_UWs@W?%ZndiE^vMokIie+?sH=Z^50L=f~VSXkSxqUAS}W!TLjbZo`O=B0h#Yw+a0dq28u&=P-jiw`I87Cz^-zac-WU-Sw72`;Kx;!<}0eUe|FB z?%eVbFGRcucWxzk-;LfcIcl#RLxp{uft%3GGQ*KSTb8Ew=I_|)oTQ}mp zi1*>nZ2+&dLvBO3a~Q#$+ZgWl6S#Bp{FvJe?e9Mg_l0G+b6dJ(@%~=caSHC-(h<)@ zJPUVjIr=#vw>;cA6yVOS2zUDu+_`yv%&mgxA6WaOaSLJGU&{?Q?MF=J|P?zAqHe{)5V`2zPE}cvr_2xO1pRycY2~+_^R2t)e*$Zl8iXH_y-gxZW~of4g$a!kt?l-qdjc?%awIFGaizcWxDUUBg)i&42X}7$hz}w@ggdtpeDd{>+ZgT~CUECA zg}eO>?%X^-=9XN#_+0S{<+cQOZfST`#~HYD%SJpG@jTqQ72u5 z6}WTr{Fqw}?cb%`>Tu`QgpYOHf;+c%#5)o1!kt?W-ugz!tq*q&1GsY=!rguZcW#~^ zbDN-jPq|Iu&TSc99E5sJ^ws@4x21@uBA$jjw+wvr&5&Cb?i_M(=az@NeF5&=JU`FN z_k|MLPnBC4?%b;IzK&~f=T?t+BjQcCb8Eq8mxSEfaOcp0JGU;}?R#+N=J_$V0o(sh z@4vyF+ZdkxZm72j+__C7K8yG=+_@z>kH_B{gxr?k&LIVNZfUsNXW-7w^YeLKZ#lFt zE4Mt{xfS7U9hczFtsL=6#H(=UR)c4TA-6i*IW*wTtqFJg7Tmdce$1_d_U~72UAS}W z!TLjbZo`O=B0h#Yw+TFRRmg1$cMdbSb6bYHeWH0dALr)z*nZ2%uBpCP>UqmbK(uH43O=Qe>mH_wl`&CveYzt{V3aObvk+2V6@ zS;r~3b4y1&6Y(tEx#i$9<&%fI-U@W(R)jmZ65P3Ye$1_c_FI)(74F>X@S%cVI89^Cs|AMV@+aOdXvF}D%gU!dH^aOXCK=f5B7Zw7a6%MnlX za~_Y|FTtH#3ZA+)H zh}YrHtpT6>H00KVJBJqBxwYYL-+?=HMn!DN4yd7CfvET;F)R2tqpe$9k_Gr z!ri_HcW#~^a~q)j8Om)4cWz^Nb{Ohy0(WlHh|eOv40mpc&g1cCej9RIf;)#4+_|OU zZl8fWH_y-Kb-m@#{;SF@4|i@wcw5IMxN|ETu`KfIGJ)-0fR% z=jQn_w+`Chrrf%4=hlZWT@mVS0C#S~h>s#ZhC8iU2lE3+YjK*>b!BeegmavQ^)+Z3L^ zD%9T$?%b9mp6KU19=BhDJGT^krrgqS=a7Lrw=CT4b8zS8`FWhaFBH)Jp??hbg(BR! zmEm0-SK!W}8u41h>u~4RfRFA8_11(thZfwqwc&2xfjc+PkGb{GzNFmxaOXCJr+*OY zZ3K62=S8}8hk6Z7w) z{Uz$B2lqGz5g)>x|0v?)h)>|oe+sYc81kIKo#!&VbidF(xq9)r!tIyf&OZ&W>No@U ze6tbHMLZApd<*b~#$SYc{3W>ew=&%AD{#-(^JAUV(EgdTa9^myU2jeJSjR26b8APu z6Y(zGx%J?=2ZVL+!=1wb?%alOw;#cso9D;eCTPD#xlQ5DZ5dwtVW`(cU){fRTZ(up z;%T^Z%fLrVA-62tIppBZEf0750^GTIex8^2w-VZ)quk1H=T?RHbzFlxw|c}I5pTks zTMM4-5^`(9okIui*9%>^+xOtk>b!1GN94avQ>(+ZdkxQK+{G+__C7K8yG=+_@z> zkH_!VSxa!|kb*n6G~DeoaOdXv`Mj>T9NPa~x#i)`tq5=HxCD1@<%m}zUWGfi8r-?n z;m)A}_jTHYyL}7p+&n+#)# zIOJJ|JBJF~xmDqAUxPb0&yTq^(EdZptqFH-ZTM8j9k_GrM!XmCKHRwt;Dv{%p5e}6 z1o!K#G2HDZaOdXvF}E4oe@D43!=2mGHH**5WgVyB&Mh7BOvJNr=az%}{oXv>ITYZo zw<6r_OK|7r`7yT&+TW$zs&MC4hYxk!fIGKl#9I+>!<}0PK2W`N;m)B4cW!;S+YjK* z>b!Bec)`IoubTLvfZsUkgB0hyXw;6o&WYsg=IV2jF=j+^-;BKFS zJ2%hI{kYyTXn&b<%fg*o9^TY(0q)$25ido&40mo7cy9lYTNUmcYH;UPhr4|P?%X^- z=GH>{JCs`+?%cZYWgYk6-mm&_=Q)78{SfZlM)1Z{LvCYu=78W6xO1Dro!boV+&n+# zmi%P#xnlp@!+l{1?%dMws*W>o=aY?iF5-E(b1T4W&j|Sx>H2eACAf1d!<}0N?%X^- z=2k=dHz>C{+_^R3V;#5P&aEBsPQ<%#=hlN4o)vQI!=1wb?%alOw;#cso9D;eCTQPM zZd3Z#LtQSzi$4vX=&So592n-c1fR)M@XWJA`!w9SW#A>X&%*uRFXZ6PEf05Y1-Ntb z{5&t;7fNV#ZK~M#P(N=hlMf4hlK9;m)B0cWzy{+xOtk>b! z1GN9MavQ>(+ZdkxuTXCjxO1CEd=~L#xN}Q%9*;kHj_Mii98z%SmWI212JYNEKcCn2 zmP7l;+!5{zdAM^c!rMA7!JS(<;+2S3;m)lFFFilxR);%>2Hd$d;cnl8J2%gdxpmO~ zmCCIPcW!<7($7M@4dBjg81YfW$8hI1p}!#HHibKf8Qi%o!`(j7Je-en^Ze|tw-nmH zN4cfp&MgbC>o^DZ^H@IO1-NrA!kt?QJ}QLV%J7-I0(WjzxO1z)otx*!+!|(c#{b)14bpLE1C5zoS%TMj-tJmiyyJBI??xfS7VUxGU~&(HJqby`9Def|>e z3styttHXynZovKg)`UCHR>a$I=hlHIMb$I?Wx;!J=hlZiw*lO_d49}og!ZpiZezG} zo5J(gh5DPpo!c_pxh48JkH@|QcWxBx{z8a_HIcn0p=vT)~?gF83R&*OBx70~|O z%B=`@Ze@5^#}&A9s7AaN@jBeOHQ>EhhJ2cE=g@*Xw>I4EJ8%h6JC>N z;hCR@^X1?Twa>!`@&erbZy&G5>AEeU{cDs%8Sa0_rwVWCxCVC)b+~hAz&%d)$2w`D z{fQc98}4~_;X@tw;2vim?r{#_9;f@`I7evzW{q%0VaotNRBulwUTt7zZTIBRgv zvkrG|4S4gTRJYnJ&p=| z`M<+_-Jj<*xHycbhW0ag6YhR|e)ls{`!?FAz7hKGz+H!3_{UX;J-E+1faf)@A-vKH z=N-X2@-f`^{R!O98&mj1{mkH_^TT?#JhMKN+=8l&ifWcTRTKVG8Yk zqIFEeecmj5tm7Qq`#_$qe-F0+A8B2R@W$04pAx(!FT)4&3VfpeYw&@N>+sT5;k*ra zMc#z_`LGS|>9_;$sGlypqc{ir zd5dU&rrMX_K3@gi(Qy^->!=3z`yO@rk5spCUq?;2ucH>+*HIguQ~w?>bLgV|SCm5! z?i>g3~7Wd-5i{_4&}g1#f&gcpE-AH+Tm=lK0>}<==--ZVmkm;K_Nxhj8aJf@joz46mIZ z`kBB>7X_cfM;8a5!Tp@P4DWp-v`>B=*5CEn>jzK4tE!VUd@>B}Gw{?E!LxAZkb@7@ zJ`bPB3-IL1(0>u$l9%9(t3vwv1tQu`)+CU3z#uQt4=d3E3& z&8r6=>9`N~zvn)H``>dP!aXnd=Q{Cyc8vCWJ>&t~_N58j?{ClGjT=M#EyF#|MBn#$ zJ!u6$I0o?SZ$do`;jV`f-1RVqyB;R+ z&R0V|(}>UD1GQgwNs@E0*9BwNJs_Pa5uZ$-uWLXXoVedR=m8{|dD)z`ZUd zxW`e24|H6EdtK^quS)~&b!oy2-wf;0f@dxX-iCW!I&k0bx)JZeJC}z3`|#ZNf_vV~ zd5HG!SI%R&bDqM}I!$!sV=PBnJ+&MSk&bbYr>9_-T&Rw{3?!ldNA6{_%zu~4XfIDZ;n>n}8{to5bfjj3O+&K^78NE&!!JYFM?wluZ=RAcw=Na5N zFTacxI=)wmn%k;K>7nH{p{9 z2XDa#dj)U9{d%nff7R~czSD*Ky6(ZBul57D&o_d*pE3M@blpwizV4Rc9$)Hri*?x3 zaT@OW$-rGdS-9&b2k$&8g*T1} zUW4b33SNg#jt<^{cU~8~3HLg-;62qt2kv_4!QD?EzCryA;jV`<+~b?ke;?{&8SZ*W z^nKr7Tn|fd*Fy@P+Y<6g!)vDm&%l#<@GQLbrrDI80ZMfI51NS<1;aPj|i1&kyTfg#TXcormYUy$;d-lp=xryox-jh$@jbDfMQ+Vk&szdnXrr?R@?fjj? z61?}LP!B11M##){4VrgfV)16@K^2`?t|OshxdES zXuq4$nN`e$s+_KWW1~uMWI%XP8$v;yrjv?fVfQM0^PEsh<(t?+1?IBekDId)W)};dXy42uamnM9s;}+cO z(uR9oI&iN`7w&cGMZ6F9x(p&djQ9xdbs59GE)%%dWg2nk#C2Il`!{P{mj1lBzjB?6yVOM2zNdu`0U`2LmA$Bx#|b*e5&xXm5;~EeClX_jM_Kh+v^8j(Qywx z({Uf}d&x3WNMaz6Ez(b>JRHA3oFZ z0Pfs|aOXCHJGU|1xlP~&Ofe*GUz=S@ZRHosU1KuA}`iYTtnSd@cAy$8EUB-+?=aF5K;V z5${KQ5bYAqDTr)9{Hr1NU=a z7ViAFpI_&7J>=2;7R{>w_uunUf;V(rhPReNomb$6hX$|0Tk;w_wRdP=hiBwIKi8{? z_6IyH?4K=oMeEmrSMLhz*M)DdTex%X!y8A3@ekk~`4B#kkKqlCX9D+lrf`pE1|O;a zWw`rEbROsF@uc7-9jD=`gTj0>@I>d$!h5d_?Q?LCCr?*D1-P&4BK%!icaPWea{ZUl z{`G2Kf%|-}3-(h(`_HMLI^6v<;2uvCevRuE?ta>E_tSyjt$w<2|2-;wxceQz{r8*< z;r@G0MsWWL*cuUKjVXL|6Uz{BG~u(rEv%bl5jC zaOafGQNctM_}e_;1*^_GMCe&O>wXXjQx z`%f#kBHYgjWq4i36?mZ^#$Sa`F) zU3le=;O>v(>7)J9FGGtVd|AgMxc9L!-0L-gS9VQrTi+?%`OM%iy&;Up=lA%%?#bPY z^_f-sCAiO*f;V;x<4MC?@(jEq&%z524*lfdC3zlRkr&`Kc@f@_m*6dV8Qzgs;F*Vn zaaQ3uc@18W*Wo331749g;S+faK9jfMNh*x915e4j@Ql0%pUM01WcSej0G^T$;Tib| zo|BK^1^EO%kWb+w`3ydhFT-c@W3jgK(gD3Ya*5QM$zu&ff zJ`I2D$AjnKul`c#rwBi^7TQ=f5005C2{^jHd*zX#7?9Gn8io{=+ju|84lzcZU9Z@ZW14hwzV` z6Z)CJH-9?xvkdVva zk%Bun&yW3N(0(WNlZAU6d3aUF1$as0FT!i`61*cX!~MQ=1@8QPe$K0g_J?a;b+}(| zHsNC(x8Q^KKYUxAwBa*(2kv_4!d(wNcJ-&a3FxW_SuXLk(sHi0|0Dcrfu;Qsq$mf_AV(Rn<6=e7i|DYq2dxuxOz{5tfQ zfjc+P&*ybNIkf+~a?8U#jv~CR;}YDtmEq2<0`F43YhW2-8{B^kVY{L6GZoxYme;YoKci^5^7w&oW;GUPy&v^~d{*W6( ziy_?e8pD@%)_w~w{U)r}6ke0h;GWkq-1ACw9*^Ji^7-vvuN2yUO!G>^J+Cahtm7QK z_4_cdJiI3_z&)=b-192IJujc1^QxfzRP(CBJ+C@^sN)8_@_>D|T`x^|N8W<_IkpY= zKHq`Q)K3?lS_?ecvyQ!Zn+~df> zy^eYKhFim#3UL3ub|pHllS+~@1VCpsR$lMP{NuNi!FQ}}Z-%W%&(Sz4^ynfh6RcWwzer{J~U22aD2-v!UWTk;${ zzkB%HqyRtQ&EfN!68wGt61)O0zcF|X{wIA-)PTQ1?OX6?>bxEJiE7`4S9W;hwmR&= zzpBsm2JjaT!Z=6p=RF|gFo9oR2tI>9`sCor0~govqxu{w1wTanXW$=Io;mm%FVwog z->1(dEAaF6Iad|_hjm&P_=ohlWF7uoeNNYaZ#X`TrwPAH>)V2_orUwZ;Rin|^wWWV zTDf)MH!A-g{A_(5-G@I<^)rAU`^0eGA>891!9CwG{0Un334EjKa|*w95Y9V;pQ_KJ zm*Kxs`(&5Jx_XQ9UxGjAqL6b6{{NI`8h(fBAp<{6^_GPntob3~~fZc07`LC*f_&3$Q4!=UL30;KM3B1pRT&UIJ@M*GqfUe`L#;Gfhum*F>PJjt$$_1{tZCHUGmYyXG;MEg$~{!ZO@GVl+4B&=^1 z-q&@PgKtpX=HWY>5XMu0e?jY2gnwK6dZA;Rr|we~_!+K$_$u{NgP*HB>+tt# zy&CYMp5L1ASEAQ-nzf{;1_EAefW3f1NcY%`~zRFd`9r+ z>bf4ouhDfhf$ytvPT|Xb{(*l-&#TMumg+OvZL$8Jr2TUV{zO0jz;DrYl!ov2-B9Nl z_(AGF3;(^=D+j+)_t`xB(;9yPzK_mZgrBeLq6EKI>sW>#;r$=}Uw;09e^7N;gHK)m z@RIho2K=46zMAkK==yEJx2Ue#@QK=Y;P+@eUAXhVQ7rFl7|Do$B1^=>M-=*Q_ z=y@Omf4Ih(h3~5Sehz+)_TfDIJ<6v5e}nFiMfhj5FO}d&X})Fnb;_p#-=Oua!gn|@ zT<AKSLuG*fuE-O>B8Tt`)m*XB#plhzf=3w z0NzqRL-?~auMzy+8qXO174s-H3Z|Eb<4@Q$v>Dg0Y%KZD<@eP$W{PF)wt?u+$*r*c?= zXY_oMfSE87uVhSEJZvO@pQy95zj_E7x8?=3lT3yycF?r#48c6 zM!XjBdc+$MZ$`Wo@pi;J5%0oVx-NR~$uq+B(uY@`6?_119TbUOFszAMW;ph!5ep7lnRC zaQ8Eg_$1=fh|eOv9P#9#sDI_K6!8>%bazBA$gOFAn|W;O-|M@j}Fl@Ra&5 z!QFp3;uUyJ{Z!%Zrxx)#yj2YIZNO_s1aHD!&n>w3u{PY}>A)xIzYBN!9(;TK!`*%m z@gaPB{lneQIO3CtPa{5y_;SRPy(9G>@f3V}{lh)4OvJPB?e!0LKlz9kB3^`VuYb7v zFGsur-(LT4_fv~_ov!<71HOI#hx>lff^V;XxSwM?aF4Sa@m|FH5g$Z+81YfW#}S`I zd>Zjt#FrzUJS!KI&e#8e6A4Yr>@o~f_5uZkU7V+hXC+SH2M?4krbi^|e&qh2K@qEM! z5idr(6!CJzD-o|oycY3##2XQBM!XgAcEmdo??${A@qWYy5g$f;6!CGyClQ}Ud=~NL zh$jz^)PKZN5l=@v6Y*@sa}m!+yb$qX#7hw`N4yg8YQ$?1uSdKQ@n*zZ5pPGl6Y*}u zdlBzPd=T+r#77YyM|=|TX~bs{UygXPPo(}Mo{D%n;+cqNBc6+RKH`Ol7b9MZcsb&g zh*u+Ci+DZajfgiR-imlT;vM)VeGbxtZ`7Y37{H&Z&o4&stUlkFz&HDI7Wn=2Id$@g z#d^C@pGT+Q_vmw(4E#s>+%pHS>+{?K{CIsnRf7Lr{a4^m(|l|22b~)}&uYMbpwFRN z@G~@?4*c8tT(Sp$VmEyLHGt>jBls2i9Bl%>N}mVK;4jeUkI5q!^Z&8NpMrl$pF3yZ z*E)ar13nw_DZrnt@t5EaR{IM41!`Y|KUD1-@b4?n7W~Po&kp=r<SO|+>T~TG{5qXC*>^Gj7inEm@T)Yh4E!Ccs~r4|x_%4r@B4E`_}8?)75K|E zo*MjGeV*8WU$6RU!5^*9kvs5LssA4QI@Q$x{vy{u{Bc^Z3H*_&lNtPF>L+>BV*Y#R zx=z9WsCCT1C$4|^Nve|q{3_=Ue~jxN{uIrt27i^t-+;eP-hw~T`NLPMeUGj`2Qq*^ zL*p62?^He$cuwb?!S7Z3WWUAyZ&98p_&V*|8TiAsUOD)|{+u1Y!TUe_NzNZ$*L75b z-==Xk;C21EqZYiXoICL69-=?z0Dp=8yv+c<^X9Oxj^NK$&J*|xoIm_88h?^m%zv}$ zHU)o<);$Bi|CwPtIrz=Gj~Czv@Rp~9%?^$v-XTf2BMHKS=Ym^=bMV(`UoF5-(eqdd zezE>sO$9#DeX0h3n(x2xg2vf`zu5O*_?!Lw3BN_>9l)>CeP;x}?BsAAP2g|R`p)1P z?HkFX7xOP`eN*uF={}o*AFh2S2fs(xR{{P6owo$PP4lh5v-0fA}U{zcctm>zF)dG5=N0AO0fkV;T5Uz5l~AnpXk- zcGW`(evGc83Ouhmufb1M&JFlA%Ao~+o9+u8_-@)id+>(VcL4vDp07sm_o)8~eB%8F zevS8^$1djoGUc3tpP+Tgz)zOv;MeMTwE%y)_JI<-sdcHqpP+TD!C$R@8u07<{11Pp z_L&a+L&~iOKTy}p0RB{sX9Rz*)@uSE>2=u*{vKUl$>SFD->DV$u@tn;PIsvdIi z<5bTD`1`&8!^gh=!vCoLYw(G3ZouE-{Re)7`tQI`)&9_f@1h(A@Mk%H_|58n0^g+E zX7G1uoXO)C^Z%&wNx{GE|1Jr>*7sldoyx5Mf0eGU68vC4f5O`uPYr&ko*NtR533Ga z@Xxyb;ahe6_TWdT{{j3Xx=u&%Psk_mTT~}AcuV)w#hbrQ|E2K zPtx_+g70*1Sg#KJV>)jSezxzw@V98c8o`g$bKnGihOVy}{8qg_N}jlw|Eb!qQt+Z* zf5C54&N=w!R1XFCF3O<q;4k$37yfzW(}3@;{#)>!-=h5Cf6@4R@Y{5~58&@| z{_xFO_X+$ps`DBAGrs>mX)*uL$y4yZ=(^6pKcMmC;O8oz0{qMBzXbo7`l-Ml@BIgU zkMF2Y zrRU@f{56H z^y@Ep&-*|8d+MhJ-(S~J2Y!~GLwfMzl+OVE4V`xc-|4$yf1AKRrTfba{(QZ@NcLaM z|6JX#Q}BEI`~&~2`p>~1t~xBhclu!%X9>PZbzXtL!uvn`bvK888t}t3uNFM5`s~1u z)BfCpztpe4;Jax2Blzy}2|VNd2fmBeD|zZ-{%Q4-g1=YwoPobmo|h1)jCe#k5~H{{1X~a za=>E#H|V~Qf>$)o4E$8pZ4Q33`YFKI>bfq$pQZPaD)3wNy0Qj8Tl+=>eueiR`0=WT z4*Y2CZ$0>1G_L{tEUo(p{&|gO0>4A!pTU2r`*@OD%)hVuP6~dm`p>}6(K_bf=jnc0 zfbaM|?LY8$dH;cbKwg7CPwUlyKhgCMPv0BP+kv0w=YRNq+6M;kk7+z3_)Y%&9sUL7 zGlS3L%Y-(USCPhZUcTgoj3KS}#r2EJLIgV&Tp z0iJUG!#|_zx&r?I{8C-NE%?RirvrbO#?ymWweAD>>6-5de!SLk0{@=! zoWaw+|2|_e|Hu3O3%_0UlYw8W=hYm1U(Krk|E%hy1m9oht-$~4{U3gb>Z$=BdH;v6 zS3Vv1AGB}u;0@(GfPYKtK7xNwK7oH*>pp`&Q}a!pxtRZf+Lu!BXX^Pm1HaDs!!P&i zU-+Y4|M1H+&I?p!QZPqJMf=ry?XE~RJQ~8FV%ho|DyLF_z$#C z&fu4+ev)S`=KmGlFH-QD>L&xgQvKxMAJ%*e@J+g|OYrkF{tEnsy3f|&x2tX&@F%<_ z+-F-^zoE1v=U6&mLVev9Tc zfj?LK%nW|D_J`!zi}_#R`iGZw-VFQ-?Rz=+*EFvJ{3JjB!%y(*FZijtUTW}X>iMAo zf46dO!5^pnssn$DULW<~&sF~e_!~9;5&XH@pC|CA=)5!dzw0_m@{9SuMe|L;FH)Wv z_%qah4*pH8V*%dR^KA+KUGG2ell}e!e5C!k0lz`lT?<}UZXNgswO&2=AWZ|A5v#2Y-t`$0@*nsri=RM=75Q{AU_}4Zg$1a9?P^pRIap!B0@#cHl>--g@xg zI)C_c{QeL8m8!!D{6orf2A?_q=Pu^ILE}upk5N7u_@`B$IrvS=xd4CaTlD?|{8y@< z3j8u%7d7~m@&^1l+7Dasb9Mc8;4jd6_29>O|ABAR^T`PQTIUbnagR_ZGx*(p|L1v& z`A?NY3VwvHyA1qtT^Bj{ADln@5YtQw>AVH_ZCc+F{2raR z0zXCdR)fDr>(YSV?fl`-(skE?pQ-(!2Y-`t9>6QA^AY@G+J`6bf!}|F|3T}UykIf^ zH~aY?{wejJfqy`Cm4kmq^D4kgx{gZlcj~+q_{Y?L4ZcOsMGg4dm0JrwR=suLuhDpV z@SWC#ePaOcD2EaJYV89P_zgPm4E`nclN`L5|IKQjf*+;*Ap<`~o`avLIw`<^;^$9z zQ@K^(U()qegTKl7!{07%!QY_kr33$*>mPoX@4xUR-SQzfItGsQ(%Kt?DN^WHJ9&Y2Qx4zoGj}2L37UKky&=^%s1t z>ZAn!vg)t`zenp{ga1_hH{ie2^J)uzxAN@3Z$BYCkM-aescr}GlQjMj{BHF#f&X0B z`wYJ1`ag6r|6i+p3jS`t{|0}d);$MLx&Gk~lb7Hp`S~B7oE_@C2H&h48t{(lp#}e& z_LC0$boJANzfN^HfImdJjo=T^I4AH+y#K&=I5Dhy^1{XZd#b|}d`Z`P2L4;ER}Q{T z`)vWf(|#e(68s_RzXIQ=`$!G`e%-Gd@FmTw1^qHpTNJS^_syytp1Z1E#`lT)-eU2sm?R- zkE#xH@C~ki`0lQM_-5}v@U+_3;8&_{8}PlgKeXUyX}%r!?am*5q+XW|;NQ^xFoNIj z;;^4g;D7b~7yb*~zmmfj^WRVNO~D^8&%pQ9d~@)-v|a`Hj!W8q;E&RMp#r}~IoII( z`uPX`3te|D_>G!x2mUebOFj7Me*Xdf9X*GP;4ks>4}4dx%M89+_m||wi}_#X`iI}4 zeINrrTXmI#|A)p|fd5tfl;DrmI#%FEs}5`MkNEi?zPI;(__6Az1Ana6u?KHBfA|Ac zKO^`SUGEe450t|U{t&&6NQ#U3uk-Ui{At?XGVmK!Z#j5d`%(e^JGC#tKkL`O@J}e` z8vMzwfB26z-xhpN)nNzzH(jSa_;<9=4B*$R{}Fr_yTDq{V?ns8Th%bfB2Q(4ebl?<5hA}CKdKkdR+7CzYU6s!S{sWzN27i;Tm*gdj`Jborrr^KR{+WUAtLr@n&+B?Az|T=V zm*DU9{TKcW=MUec^=iOR(er8x{&Zb09r&MgJ@()a)&4MmU*zXc_=h$A3H(>yf8dYx z^XE$!^MA9(pMvl2`!D<~oi_)+ThA2*_}jJaCHRxQ|HC_~^BR12UGEL}4XV!;{2V z@cs|KTy;``*ZlkmFX}pKz`v%tZNXpc{U82O)lUz8l=>gQ_tW(_g1=n-PvD2Eu4eF* zo@0}v7V|$%^G(4o)O|SvKTGSCgCC>u7vPW5byR{sO?6v==jAo{MSlK)zg6RJ!9TD4 zssq1L*HI6Cu+BSxe@i)x;8WdaC-5VkKYWX>ucWk?{|;Xc&y6YgC7N#r{wF;b<>1FT zfB0C}MG4+k9ai9cQ z8T^l0ujCbr`M<^YUwBsgSO$KL>L&-kOY2gAAFX;Q!N=;S0zW|Kt-=4UoEz}2>bwR2 zp6a#(-}x7M{~!J)@Bi>S{QM7p(#BA?6ZjWZKQnki`*!lm#r)sp{NZ2GzMX+TMC+b| z-=I7T@S9b)CHS4HhYI{@npX|}H?402zE$N*|3AE*1*2>wjvHi6%(x|+e?t@~7R^kV-1qj{y^A5{Ac z{3qHcbMOOysP`Y>7ptyH@SDB=!1r|i@V{ywXuvO5eYW6FQEnahp{k!A{BW(~0RCJ* z|G?j_{b~aLfyOg~AF2M6S1;!OC|!>!_&&-Z13y&b$-xiT^Fsl?^Pyo~O7L%MT`KVJ z%WLqxweL0HANTVQ{Kv|n1OF$#{{#QD`We9A?)xu%$6NINKYU+}a|ZvW_J`z{#rz+u z_9^(ky8huW_VYjdaazX${3_Q!{8Ejl0>9Sz!J9GJXjG5>#3ou}YUU0)ga1GVls_;WPQ0(=kcwm~*J%S&v`^5yl-p~K=k;a*n7xRC< z_U#nBrhYQ;tghc2{1(+u0sdrNUnTf$YF~kWMC)FIe?s1XAFX}01;5_=5BwRffB5I` z4g1LezDC#A2>xrW;{^V6??3R9RBy>^7xVv%a!A2{tNt_amwW$#r*vHu;D7h?C;Vrs zhYI{x%B==JKxQ_Fo9pKJZJD*^}LrHyO{rT{QLvIP_JJy@B=iS9Q<2qUw}_l&n5U_ z+V?8(BlLV&gFnyp55LmyzrhdDygKlI)_&51|H1VSKS%rC2)@%t_4*h7M!)|6|Agux zdHrJk2m1aCKh66;{0kaS4t~7qumImh<1E4N(!NoFpQCZs;CHBg8t|fWZoyxu_3FSk zYai~xZ+HIiqtyQh{(V3H!2hao&fvQ%hvc}${Oek;6#N>${)Hc{=YbsjZR)20e~-Kb zKi$ti@R#eluE9T~ej4!ml|vo2;Kyp59r*e8>ir-1&3Zl@z#AIp2!4$A$qD>SjdKS7 zg6sbci}|{`{4jy)xJ7{U#@;8@Uixd8T==@ zFDI47{MYDtAO(Mk_RkD_PvxA0r&JFG_$H0N1RrP}EAR_cpEdaZ$EAIT?=Rhd91m)i zikj6THbteSX6bFURtZ|GMQYZLy$Pje32LucwYgDy#a1(BZFlW0R#D3D`njLyk39X+ zKOR@EbDzA<`@GM|=i5K9E~WfG?7K=nGxMzF|Ks_gJmG%T$nW4@(aN_Ww~>5Ca_Hp$ z<$Q_$*w26MaQ@2I#}oNZ{BA10mpljZ&B!g2&%}9@%lBmbLOv_+Rmx}Jd%u#uL><=h zi5D36lcD?oez%d&#yng3=Ar+~*Jj^!@*AkDXw!cF50hIgzby2B`R>$nD*p@fAIP6% z{7n82#?R&dq}~eoY}9ip-(# z+N_`dy^IsfzvMhhj z`XBcn`8Mp+MxOCK(#l8S{waSi^ndvV?2Bmge*SY&C$W5M>LihG9RB_+Ux0B2@^z_) zOuhx<|4|BRedc^kfe<^RGn`6kpuF8@CJy^wFjI+pU&xNleT{rP^Z<=e6D zL-{AvZ6kk!I&b9#^*NHSz`A$x(^;=*%YOdPP(QK!OXicvU#5Oi`F@-W1Nl7R`&a%V z>ypcV5b~ElOrI>}2lKm?d`s%KwzKX-`Dx+&m9I*kt^6F;eI#F!`t0N*a*np@=YKGD z63d_DeG~aw%qNv^&-)JKQ?u_f`3!h2pPKz!$oCBQPx;}@zmm_w__cg4d?-JXb!_B= z<@x@SuTKsm`MK12Ctr|rGx}3M|6R!~mTym;B=TKC|ChhP`VQpJvyPd(BDY+=0QnU1 zS3>>E&!KKB`9|UIZ}KGEKjoW{XCq%PoWJrtLjRF(9_~N#y|^btTle!nDb&CGzHtAP zzZLqAykz`=dOKmQMT-&p=g$X|X6^GxN7u)hZKy~!byzrs9o`Q+qT$lqsw zmGYYTRPv5GspV%;rW zKRVREd>{H-Cf_^UKjpK8{x9E>{!q$~V*ZuY!aMwt498F>baF4&ianzpR!Lo`RC*oZQsxTYw97EFG_BS{5o<-<=-&=KzG+czmzO<%?2pg?y6z$9=VwFUPu9^3B8fE5DWb59RNNzdy*obIJI3TlrzkeRdsa;W4Bl4mX7o^xaxqL77Q6b-j`#>qblJP6~4D9z> z{%)v$`T5jUBR`&g(#mh9ua4wPvM!x`;$6o3E83}_|02{$EWb3=zx-q7pUO{Vo&)(( zjGxI53-=#+8S59RqvehlN+@-NsgL-|GF?+@~;$)S};M~w3v$(Q4OJNX>c zZM1Vg|IfqyUw$_GC6Uh`>R)~*^)Qg{%6`w}KjVDK^l=JM}{{x83dbt&Z+QYV%CR-UirXR}X-^7TUhmoLt`wDKv)Z6sfo=R0{xp3$!T z{3m&Y{N+>OiTn!QH)A&G`Hz@SCO;wcANlR{ zlR_Sc`j?+hy;brFP8j#STD~WBK9tYPx;OIs!u>}+P3ZsfDY<8M@@wha(VqSMA7T7h z{w#ejk*~!0lFDyk{DJ(p?4wLRC+m{SpAP+Bz7h3Y%4Z^nO8$F(x0Y`k`oH`V>aCGa z$2r)_x1eu~iEf zKPG&C$*&K8f0G}?`B=*j3*W!;+t|O2{B`Ep%I~7yM)Kd$S3CK+?9*uPe*QmU-^KFx z_}xVQ37*PdCFgKj!k;=;wv}jL?7NS8$)KRPIR(1&aJJgo0f{vhww$mb)UR(?A7w~>6nx^(i>S@&rFe*V7*{a-#S^^nMi^vP8I zMaWadl+AM%&qz`!-4!qtXC%gW%&M*AF%v*e--k-h5Y54(dR4q zg6xZ0{ujm{$|tA4HS+(ELo45qx*EwZCWlV`JL)7lsGt8L%rln%f#(x>N?oP$^QfPJ zJm$SJ`CrK)moFCXpYlEF&!v0|?sb*Cp$=>LT-5VWK4ZvV{wwOFl`lnZBl)&(jQ{S@ z$#=%1Klk(h5x*PDmm!Bl{y*v=mCs3?59I%&FJ@@a`f$?{7*dJ$nR$#wekbP-#_Jl!8>`!K8+6T z=l`9*jQ3G2zmhshn)OjJlfagp3(~MuqSLK|o9?D;5{*8Qf>a&$^Ob#RYf5^F$|CMtgICzDS} z4!OJx`OEL2@0Ie0@JfC<=Tt3!k2)X9Ph))>c}mW$yx_e?^6zq=>Ew%t@2|i1^M5{^ zzw(_z{_-DiKTPH4F#myk+fe`VWtmSd{~!CekgvqPE9IYr{x9E|->v26vMxjUE9B6~ zcj6px<=>f{?=SgUoI9QTQ~G3dbU**Sm}e}1i8@c@_i!Ib<W%7h|%;l?- zb0I&JeNoEqp?_BLhnQzAKbmod@~=4;8u>$v-^#xV_aFJR?CVbcUDhi)rl0>x)L|@t zg83)%b2t}L`ON(8K>mGxH!ry1NrQ%OD2DUzMacU_G2O6 zZ~yT*Tgva|+^ppDvcGEiCgJZt@|oDbjeHr_x0P?m`;O#4;deXvspJ+N+s}Vb`dcjj z3*UE%d^YBp$`2*Cf&5@{%j6SHJMN#k{3p~^A^#)qTgsmc=dXMZ&c|AQd#Hc;pBTT9 z|C~Hq`F)Htl0Qe^=;SZs(Q*C!UuOQX{1^OgBLAHIlFBFknD1ZtTa2H{cjSF@dB^jG z`~d2tlz*T5MkRlQd}{f8?8l*e1>U!j|0SHi@-5j%Bl$(#Lpu49)M0ddKmYxylUV+G zIDh3=(uY&|56EF4KY)Ii$%o7{m#<0=h5SMKOey~>`Bd_CI7e#vb^Pv7{u%4u$akks zTKRG0Ig+o#{h^cJ%I`*h>*qfubsNhc=Q}!)Umx<9AHq5g% zFXiu1CzX6H*0Gjfv^?Km^7TXg%YRNkY~=~{Hj@7(oWJtF;?W8H{1;|F#`1rM{N+!v zuT%LS$#Wo|f$zXo;A%FQd;r=iG1$C9nZ)1NI@)dZ# zly5@aR`M?|8rOL(-;sF^9%A`o?2APH zC+aPg&l~!`{6gv{lOIo==ko2DXCa^P;&Gm({IA^8D)|-sZY}>0`(h|xi1|12(?kC9 zwaINHe~>=e$-8j>Ke?a(HuD_GFK7HtKJg3mpHuqzckH8B{+&n0^_Ivd zNyeVauch7w@_pHlnf$ZR|K)$?`9gjn?_0{h4Ck->T-{5x(`NiZ} z%Gcz*D*5b;U(3&7{zLiK)I%fRAbkJIj}7_D7vena9W7)}@la5Wc_U&(c4K@|gKF^3UiGt$aqF zAIZ-m=T5#W`zt!VpZ}}OGnUUs{Uq}5K0^PIUqoFEsa;y;;a}`amgPhI?8if0N&>{{ANan*CnLf57;q`~z~X zjCIFB0n{nS+}pEl$#KZS8R`8nb5KWF#zAB4X@$ma_EN4`9{rSf}1|Ci4k z?mu?wEtlWMIatVR-m8=+^rcEZ5B;r{FGGJA%DViKb(D$$$!nd=knQEuR^{exs~z-LjRFh{BA8@kiIvR zug3U|{BHJLD_?~=9LfK|dUf)x>7UWL{rtD*9FOG>QO}9|!O;KZC-J@m`C;^jO#TLa zCYP^G4u$+Zo-gG`az0k_=R*IHZxH&AJR{FWzDBtJ$S11<7az*^C!a>1vtL^IUA)&wz7Xe5C!d#dD*Agr|L4dpmcJhA zUp^oArBuEveQY3~gq$<^?BV>C-$~Aed_K;FQvMU(tCDZQ{;K8wrf!Gwuh`d(e9Mr( z{5|p+$@d|LPChgDrRaiw{tvK@vHXru|MD~G^QrvjxG$@^yV`9l8km+9Mue0y>z z<^N=!mHY#Kx0e4Wva{v$t*b2FE}#rqcW`It{B|A61ETM|hF7s*RD>8m7za{+p zmwbW)sDJsw^xNo?e*Q;={N=~viF`4}N#%R8?gRPPtXC#qCiH*#F?=r+@)Pk=J~=s8 z^6A)Lwfq>?aVS5ZbFGmdPF=O~19;z&{8V!5ME0;NDjGtVfuU_pZL;oU6t}5Fn%RpGu;2>r-%II>+*af{~_zx%ICmG^2L~c zC$Ct?=(2wP^D|B?pNxH!$WLONRK6SM(Lg>5=db)t>LHh(OFo5s%84{`1f+<)X3vfo?zGU5D{uT9_RsjWUzG3Mk^G5p|CCS9zKe$a{C`h9$MS8dt38vgzxUx9ow z`D4^!F5iIqDdca^$4dFqNAyJO5YgBufj9= z`_ywT-zwC<{L@hX@)tR0EBV?XfB6l}XDGih^dI@moTsgP2j)4Ff5kjI`6+zwM_2dr zuklzu(SqZ1Fp*d6yHx&j>T@99jd^DBP3e2N{0PqLLjDr{yp-QY->Bqw(+_L;gZNPX zINr!NU>#feDAd3Fb>`X0*9!IjkAD8|@q8@*E9X=qe}msm<-Z8|%eN-yOnwP{AeXNe z?*H<8$hnkX#WE~aB^H=_B z`g09~s@;!Lpq5Qtkf8;xJ z4{7B&`(h-2mVMO8pW|K>UE9z9lTiQiW9b8l{5jS!mCwn!KajuAe#zvo(YJH?1@yy0 zz5wf6${YH5CEuT%Yx!Q}Ig~#V@|VBN`?m6blk-Uaa=3rWFQp!$>-zcsjQ5J=Q*dud z-+hy&$$rGZ)f~Oeh77y%CF!%aUkC}oWJsp_s!*BvL6fi6x_#3 z`JWlTl7Gs+tL2xozlQP;!uc!TAlyIY(=eZrd=v8Q@}olj z@=#(w@e>l@3rWE~UvTlBY7egt(mkk7|?n#n&4^)J7LbuZ+r@ts)8*TO6L z4!mzIKRWb(`5pXjBVUz%*vhvjpOL%^{a=1B=Tvl4KmTdy^Rav_em9Zdz3AIH8J z$WP#XGx=Hc^IZNl>r%*f;2bIC3$wnJ{3*`UT0S}ZVkrMR^J(Otk!LGkpL|C0T|@to z@5cP2oBR1+6TZLX6CKI-ul%3XNh<$@{yC7xyl*DIncvOj$Fh$K`MW!gzXwbC`l0^i zCo@hhKRwjH{1ob^kx#>YyOqy`kL3H3b0^=2b%}22=igAzv3!by_}ANii) z{JpiG|5BWfv3wuqpU9Wv`BXj#-(T`q$vKlx{LOfO*Q;)zoOgw`Tv*kWBHTxw?sZI`zw_X+1CU4vAkC% zpXp8dkNmQa$6m;1W8anX=fd9~!kNhO&Kaju6x@7VN znP)D)otz8#?2)U*5v#67S{9*2Inf&kMkjv*Ihe96hI_^uQ{3+hIlD~l0@;k%%D_@m* zZsc2XA8zHBG5?W#Eyn5OGqR4+-TnNR4*AQs3H2|(l5;bazsma#|HkU8Y zdSQGU8|OkNzn^uF?&;^hC%+rZ zKc;Ua@>R$umG93u1NlPSCo}m{q5sIwXWtd_MH#1*AHwfe@=F<~mT$tk4CM#$z0=6I zR>Jv*Y+XE|p(TUmD2&$vBz(QTAgl|1bNlkT1wQOL@!qmHe0FR?GJ$hoSsw=F`ZR zX5Y2)naO!1kIo$D)5*6D=kGuJ`9I10WBKzTfBB89dn#X!eln2H&O9^uBIKFNU*UHP z`C|0jQoeJjfB7WEIEPxk6u&!^&&NC)`2GWmVfPcGk?K3~Y! zWPMBd^5On3e}wUC`HS?Kp?qD&Z{+u|zO8&O_T5OnI{9?++4$a%9_;78HqXcMlJOJy zQ=$LJS7Cn*dq5kE2v0jz@XvVMQ8S@;8sI0{rtD2K4baY?B7H_8T~Mo&(A&@$Uo$FGxTcUtjj=t zHT9XvSL9sC<&W{Zh5U1JDCIYX`j>AM@|WMv^F#TCkgHd?Eij)W3Yf6URAM@_n~}o&WlF=H95EP*FycvpJKf_`NUg{ zzvH6E`}se^evjo7UpIa}kx$7usXQmQfqbiwzx-p?H<#~rz&OuBz8-zPlph`LpYnYf zrw^IgJxNqe0A2ZKFz7p$I%9p0kSMt@v{a=1==>PJ=n13U`mU*`FSy``<{Fre5%JWeF z|LW&|5%Y=VAJb0~`7O*RmEVOAay{J+#~C;uVyjGpP| zzg_4*@)OxdiJd%C`H;RnkRQr>W%6Hz{N*q5UWNQMyp&(Z{;KTErslV&MgNgciZ}8yEx&LyU~mN{HLa#WBC*0kjQ^Youu+}8Gj%@A)LSRsd?XA{vh>G$VaSWDgT&# zTFGbNy=wWw)YVWv1OML9$p6dut^8M^|I4Rm|90{p;n7R|{C8u2#qxFdeo5pT)5lWz z5uyI&i}Ajhd}{h$EX> zM(F?YiMAf+GnB7K&W-#k?gOp-ea0Wj=M42PpFZ^efA{m>nBR@%Q&U%o{0zPeQu%ZA ztATu9_E#qVG4ssjzof4g^2bB}moLNqs^nX+Piy(noEJm+iPTRcUygf4D}R~y9m#*i z{5$#l;rr|5e*P&Q%l{m{f921TLn?nOgK5FDY59hD^7x+j%7k#OdFCOxLrJw&L%rllZ4*f^oh5Y3UFrQ35 z1N$qN|Aup|ke|*uUdn&V^Od|{p0)fc-ghX!0dM3duwPpF3L$^_5!7ubpN4viUhU_9 zGUq}p|2K7>$hQmUuY6h7cOYLeoWJrt$S0Sd&);nd`H$$IrF>O#sN_wkfBB`1Ka}4a z@|Pb=o~`^h^qG--3;KB{|1Hl)ul4hvg*uGoGjKmi7Wel>aRBANgvmdo5p*=ZEso*uRZ@CF-h`|18{pw}Q>l~a&3^t1g#II6lKqv) zZ)9Jj^0nFT1No-ZLneQd^Cg%6n*CVFHz&_hz6*V?lK+u=PA&gC^)r<3%{Yzx-f;hy z&&hc-l3&Ssb@C5*-{`G={-5$45zDWnt`hmf%qNvUi4Wx0GoMWUGuAzqpU!=wkbf2a z{viJk?^VgyU>$4u?cw~De;xY2{P~c-{2%Pck$fF~x0AoZIMLhv{5PO(WBFy&Ng_Xx zI!Wb+h4WW_EPXYT|Csv8`7w-N$`_!&Rd)J7Ex(O=7|OS1pEmOUvA(T*5YAuu zccvTvUAmK>L=MqA{rvyLK8@w~h4WXwBz2q0x1~P}OP&1fQ2+1t^DkKUSbjF=MIyhC-%aHo zhW;Zzk-E*~|KT2z%je;IEaZ>yyQO>&=3mJdVw_rjAoql!{3_O~k)Of*Tlw|$=aKvo z>b8@w6TW}n>*qfO`z4ltPM(Q;;>*T=ze?q29x(QSd_VHZcK-cQei-|rkzXAC{vf{+AIWcE zA9eCShWh`YpZ}E9b1a`|&G9@F`Oi4lQu*8DJdpn~^ndxm?2BAJ75NnMU(x4F`Ss*b z$^XRr*77Y`m!bRx>aCIQ$T+QhL+&{v`Sy(8$zNsssO#rH1v$s^D?G~ zxt~NI_VfSFlH)p$<mJL`;&&7IFS%Ew^6BW?1NpCbK9m2M9CG<3+&2pO-)yzJ#rf6BCyB;=V8d4fQYI zBjo>SKmQY%e=M(f-$edI$X|X2;|%2AXP;*BlNdjjpNSXp^BBLBmz+nHe3FI6^;yej zr_P7+5xF(;-*E40M)Vd$NW?IGok;>-{8G6 z`43o^Tz)lmTgdmPeoFbl;r=7PG1R~OeAag;Ux@odBcG1kTKUH0Gm>w{e(B`DWgVl> z`uYEXJ`>At;2cln|Kxs>$`@u`2J+jJPY|6pN#K~rTl4fsN{bPe}9%wMPC}q|HSi+d^(~z$Xn{Olm9UEpD+6P z&mF$MDtVf}2y&*;DX{I?1HM}7(CT_WF(eU!>iXPyK3L!3vM{I|Ss zE`O7At&q<_{gm>jsJBXf9CcF5f5$!=%I^*RNB$~(ua#fT`i|r;g!-4?zbsfS#?4E?Z>??`_rn=9 zpF(~hUdlgUzf|&*=zFz%ch+$zzl#0d$Uh48FaI1L$uD5tJNZKN&**>s{BI|RSpEck zBaz?7^QnAmavsPZrq5^cuQ;!B`DX0LLVgN$Sla0`mHaT)tCml67k~eeAHaTWuT=gjeQzLtn7*3H|3ls8@^=`gkbi`i z@<*tLO8!5_ujSu)l>Fsi@xG1xIqI;LAH({NQw@hAB&$;|z>Z*`8;rmy95q+iG0pUA%GMmVLjJ3V{N>SM<8c!CBCJa)-!YuO^7p8>OuirYkX(K* zeWsA_&iJK#Y3_lQeEyKX{CVnpDF16Xf8{TS{x83jb8{qLp80h0YgqSaqJI8wF@7vx zg>e%33*r7Fe~|SZ$R}BGypEatVRFvpQ}KKue}&vi`F^ZRC4Z9fYx%)EKa}rDe{1At z&`(Cp7V2OAEc0pQOVI~L@(Z}nbn<_P{xfku|NZI1u{VMLH{)dGABVQ%tFMo2^ ziKGAjFO`2r-45jMkaH&gko}U&k0hT$zAk;Hl&?I^5;0e68S5vZz}&h^dET~`j7m=(EsIcGkzgInH);_ zo#a-@x8;3n`3Ai2P`)AOW+R`M?}%1@1@j-tzsEV-$*(CU`L=GO$@}>q73yDp9Q`Db z-^@6v{D<`0f&4l4OC~>uI?3f%(KibD9E@Mek7xds{1oP2%Xem7hVnz&aXyWFM(U@P zzf66O%tC4(?x5oXslP}Ks5`DLy|4-OovHVWz zJdyvL=TrIpJU@^x%kO6Lx0rt}zn}hG$k*gND&>#zd?jBge1FOB58uD?X}IS!^6BX( zt^7~a=SZGXC!PFw>LHr4pMT9hjpdIr&qO{I>zm5gC7*%3qn zk#EHBR`QPZt>u?d=R^6wm`@}BQOI9@5&d%{ABOW+ek}8krt0Va2>UCR-$EZtoR^V|B7`P%8%r|8hOKdweslt@qK9|pEKM) zypXurk->8XW{-Mzlpw4${W0r z|B3n6^0VlZL-{%6(8#}`Z?y8S!~Ih}1vzx`obQfkntuN8hWeM^%ep7>v+1{~d?o5| zAit33Gx(8l;ia+<@bjCQp@8^FU`!1G09P*c!q5kD}ab6GPr?%sLn#sRM&bj<-o-gEI^Y@KX{v`9P zw4?}sh6W?F*Q$qijPapb^d3e}LR#`PZC-iTokzEtM}y ze;ddr<-IcbZp<^6|ByN?(;qO24i$edA|A?Gh`GTzDNWMAy zrIW9~y*iq%pZ_QH=UBck`6Tj1!u?bJ_i+D|PkhdJzi0A;LjLkOLjLk8=^LedrjWn< z9qu8ue5p|X^2eydM*dN_f6C_!^)Ij4FP;3yJReQp&wpCpE0(XydL{Cic|MhI%eoKb zJL8#rcE-u&%aKnZKb-X{!JSTkJHZ^`S;krt^CK_b4KzZ`>2!O z68g^!{rp$vePj7rA%FQ3q5sIA3H@KbSIA#}8T%!df60C<*4&BFG`)a@}tRfB%h!ALnq&odW&Z6=f4zv zGL~N*>R#&ZE zd_%mI|2uqt$=}2~`O>Uo^uvDsKc+ro`CFm?%a>!lQu)gGKt2ySXYymh{Zqa?^DN{G zF-|FentoErPoO?)`HZaZQ2r9*H1b(_zLhV`c`=f&5cT@LjDfj$Nz8LEj&C<_*Qu;$IKb`tX~ePj8$JfFz7 z;e1TxlQYkOJjOHmxvXz4Uzd6=SRq~Cg^IE=i=s)s}*zb*eamH`uS2E9$ z{1Wo%Eu5S`OlF=|Nq})-~MkDEl6&$d~eQ$ zMBaw{<-g~52lDS+%KcwnvafUbapYOZx1}CR`E=Z`D*2S;S<9ae-(T|m*uRbZr{vbk zR}A-0`Jud5C*M5u|2dOylMng-i#%ibDddpIuV>v;`N5(8$af~UOui-cnaiKwdi?jO zLjEGTmGWuHxsu;`*LdA)`FE*@p?v00|MCYoM_T!&)Z0kDEjf4c8`#&;T>boaXFtaB z_c&h?`6BF>RQ?+I4CL3-Co}nh?5|wD1%11aZyox-{FCtgC677pYWXUm|I1&b4jcJ? zDlhSY{LoPU@}oolk#86Bm)}o4 zm-0IFfB7<;JGFde&by)fU~+Ec-)FzH@}qdKk^Eh9>*OEf(LDY9f57;$e0KI@BL6Sr zr1F(m_ksLN>NAtCNe;REhxGG8{tWe2%5P(TRq~8FujQ-Jw}R}@SLc04 z@<*wYPCg%bM)UUb-eZhy3LqQzxl>S>`j4pFp3_780Mt(#%f91!A{x5%s`FHZAxVJ<Q<MEAkcp|@;d{X(@yw^bfNBTo1&qM!_PtCq4A==gjY$k${YGx>qMZ!W)(eN@PI zA?H&5KKWGg%js{md~Wv9P(B0uu8}Xu^R0Xb)^{ZTML2)uSMXlZPx|@q%6^IEf8d^z z$fshxQuzkd!$AI4=>PJ4!rvd{hlTGi`IYp&QrhWo$#MEX)LKLao1=i#ON zsBr$u7YXOD{Cd`LC|`=_8~O1efB8D${FOgJ&Yk==em6?{`9H_=vHVniH<7QzJu{Ub zPYwh5r}T$Rz7+MA%U{21+{X&}GW69_z8tw#@|St9TAs5{hw}IM-A29vIk)n|sH>6u z@NoXhPbHt|r~Uj_C(l^^7V}T!mr#eP{A>2nK)y2hWbzBRU*++ke?Ltm#-W8k9_7(|MFYN zXDDBr`84v4sGnAT7W-l(znp#D$v0=5XwiQDZ?hj``EvBs-DO^<2nrCg)N<1@BwQcjmon`NY}y zd>_j12>o9^@$}>8Tlr?(&qwkN*higwvPZ|`M2q$F|1i|Qd}^LgpE{}K)39Dc`I4dk%NyRel|M#(j^qQz>EtJpPqcVH z|0$`5SbitSrLIoj#n&cib11)*_if}?v0knGPxOb8{4)AOCtsNJC0eGR|JD3%ET5A3 zC-PIsA(h|AI0N}Vm`^4@nH+NYh;=FCv(U#%`CIhoN`626q?SL*IXIMejMK>1X5Y2) zrx|A?|BP`u`CQCDTDG76VysInpEl$#pM&~LFIq z^E01H{utxb@&(9YD1R~ZANdZPr>%Sg)@3B$hVQaYzAooi^viz!yKxVX<+Jm?iF_e) zPUUA&&jb0BoP(KsQO3#TOXG!n7y4T%U!LbH`O)F~SN;b3Ybc+cdT8YP?#BQBM4pHI zh$s6i08uatO7mww;vF?d{@lgNry~F)eel+J)CVz?jo6CPjeHQW?Id@9=j`ZP5 zzB^vazotJ7<+q3aFFzpEzx?&kf8_h|yPf<##*dck=l^TImt*-d-b^*NM3MLjg~gZSN6z9jdwk^BY5>E!z}&*<0v{9onySbjKlmB>-xaT+WCqwiK!+9FZPhwpXdBwdhmA@VG zm#;`4&g7?ae&zBzsGmZ9F3*?pwaBfKFHD`(@;B&{L-}gVvyq<|?*H;L@R58c>aCMM z&pwJ)?dN|v_mEir68$rgFBa-wzBcn8$XDgO&gADY|6INY^DpGTqpnK%GR&uvpUVEN zo^iMq<=+lTz+ud^RZ`IM|nC4ZZGtK~C= z`;Yt(>Yo|eyD%>Y@z?lFQngg@>S{E(VG4IucXh%@=wC~E5DTar1A@?tAYGW#>wQ{ zupe{z3*=eIujY44`O2(gCBGuv|K)4){80Wc>b#Lp%DT7mOW1cK`4!~Q$=?ruf6My$ z|2O>oM?S$%#`TuSC%k)n4yN*XsE2|4^U#0fM}_lOej_;-@^2WYl)oD4Uw&-(`@j5n zes?H8iGA0|PiCA}{vzu&lHbn!JNZ}i!)UF3{xN+fmQNP)mv7JhP33#hmj?11*w>l- zRPIZ;{7(8|A%BhcE#)&Z|4M#ssDJsC)bmh&YB+!84fAj1_mT5Rem3KD@@v@_(c1m| z*JVG(@-wN2M7|S!Bb7hGdVQu(yh)j)nB^^nP*V!d+t z)!ai0`9{=9DW976s^s@lx3zqsaQ~D~^tW-pZRE3ae`w|J(Dz32t2h@r`A0k-{kEU~ zuJn^wJ_Ykhl{Q1y-{|);2&qF=L^1rg~iTqynODf-=oCosBsoPBcH^$H9 z{|NU_`O>^^DZib4RLM_c{91l4{b49SgLQA@hwxsl{1I{<$zNtacJk$zf3#sg|DzZ` zmhZ|rmB?qOPEtE{GLT;z?w|54soPxsFzZ{$7hH+{BcChWKjlw{`;UAP#u>`z3-vEQ zk9BF~S8&f6$tT!(JkL)47V8rIzMuas0uX0Z1@|!~ck^hwWl=2%w{_{v*GS^DdRY zLp=}VOS3OB`6J=}DL;ts+(N!)IDh3Y^1hY)aK@?S*V4C#@(-DRBY%wk*~*t?UyS6( z-A(_OXW{;{Nk9LNb0L;L!|x{YgIJeTzApI;a&pFMBgaoABX-UKZ*CP<*QRy zL-}L$;YNNE`=XVay6FaMl=Udvx({|@DgF@7VTpdRPY%D<*=NAgkV zKk}9wqRolVJ{7lBl&O^8cgLKBKND%Kz;Lq)Lm@tD#AUP?QcqdNniwK?oqdZF&<3 zRg@OG^a#jJ6GD@!Y(Z%XZlv8RB~+;rid2E;`LU0Akz=htFI>yd%9=g-&N=5yE|{Br z)XKkPKX&pRL;v5kpa0?H5X-L*{YU-+`!SWj#rH@i|2KUgm*2wr7V?=w{_>aE7p447 zyt1<&Yx(u;-$uSW`3&U;Qg0*qZS>Dp{@>95nNX3po_>6~p~gKKZ6oeBDn<`4Qw($=?h4%co_WM!r<|``TOkC#!mkn%Kt|_kL34qPj2P+urE6KXW{P;d-U`F#*O3iC6+(H_=$Yg z@b_o=e$;s;f0S`@`G?e1Azy}d8ORTzeoFaW^s!3*8RuOspJdu`y*2VLSjVCK^HBft ze^L*v{3zzx$-m9Mi}vj2KRxGTET5Krk;vzyKd15&_}xst7VDME4-EGo`CZ}uDKEqQ zN4^;Qv66pEo!9bX=~s>XwebBVUxx8V@=w_>t$aT6>EzKN<2sM_>gPXW=s)uR@tu{( zCp&gLPAdOn=s)srvcGcq$>dPTSLA&M^5?ndl=4@?{YO3(bzaLKVxEososhr$8{3Wd z%Se6-_14OtWWRUvjB_p8yPyC4^s89D20zoI{<@=5O+uS+K1Fx0>NMfPtY-;C!6 z@;RuJQvNsctmNM$w_5%^?iG#vb>=gaPqG{N%QNbvm2boRJNY!!ZM086|8Ir6uR}KZ3s6$#>(uqJ8`M9~-{Ei--di*`M#`IB407w z|K&sOnVCG}`CL8&c^2{wLjLk^(N9WwM?F;XX#R2C*78~S-A4Wp`+X>1m-&z6voW7m zJ|(}~$qx?oKj`QGNa+9a^H}#pzA5>n^6B~AOnw3TD3_l{JrweF!}pi`J0XAhA>4Z_ z`NynFE#I2?H}cczV?+7hSeKD}=ForSM>3yIJ}3Jt+P|Ow)SR2Kd@1&CB2U8oM}98z z&*a}Fw_LtZ=s)ruLjB7V`gtk8IP`z{YV?6xz6tf*$am#;hw>S?M~&obF@7t*gu3nI zo6z^71N!-I$T+e566!ONKh1q3m0uC^mp@JYd|G)yKkwwnu`i+n`}xltzQ5#i(zyGswzs=;c zP(Qi+Io73+Pax-k{7d>tDZh>!D*4TLEnkv-*T_F*{zLi9^wp94dyLb{=L-E_eiL;S z9n{bNPUacQe-_SP`77j>%HweVmp{jT$>leYb0J@a_ZrCWVE(0iH|njDe?^|P{9eXs zAL;2(%jlXL~@-;*L@=5j&p9}4*roEHOm%s8d|E$X3?4@3RSzsI@O$mgOD4DFn=Bl&jpt5&`MeWQ~<5bFPs ze*O=H`j;O`K8bvD)+?3I66#+*Kl9J!kN^Wh1^Q{wA5!M|C~Nw%V*^I zMt(BCJCsjko+J7Dta~fpH}rq`+~gA-+RuMJ?nSZuLGJU3{882`l}~XL{a-$J=>PJ? z!u?K`jm-MBP{7k%+Z@@U6{Kw(%KZo`6 ze}VOl<;#TgS3U>zlgg(H`OA0YcXRo_$hnYzN?i@)cZd6r{5sa9lJCy%*7Axz+{oXe z-wx&H^ZZDDFm>L_H{e|8y^nDquz4)GI$|h z5g*7WZO7+eDL;?*s^kl?uWR{C)JY@1kM$kOqrEtP<@>R|tvqA?o&1nc|3~!mUzg`& z`8?#D$TwwwrSk7^E@$$sc|Mmf%DGU;Ut>QGf zc|o3~dl_R&Z_HT$uZzeHc{G1t4--`J}NB8r;lbmDuS=3b` zUxD#c`6?lQ`Ss+M%Wq=-g*>G`2l7cD7~gM8`SLtp$=3|$uYAb+Hu7EROGEkm)Xzx1 zL-_ucZy)-<{6T&0{%H+>epSgT>*0GRp$=@dj@&)*g zDCO%gP9@)hIKF#FO%Hz7t<=c^SA-{`x4&<9~Pb=j+GfpMnAk@EnG1jq>AI^P#C|{U!Y9#+7 z^K9jNGM`Sq6yrq4_Va(0`i$kfvcD4f_ZcUZZ%iF#@_iU5m#;?ODCASqX9n^+=vSru zDeAV8Z^3+O`9aL5kuSr#4CPDH2S)OJ=vS@$Ugp`!_h((AFMpOg%;bl$?zw#JQ2+96!~IkK z2KVYxz9oIBl5ZONkNjok*~mBKy@v9aS@)5Avyi`hC+6SDKcG&c6Z-it$9WXXe?c84 z@`vbqseBvelgW<=^)Ek&{aeVp@b@?Ql$;}_d=u)YlCQ%)t>woupGLmXA(MYymqYml z+yh7QJ;=G0-xAJW`F7MvbYegMEkpgwcjJ3Ak#EQIsr*IuS0>+__sZoPh5DCQq5sG? zCeKp7Ec>*QZyWl*d~5baBmX(;GL-McI3xMr!uc!TnVdWMXQBR2>gT^j=>PJa$vKg4 zPo1aor9xqKJup^%?SZUgz3;rx|HG?C1X#^%Kh{gunmD7bAyM zJ{9xHLfa)pa1EsODz9I=s)s9sfSd42G3{m$uF4v|L;~&l*>;gw?e)~=>PI_ znP(}#7_a0fvVUtkb>7I=p-zVK3#o^Ze1=f}^7F`{lmCYGjZW?7|7*sND_r;@^D4^dI?RA%FR)oTsJy7W!T#pO$mKmS4zu(a3+oejLh|3H@Jw1i7{HE5rFK z|CBn6&g|!Z7xRzhPjfCL@@vU6l}GDP|MEPXzw&R>ZwvX^`BAJ_DL;nws^niVel359 z92)s#=Z^R9P<}Fbj^wkme_Q!0tZygZo;r`t>E}O3$X~ugxPQuTqpni<-*~S~emr%X z%XeWuh5StJ2?P1>*V zKmWJF{a^k&o=@a|3ip5cPdUFb`5mGD<-ceALVgqX=YjlJtV=0hh&(I#l6WnDiF;Qg z-#PRj`33Zuk^BVqV=KRx_wD3QhyHV3KmP}KK9)~UUrppEvENhqX?P|-g?Z-kY1r?D z{Cng!ke@*xDCITzRPu9}Pc6TP{oBa@LR}5zPf^b!`QGf`R{m|?tCJrX`p^0O{P&`6 zWBK2iPa;2xeV58F3g2Jy^T;iiFG}AiYDw4eVCoG-Eb71kw@U(P)pqmvh>zqOa35>sBhI5v-tfE8W&Qjg3;jplky|4F7*FL(QRkWb%aFhP zztmMB|9i+^ej9aL%A;S7`#>c>E%bl+J)!>PJJYX*^4r4wNB%$N-^%x7oKAj5=>K&; z|3f$*WBF6z{FUF&{8RZH)JZ14fb%GqzYywQ{u(}zpUeD9`II4l`90)T%ctl0M*d8= zf6AYv?~UZwA2RNft^6GNVJBZJ-2Z>w&woQamT!h9@{7qKmCqaUmrn?Pe~^FUu5sNK z^6xUvKz>>H`;UAf`g|q7f#0p=OE7*Tzm4aI@|jt$k^CZZZsoUg4tDbUsfXzDB>KO9 zruzDSqiCCuzkKy@{>tB{-=^~KhyEkKC*&`$*%yWU5#~9N&qWTU{BfSI9{XNS0rC2AM*b#^Ni&$GM_|#4t+J1|CQg(N_ za{23wU&s&QJ~@z2V7*HDyNpxGS0?9L{to@Ek>9}hLwP}cj^sy%`@eiwp6}!@lS6c6 zKmScR$7A^;^!Y^o56;I_zBS`y@>^NoTz(w$FXTT6{YU=%pC|viZc6zM{B9+m82Z0_ z9rkr2|0vYI{0n>}f0g;P^2H8Onb_os8uBFrQYwG4t=_TQGif zRX_jx>CdrzPV!0Qza!67{yFQF$!}u4a`{2jLm@wmbsWf-nXnO51D+jpNu`1 zuf@JAD!(B zF7{n?bwB@|$TOCIi+LvUx9NMSd{OEuli$vFUoM}D^(y3hFrR_^zL3BC1Ljl7Kf`Nz zML%ieACl)#{zq~f$=BzcYUR;!lYdB5W+J64OAct7~S8_|_{|)ya`7ELT$R~MVTo1W?D!h>2%li)G6WQ0Ld|UcMC4Zef zYx(2!^G5zZ-fJkInVd)Ro%r2W{wDX#PCgs`B)YDj|AyiIBOlNQ68TB&i&Xvs`DF6f z@LaxG=>PIO+&|@e@V=#dE1s|953r84e3f&@_1VZLKYd&$L;2_QrIGynaQ~M-LEU!p zOL;!JzMuc!*pIP%TIQ3;-(!7K`EM9MlfM)4mw&=Ih5Rh`=|KJ{d6x3O&}S<7Y1BzA z|B(4K@+zFa@)_8tBl&lz=T<(TPCEJ1O!~Uw}=ddn~eA17pfBB*0Ig(Gs^R4`P>adePN&Q4O z^z(l>{QW^bkv^Hon~=Z!BHk;LpHE%o@^|Uah5Q!gKafAqd`kI?tZya1=9tzmH&(VmC64T`j32j&dox;8hH-nkB9rG{C?J}lFv__)bfdp z)5u?9{zLhHL;mu=bMCbArTGr(jbXX-PPAI*7`%VXwW z$aiDk4dm}JpHlup=s)rg*mt%3U!niYYwlM=`33Bkk$eKrxAKg->g1c!=c8Nu`M*Km zi{;DFmlFA3L;sN<$KMY#`C`;(F8_>t3VFwT2J)I5O8GLO{^hTQ`j=0#_qcu<`GM@a zp?raG|CgUgUA6KF%(IhUM}LlP>*t?@{v*GOdtf5JnQ>D2ouU8BTl!%xUyNfgaKmT{AlURNO z>zK%=qJC2OkC{&<|0lnj%U_}o6!KH4tATt5ez%mzA%FQ1)N?Ighv;w zBwwBSY2}NtUpo1Yzx?3YUZ6?I$7KcwCo`J&uchw=s4cO&_W)Kx3rot!)QW8@Rv(a(Q}aQ~Oj5&r%o ze~NKZ`4ZGsCSRQS=kj&K{YSn6_veB9p3wj0tMhy%UyyaFh zu1fiGta~MYgZ@^_m!{4e`A6aVOJ4E5Bl&O1xs?w&2Rr!!^o{7Qe*QlQ-(T`&soO+8 zFZ&{uKO6dwe4TLrkx$3E7xIlm{mWM-=TbiDi{tvKS`e0lKoQ3 zZ{QwY$rq#EYWexpb0eRLdKk((_R&Z_HS6BWw`JWs`OBgI-_y_kO+1z_OFvBH`|^F1 z$`1+mANd*~fBDJb{FSdje;CN$W1gk_LH2tk-;VXFndMzl`LMa4xj+ zgUP3puSX8i@B8_$M9#7NqXWkEnaKaceI}Jpdi41DOrG(+xqMg7{X)J7`*$Eu@lw7p z{jid+5zb%voYZ+E-zxMU`FuP-lHbO@Yvud!UY-17_EGeQe*RzLvHXjWzx;95J(W-X z*0>L6@^z@IT>c65T*xmB_kZ~hsh?7QAn#krcMkP0KZAYO$fsrgL-|qc>ydmt*0+^! zf_L&gnNM_YKmVzz+gKjc-xB$c$RU-lOPy!($qpRvi(LK%`?`>S$o?A0AEVEd^3Qp` zk}nhbzx*ERwvjKuIXjf^L_Z(N7hzwt@`==EC;x)`RW$79zasODRFb`LC(l zOnw6OlgpQLHfTjwkYM zL;cHVrC(+8r8pmR`8+&d$YagS={ zzoQO2`F!-f=+FK9k0!TR{xEf($amp=Q~A8%{wev z-;Nw|`Ig+13;B1b&w+fQkiUFq#;@c{vA(tZl5qc*UmxmUz8&)%$tP~FrH^FKPudRi;{Fn%LH3m?iKe_)EQa~{cmOdoFL6M4RqZx-@@sGt9~65km9oD6h@6Y%{`IYRKk$lIHzkF@_LnmL3 z9HNK&`7g-(#_|uDXCmJy^dI@|tV<^UlyP$TDfIb5zBBuMAU}nDTFSpmzpCW>vrlXJ z@7EdEb0c4b^KmHu9(`;iKallp<%@^=r+jzTD|)1#{}RkUmhZ>&iTp11MJit})W7`v zaQ@1-Vm}u0lpF@~vskZEz6$wN^5uB1T0Y`l(a4u2x1sz}=0B3}L7lYn^XQYE`~v!0 z^p}4AD~0-(zd#>LdQg zu|i&l`=@+!`d%sDkG@yQZ(#nl{1EnaBj1qv8OqP1?~UZYVPCZJnmX^~M}_|LXg~kO zxj)46KhY->`IW3=DnE?#E0h0`ewE9wXa0r!F#7gDenIH}@KkDayDCbBlKZfxW`AOmXOTISeS|;C*+;aILcp*PJ+&|@Oa8D@Z zM=?$%{|Wo0mj4uQ_$^;60>4EG=TiL7HS-z?mJA`41TtyC&MWy@ ztYas=ZXB@aQ~P8 zh;_;27m!0PKY)E#$R8okfqVzf!BT!DxmEJVL;sPl7xI_?B%HtUDOMThIg(x}X1T)L|^Y2T$a?l0zzQsM}0Fah>t}bNSxv$3lKD`)DA4fpJRtbs>NG zyVP4Pe<1W9`8J%(L-|LHGm;lP-^z3PZ71J{+@fdt`JckRj^z(??j-WA|APHKkk82amhzf9tmISjzO{S<=F`Y;=A0eMAK<(j$qy%oR$j7yJNbj; z9R0JO|Mbi=mOmZ(k9%YIMg&+>dGe}a5+ z`JCiX$RDNN2J&w_Io`je{F`_se}{Z(`E%4&BcCtye|gOKBl(uxXIlA({B9?Ih2M>y z?dN|%IDh3^Q|F2Na&q= zN1j9ZRMhQAepcxJ@)>x(lTYM#qvw=A|B=7P{44ouq5kFbvhIz1|8W12 z|C9bXk{`zNt$cgxvy4#jO7P&?j-UX$S0LQ&HX%+&qY1t@&~E6LcS~O zIFLUV?*H;T!}piGrmxoWhpC4~{xjZpDBp!VNAlbG-fZO`l4mEMm2)I&`}yBSp0WHz z);*D*OJ7Rm$Atbbf0FYwmp>Nrm%q;M4&=wtmrD8F%)gSq%Q&_CV&>n-ui|_e%1`3G zM)C=)Z!3Qx+<)Z9v%b-b{rrE4$MO+%lE_achg3cf>zm03^s!t%Kl`GPKf~`1#ei6!LvS|B=tl^QHVE-m8*-9{Ruh{c!)3e~J&~lO8`l zcSiDssh?K4^smy@rP5BVR&`^NH@$Ssk7!g-gsDJsC{O(ZR@w+4W4Wa+YC%bz*|4x1_^%=d=&wq+< zjGvF?3v*9M$K`QOVtWBHNni$wnYkiUG$JtULQ#Qbylag1Nc z{~Z4Pi~QB__h0vtf}BUS{4K_5EwL;3qWKa$VPy0`MV@J{|L=R)*aKmS$9 zGnOAre@Ns%!&CVKtYaphhxf|m4Sk@Hza7qB`O)l`QvLzYSMtR}{_^`ip8V_n+sGfr zhw|H*=Scnw>Zg^z9qvE!KQK=8?|%MkhWzD!r4AGMo77t>|CBn(t z_c!^kxbKbRbJAB^`TEpzC;x=^jo#?z{}mp~7o=|_^7;7PRDM6XWp;AQ<(G#3FVC6( zK>h@ESjwk3Wn4d%d~xPq%gKbTZZyuIX6f0Gx!c_<>%p@{3-TD^kzT*lNmpj zKSG{~d@J%v)TS z@=M5bBtQ4i@j24U8-BNwKO6FYtDpZFA%FQZ^tVJlKj(WY{|)t=$rmKITpq1EUYA0? z2J1MGKTbWA@|o!;m3%t(MJ->1{oBYdqt6WGDbJ7O7l-`i=aFY8e>vp;c0d1@xfjLq zyZK&A9M{88 zer2eC`4i#qZ}O|ir;}gDIMF-({AZ$0V)sznOK3 z<)cvl^5w(%E1#2f%;eYdySe;5>b#I|AI@L-YW!{~f1G|^$$v*?o-{42&!R&!B`?Qt+gmvuXmvAmbT|fU_sJB@DJ=Q&u zU&%PB{B?53R)~r}gPec977Y+SKz9Rc#DBp{5M)H~Y z-BzB3zyHW@3jOE3e*RB}zyHV=<(`wsC%I>Q?@Hy*Q&*Y%dd{O>E`5!nh zO8KqSc_m+s{Zh+6q@El39pp2Ve;(>zK3B+JejWAM$u|uB=ly>EFVHt)`HbwZM1D)S z|H$Xy`Aohp^^nWoWxo{iU$ZU)`2o~nDSs{We|gUP*7BvPt46+f`1_~)YUVSN-^+Tn z^0hgqIy?0cebCQ;J%ev-@Qr+y0gAL%m#`6kS>l;21` zm3)fdjQ43RUzFc%MfVQ&G?13H8#{&2Ye z$p41d@_D%@H1e2r9LiS--(T`)S(jEm*-PVn)X7(%Uqzqv^S_gQ8q25Ro|(x1&Hhd0 z>(KWy`9_@kx%_X`Ng0_;YX7cRh8&jXrXZ`#)rygSYkMTtQGW#x- zZ%fXZd?xxvE`KZZANh3L8wc`rsE1NM6VF%jZ&Qc0{7vTH$lqXHhVsw2FOB32hrd6_ zXAku+-#GN2&-?kmM7_oGC)i(!JYoD)zB%J(^4UWE@;S+^knhAg4&;+8I6m)6`6HY! zmHctMmM_TuZRAt2ABXa7I5$V~AG04@`P1S2mG8{{ivHKne68TcBdn%ub zd@}im^oLyjeyD%>p6u&^{ONH2mw%u6RPrs@@3s6p)I%fRgLNOu=c7(W^10|Ut^AE} z|CIledXB#6=l>}=#PWi5Pvi@Q`j>a?-%Nf5>zK=5WIly_TXGx7qcg_mMJazS+<)X# zagNvWofyB7zrr{}`I_PWBj1_(UMs(r_wD36hQB|2+0TFeaQ@17=lMkbUFs*5KgYhy zF?aN4-_@Rm1r!-;+Mj$d{z9hVn0%|42S1_0YU%or_)5`a!e|GYHA>WjBAIRTfoKk)m<5cn`*r&C8|4{$(PgwV%{AB8RBtIHvu;=OYDhvZqvPYUO+{4w@RDgPzE zTgj(*dwgEh@)@}2H1g?qekh*_AIaaMPqy+inNKJGm_8Xz+RuMg?hmnizi|JT-^BTn z%4gxdGWlAJpUXGI3;Fx3`#^pg>r%?6rw>>1%^1IyAIyGl#(s?DpN9IEZ%7|Y<#V&YGWoBlhg^OK>sZL6&H3*i$T?ohr5_ob2iB-W*sFF+2Rya@SE*3W-R-Zz%dOkE}NGw6q@ ze3I$M^Uvfd=SVK!E!4k!5bmGyFWJAPe0j#NT`CjB) z%eNx8M!pPnJCv`8kK~V1SFL<8a_;2kurAS5{rq?5yCas*OnoNuS;--lugiYP^uggA7<-4%HnS4IZi(LL4ax3iA;XuA(xc|$aqz_l}$q(cESH2(pwvlfc z?w|6#d9RUtJDzXlXOVL!&%*gTZ9o6b_@0X8pN9P9-zB$HemLux$q!`yx%`*ZPa!{> zJ~NQtM}3y^FPUd0KQr`y`5_^H`6BF#p?pF5`AB|c=>PIFd9O~sZpeSSe*QbNjyW>E8s0aPU&ehVmtTb!@@43^19{E5 zl=6+k-yh_ch5DDj$GO?auj2Wk{OXXud;;@n--LThD!)10f8;At&$)by@ckwKHv4EGUyXc9`Q`k* zqLN=mpQ+`)rOq4q$*k8~dQhqD_q>?v`U(4?f{YSnB?>m(5M?NF@CDd&zpO^jH$+x6$M>F;F ze}jD#%Wvg<6ZsFq{a?Nv<7e`PnNKcX3NPeavAzTOZlV6=8-~CC$WNi(YWeM~OCz7+ zN&fyUzndIJ@+(9B@>{tlbn>m(*U`-V{I}zK)Z$2p$M4`W{! z@^kpzfqWO{S;}{2-7EQh%(Iq%pLK8K+mp{wzA5WFl7Affzx+P-S7&D(qgnd-m*MXZ z@}J|0{1|de<-0KdOg>;==kf5ORoS?dSh6>lMpS5BbZtrXEuH4tOU2E%%~a{#eLg{s;QQKt5ug zrTi$~tCH_azpCY5Qn!u#5c>R3z7=_n}TKT%1U!D9O)-jr`pZ~4o6Uz^z9uoN` zjGxN4<9?ONw`cyje4%jvkxxe-9?18no=f>Ij98FK4PD? z^23;CCqI~ah-UBS|0?(1SiV2|G?DKb>R&$jQ{(-f$@dQV%lBcNLjE@M8OWFAJF%3n z#{H_2AIAJ^`JAjvBVUQ}hw^jj10(sv^z&A}1AVEJufh1y9R2(+U?0WuwRx{ZeiZvO zmG2ztUw#DpD3_lW&R_Xi)Z0LQ5$AF#|2^k>B_EMbEx&;A8~L8he<-g*{mWP79B<`M z(N{b9e5`vkXFvaq$T^lz;CB=GGrU(S{~7x$lkd-aNNhfcl< z<3w}!^WTT}isi>}jwJG5QirMhhLFGfHawRf#=a=z&xHG@{B80n<(r58FF%R-*YY!1 z_eTCa^*NLu!9E?yU*-8$zBk_~o%{plAI;Ox|8n|jEI*x`6ZzZxZYuv1eK?bUz zj}84teiC&%kbl7Mmh#icxso5l`quI_sl!IT-a&l-%4cN1jO1&x?^^jm;rx|fL(b86 z`uTqr?mzNBa}Q7Cf5lVzNxW|+KQElW@&l=pLVgPUb|629`%)?YjCHBxYjHl-@`fB5 z`OEZ^p?s3_$LlhZAIW@L`8}ck$Tws@QPR)<_E7)w^{A6X{$uugDnBjcFMpHw%H>b6 zj)nZT(EsI^u#TmC6Y92-A4fj5{D-0b<$q!S4(0!3o+J6S;rx})#(wGKTk_o$&D+oa z9CC~0XHkcV{7Cj!D!-fDGWmA=ZZ5x@@4iC*eCYr3zmrcXzmIV$`H*wJmTy4cXyg}> z!%%)7?=_MiM-Hw0k#PT!-_O2?=2QOM!((|H`j7kvCJhkX&v-_QRv&bwIt zbAC6GpUb(N%74OpW%57J-*WlR?2AHvI=?%Rx1s*!Q?V|U{0`Qomj5*5FF%KKd?^2U zxc|ui&3syU74nxi)I+pDKmUJI=dt{|tXCp`oc)!`r{bK-fT!1#@PFZRVyJ_Gw}WT)O* z`7Au&$tPKL+&`o5_Vd4;eG$vA5BbYC<@`$J*YLiXe7obuznjaaWIl!bWBTeqehz)I zlsD8{CI1imx|Z)x{WS91mK)D=D1U+dJCgs2zS_#a$$NG3i|O0ZLjC;jrBBB4>&Y{b zUrIfv@*AkDO#Vpd|MEYu?uC3M_TxZ)5V@7|KZWyGelh!^mcPfkH1g@mZ74sQ-yO-1 z!&~`k%)gUg!+fHJ`}uE2--zY^;e1KtbF;rv`Bkh-CclVtEth{rofq<1$YCJgntfNw z9|`%(Z((25@>jUuHu7ia^Fw*Ud`9wn8NZd^Lf`1*+l2nVNI(Ar=zFnzH`XPQ?@SJ< z{E%?}kv~A4=kkZc{a=1B^B>5cVVqL_1@&CXp9%LL`Qx1Ljr;|CD8G@q8p%Ho_aFIP z^np%3A@rX``}se_^Raxgi^l&xC6PZ(KS|{~@?D(CZ>5jr@^#s#h5Sj@Wgy>_=S%s^ zU-{qYpN0Hf?)d}xQLJMrf0K2u zECUR)yFHt|8{5PC~(GvaqUkUe5`8%vvBA+4Dzx+Rp zlga1g`CNWF`?rwaM?M4j@94v&{88$slHa`Ucwf}=|56W)d~y2HP#!Hk9%m%qj`OsY zKOW9s`D1)1Moaede=hWY`6|pOk^h%(WHQ@4fu9M)wZzmIWB`J^X| zjY&`)al{M17uf0rDF@;w=6B>y%0w3RQ!Inv3`=ldmEs-OQ=j1$Xm!V~#Dcq%`Y z{*cKhduUugx%@Ngu#oRTT@B>(u-{AhN344#pX~Si_gC_LpB&Gpk>ASwd?=4jrT*o^ zkiYyNq5kCu@w-vl&wpC#Czd};-%I2l(q~e674nyVMqTCddwJhNz92adyR z$MulPAErNK@-229&oh@#yVZD{LjDiN8OX1GbUaQee}Q>c@|UTfTD}4I$wt0ZxPQuL zVIPg;`%O8Xe=DDp=R5fvtb4RfKmUI-|5*MO`zVoLMxCef{pcH+{6p>oxjbea3;9yq zqXzOX7^jrK8vgz%pPhYC%a0EINB%kI(NKOR?>mxj%{sR7H(B>iz6JXzTDG766!(nR zF_y1D4vG9t>NAxu$+~Cq87CW$pUV$p-xczCSeJo(2Ig7Hf6u+Bk}nhbzkGXgXyotm zUPJj0LjB8+=f2v?7iZl&`Hrktv|K;`W$3rDd|`4(lx{VSiG^DCEc#(Nd= z)9J$l`A5`MDc>OUfB7TKvzE_EJv8$9=+8s>n$+P)erULV%8$Z3`3uY^`d&Z(4H-X{ zzsB>4{D#nfW{QghIUdf*de}9($l6)HZDeT{&e1Y)&CEta5YvpG%PAC70_l=hC=f70Af6BiI z_aFI{d`G16ov8Cnek0H4@;dZ?`BwCkfqW18b}7GxI3 zzLf9AxmL;7qrcVi-!acdzA(Q#l+U={xNnT)CxrT!KS7?I{M+o`XvKd1*R$Va`Hs|K zBHxntO65D^nfwd-TQ1)x^nZE6`wrxPCbv?aQCF4xaMq=kKTkf5{5RqIOa6^5$Y1_@ z&Wl!lF8iXBAH@4cD=B}TkL5SxiTnokdn#X#{hP^Kz7uo#;7w}t#$Ta4>%AitRLOZo5EzmLHc?ggi6(q2c~1UxN24WK($X~uO`>2t>L0=lmA7LMjTfctPN z{}%lulRtXUxSn(Q|EPyTz8rNpkUvV@mh#{4cALsd6ek|+K$PW+qANi1eGLrwD zKG4c1duUv@o%|Tak5=pF{{-tB%gz|8KlJUawgGEcKJf z&kXlZ`L&_{%ReXQTs}Q@Qphi4{DFKv*0+>j#`Bf@-{e-yZ(yE{{5$O5q5N6uXC%Mo zjPbr}e-`rF`1f1``MvbFQvNP|sgiF*o!9dJ@m`Jmr}VL*{2M!r=Q)yJ zN1e3t)98Dh{DRQ`*Xrm0Q`RMxUrRkF^68%*_rp}a41FV$U&1{qmmkXe7V>Y=ZwK;+ z!~I`AC(l>%jj7LC{$KV{BR`YehVo_Tdn5V9^oLfy5B1i`k7l1nYxncNkl&5v7xR1~ zkM*PR(B{~7Dj$**I5qxJgv&lSGEpPiT;_(AK*MKbHfqj(7ze&!id_DSbCjU0|kjp>$WW2tGe17)h zK;DpBDc_&^tmJ2f`jQcNr(zsGt8Tcr4$Kb2E|O$T^bA7o%=7`6Qo?>mird+;0o{Gvqvw z-^lZ&{0*M3miAOeX&h`!tt-8R}oYGdT?8Pf&-Y{5^86)`O)m_T>c*OEaaO|=L7jFkIhMZ&h2FyQ^Z%7WQ{8RGG+eH2&>NAxO!uPNIo4=y}%RhZ`?1g+O-fJL#CiEZq4V+Vz{1oP2%fI4x z8~OS4v7vl^)@3B$m-=bthmu1lpM@NvE&KUDL!HF(#rfStekOf8m0yZy@~e2STz+Hd z|MFY$f&3)itCSx^KdIz*FitK1l6~69pXa+{D1VUq_DDYYmE-zp<#$pKo%{^yA^K52 z|1H>GvHT3)H<9nid6CNJW`AY!1@K({Is3GbpG=+u`8%Qi$X}#>DtXJkuH_kZ)yNm7 zK8Ny(`HbYthyEj<_Nno@bn^2<{#*6)pOJNp<+CtOBEN!hQu(*S{a?O1-&48#EpjX5 z?}q*_Z$tl)zm8Y(@9lqz9s8k%Rdf(f0KX6 zIu7NN{9!zwk$kEX$KJ}9<-XL(Kce2EZTk6d&v_clH|M<)`5!|4%a5Z@GWmA&w_H98 z>s!c&^v{8Oan`+*-_7|_$$vv#)$&Q_825)pz9%^hSN!&c3)3en`F7M#E#HLw z)yO}iZw%#|@%%`B0PEGtucW_q@@d#d(YF2kXJ#M8^2Iri68QtvXDZ)?_s!%FuwQcd zeXL6%--mGq@~=YwmoFLemmd)Fm;a318u`lf!=e1TaQ@0S;rpnS?-S}@eh?mQ*Ux`C z>MfT4kMk&zf1h8mhZ#4naG#qeN*}7;qPzq*?F&AK0W)QknhO89?0*a50vud*>{zEaelX! ze`7VyU-|p2?@<0Y{c|MWo19zu!Q|G-H=(~pJM{Cvm-mY0r_mo0`7Gp?%ICr}`LWC= zmoE_RKk~0a{mU0+KbG>FImavc)8tmmXAAW&-<|py%8z6Jj^x{rXDdG`tZL{;7Nop3mfGvyQoZb?UZ|FVFrO$aiI5l=6j{PbL2=_k>!WvhIz1 zvWv(4YA8Q7~7{YRdWTPFW5{VkU-ffw>W zu^$KWpB_HWr<9*gy;btHSl?QH%E9y>`O>W8P`(mAl2?q=$|rK4@8rKG=V+&X{wvWZ zWBES3Zz8{zaZ>rR;r=gwj``&B55xH@ea;MLjLmq zg}=YaA7`Ge{8E0mlOIH$(a!z+_h(i^-tk`KtIme;S%`SlF0mN(?q$cOkH@{rF^K6>?AUyuKgd<*U&Bl%98;}hi{a!$4K z9XJPHm+#Jg?Bvs|Kkh%D$Tv84?9osA_4z%{{i)>t4)+%M_Vm3(KE;G_|4ij)vA(%{ zUGBGqJUm~@f6czD`{cC5y2tW~jGxL6;{Ke;!}EoFFY0+9 zf0pqp`N!e@DG$#N%Wp3me5aR1NcpD_MFekJ?8l!xbQ z`6HaCjXaued>)PD-+5--uUdI{KH8)Iz8(3;@|f{c`TNvECJ)aS@;Af1NIo0mSMr@W z7ixKUekebN{Wy{*jNi#$=N=yI+0P+7pUAi7ewE5o#?R#kv)>DOc>ZMhi2q;7yT6a` zC%=-{PmH~mkGip6Ezdp}dn1o{{tkJK59Q%r^pN~YatQC?d(lKae-6*L^6-1z|HD6# zx0B30zJcx4U&sHCus4CPsyP3+pE+~q-gE9f$-T)9NeH=+KoBA-ksW0Tn;@WuT3fYS zsT`&76DnKtjZP<6cNe$n|tPllX>5M-h7%e z=X;(rGv}G@d7haQV|J0L@eIR}4{1C{@l=iHDxRV7m|c{k@$w-~{<%6I>F_*_SB-ag zzQ!}AIlMsQIqy2WP~)LR4lmYt@d}5RYP@o-!^<>YrRJG68ZT1gt6bx6D?fLa#&4+c zRiW{AYTY@easP*o{Z^^*bt-;%N#j#geX2A*LDlEB#(z~$*@v&!?k6*pKV@qCm$A5_ z#($#fpQ`bBsy-PSe^AZCIU0XeUANPC%+Al%c;QbS|Du=1%N5Vlxw7-0*Lcj%&)0Z` z$|qJYef$<^@_!9F>&}}R&sTPNk;W_3b+N{Ce&xK^WQ`a9+2N%cuTXrx#wp*CFVlE| z%72Z<3l%Tdc+LE#@%KV%oz-~E?>naPVkKXx@mP6P8gKdsr(fRIcuYT}?#J4tQt83e zc>ZgS9zq%~R6JGVG5us{yz*5ipIE(O<;LDON0Z;8-nX5`WAEGRfABnw$KLmOjmPHm ze2vHY!>bz4_={8j0*%M!^EWjfo6n0hUaqc-H69zMlQmxYj#F-_#zO-fK40Sn107zb z@z^+Bqw(C~jz1o&m)yjpr*~s__}IeG`orW-0w>{G?iUsx)4#nEeJqvs5bm#X^bXgpWR=V^SW%D+J4c}l)e<1eYWP_f2e zRq|yTe>WCK)%fd5zEb1Im7h|j@xLnh(4TATVelr$4$07Xp_0$hc!lD*8b7D{XP(AW zA8_=Xukj4U3pD;;WzQ6Ays45e)%aZ%hbh;1j*_p?_-W-AS86<0$*v?XPY-E4 zPswL!yjbxZjX$U2Z+RN8Q1ba2e?^sBpz%s2U##&blwDV<@hT->uJOIf->=m8T_sC_?nnMtMR7I9Qi_xZ&LEb z8gHfK%QW6j#RDreo~z_5HU8-Dl|BF0+WN^?@*$0PQ}L)AjTbBVT#W};IQi#kyi~~- zX#5@JUleP+T*;Se{2A4M$~0b~2J05@QsSMWN19&A*a1^G=4<& z&peIiDEWMiuU39jp~mx-e6hyYD*vcV;{{5-T;q3R`%@Z!Q^{9pJUO;M`}f*^+TFDft48r>b#RsPT;E zPCZLCzDdPt%QT*^sPU!o_G-S$)p(AQ z&)0Yz)gKBqo~z`GH9khoKcyPaSMuc=PgnU@Xnc^8uhe+4;#C@dR*jd?TebC5rsOj; z{-n}#j>gNCe6Gerk2?Cy)A$WF?g})XujC6g-bLxZRO1CozD(mUs`OY<~dspRuDPAwe06=?ibwT={OJV(iwYJ8O% zzvUXwRq_=Ye_rXiO5=q}p8i!^KU>xK4Qaes$>(T1Gqx^kyj02OYy8aLoN-#H@iHY} ztnq)1RsNC2%aweY#>?6{dM?+vneUWaq47``N4`qqo{^3`{kyh)a+Q2Y;}wc$XneAo z2XZuCrR4K8?pNjJYn-||^(@r5t=_9t;~7f6OyeOnPgZC=SIJjueD5cY9;!6{rjn<( zYwPFViib2FJBN^=@nR*Pqw(Cn&im$SJa#@IPveD8Ir8}$FL>JF1sac?XDHNog_1AU zc>c3aKBXFuoueqzIKAk|S79`OtrA>v?Z%-Pic-R~-2qjsH33 ze`%b4LptR;^$3 z-`e^qR`MAdAF0-b9E~ql@_8Dcsm5=C#>(T1_uo#rxf=hg(tn=DLvK6s1sdO^`e(7mGn9O(#y?YW zzcP*IDESJF$IkUtYCKoTS7|(7aT-)xZ*Qn}4{5wu$!BOh^dIMab2NTl)jv<;`Tup~ z^EJLD7Khh(fs!xQ_(8Q!m1?|L$(L*VL)HH)G(KL*S84oZ<-ddm*Va#|lF!h1U1g`` zYP?*@=V`pDvcn5BUZLa*HNO8lWmjwbn36Bk_@_#r6&kNp@|7Cjq4YyTYQJxlk`HP8 z=5EK%%+dI5C7-MD#?LwO`5LD}HC{A+Oto*Z#!V$(s&VfgC;u{yhm?GU#{U}gvoxNk z(VNn9^IW#w(S4p2l-WIrYrf`0KI#DUBa1cI1mSzAzRK)Oe+mFW2~VH9u5n zyh_PeYCLz0Q*M>UJE(ahG`zOno>%f28o!|WNv_88m3*GYbDneN$pVcRDEUH-A6E8Q zsm2SHe3{0>s=t+Myj00oXuLx4N{tuC;_Vu*RPr=J)6ZB(pCOH}QtgY?32is_{R^;;0%gSMn7aKd#10mBuTS zJdLcaw>-747t;7KC7+}5zbpGNSL2mRK40VGR67=Eyh_OzYrIsAqf(92WJf>c8c$RD ztkAfr(Z(pz0@i8n00D1sZRs z=A}Z7d#0#yq4Dl&o-EV2spKm(K40~#N{xqFrJwd-FxFFAV7zg**m z3C{cO(s=A#W5t}>`NXb|X}oEfa}Kjoq8MHJ)?Ed9NIePrl{wb{dbpZ?4AYf8)sa(s-rfc^Z#B z|GdU0f9vFv|37$v#$)w)Q{%DrD%7}n!pXl#<1zVSjmP9CYdltNsm5dZ%-4A35~tiU zjmPp|qw!e&d@3|v@S5{o&oPY`Dt<}h|4{4iZH=eSQ~fgbuk%$6rK{^) zHNHZM$Nnk(AH3{;@I1w(70+Us;NRz6+;H)H7w0bis*8JEyuigBOT)SOri*uT$rrl# zlP;d2^dsx&=0z^~U%KRrUHmsLo~!id19R8Emy5gWpXcK4`akdDjtArvlJDZOjM&es zE*?80F7Fk%xHEG)PrT{k{#YjXx6s7{E?(r~wu=|Lc$|w*cJX)@FLm*ti_dp)cb_YB zaW$hxpIYPMiLQLgT|DgKyIefU#VcIg`HKP0&0{X^tV9m4bnz5dK9^iP)y1n^ysnGi zcJVY9r}vz3Dcjch(%-phy0|j~I6UOy&KF(|Pjzu;rgC_Oi#Kpk^#7Z>c!rDTxVTd@ z=Vm(>Z|IWGb@4_n-pj?aTs+Uk8@u@PF8+Xv=eu|l7k|~oo4R;`i)Xv|n=byKix;|h zGZ!y%@rPWz*u@`q@yRa!h>MrHcykw@@8UTwUgqMDy7(FwZ{gzQF5c3`ce!{g7q4*f z)-Ha`#oM@erHi+9@k=h=&c&--yuFLxcJaqtoaW!}|Bt)4>Eaz+JmlgXT|CvrJGpp< zi+6VMrY@fA;yEtf#l_pXcvlzCb@A>l-pj>%xOkq6KjGrfyLe9*&v)@&F8->EyVtJ* z7w_$of78YLxOkz9_jU0i7k|pdi(UL_7oY6n&$xK0i|4ucd>8NM;$<%WBNt!e;?KHx zxr_hU#do>*b1q)t;y-cmV=n$v7q4{j=Ux1gi@)IFRWAObi{EzfpSd_KxZnSO?&7A4 z|H8#XF7CEZQeE6_pJcfB%dUKyy7-$ep5x;G=i=>L{P!-N>*BAwcrO=!#l`bn{MRo2 zyo};$gM8o>Yg*BK(>&jb%FLrM`-aAerLE|8yt%Cv6$VQ{O2SP?x1i#rXD8sP zq`@%tE~VL{TGGw5z_^w)yqzv;a9e>oWY(*CywKuh*8;{!STG72n?Uk))Sc@gn2CgpoNU(L{$C>kf zM8l1=;Xk5XhJCIdT{Xft`q4U`d8-o*^hByU(Hc+U=l$q_C;0v|yvp0~n_T{`nR&e< zzhXxAbmTh&;i`T#(9XQvi9WI;7dlb@xWr}8(A2o#z^C}JxCgf6@-Kp!8$0q9p~#eu zd`&oftsmVCXD;bPA0|cSccL3fiTC=_@H)YRz4_8Q$y0Orz~s#F9r>K(ub`O;sgdg) z_=?nB(8itg@DoA8l%sIi(X7WktrL#H_?=z350*u&M{Yl~s37XFlSA%yLL(N49t1cd{cT9r%Vv z!k7Bd?ME_4ccRVBBf~n;H_a0-_n^@^!A;%xik!N$JM;G+4PWR-w;s(L+==$L$eh@L z&uAGb>_mrJCa&v7S6T*Vbm600CEw}9=eF8|UU$7s=9S0!;kJ>l9_P#3MRq>UzielI z*OB*sEc5%0wCAzN&5m^Ov4%wNJRbg}6U}_Q>55LY^Klu^InyO;ZZ}%gH8QLl?dl3M z=Vn)XaW^XLCf@5tW!=Q_eqvQO%)&#vXC3K5hq_1B_n@loFmGn`uut`%g+0Wv9`soc zDR-d<KVDxlVrqK_CA$0^%lwQCw3zk`&3#77Eqn%Ycc01H+mCkTMaufo z`8>#t>t}!5k7o80`})zLep2pOzvwK$-iBu7PsZAG!kCZDWuYY=TEzkw#HZPBmwD&{ z_fPRq6?aw<$fQ7K0+%uaIb?n}iPLsN&Y{&M9ofSzQwMv+Q2u0z;1&Eb`qz39JTGXE zNUjuADc}QD3Gssvw?x#PFy3Y~ZB2T#4$ku_(dSJ(KbkK_?f(Ct|9Q>xaZJ>=syIWj zvOd+U5h_UdwbzOjV3Md?vSq!xda-C)JP+KC#Mo)Yt8dE!5K;k zw4;VFvLTl|hnY7REoL1sp)s?HQ58#8WyRB3ybrRc?ojF7&>1#801lKR|klC6GSg7r1-GAFFo{OkRXv^R$=7 z8E`Vz8^J;!ZR29FkIwPSN{fXDYGpD@+YV=j_DYKN* zb9Vs_80kn3;(^(mM)L<8Jtw295PpOA4Eo$izHQKLD^{DlMicv`am@h^vdtTx!sD2E%)SA&^{ZNZ(Z6 zZ!azy^i%VOL8}b&3xkduGX1&H$hn_D^YQ$A7O3{nN>&+dKKWcrItzZ5na4R@X2X&G z#xSpPn#Ij?T&f7^-5i051KfhhaV~AM>XHacq^F6Vwiht=o4`ay1zqk}7x(Khe_iXE z!5Yft6q!0ds=Yr#doMnq>KJv?VUhZ@%UAibj6_lMv0SEG@l2o zLw?p={#Q;8dXqQ#>5_Lg${J<~q(}dMco91d~l1)ivG3^Ea8f-k`g(Emj)>$@NB>ybs+Z(Q8Dn**`E=$O2z5 z8qQ#;I7K>rp2ngwV-55iHr<$vJFB=@$LT{Z_Hed^V-8W$GQX%U7jV{aG-~JKvJHMt z^mA((V+DaCM*qSPzJI@(g=h{g=dgtD7$!3qz11a;vZh;@*vHvUhUS}K1om=PYLr8) zl8f`4E^%>-v#YKeM9Xhxt>>&&?eeRcP3T;W%r?{v^Fnn=Hp`@UkkdQNoNCY*2CMcu zvz8caf*}B=8VHLx)kDAQN7Im=#!}V>=m<+46QG4g@PeOC8mVZ%FSuD^(N`RfikK`PqZh(Jyv-#WX(#G76^) zW;)UXtW_x0sYF!14zoQXz$`yeSfs0I4DS-hJnX@C_phZ~D2XLuAf(4Xc%MJYnh+Wo zzy6~Jea*6nCK)N87_`ku061fK`*U{Q0FD(Z^%^wzehywmx(s;h?M)+FR(lsi(JMsF^RF2%--vv z@0r-`q49>b(?cH^UAKE+$2a`ILn}Bs+deLkJjk*5UgOC~7JHgNV!uaR_pn2r`;CJB z7@p?cY8;NI@2utr<7vKM8vSytccaZnZT6V6Q>at~=cLdApC9SdmN_kjcKK%;xR@Mo z&cN+>I25!obesd-tz(v^(4geK^0kpq0)2sezVMjW;^-^SLaZ$gHy<*((<2R|GGc{ z?37i1BoBTYM^zs4ZXDhA>_XMbz2?0*+U1>$j;->Xf(YgmpLxxqS-x2{^P#5#EezO^HFnwi5)y1>?9K+5N)vp|`Fg=oCW zr(uclm4`kw%AI@y^*_UWLDj6G^Su9M>yAOMZ;8G{w2$~i{QW-B19nO`qHgvGgZ>>T zHfRcy$;HebWw132fwwPM9VG9uc;In{U2d?MhE24{sDq?aP4sy=E*cXxwg)oyC@h2* z>LBq)s9Z^)?ok=^*KB(zW33_ci#kaBrIt)JvpNX$(JrsV-rsse3}Vz$U6<5l@$_G! z|C+}c8_NFZ&cB)k4ztW#jBdfmiRvow5zEBtbhYMsKg+z%Fsa7wA7q(dGt4@%>uoF( z6U+IU>s_o#R9?mnq5Hrur?ESX?$q4><#+~L??%ma{&1xep+)~%^q9E zQnz|&xsh_O` zaC#Ms@~15HwSf*~ospW$UMSyD-dkuyLq*jk{n!&bSlxYc3iTHWG>tVfa68Ms#^{XCB1h9yow`45AMj9tIZ~h~^MZ%I zVTd$cF+zoc?ipE^JT!-!V+AedaYSo*=#Ymhxb>ljPV(%*9{3&$P;I>16Zz(7FZ!Q( z(M#oq^n6BmLi@cm!;>|}OWQosQ90;A9%nqEyMn&)SeFD15ZNmQEfVt4l|tGNN*6LP z@)a|`<7j!Q(<@x2Z*n*cQTw^Ewb0KV8HZ_kvsX;=(~hW}RB5Ioeb-!#C!%?&{OWYH zF;)Bx^A;2wK&zH9a}k`*gn)-@|S$6M)Ub@WB-*_W|1M_(H0wGxim0q z#PM#hFP>;rz+OXdeN+6mW^9%`#k%$pnDl>XePXcRg?3^kVD?diEn=D54R#6L$zY=m zvC&}Tjb9^dWQhJ4ng%L0h>N+Ly~FR9B6S$e?=yqFVtr$oF585)|8tKuQP5?N^c|!>F%XI$$jncqOM@8%EQQ8_jbjNI$#a-JUXJQ{ zAkIC<9Y`pJ^&efA=P~Jp%6q{F5~ee1YeS+NTo)zpn3xHe?aP=wTh0wJc^Fxcn#>Z= zjTSH)mewjXoXRUF0m&{lCef#2>y1sAdYjhyFLlJG0m|!)ya>N3I|Phh85X>1q-+yt z+J12Jxqp+Oi`=_N!0E`CgkkFu5F9H6L?(&#P@!bG?IPir*Y!%xVYiH&cF53XHpvPqhx=G$_@@hWd{bNEG}+%zvlhn zWcXi;%69D<9B9MjdU=?QC~mJ)^=;RaR8mEsw|$^m^KnKN*$B#SQNOe%wzt zt(7(PfxbQnKwrPz7@$#7U%QR)s({qjxd8Mvlzom1?{9GNz3+3jAwUQH0-~4w;W+`i z=U4iwW{nAvpErJWiqkOTk7qa{L48SvQ~-`~0dR_UjOm~;{fX#LzuL-ai1GY(gdL^q zLM{L{asjaQzHCF9g}TmSDOd&9L?_)L-209Pn->D)5`RYNwH_>SxOzro1e|Ug=$5y5 zxYR>M9z_179_vBdgx|(vu$IgFpYU*@2N|gQeX-ih75}+Ie)>>|eHI-QiL?E5PBb}f z(Qxm>xamzhY0*kwcrDy@pMc;nGyH)?yUp+_3*nsbN{dS5Vk^t*iT{Rn1H2AA9w`P6`nARmb1~9C?(10kSBH(Ja4+u~FzvtR;dve!bzjFYSLuDD=_D-o z^8N`P9^!#Ns_XcGpEd}w*P;U=vD8mzMH47?nAFJ!-nvlLa-Y=eb)SG>QB=D-O{v`> zR(QFEz-Nt)(PPkAeuS>QLx^J*oe+sj{B%_`fnvwY{5E^*UbJYVPwMrqPe5=&RJ(^w zsoil_c%w!0V)-?M2TAx%gGIR8_!fF7_Xwmv^@Ilr35tO4xamsL(Hv9_+__f-=(x zo1<@~jN(+xWIf*D0_jmQ;^n>%E4Y|_gZo3cKzg|A{$Gjynu5UBaJClrw;2NI-Ey{y zKA%p5K?k#xv2n0*zX~AsA19C=8|RdtP6Kd%089B4orkSQ{SI3K=})cL{o%MjoTaRa z$ILt`4%>|J0_j!p@_y78mc_1Rbw8HwmI-U>F8fc-$dWRM*Zep*%c6BGZ9c}X@x(j} zubG1HF)F+q=|f(;!cL!eip6gGya3oEfZ!R^#;11oOfk@+ffoFo&n)jiKf7q%x4x=N z5b}hZ4d)cU&kWASE@aQ8+4QyOxdv$;U!6^50gm+N@f>_+FmZl1?F|MuWYf6N8|$*^ zg9MKBs>JyWw+E)YQI<{brt-zvbSAYc$mO(AY+yDWN}tSE;Hm+__;j}6O13bY1~j$4 zZ_0Kw74K)$>86o+DC5Dn;mz3f2i^61j`hh4A_lpdrQtmHSj2c|dub^Tz}&vhO<41# z9uD%Z=MO~1!aKpsHVZGn2(SF_ZIAI{@;~MER(si5@BM6@adsLzE2o9n;>8>>5(<#* zUGg03{o^1M23_NOJLno;=|R`unQ#l+A$`+}U9%lN@AoEKU`*Lu~7{9$yo6-+o2>++ndm#x9uIIeW`b|X?*}&qV!%|68PD;-(Se4 zs*rgto7N;OG^A+qKz1XWwx;ka*|ag01ASk2I@=22dh_{hv`T}KtSXxdGykzUn^rd3 z%D&8|%}uSf*=$g@_y#6dcH~kv&2ARAw;5a3?7lLj{oNxkIN2w4JktkHV3;x8N1Hr> zQ9k;?V~+6AT+t5Xl<0$v53je_$ArUV2Y# zSGuicU5B#=wg=G5vz7(uy7Afqo8I?42V-oTSHM{N#``u5g0FqDH(s^`?hf*YVX$oT zNAT2V{$0>sY|O|Fj1m^CWVD=_i1Mzj9eb^2ZD1*5zm%nIwy?69TTOT%{kEG_F2rUN z_U0h=gGtri49#7zv~G0KzRV}Ugp98Joncyovrr#NvU>_W^3=j05Qz%xC6c z=_|-!qU0s;9j_wTXP&cRM?EEtpS;y^>|GvO5XV;XI!N#0q4(n0v1l6m9CPE?57G2k zPw3q^HrvwyGT(drv*K8>Sk7=!?KQ{8q3g(pD}8~XaqMz5U1){|L&%hTt{EB_$5utt zd(2S(ICeamzHWx9ZB`vkZ?Mc8Hal)f`AL4W%4VzlGQGzix@NQU(exdE=!(rI1YQ6i zV<#Q5*T%70kYbP`}~78ej{I(1`$BYt%t{ zzY+R0z)l+tkp9649SyLNT=LmG^hp5wW*v|oSw)=s|lW zTQYso3Y7*}f4@vm^oM2ySegG+HmunP7>`W*azFi z0`P5T8kdX`!G?+lh?WR(N6-p6Uts@!hnEfTVx~|}L8sZUAn|qPN^A-s#ABZH&<(>} z;lT)%5wL|Gd2@wF`kAr}#Dd$KaG1Mbig&Kfu1RmvPn`BKn~OYZ)l&9Z#1J17KXdu! zji>eV+;#$YIKoD{x(I3B5@t*K~x^sS%d}nKjs#LhOh^* zV6#L>Z(%PZ;9yvwOXY*n=a6l)_&jP4spxfcqUM2q^jPdVhbSlhXVeM*;(mLQcFi7U zE);Z-!TG26xH(?XGHgr0hw(#=%RFMBkl_M|-0@hrIZ*sM_TFjD?JbP*?Q%x1CoN`( z+*xquCu0xL(Mz39Ht6rea4ttO3!BLknevOIZc>D6zb$&*!lkQBx`+B;#DB!)HY^;L z^#;s}W!wT-!@I`n@BlQ3&@1b_f#XC27Q-!uxq#Ci17`%W*A2RWcTx4pM0^zOX^6X= zE*R3Mx-9cN!!3YI?mTxO&mHWi#U7e&`~qi>3>jh@z<(yyk`DSE@1gRv<;Mw6hW|`I ze<-Tc5XormP!@y%JW;wN(RRRiemeYj`n4kyPw>g!4z6G{!choh(mL6gzyD-^jP2f} z^$cE$g&mpq?(6a?8}y%H7-!R%g?Q-P+PciE(Oq<(8b>6O%}(@y*@it~{)9b!f9F}+ zcy*%zQ*#)jm(2mv;xcD5#7=62oW1UPIPh-vqh{ul>;>~R_UoG4=-V}UOp3W2)0nvm2nXR?Rm z%Ngrtu3_w#^78-0YR3M7AQAg7x{9iid}pa6jM0~aC(v-_A%-mn^J~T~yL5!uOF|~r ze)g~^V2_!vvaY>jSxEimh{vdPs?4hk3=&3aYTV_TONy4I`S)X5o5o@K)7Fb$hH$* zsiTyY?PxwrPe-4V@6?N^S9q3e_bH4mVCFu?mSc`mk4yRH;s2wq*v3FQOi;=%!idQK z*Frq7h{=%)9#1eH{$)jX5HL!jtSzDyu;7d*A>Ic|VW)4ez3#SSx2Xwy=w9nu5+ z=Dh$d@Gq7(J_-C;J}Z3^g#YCJcf(sPVVRg$Te1Z_v8WY|@w6D$iry8ONEdqpBU<73 zz-k}R+W0ISpga*gFE4h~&Ai-_F4iBKgo|q}jv*_hjp~xOSOJbQ5Ps`jv|)vrpW3Xy zQ3%qX*E?^^O@IS7Tg)-dA$(ZxEMz?9ZpetJjJMurknx(EA>+j)NLzdjHrZ6=GgsMg zkTA7>W7hl7hO2KbvDqvOTNU@q&Sa_7ny7WcK1Tg)gy&xuA29akb6%HBbnbMW1wb60 zF#G*90m0NaZ0wzmVxkDaW%fEW#VqXF78zwV8OVOB%2+DPD3xWvJ^1ss8WHGM<|#K> z5-^DWWM5@8jEVD%jfRgcgDhYA5M)_d^FZ{hL6&%%bqfFX{=D=8j;3@Z>KItyrDw!Y zFa6xC7VPy+4A%#^`89$M9Pc{H(`*8OM|>;TZI69RP7X=$je^e$Z%c3&D#QzIpjUk^XODB8cEz0><5nxtKc1x^o5`@ zVSOU#BVj@C6Fu)z#wH+|jos(~Vwca0)qeVoS>k7Zdj^-snF*KR3TrSIfk(p}=V$L5 zy%F3j;tj_5v0RxySgeA#2N~ySFxpS+JmxKno$%No$GqZQKm5TbU<~*kLD>0*PYm_5 zWv0Bj-V6@*(;hSJYm2@$ZMg6gEgMCxva;~dr4>?^x`(#|Am-74va=N7iw+ zuhmC>`qn2#AOln0EH{J0{PeMzh9iJ?O&h_(63a$W>#Qt1bXuz;&bH);V#fM~-%_0- zY9rYqT+>Iem;DI~7aBOC2z|?WBGBImFE+4^8N2?Dg|QJlqUO4ag-Z+sfYtpFZG~30 zvM|C$M`R|*BShQ4wy`imiANdM>u9+i5vnOy+9UG3kWy%LugMZUS)$pQJsRKB&Ex1d zM86U9F!6VfwLrAL`$Z=v*W228bY~6B4(yTm?)Q0P1ZY=7YBduZ47Q$OJ{@QzV25SA zkrT^N))D7*>X`pw`R3p6KZ{tsC4R?J&0n$R=136hUl zPBoK9bwSDQz z&UFJ=+&ydvvbb{wV(bB=KV`0+?r5E#$M>-Qm*^$+{6kn}+P>uE^^EmAJumBB@;COo z-^G`Dd7GUpUL~66wU=6Kg%|p~ z=gpjG(FR}r5f**pi`+J8ni)A~(pnRHu%oTuJd4e+ucOzy=Lw$ku#+BDPSaR9B|TW%t-_xpEMAOumDaPu()L&zro4SiHUH@#E?YTFoE%j zw9zXDB~qys7@J7@{bE2OmBj^$6X|rkC`_bX3D&4YI*?$ENTlNl*04l6n_v|s(v<`O z{#Aki|0Y3zf1e=03lrhYIb}s~b^!C5=OuKXc>LGC;MzGkGO-aWBOZnUvVeg!q{7U@Wz7ekP~>=j5?d4E9qsu@9g znAIQY;r?W#XZgQFda5mDX4=1%^^$XGSlsvJ19Kh zG1$v?u*R#r*!hqAAlMG0@e(f^!6UZ>Tg@BK^|HG>a#gS+o+i`1Y_)g*@l_Nb{gL~a zLHZ-nA0v~!Y!iF9xc0qJo6r`<-j1weYz`Ahst2QULYvST&Ze->*hT!q)6q4tIR{iD zgEK=D*h3%HJSE=~=OdnY1as{;7Qx{KcRdoGXgn7u1z2RdfrC})(6)0|&XL|>>>-yp z^1+)}HN+r=J+9e;~Ki#6rG&3<3Pnr{urP!INmFB{>Z0e;Ug2M72_Zq5ww>D(R| z;466YRX^YEsW&|UQ@0-Wi0_KXI{}*Qja1_dy=kovhwY?KNzZ>-K;V?_ z{(yBRAdUtwZc%XTvkJ;B!gm$$JK9qLf)zA9f+jEvxne0=1 zR%No&-alb7@kREV^rcS@Hs>X(OWtOGiv}G4T{YjtU6(ltnq zOo?o$OAAvFkeQSkSqH&X=?kdxu|Js~TIs{_()x>V>WbB!V;H^K>2bZDla358AXY@uX?vtcTc5kiexzA`P1*9DHlV#Wh?mY<_$Ea@ zGs+Z5k27)7Q2s>6$BPf`Wo!~_2ItqI(Rckd&JtqZjOBbDge%hSLJ&KPYYki+5Uo!K zsnq*aWstt}CL=xCmj*u3G%p3|nAzbR(pKu1LBzRJt_0~F|4xpJ&*Hj6=u&*r$spa0 zm-}D3|ByxC=p1K{M_D}m zh3GF4#A8Owl|#wN(=A0T0`a+OJ*kI2CXX>k@X^3B_NqKG9$9N(lQ`3PCK;nx25lqr zIkp=j^Ei#*2s%XbOD1eo<2SOD;{;0tCoP9@R9@YyK)w|$f-T!oMg&XLBBOrv9(=cC zS`?2x@OW!gkR6RrED6#V@jtHpo{tbV&+!{M5Jw9H4lUADf8^sh+KLT=II8sH7#>ZF zi)@UewQS-zzp#<9_eR`d@!_WhxNcYz#R0j%N&eo!G6$Z)9=;%@T;Y(SC1aY69Ky1`j6iWxnL034977 z6i33M$p27v2;I8DmIQv+Z?D5@8kmc`mc&JV2+^LnzhetLKJtBtPQ|Z6K4U|XX$iC} zG)lfwV*L$a`Zh5#KTPk2#l|pg4vYO^u_Fwl3A&C=nn?rYocScnRSrkOEKD*iG|&f| zDKZZ}GPkDq*Z_|;#>a+x(1Z)H6OBXEo(OiYD}2^a%%wgVB5+iPyq<%D{W0F~V-Xk| zrq4xM|1hTZ;CG2~%i`Na>`tX!NTiWw@N^=ZW+GrqE6|XMv^|*hej<$trAre zTWr|t5!W?luCKq&F7mQr40{^)pOJk7&K-Wkvpo2ANbakZ@G#E8EajtP&*FQL z9uMGH-ZBHRF&tVpk-lO)G?nA{+e2S-cG)pgqi+#?KdU~zcX^=Zd6fMxKY}KMncOaS za+UR!bNw4Q{DYV`>J>nTn}AQPVggZni*RBNzoB~`w#Hb8^bL=VXyiQ)j7*gg^qB(l zQJRpE@({giq#Y08sQhvaWU1@derENza6TZg%)(h&bB;x$4Y-Hcq&1gV@Xl>~8*|EI z!_uqvm~a|~3Y^}g&%E|GCdOUdP3-^rtSXak`OF(8{osSwbj&noTi6yhv6p6_|ze?%S)FG{|PU)WRgGi(hly&Vc80ve9DWXD)o2Z!@sD5P$n>OeJ(<4 zy>v@7F7?t>?|-6sW)ghDwt)kJe(c9UDmE@NM0zT)qa|<>rKY_XGnjuJgG=hrZ^8rX zP=97`vv5ieJ`ybx^%hxlK}6uHjrGOMD{1qblcy*4!vg&mqRrnRGmay5>l=v(WeQp9_Vs7 zSe`^hN#^AwT9zbMCyDoyaJC2?CpN`U`G2C{yVfE2k9`vn;+x6*!{pgXw1oUYJ0xTE zLA_ZJKJh-5e3s!mKsad8dnpK?W6om{95)(kG`oxwV8#}-w0!Q7<(^KJK7_>lJAF8F z;ot6~CEkSHKHB3A?e)=d?{mo2m;AYpw)=8aem_P4_*4)ZFw!c#XxK-BIK&Xz8p6l2 zq3t0m^_lO4aQ4VP5u`bO|0sMt6bOzB(Z;y?n?rOvF0ur{nV|J;koE;Fh#G!hD#aL$+Hw1Q0_lS| zQ7Ba=->ubW%tWwsG7qeQo6BW-Garf~b_s#>K7sn&5J41F>`exk=S4gM&!y0G4EBD_&1l|sy+>o2MCa_Zwz~~{1NplH2AgGskyh^}=kv6Ll?Hpe&UynoN>dop z_wR+$65tE{Cmn9lZ1zTtTZDSabxwXdy4Y*ZPoQ&Nb6x^IApI8lJ!?lmD&jJ#5~wOJ zcqV~{#wQ$2V5{RJkl7GS-I_p$gNZ8==<8s@!UQ%bG!QC@l?lB>{H^cT9kzSESrU6W z>H8&9SE8=c%XlU5ANH2?c`?FbGqu#MjH@9c&^sQB)9Zn$G8!z9T+8qs4-Ut}Z*oeD zwo!sQHywddrA|!CJfPpUzTFm`*mv19%*cSjx+na(Br=0f#ZJG-*XTz zambsz1K&(~<%)dX6i9z*%AOc24*BB!TKb;6pBMbK$9(4ktjiqab)NfA2H17J3C}F` z*4c#v%3gdXPQ&~H>Cyi8kLrjjYXHdguaPeXIzL^Wp$cV+_$fB8V?p zL)%D zoR{Fg!r<~)NY3*#apX!W1p7ebg;)HH<}oq)XS9^n9r-hCQ6(?y&hHxGiQV}@9va@A zf9t7V+KqoFQigWp*KjJQD<9+YU+&7snkoCb@=K;YrWe0y+S7XRC06LYUVM~4Wk)Yw z9Pr=i&c6$!oaoMX+L?QL@Nw}e+k5cY!G^w(OZujKt8rZv^;yW5hXS=wed7%%V z+^{Ehx*M8Tdh?47WeE1`M&`xd{QE|5K$d6u@Acs)8k-0D^5adiPW0uYA2cg^^SKWW zVMlxO%15m8y~G!f_^UM``EKXfw|a^7Ekb*{^4%>n_jln# zT1Cpb@N=y~2fFc*t2!p@FkrDL{@ad&262+`+D-D zol@34!N2O%_;L?k(%JsDmss4{uI$OzbcV>D&f(kLcxC6NhwuWq)`9MPVlMFf+|cwc ze0^@?d%664u7&rymU|0J=h-LiHNE*)Pd1s}n@{eI&CDIW#fcYudwav;r?2{D9(a~7 z{ZVAyvwZK5uz;368#?wZFMC!TdX}GlRtlbd_GLWtk+we< zE`E`YJ@*(!%}+viU!Z+I5jS6;4}O|?Zt?PQ zo@K^CFW(}^_XuBbHQWGSG6FEmeKKR8*zXfNWKxb+S@D~Hs~M+(x2!K^yj9MDa@;;Z zFW@UqbEgLf7tN74bjFj0%kvoWLt!{pAj3T`$(+>Rb3Hh(a~3zMOU8TH z_xv(Go9%cwR1lzmB=63*S&j#_JC!o9DamFH^Ixcj3k9=GqQ? za{3tX1(9Y;bNS(jG;u03{O>-_hcrwc{5b!tp}D7%_@beIWhZf}k@-$XalTO@ zWBd9BHnwMW5_=yA4t$(XXi~TT`RH8!*`pX${K}(gAVXTnI*e_d+`k*2-?|Q5?bgAA zUHHeXvzBz>w_Dp+yYP3~*z>yZscnMOJBbBt(EZl834YUoSG37I(1Cx`#$Mln4{d9I z(1DL@i<@)XhL^YJ>)SRR(Vm}`;8xq<&31ffyT+fiMeHn zp#y!y*L{3P`gE6l!adIN-bklc@Qc0*^B2l1Whr+UeZa6QR$YRPiBIrtZSZgkpJ{~0 zC-HZ9@NSrI;o&=BevXGOr0{(rd37Q`EBs>;`7p12D}n#u&HONtuk%ILfS6LI$jZEs z$hTXOU5WgF-#j1YpZekF6bI6Fh53O%quF7;*|x4F^2Kq-QK2N~6arnDe@f%XD4l6weT$z_C_IA%W39> zu;I8}6v@ZDIEsg9W|}t$A8wI183w{;IiVcD(Oa+Yh@56FMUz)+PcsO_rPGx^$L5(z z#y)0?S#VWDcsk~~hS)<5uddI_46}cIe%!F%ug||W?CJo#GBm;ur*e>^srWlWbgf?Sc70x5uhG%^e0Dl&|9*PtN(5_i*8T|pGJO$5CS+t5 zH>86Zk-M36JpXH$heQ)3>{<){{?QR%L zKaSO2@6Jzp(gt?ti$ogMsuA8a{CTSHy!O?ue2FjdbWgs?XYcIEE6vc_9=tT*Ux(pk zho^Mo>+P2@0^&lLBbLWCzL?9uj>|mNotMQ&wsq&@gQ00X_^DvprLKH&C=K(=$b_`T zUHP2^`J@}?XdtVP;84eBa!FMHxM|FiVf{T0d8L7#Gd-DEu&F`NQ<#p3` zJi&*iC9i*ipH0jBq9?D&NSM@%Pt7!8oXpPr1f%fVCVr5Lha*i8ED6_*K}PUqE`4FtEpEpv zJZayz;lxGUyAci zX~U<*`!BcV^WvAHK8J%@dt39Bp|tYW{8lJ!T5G;FA?=4&d{<)Nz0Uk*V)Dhu_{wnb z&|~~uIC=PEyeujCV0(TdDS2IcKDCZLvx^v(Y)|SUM%NX)+VTVSBRg90oek`gR{Vzs ziJ!IQqcej0+VZl@@KmI;BC}iZD_O~dA}W> z-6DBjd;Vby`MTR%pb?I?2;XkWue4~gu_YhZQvQXE4?Bc+=2Arm%(44BW^K=uH=f3bK(8o9~0&uWgDDi>>zu^JdY`eg*T_+_^Q1- z4d=eX2hwo%Gx>+Q2(Z^3mxg0Ff5Guhp1dfH4ts+0Blycq;j#$+wp8+t2%;E?B@sI6 zODc`9{eE*$Ivw|i-@z9?app2O3vsWaW5s2iN~bSF=E%BqBP2sC-zQ{VO`{2Q{FSLR zv(7w_?a58gMd)fW-nBY8v@e~;rZirjP8(9}CF!&)B@W-;rG&25rAsM|57ecRsqlKH zrG_@wrDds&=hmgesrIc@s!ZJg%^)}VgS8{lGii=VpRknvCj66>b3QueF%kZ#^vF<% zlqo4S*gN=gE4Z*R&H;z#HQ;l3cvl1do+sr>eO@fANf}~_@E2x?L0v9B!M`SPN z(6|ut9~_^tG{X1AN5(|>nNY@v2!Agjaw(nvkPyP0do#&Dsy@G%TF3@A;1}z@0@E^$ zW9HqT_6~;Z(RBOE2I6e`K)l0+jLCz#S>jlhuduP0-Pkv{ad)Ya`>XIrzfY%1{7QSV znLU%!t08;|Hk5_&E!$N24S%r_!okURjmG$fW0&z(RG-zX!;3OazVp24`k zXG}*`OMG$1O`7jZLF8$(ugP5>UGiDSOnez>;ek6oc~nK7!RXW`>4;a9@BnPyxgMNq z;Cnssd%S#|7k{D*<}VI04EF_B`LN5DT!vjpY4eu*1R_M+-R3XZsJp7uWn&c!ZOp`9 zJP57JL|i_!A_E60LNhY32jm}?fxnm*I*>`zJdIa3#Bl?AcP4gt>W3*F72Grp7y8Faw(-@*SQCeV<+!1?-ybUKu@1y3b} z&SlWngwWm$s!9lb+yF-p?0pSrMxuXL1KN@p8k|7~6GQ(GZSMjecUk6tKfjqwJ4t48 zD5Z_i!XP%J3T@N$%*jbIX~!fpF*BLcMV*}{(>9VO*&Ki_tAAJ(K|J6Ja!~MqM_@q^ zWEE5vQBXngw90}CoEo+_`UDv``mM8!d-t* z)7N-&Q0(6icMlv?cgx}Ky^EV}BIM%OjfcBi7S~;MI2VPQK6kkL!P3Q_J>30qY11bT zcNes@eEe{Cb4%T2hr35!vG|^ZyWx<<-%Pk)9#VIE!aaV-k{c54ZHEfRerVZ2?>W+4 zx2)|h%C@ZblSjITmepQyq9pQekJVKD4 zEU%?u9$Ze4Czdxob+|k0uoXW%+48?ykdD{O&OKyTi_k*u3-H`})nU z?N#d2|9@-|$MM?$ZI#~#_}}U&5AUgBApFm5iJut{wqC_h<*?zg7}wl&diMT>cL$He z+|Q!#=i$k^2%mpnr-KCAv^u^Hg<}ryKZyQ8tg7|m_|)?8@!$wTFn=1>Maynm^kab= z;J<&jH`PBT0@~ho* z^JKXbf8fH%&Ih1N8W-kYqY*w|AHB_rZEGql8TeO`($k!)ED&Gj%h3p*Z;NsOY-Qx^ zZu7>)?}3IHzA(6ALHNTp5a`f?qkO(%K>~Bxg2u;j*0Es8!<Dm3wdfGCrS!O*+nPP>vOSZPdQl>bAuH zr6JgQJ-f@FYY)aL)O+i6LeEVaKEE@jaZ(Ll4SzOc=^S*S??w-PI>y0|jzFKTi}3l` z1Kbx{_#?N(|C#XmHJSG>{7&$M{2l7l>yr8ipD(X}jMS?cFrJoG?x00)2*LJyFW+1n zitzb{&_TYur1ydUF1_bpoZd%yfj_^`%lVYdAyBXQ{CS;_Tm6r#CB4)w)aR_)B`?I> zMYXaee4swU=Zov#_LBO)s+P0vW1eE9T(;ogzc7N#w)xFygwNl3iEXp}JCKPM;*h2B z)`e^(^*>n1@%`eTEac?ko59Z)veGvEbRq1FD1mNWsQY*P``^y_#pUXphcne1IXmQb zLh}pCM}V7xMwnYy1WTTjWt!FP&YhXZniAzIaNJ*C+zyp0DIErb?ei$_V zAcPmu==Va{RnX>c4aInIS17^@{9#&|u{F1bUlaH$PzRpd!kg!wv8leX?r%_YgQj2N zLnr8wyQ3yH*gpg-xaf3g=rmtX{$g~F`aJUYeK+nuKjC?+&W*@dX~7#gbX5~KQ+n3HRe5V1s>%m~!#GvD zj>Dk+jYghJGL66B3Q64qy3ezK_Wpbzoy^L!k%r*xITXFLhC;qcE_;Tb&E?_v z4K++IF8};B93jA2H4(l$yQb5}J-D3F?3ee>$Bij|W(b>w|E{h-d4B*0R98k+??cM< z=Y`zlYjvfr9(a0x5aAT=L8G2im*v4h%NLyyWR5vA2+nwg#?!oGF~yHL_(8`mF1sva zN$I5<>5P-e@0#Gyp9k)n!4jT-9`LPwzv#%OEAbh}T>Se5%(`8s*ATA>r(viO;l@Y8 z?uTKY)?wEK%g%3rbiq)-waS)1$FT&;%ilxwx6(M4-Vv*6>Be^i(Q}x)0VCqe!K*NG za>s)g=Y%YIm8<0({DVZ?gE%UR&|dX?es_&*K76ANSM#dBC7R?O!}C#gK&98_UZ5&3gf?LUx+%;$g*TE#TswIzvIPF`4Pe&=oGkhF znEZJ7+W7Z7+^vfv_jI^>7w_Zq`<7l9e5b=b+j2+nA06&Tha7ZQhx_XCLx0lYzH;Q{ z;U9LmCsy8DLx69sj(oAhy|6lRbBDWUP4rtG?gwi_y#LW!6p6Ds8o%7(F7H^viyJy3 zB>TmVZ>w#@f%+=9TxtV6Y~99H`)KsmT6c4;-rQPCC)#kecKK+q?41obA^INe@}B6H zr{eCr^^G`${Mr7#TK=t|_G5M2PkNFmbz5!ZBhWHy>o3EBN9}f6!`dT$cJRS~q443* zU2|!-L9u42ht^}cye|A@K0gs@d`kR=B~YgySP)}Pc&yS!O%8q>{~I}Sf2Ps5TN@r? zKq;a1zgXb@HGCbPe->$cU;!RTmf(Hvo&~Y{7jV17hi!G<$FV%)-!1RPw}uz}LXI~7 z2}Ql8mbX8y$yCbP$_;XGqg_D0HZ+Sn+VH`c0=}rQ)%(Z$!+wLX-v~wVl=q#`B)&Am z2mi7LzdMBeZcQz3e=;xK&k*)Aq3D-tU}+Neqv3<`s&}KpURP7g+b_)v`y0akCKUZE zj#qS(^B3WR-x+bwD(o{gwY>f7ys*N1Uw{t8NuTO=UHIVtsDV#H*xPDqdHe0Tu0cYlgwm@P}r1YxCRZ`m64! zMVt+9ma1)iy86)|^6fZ8o%lE7&~fX26n78TMt&T3e~8uI5qDR`8}1eDuHl}zyCZ&_ zk8_aSUueEJaQ{@xP<)4&+8Sr|X9kC2aQIL#LhXhnC`{=vSeo-1fgD+yd?Q z-@?sr$D>iW3G(lyHAUWkDAK~6qAx|7AAm$0iQiY}E?Kbb@_KjUf+lFiKVK02Z5{Bf z?!$y!*!aGBeDOx#1AREkQu%?}W#{28sNMy#1QfNLmI2MQ={5Irv#C{}S1p+wl z)`0S99AeiPo6oFqJFcJ{e+{C1e0%5vBy8nuA_~{V103Mt?U(JBvR@XstN2u49ZmgFiyPXD@AjbeX$oDeKeMmNx%#nY(}K zrvn1L?G@72t~{jqx69nkhlnwB_p;`PmZ24|sg(OrK$n15`?X=%lOVaTgtGq{=I)13 z2X#1>d(?82{tA9(`O#49Je;G4qvwi%(!hwl+=PeT8jWJ!!yVsnNbI6e@cO26LqX|?=YoKn z>wLU7i2OMSekMoNS97x)a3EiIH-{p3hJssPjKvOdY(A$pgJG@?;veL?MX>Cn@|13i zreV;Q=$k6=LuF~PD*$nZ(ixrF5aZ7VY@&SRyJM{Tnz%f_{YWly#BoITPN|_c2Tfct z`?g@+Pl71p?}4D?>!7`vphq?p-TJ0Cx)iHE>BJl-7l+q(x>wV_JU5w(+4#wBU3`a2 z`Y=lz_Jdb7p^&UQic3pPtPrQHvUU;v<ljW=FtxUm??{x69n+3Ydd(God<~Zg z{%Sn<_c2F)uW8P^<5m++TuZ)2?Ft&Nh@b*}m(Ndz8r}!XEnNG~2p1WMKL=E;t(Hbs z&1@t~HEzkz63sW0f~74g@)R$sRk*Wp}899aI^Gt_!< zD7klX$;Y=1)jcYX4+ND_^IiR?YMvd~z_9xHb^C}9a|J1Ub)|mtySRGsg{+d{BOZ_A ziDk+4ad&nka##vvKlxkD>2JujB8+RzhA7ZTm?qIKOu3rCdv{i+nKV&WSZ#9CIIxy?ZWu>Ub2M z1JEEta%TOGaD?A;0lA_CToq0Vq92YXa9$12*0+&xWb<`l*Qd?i-Yu_i(duumfiYQ+ zSJtz_@fBrJuC2zG68@!7Ge(qehi<33?^_Uu zi2UIN8Ztgi6SGNIrbScxiAbE3`#qulMek{GlP<r`$&E^WcPY5w9CcWzD7SuOY% zX?$WSHyJ(}`e2Lu!ot<(wZJT0dO-^g*z0(6ac$$%OWh;2bp-lbZPR^AF*fM)#dY=f zE_K(`De`aYcKl0=`_KBO$CkSDVv6;#SksS}x-Y~UpIYj^9y^pb55yKdvef-0_78l1 zJl^!9rSAFo7Czt9aM0~b-M1PV`FvkPl+@nexc=N0E{4=Sywv?iQ`4PG-EZvkQ%&{X zSnAGgK7{xG+N@k|YCh;T;x;SpCl|fpg{AI>#m(<%an~Gt$gNA=69+fFt;PNAVCf*| zEscH!_m@k3Ls#Za$GmydV7Oo2FZ-A~zAfW!n5+JWV&Hl!4e8%ugr*N32z5VLgB$+T z&$T$#LVdrv#_uOhoIS8^E^IzGia~jkwf~9kW5oHGaS*RH4&u$mLA*WkH?sPlaS#t4 zuX`99Ts;h@tApi_ND*NVQkneyBYAtUa@#wtH<$BxdC>eQgz12NvhqG|>r^xP>4_&- z-z{TGvOh(Z-&4y?Bm8E#kJL7Q2Tp7)dOYK2*qujO=iw}<78^kQop>4wnsNH~>)?pH zYUR-McH9Ssb=>uZnuUD6c0mMeeFL|m*s2yb^BFQbAwL{ttA2N_Ui@3FB7M150dK24 zf|8KWQujS#d@qQ+o%<5OLH`^E59n6p0pGnVjK|@F&XMoM>UYf;B;{{(#Z_`ZAa*EQoj^kMTOCq27q z@wwG*qP~mW-=j80f2}(xja2>e;O{KYw&7ZS+~OY>UlL|Nu7#X`N3fVCc{Yf$Ctnnb zf_**`(h{pS`#&)}9bOU~{I@XmtGzqSMt8po%6H8l2%6B-@4%1{F!4uKe7$cl_XE+J z7P{lP2j$--&kRm}8{~%2qURg&igLtRO)M*scQ#>cjlR(6&Z>{RqlrVb7@yB?5dZh$ zh96UcCmS2i16CQ^*3$lPV9p_;cQoQoAo|+|=^h7#y>W=PehLTLnCa0D7Sy!v7m%PupsiEjqX1e9`Tn( zcVBJfiAMK8Et}Kl_yHFD+|_-6{p9P?D_x`?F> zYiaFwgInSpl5mXV+n``rqVC5ixuH7lsN55(y#Y7Tq1yLG@F=M_-w#JWjgQ@M^w}D= zm&hk0F!x^#x1uIOq=#!F1b@6HLXfBJ%V%pe1!x!9)%2Sb_M~V0Gkh)_`C~ZvK{)zE zIQW%){%ttI=V}=IXAfw)pRov~0b@SHr^2!rSi>i6hVxUvZ?`b`*1 zY4p}GS63I|%JH0Vlv_#XhpU)=IUX_PShW5T{D9TOeiLQ3FM2r2xzabS2{bmExJHa0 z^l;m)HFyq4Szb*jVN%3aT&e5DSYfRE4j_AU!?4>@e_D_NOuQ*_S}?q9Znq|_R&$@< zd=?Jh0^Q2FG2Hxu=>9;EUxu5XUx0(_Pgus*J`0io4H;8?dzhdpP!vc<{4u3nwjq47c2e&&V451L0*ij>-FiX#F2z!AHy; z+f%ixpNhFp*Tr%8aYJ1_e!sq2S3;+)Z@DSvzFOaWMaN z)mc~nC;2e@d(2byE%$R6P~UuK48TR1ujH&S9$XuXd=0P`i+nsDd@&Xwm9NAi#Q$0> zLd-i~zRWVI-V!W+FwUj6*uC-K{$Q`}#T`To|0>k`r#RN2rZ1uphhx{pgWrZ*J`%^E z)Pl3hOKLvG?EO--;hp@rMKp#pw;x7-N@Ca5tvNRiw1_`SkJQER&GygrX-is^z6xLtd-4c$ShnKi;%b#QJ>2S-#SWau+MVT*%Har+Z zp^iNl3$C#;pI5hr!hNkS4#2#pE{0Q?`|Bw4C+k~q26KCTGyVpDq%xlui(G|2x>)2B zad%-Xf1XOgs~+2tY#6b=AUYM zKi&xKv+=$Lyg0{x(10_ymfIS*anQmwq|Yq4ny5dlZMeNbW~wI|g2&Bq?CtfdztKQb zHeT3(MY#U64Z(%65~TH5%X{R|uNf=lr{au_`{I%38iEJnk;fX`uj7$#Hw2HwBc$?V zJVN~6$0Nl2bNpq;GpmsMOt_Ik+!T%x>}z4#`Kp>l_zL`bP4Arz?&3(}Sq=DcjQyE@ zi?lq977%HXL4`v*6!~cUk+?gzw*G>K;DXwp&}om=t$r--uBdOI>#nP>|85-5&Wz^| z$67wGLD0;+zFL)%>9Q;st5o*kh;a<$7=hQSZK`*M&1ii7Qg_7J|({g3p{krA_%KWQn!)M~| z>1gb>c<{IAJ(T%7b!)zi@>AdN?zp?4KK9Xg@X>l8*mLzQXe^h;npqUCilJzIKNfkK zp%{xi8h7{Ge0(q#A(h|6BE)|*79r+SFF!UaWv1UK^E(>m%FOJe%%89_|Jus@Q!Dc~ ztjsKpzqaxGWVC_idp;VwJ06@{t1{nPx8}~cdw+cc8retcV~p9WROYi|EgVaJGS>VY zUMp4RpR&HOOvNI9#F!n6{3sqg7K@O|?_v?+|ItSKb1y2h>^VT#T=TcWZsZnP`=jAz zZcAJjmIXq4jP7Xc!pZvbn{;U*!mZNvM>ve$5BmhxT|G(I14p9;mE z3%Tnd@{OZmEm#`QPil;sen}-tSAi*PRHuwP()TRx~BudjH&HeqowUcdy5<-3Z-EmaX!$@hE{Tu25OZsz?gHJ_$Ug4g+bpjdA7~a*BERNO2TM@G9t@7>8rtk7k=`Tb=0)LS?}&odbVu5k`H0sx(kTDf{1u%Z z$}!J(gDBJGfgp;-?crde8g3=#Bl-GkzNQ|H0_&i=F?8tF^;GUkY=M7?CUDn!X)TII zCEW(QI=YNa_I)0lz3`#+7^Pn8+-sxQWgo0r#Los?9f>}}@1jL|uCM1}%W)s8cem8G zT~Z(Xs`d#our7AlvgwK6i2Ri>|1&+2kxLqvhIWIj&IZ1tMnh|-GfDHzU^$x2Wx)-VGDt^RvWA&feb(>9$q@ebpbmr3Pj&hl{2F5y znpuR8@6a)FHLQML&F`pnKhVA3I(*4o5su(~<1^t`Id_+R{9gF5Y7EN3Z`>WlHGO~w z&V#rV93rFPDEc_5HuKZ#y;asAr*R8=8 zXwR*1MVd}_8@wIhm8N$!JsxtYgK$RF^km5GVdlLv(2vzzgcCtk#NvUFmp7g0j`8(4 z5})<#+@X(#Kc}DBY`mJEw5V;m5--7Z4cLdStUJ=j)2(BTpJiw?Uo{&#Hmk1(QTpNT zAO$5q6uko9nW4k!o;&3b=U>9LeD|U74#np_TjQ3d&vUTM@D9o{Ia`6M-zA$km>8?Y zz16HB<^J!ue~8Im1ia{@RcpP~quJfZ>-$3S2XMt1k_|y=X-%O&4~vo9tFhbqcU$&o zkN!oN-C2(FIVL^3HuByEcTueF;s*Dtc*6&EH?x*7&&BoanGFqm``!jtsY;t+$2_LF z;7&jTAdkfk^V#CML3bF>{gFX8NR!+7U*|EXZEwSC4)?c1?O0ZCHOuNZ!_Umsiug@% zCge!FjbFn0vd*EbUu5HLexo}PXu(@+b!OkD|zU0?`JrT1&!A}D^Gq6-1&Vb zc#F24+7}k$qyCs@7NYxL$^C3i(;pV1$aZr!Qq#mW;rB$yBTZjk=&o4M zbN50nEs`9xt(zCZ+p3wAB&L9nrMIwI6HG6*~&z=Buzl7g|sil@ywk6n-05zc~ zU4nV#CYe{(RlmD0XvCTSBlx@F%t#-f2m#<<{O`>F!<*vR=+4>k(?-Wfwj{=;M@os( zOk&OHE5=Swj7*fL6XO#ziRqcr)J)>E(V0Db)6-MuPT#wCo$E@c5~r4@rbj2n6Qe$0 zdtzcL(cy+C#;0eB!=>pNH!)eBD$Ptxy>;%>amO7$_c<|MKF#I#xs%BO$m=va9q zF*A`U4gbUJ=u|necXp(xW*q6bk=e=gmUG*lQa7grIC@T z^7OPT|HEu)Y})Ow$|x;S8lM?el+tiwPicH)tUTqinPg{nFpum^^<)QAUD<&hJZ#P8 z2eRo*J~h~t9LNt1rg+zvDx~_lwk0!L^)xV)+lClS3lky$>o!l-cT+z zI53z!DczsW?{Hm1gM+C|zL-zp0<@)L}n<}hA3p)+TEJ=);2x0x3>4@BvNf& zv=fr2UJH`yE!dO)YKXMjTXU`URj$)w=Xx{t(tlUz(aV*sg;t4UQ;N5;)3RIHnX#9h zg)Y6!=Ikk(>(tXwN~s*(RuMUs;n;lp(IyJVB9hbr(a8+uZ%T+CQ@DuDfeuypv1BT zYa@h(EDWh$`8F$iKGm+0?chc5J>kLnO}tcF zW+g?Eptz}ax=|uS8B*;zg=;SyE79B5PQr+sV)fFFCXV%@w<1VC)tm88xmGew_2xRg zP=QjkBF&y6gkENPGqiq+VkzldR-Gq7C!})f!c?Kv0v5a*PBV2$R(9ZF98*#&|4rLe{vKh<3}h$Lm<;?ZG+GV%`hm5 zhsY97!k!8mqM6o$I)UJA3|~ZnDr6XxWQ%C0DiX9XB1bo8R?;vMy%~GTYW!u|vub@r zPWc_n6oJ(^mJ@qJ(Nr4H3M^N>$JI(OEH&tVgM^1gH-vYd^u@o6)-jAzq5wO_0pNVq)`=t zMpckrjY|Gn8M1BG9$6cQ)KSfdNGkJed#8$-WwM$WqAPP*8*3WpYB|6`k=sa&UX@|h|q|ZpdQ2Cf+sveVz{YFABvpKEch`dUhPg!a6%yFLBkrZSon^BOVe1?2Za>?%Q?qojY zI@4R(u3cq&?%I~>>dg)H7n57J&TYLp-u5SR_FPEzIpQTdletuJU@)EON)IIais^2b z$`GwDNpq!h#r{-(XKK*(Ww)mWi)nJ~8%TD$fvv?{K08qCOm_7S4LElG?reW&U$)Z? zrjp&o?Sp9w1Te@Crn~YE7?4i(ZF0HnP^LR|k`n?b=Cj2d%LKwopC8;&97ql_yN5DB zh(xll&kYUOSKGIxKp!U!6jLXqx=3`WFE7MV>`Z5pgF9TYKiM^y?N4T0d3oq!>TZe~BS)Qi&SctUSoCopfk zJY9BO6MOegjJtWjv^Zo4wK!NBoi3+NA1+TSqqg$tMWEmDD<)UgxtXciaUrvj1juY> z``S&3^68TTWdsypUE_MXQvk5(#6Om&CK6-i@!d0fT(MaEJ2zC?Tplm&94i+`cctC< z#P~6%j!w;hWg z==h9Vv2DwW{w*tVt~@n0G38RA+S$^K5a#Ikh?)OH)ho7QY@U(?>+ zzP7!ieO>$d_6_YD+c&juUb|*(``Wc@JJzmSyMFD4wHw!NTD!SpO-Fmj+K!Hnbsg(F zHgs(4*wnFk-I{gn>(;L8ShsH7`gI%DZCtl$-RAXc*0--;yS`)ny7lYVZ&<%^{igMs zH>}yvzG3Z#jt%QJtlzLe#ey)A~&tHf`LrY18J-6mc`zZzk!@MBB_Or^(dqdIppI2Ab3M3GAF4>P}}?vT3jg0oWCUomUH# zHel8w!FLn3)q~;PkCxhXBQy1_i zJA^^|WAlb~K}amF?VUMGveWjacEcC^)fU~(Qbr})S%{Q(@5&0)Hf%0L$IFaI#{^Wa zU~Jk!z)B^`=2$EZRnsc%IYYMW&}t*$XnmIlLQ1e=BZdrTu`Ae1YpHhDX-b)DYtIo4 z5UnkW?N=EP%)@ApSBcC{0BRC)k)S9-&=eCwn`7F???Ndf)n z1c7B*Q(Ke0G{l%<$5tVHS0dUKopw`xf(-~ZSS42IR4r2=X(h&f&r6LCp3n}oV@dE5 zfFvN(+n!X&Ub=*A+5Xj+y}6_9B{@)BiYX8Qgfe#uSywc-SYQc#JRw>GjZ`{a2{~sqJM!6DO0k7G-J~ zZl+rl4;-r+AXJDU50P!0ftxk8K*|PLTj^arWu*0yi@`pchS~`}lTQw1g3=;9LB zMDdgg>aU4TjXrRPA@if#)bZeE)sl8?qty|ba0sC$geII&v~uX=g9$4c165BNp(Zkn z@0>^Lse+VD5RHM(UaJ;RNNM!iq(ykRY)!;?w8)@frQs(bV_Q|_uZoqWaU^mp?I|af zmM1M>eD63is!|ExS*CGHe)iT5E)vPLX`V0{E1O(Shk2~pZWzJfn zG-)l0OzkzpfwITa1872qZ@vDaH9&>PGLI!}ZA3^Nkx&kxR~nIC*E9gHs!b&Ho#_<< zpIJ;w>CSYY8HEPBI@St@0vo$scZzMw|7Dx(?oVd`qg~nVl-ruh59PZI4)U?sm+k6x z+pM=+C)gZ#KPY$o8{>FOBW{$8yCGibZdxyvn_KVrOyH^yoj9+g1weZ7Yqt z-aUIU?)8`Ul-^t#A9dN?drEsGDk5iV=V}U!}Gc2U10W_^I+(VsZkw3$iSa14UI6vmwt;PNEKg zQoBmyN6jR5j*ge6_9dpN?C>7hbx)<%g0U53?qh>f)iA5v)Xvl0RC|Yj{AT@WU+X}_ zZf4(Pd31c&L;}>BaL0^}ALB~1GZS+_ymju=YhL4q_mrmG8rOX~s9z1Xd$u$+Qmh!8 z-HN_lZgj+9ubv*Ap25QG)|6kfW{n%AD)e^+hrh_CJl8*~We+ZT2yfMVclD*2zS&GqwhJo*rVeb$NkA!bFx8(eq%b900I%%v zI+sfIvLaw)&JJ~L^D~gAIcTf7nayOh1Y|SYlgx4^SWmi-NeNbE`(}-51$wc319eTX zEU_JfTxEy?xtgW5waxIT$0V{p6AvN0fY3f69BVJlD1#|Siy`)8ZBp&*{`Q15M^9K~ z5UkdE$@Ao1Vv)4HnS}-UJr)I+X2=r+^ys6q&eh|`-+6rN*0QGnR0B?~nn2H5XI}}h%s8d2M$Ht=wv?etYIwm88 zkc>dG7ivdL&f1_;?H=XI>~2qnmC<5I6GODH0+kfN1eeHgD;!O4NRT@?TJ4XpG_les zKTs=$VDGkvob0b0Hdye28LsJb6+s4D34^mKAiN4B_<`_xQOdv4Z6 zHwc;ddcb(ZQc1l~;nb}ZmGc303~<@dXRF7(?FJ&Tno$DKvi-HugP}?A0Tk@va>1Tf znkgIevC_|Yq=zxduqS|z)&dS&`V3P_iOpL@z+h~c)sN2%mQIRJHo_@1k^y&b%#!(- zmK4a9U=%4j<3=G7Dj>#MC7spq=C4p=CToKa#A~=2W3n>v@C;Q@+-sn7WviJlK{fO#xK|2@jK~sbnBPF{IaYo>(LZVl7Em$*Dr>+2mDNEgQyR2H zQ>6*7fy2aCZRuT2R)l&;eN9Li5KPz!5h<(bbaFt`kpZy*=4xs|XND^f)<7)AWP52Q z0PMzE6cC1G84$!{WU2i@*M_GNf~H;}6riRk5(=`CzLns3K*APmY|5&-Ow0Dt%n_(o zTG|mA32&SLk6TCvm$F5HQewbz70?cI_-k035uqeeyG+F zdcV-KI2wZX0|RibW(0>@oqb=jAsAw3QC1nUwz;vzSr7zG%#*ca^((bL?^v~td#y`q zt4v@G?n^n0!WmF{1`J)?ZpLE6-PQA3kCj2wNer>BdkEBP7UQ1uAi84eq_m7JJwWe# z>ZCkrB>S>kIRzd7`R)KDec}_a3j4nLwztW7gb8LRFhmkHt%ki-vO}1K0cI_VQpwA-CfLW z)5&=uIatgQ$n8ia2e$)NT(N6l)B3gU4LM=>(%9sllEL{Zntzk);$otDKSK15Q z0$pNsW}kmm_g`hR_JC}Pb0A&-vgi@7*G`T?-!C(~AT2 zb*jCX8XU|H*2(-_Mcs+hH*F|xSSQpkbURfA^wXuiW!aQx%WZshJgfg$C9$(KlGrs;-jY}`U1ubc zba$0MBFnt2(Nb$rdJ<|x{{eWv6K&@dY~_hLOfUM#JS#lP+!WdXwWDAa{>jjc+ttVA zo@~msxg3WT(nrQ7b|?0tOOy{@L116u0S5{XP&5Gs)RU$q9Sl=ROujeII zRY~zXntA~Z3~09%REHD|NXdl!^&PEaFHJ)M zFPI+C>zODZcya*9t80MDYE-On9=so|B4GejSS;GEp z92CYO#RR{q41)g(!U2nl;)f%4Tfr&`6$RFW%78IZ`7l}a1bR`LzBkYp48eOFFGE_* z0wNSl*Rg~WV+g7We(W(DHBk^&S+Aw=S1BV@Fa%u#hL%Yc)`@8%p4z~ZDHxbTl`=z5 zRbc2TB&!LtM^<#QLu9Ee^)L*N;$Tk3i_L(uFag zxCoj_cwa%l`>Gm?i*oUET0mb3B2)r~u0^mJPKu_i030od&39h*GD0Mj147vX+?4~y zb0y7$MS%A;L_{j%J6XYLUtxM8(B^7Gp?LCRkZN{-*bSPZpHe@Dtl5;AX?kMpk{=-3 zBX7#9C!=(U#zA5dBrTAwGy&O)z^L_tIax^Bf9oy%0UpcNn>9D1f@sGFA>?)JT-cfdPMv>&gQznwccOim8vfw}-z?CdGfZMTwUQ^s0`;jBA51o}@5zyM9>IAi9R zI@^z#JD1w9&ZT=~SWi^Y{hYzwXd15@>Put$15J}haVxmE0^wbMHj~;h2iLV-CouP+ zXc%&@*yZgYI=em7(VghT2Ja2-MJOe`*w!JE0{910?Dni{3d7uzQrtnN_)SbS5WL zJ(y99WV7lH?{j?-Pe(zm-7Au~HZs*Wnoy`FDs zSL=C0yUJ-e`K4BNx;6p39WXu(pByNzV-iT`AZl#FeM}q=r|0oN_^v|I?q&uV7EX5W z0PW`YqwDRd&fau>KRa88RvPX`1DT_SnTp?+?oGK@3m5~(Sf+%JS&G>I4DdnPWVw|g zoP7W$M}=)I^9pF$zOp@D(3sV&-5Yw~Ici}A6Kfk3G&W)TA1n`@%Zg=9KB5q?0E$=5 z!YVMVmws7C7`TPAsqYZ>oq-Ubpw3a*2Q8lGWddCczk9MP{Aw#LLec%U+K$Q~Ozmwa z2n)jA#~wj$+IE1oRwV6ndNL9vY!B@|p5DTysBp#>XTLW9$-c+ZEmJkY03h0lFt++9 zI}{dkko6q4w7#OKl!_w`&xAI1D#;omhT}nT0I@^tD4f*5|m2yUPeq0Bie;4dRixlu^v zon*Wt&@(7AN|+1QA1?-o)!QE)cO{U{36n0bogGhAFDp zutiMRSv_dkpfKh}l>wnLXl(JPf}G7zPo0FZ!5AtX>WG4PLIj;|5FJylYK7LO^s^qK zqg~MU!pw>QN|qS%m7|b~z?f{6=rxr9tfA-}IUzty>LpY~1Bei}i4KC+aHI%kNXGPT zA&tiXYF1fffv54|Z+p~i2N%F)sVpLHD}wJ4DybTYYeL6JFw%1Vj)hRO67yZc99GGM zq3tE3L{Idr2?NKbJ5wyr@b*Q4ylVkX%K-GNd$F%u5WiZ8zO8zB0uM7!iXd5A5D+$v zBw7kR1E!We8R5UPTO8nKt24c!Dgg2<2Rd z6zuB4hQMu2E>Tc;DS&OlgKyfDf&moOQA5m8LQ-RmZ-h63_o`q8x{V-ct!7>3G|Qe$ z`JipoI{fiy-5a2R=QJuW4L-AyD}ukMPyz7X3e7sM+`Jkj0B;XUkHufb!Bnm%bRnVZ zW9>+V;i(SP+92WSd<8Bk=!scG6=e?>3I=)WC0dbEWLI*KF zde%ug157nSPnx6e!8|2^7?bM8{+-VXWn(^vFr!26K5l9m@fzG-NN0y~35eZ8sRTE< zfW`wUDl!OnW-#ONuBT!q>}QG?=&3@@!SeL%-tx|3%~t zGZpVD4VR6u;SsO^shA~pn{N(Q4nW7=K05A-)Bi9fl7}Tc&lc->AJ*Eiw_R?IT5WuD zAg0}2oT`|1y~%g#CH+=qWY59XIbJqZF@{SeX2M@HOjIr(LJgm!(Wl+WJ}NvqY?K*M zYN`pMUW|aV;lYp3iqw<9Y&|m}yZ7$>tP*VI!*KNp)Rpb;XC*+56TumPTRF}#GKdb7 z=8VYPPGs5;0`%M0=gA_eL5#)H)gX?ba- zi!e*7h=!a&3l(ED*0!ZfOM1zQ!xAZy0?U>x$^?NkqD>maY!KGln?TH-9&O7mkl9ct z_BxhwnTlCX5hTeDZAg^uQD1RX$%@wQ*G$<$*HJBwCaL{e$7Hz{*W8mh8R zfK8a@;4Mg;-3uObFs+@M=CdlZ&+){fujt}@kb^iMLeL1xfKW|j`6WLYmq-O+Slf37 zSk(|Ng39XnSOP4nc8*qL)yI}R3UBO_R>ETJmZ?H zP2gsBf6`L_P!t)8_2d~RfN7&Rw;F24P^^vu7?}VtoB0>;%1eJ70azF4mk=!U+q4+c zddVJ4(lY!~6dN4;QN_iDrjTL~DS&4uftd^x19GXj&@IIS&#J-LOtfW#VGV90Xg8jO z5D*4(v03VgEn6j_N&SGe6>eW}C-35d-c+2TKjiLddfMQ=sXnKH3MD<5# z9ANNO5x9X^fv!SQKv%U2!lP6Vs{Or8(5jXRgwjH%^$%(rkw~a2^HX0@VByM?p*X14 zsyD)jC5$3Pqf`pd1Tay7rR{297AiqWEX9@&-LJyZ_)5jYCJk$)!gM$M3th#Y)OWnp zx&>IZKiu;9DYg7!$NGSc%y>=D%C`IWD0#xPo}sb42r|nQ9M7|sipBz zyfL09F~yiamV+PBz*TjraB8awD<@i+iiDt*bO|uylBpc*W>0hQsC5ppRxzyW1n%{_ zF5T+u!e(jo%sC9orUx8Mik(?uc_eyURquk$uo=o#E3OlOL6E&yQO;YrhG_{E$GnZlpky4>sGRj0QSPM7>J#bS{a z$gN;iU1h{5>-Ti{7}=_I+TSJQfEgPt#^0@mVu21|;#{uE^B&4`yD@|9d~lV@z72!C zx1Nj1;lw-vGbaqkpVZ%{yxuS(`o=I+fiX<7X1~pqxVtvbJp|YcCl1EOo++<#Rg@%p zube+%q8uwtPbav{wwrVF%85Pp$T3W#-W!EGao{-Dw(^X(oczXDzvje$TJ@$^y#ZRX zERC+W1YjM7!#zGy0%P}=Ijuo8 zrI?4D_q(f@<5>H8bZ<`%_9Q&8HePn3O+1W*7{C*|d_C&iijp(J@_wSUah|6qU^CCo zj*d*=odN_$E|B}N((auc!;T3BWRkbLMs?$FAIy)v_~n=>@1B5r?e8I0O~+7~{jJK1 zMR#@*=5^T(irU}{GBXOAK5Y;1H&Nk$2-2&1lBz?F?#{j{ zwK~<$bwC5bn0rks8gr@2u*hvo4-D{}eaW>!-8kefNfjH*7OV|Mh6Gk45Ut=4a90a5 z!oV+3gw+U`Wwub56ZT{zSeOygy%7Qq60FOjOAuW)VgHx?1)78<;^}u-hRqu5>30A| z!?_#+viJ%7nlP(`P@$4!OQo3Xt85#x2(ugma_zxmYnE??OZCdi!qq_4N;YF9#Wthw zda+1JfZq{WyRpO;U>0s~N)YH}vl8gFAW-z|$!O2)U8Bv;~cc_R2Wkwi;B}s(@04pAbJf-4| zTL?89LIyVAur?rs`anWyVsy56zz=&;`?1^lkhZVOz)UgKe99K%o#J6qz%+#M zu+0j|O+qy-LTE@A3Kg3kQ5kfmiPcXj0fwbUAXHU^ke-CSBuPE>sKye+6Ed*x^4@A~ zgF(Qb8bb7*Uu?ctcD4ZC{h1aL4E1Wc`A zjZguB)&hzM1H01ucw($8Bp^UR7{ZF+&o~guT~=s)=ao9B*eZlvcqpFl!;Gg7h@e3m z0rF%AL|BkxQKfkDhav*#iq2?PMxBPgK;wq0L4t~o!e_$HtVPLCV)V8b6*Ml;!rava zl28s9*OaET41_m&g2FUB5TXMiWZQ?zZ8?Ht4FAGvrf#Jq6_ogn+K&gq|3d5_Yg4x`xmwS~P8iP(y&2Wv--W47LOQ7=IK}qmlCyV}$$O z5Q?W@pm_hCorr+q4d~MwN>j@Kf=iN=9bvh{0#`iRUoSm%03fb52G^OzmTMWx5306; zcndyVV)c@K#!I}%=sRvWN&}%e={F#FtG)DAbS%nh0sy&1$hCRUEd`KyW2=613Kh5)syUx_5_$<%M|YcQH+WhS8b+7wR#NEueO z(qh@uiX=gIAq*}Dv&y_CNv<14wPEur zGRKO$HOrmIDyo$=Of2i|`;E(07PUv^;&XUXbd_`=h~I_9x=NOYEdeJ2{dG_V;#EDF zqJXn}734j0TMTbtxY)iPgyh79<|FsF#M^iSWa&8~g9y*3>@B%&?u|_slc&s1&4O_E zmG%_-#8Ma$;_WY;Qkv#wTtDPntc1hGZKHhJJ+^Oh&$P=LJuh6OVaUH=^|GgArY5HU_<|RDGgQl7(TPPi;zO>l^c&U zVnnZU=nErIJmv@-)na<`2QK#9%$_L-)yL?n=UC!2ZmFWfB&LAmXd3?74Qg{Ks3-gb2oFL^j+nV#d$u)2c1B|N1V=Nz25q$=zST_q-hzr@Hg4PV(am3fH;8?>$uLtPM=SQN0> z3GfM6v$(UZnN^u}nbk&=3zivHJ;SjC6-X-#L!FDUN7>j?tfi1iR>;uIULZCytpO4;mNY=pKbd8krcrq?9w5KYP!J%W zy)-iwMK^p8@m$}5ZIlCPDLP&hl&$GCpkcXy65LjH@|R`8BWxonV*XUhlv(=^LRMac zu!01QE8)LW!9XO!ya=5WAY{uE)Mhx*DE`St6|L`qw>6+hAfy7U7xfLdk~2U{p30!lFK6&)slLYl3WF=7v32SsBv(<-|3s3&iDXA~=MMzNX!oWNkt zcqTv&piJ!m$`zqX%7Y&1GgQoU%o#q>$9!VO=`daRe8 zoB^y=NWAu`fGo3Ks9rR?vh%olD5XB;K_-eE&|b0lOJ~8_Rd9bz8MG`VUYrcV%d$;C zVP~RA=q1_{FSSxpIpsohgixRaC7_Q5nh_dxj5$K^*C45PF9`tG+Ke!43VEHhz}i|M z5fVhLKb zU3)|*AAbi`91gllLP=u&=4v^P4%KDsnQ9CgR^j~|LTJg8)4ZMvxdW>(`*b&SjyWG7 z0PH^3)rYCs@UM)}jtfZ`wiDSwP_lRG(FM4j^O;vQo`btZeHQ6@{*6V0?{ZVJD%Y1v z4ZyWFKPFJF=S=mLqwLNd#>w8#9dSKLZ0?5Z^I79|^ryPhL;Z%f`{fO#3gr7zJ^2F| z)v6Z<*B*M9vl6O}w-l~gnqk{}V94dWVUluXazMDg;#Vcl6)HOxxG{QLbr*9Wu-7mg9yE5}UE}0<))Py#Kv1_|2GoXUAs}SkS8m^zk?2r9|#Ybn9+v z&PCsTOZXc^6F%Jy?3pOTDUm*qn=o)UJ?AoRK5}0%jbCJ4+?$8FUxemGx;OyM@0{Ib z9!HRUtmxH)f&H$j=Xt4qjhmJyL$_~oX55_tf_{se96g;&f~UfR zo|rn>88$D!$ju43nTFsv0$zrEP`}ubUxgz*UK($k=FiHxlZtr`sr^+zVpvj0;3nnTHQZjcMkZ-4IYkaV)1=_K@>KM59*!=B8bhZS4a6%e=CjQw_tI{=Tk4S#~a&5tvi2=$l0I}F7OR|i6Vn_ zskm^oVqtkw8uUAL)J;EyWWQ2L5Gog~O%O^a3?@MkgdDjd#8^NmQ%a>8AUs4jJFlR& zeP^(f$_Y)#fGq)jqyfMS+A>jJgtX-SgVCxRf)r4KkO%{>e$WD^se-Vzg#gmn6NP0= zYs4eW*bCC8M`XU%cTn^d&31DARW88BRtf7zz_bPQhBwisl{HX7*#P;Jh#!#%%M_%n zmo$Jv0vQw%W3&nV!RDv*K}{yiZm-*c^dB82=g0^-wm^7adFCM(5Urw80NO`{5H$>i zQ$UzV%F|O0D2CakIbF|PN0oRO-0ARJeOlJqzWAx%ueUdnuE z*qKHo7ojd{FUl-Jc6@BcT4sfY`7V#@EWye}#bt&j2P~T^G)sbqx>_z-7^EE|s2t0K zRs_bpW;jAPT;^o`HP#A338Fcf1(zjPHdDXy0%N6LVn5c@0nTcW0bsQNA@wjZ#5@QJPFr zd!l}N@~NN&C|lSyN(GHU#r2D*kgR{QB1u)?J8!CIMbY#}h+Bw|i38#NKU-(ZtzMfu z?t}`qH9yUj&tgRw$=3K)qraa6H2mS|n)^Ty_s{6(*Y6#^!O6C(bH00gvU2+GBr5l1Q(>CPd%d_gUl zp9|06Q~c~1>@Wuc=9L3l2NxfWPs&GeQnsi1`mFXGk@wAr?NPmqmznTnhv@`<7)0mr zOb+bXT*3ouiNhc^1BOt5uU+g*49<=lNhCYoxo-x#!U2y%<3O`hh*tSQj?ymCLm)zz zXPokosd|3ubNBw*PTYb!>b3Vxfe?wTa1zy%W18Frbf*mqtqa4dz{V)x*H< zGW$kLr*JEjS*|$-#p+na+Vfv6f+!%tP1n3D@oa zUw8%iFVa4c%x#Lr%egbD61e}@j!lXH?qN7G`+axgMrFTW6qJ;i*(v$VmS@5jyEa){ zb!{@|bWkor{s&G636JY^{F30nP?m7dOP5llCpT3>8A%+MSi63`X%M~;i3$;%GVM06 zDaM)}({;gDIu4#H=J(T}RgaBL&P+)+DZ69baT>dF2{W=~i#`^yxR1_E+v^k1*^DBO z6By&U6nn-dN;7)P6;HT4jP;q~OnkRzW@d7F%j(sqop#!)eH@<5?u7L}v3E6E zjrjquo-AlpuBbH}GgJ=$DhCeSInE7sRoQy};)cAQvKCeqck^_}h9+AQTMFhM?N(qx z?Ug!TU}r(`k-~(=BV(8pA9i86I3$7u5v2bRL4v&tVPqKZO$((+@C$Xwr{eV#?6oq% z`(t<1Apu9gy?<#IC!S=41cRAk19L}(!09r(901-j^1s8u4r_-4U=k@y2o=y@3c~4$ zKpYU+^df)y4uCL4#c#@}IaV?clTfh)%@L;bAk+lj63;1wkO8RMUUH180(yItR3XLl z>|=y!J=mkvAe{M1z-d=2dE223&{PGTMlaEa1Q7V6+@;LWAi5k%rFA)!I&l0TAd32F zMt+LTMvuN@Bp!smqaiA459Q=ZI+wH`<$&4U3qcT`q+jW1f;xa6W#mH$bU<-8#R}`T zQh^GiLLlrrOmaNY{`w9Ru@b{}YcCC3iCRLNp%fWA3Dr)QD(kIYv776f`m9$xebYp!x`eAvA&!MxBAw z!^r21!A>tcM+CJuF%g!Shm|zH=_cTP1@W#IJr4n`NRtAJj&Qzc zIJ6j=kVXW$k2q3Nh*Um=%Ewr(2&DzIwmga+6XvFyR#lxF+B@`C#j}z zPpqcQKXQHaQzzm!;i6_?>{?@Wsng1j$}tVSk6rQ+9ZFC zPZ0Br!#RPL8G6GKVS%8X^m{$_(ppWnd+9d_MFYI6U|h*jl3epqy}T`3?p72Xq3F0T z@xqi#u7+9!W{|$aUhaiOz))^aI(p+ftu}O{C58&6mk5PKJy3M77cl*-vSw-ELAnqI zT&)2T%G~!Hny_+J8dO0lFi&g%s+W|lRta0*jkU!et(pk0f2n(zyjcbnnNh=F(BXp8msv{$on&T*_b$jWLgm6`<+p0!-*+W3 z;6vVpM1xACPpB(Q(kk)Zdk(KY+KtRPy)2pQ=kllP_O2F1SNA`WgKrk|oH$3?H{DI% zSmrGEm5Zd?4!CWqANeWTeNk6&hTf;&>hr!C;r68XK|jiF^Dzr&3B^|mF212 zyz@=)Gmyi3aJsVPO_Op5$gO}Xr(cM51n4`7#!yb|h7>ai;R*86Ddn>324YsY`mZbT zQ9mpsSM1wnaob{4|J=k>shERgj>-nRX|z}vMK1)6kGuqnRy}i18}@uLQmqoo?aKqN z+u|GE19Y!Z%vbEH0l9s5mEY$!L>d#`zYZ{JXyarlDYwacaPUIULU zu|3&C&y0QKF&; zCMq8AT@S}`BV3k%)1>>jb|C@XetP-4(bX&$iAw?B|E$DeOpoGjyM5TiY=BsO8ON$gY+x*41_jH zgw7Il_Qb}evnSK2{PhA)$}%6;(zOVHcBS0~A=HC*2v34KAb8WnIFVr|2mM%q(0Y`gr=WRS zGv`i16A0ZHkpCT}I;5V?J-T024G1oC_!%YK}C42%u0!Y)xQL=Du`Kc zQO<9I6U5RKgIrJkxQh}gX*yi-0AdU;=~~o}x{4ozkX|G~JPCpz)UOCx zixHx)OVlSQyz;Ip_o2x$OP!2CrDAvUo(0FW|q>m{I)9HeUz5~2J! zm7}YpX28}q~iqLnSiG&cQM*;Kd znAr?;KBWjuZbhIERcMB<%7;4UUz*zI2eXtjduj6( zN%E*ys)r7%s5qx=7M^oFJ@K2i(!e~*mY}R$1QtIDro5?F-YW|lm4)=jAtG|AL z-AQILsZ9{7oYWl?c7n(HV9JtF@AX@!^rKF&R31N&VI8S+xr3{=M1xWfNGBtR4zds| z1UlA}AP7RkM=AlcQ0owFLp~Lu5lKF#1~GXl85AxJeepFbf{Jk3Wtt0}Mw+N#%3Zf6 zXa+yqF|*h~6Enu%SvPU$WmGY5k3jj7fk3L5scCi|DvgXL(%lKljz(s=Vx6F1=wNC$ zkua4e3bu0fWCR_E*^^x>;v`Hffo?xjb?z{mN<|YZ9?c=uh|O0OV);@KZ;6xgr6L*U zjlUWP(!@;oy6Qk%FMavgAyiCnox@sX2XFok9wT21orH4r;{jnxD|J;q{+w4-Fr`Uv z&@}~fWi@xMZcWrgYx_>}FLcqG2V#L6} zm#3Txn~=A2KbEAQ05wcJ001_hIG!em>SY&%{Zr)%gS76-&+p2eJ2>XeD6Z4PQ=^kJ zxKE!3+H#1^vY^uirleN1|A(`8?{DL{)^z*dv**lrKypU3l-RPRIN2V>m*GtkBi=%Q zl4U!=KqMr>A_;~UOUgL=v!CaAS2qC4QtWYlF_)@Q_pYvb*Q&Qx@#%Yrfv-=&rwmQP z$pt%-(-)xEPg1DMqA`lfbEc2@S z2}uxY4^)DbS7*f}Ur`r9uazJ#1*RuUFa9^%56*DzJXjF8!4jBdEnU2)z^aSD7-tkV z3z%}40+&ojsZ6>EJ(eJ3t01xuQ3fR~`Q{03N|e~*^_*@cP!6nM=?DlpJ_vgk;YI>N zmH6vo=6=YH~s_sIg;62M;q79nx^RTSw ztd&bX1fn4zc2P4u-xwmbU<_bnL0B(M&XUP11sxWWjFp!TbA^`fWX!@q`o2jhBxRAL zQD~qglm?_l-@4TLhGE|!#P_0hY4B{4BG-#lpJ4`^q)U!BuAi|W+_#h^ULki+6 zq@Erhp(g}`5#-1l31BO_7mIJeRee|tUiurGyj|p`4~2;mMN0)BG|kh|aQ70W98rlK zO$T-?;XBk%Bh6u~SS$L=ys*>fS%FfKcUYa;X`86TZ<3}6PA?-nz<-=b#ph*%OJ^9Mem5192zF{+r+ zERJu?;&Di7VZ6Us$Yf*n3>9+nD^$Z^+FVq`d(oQm0IQM1C`rNnH`Mc;`*;oKT274K+#=}RLgm<|m2qU=FzT0gS+y_+d(^ZwKg&!C zk}JP1_-TaT6#O-8%jr;E>#IiglFnjqrU>NP-l>c0wK{|5@Zi|&fCs(Y9{eUHscB8# zJ-jf&OR9BLr!{Q6bM4LC*&{UN*saNlCiROq4_Rupfo)$VZAkQZP3NjmsYz1qy(#G8 zU^2m|{O!G0)AK|AF-KN24=Ekg>djf&`-A%*eb#){e8)}h$Dj!a439^X=9}i5V_+KQ zYwUwl92K3C97YRJ|Xy&?6qDc|^JNUWC z=YK~u?-_z`<1vBjF&8@NCz!Q8oj@n&>?6K9%l`M|k@3!H2eaA1Qgh!`dFOP{8vWxFoNli0~& zyR5Cfbx5`xoNqz=XO^!+0(_frMRfe=VF9o3?Q>^u-5ttt`JL^JZFaEuPH_OqIhXY{ z55UXfB}1!J-El;gqQ{;pPL_P6TZqF9BQ0-4!GV4_^+d+=$jnM*+)7*d_8<=`-_B8pOz z4WIxYvhbL$7DhzU)#`$+DxjD( zkkw48jxnhU3y?PCAnYorj>!VFlbj{j(lLleRDv?Q0kmCmMA#(+ri9|;2ZU{(0tI|n zuIRvP>3!C7e`D#k64(o@Jj)(!_rNd-SQ*QF9K(d}Cy4^4wp$VAq8Y@H5V!<+k~k*_ zz}poP8VS3XB9a`A4m}SF62KrdzyTtJItURN!kV$iAKNh4(BA-lRuEuGpGnR!3ZR^} zQwNu6I#=}oVR?2nCD0EQiuY+lAt0lN=@W+niq_)xAh zz!bPcK?wB_5S(3y2rO0;B*ZAC@_5BJ7qNLnnQ7sKP+cIpBT@q}4O1|ZrDmBNVU#+D zuKyv6Whl}HSvICCPv7*FzA&mDk^JGfJDij8+u@?Z;dYJ}d*eP@SqEVpPSB zZgTd0tVC(Tz|kesLB+FlW{!v!)MwuD;DS-03i=!4qeRF%eBb_7&JYo03FZywp@xDG zOgQlx^J}V8*1wnX^l|h&g_v;bWKVU@LiJ92GE7cp3Qxs)$Csh90b8-oF zAl+0i4pvPLS|IF^+E2m~;VgqlMa$9+5#QcLIky~MW`&$`4AsLNS!PnVBN2%bvw=Jk z3ecoX0^qtch({!diCW>Cb~Zv-D1~Doj+GVFmOhKofIq?aOzx!OGY;#e_ENMAlRvtG z86zst(s3$&<_&GYW=M1+H)S*x#6n8wv?YA?kZ!aT9*D(aArbF8^Af18}wWI^?Qq}&)2Ez3&KD^`$~kdRi8 z*h`1HXY@2f{LFM79y?`XcNzU*9S4%RaVU2Uaeq;FcTs2nj)rH| zgZ7vNnn2Ftwu(R~FkUXC4R~*7XV47S_g5d`n0;nug?KQG$D7uTH)!g+C(ZwWxpEB4 zVC85ud3ka5a?-4vkB&|z9qAu;+<0z3K6!J_-g>k9JUo-lG2$goB(I$Y2sTd*9dcD9ganS|&ips(Gcv~rIO@89nSU1OFKQ7pyKjz;=IgeMOuMLF3LR%`lg9f}MYWd(-?dIlsJuV{v(e?qT(27#nar9KC`J-+cMo z=7;sS@b7`AH3EExLy`6{4~CSc>&d2W1;TPX6lX)NKjxYzth5}-Sis;j`)wRdHtop| zDiMxEaWG~c*01C5#F%@$xtprI{W4k_#4L+lZe)e3foC6FTb@oY^?lEQ6T%RXq8Lo^ zae1y2&9kdZz-)P^SzlYmi_P|cy$+sXFm6r7^f7KAg~rQ|m!W!jIWV9+cY4kM^tCHExt=aP{1Bl)Qae5xXjH) z8!KG8kk5t|NQ)1pt>65GY6H4!4rZ|x$bwRwbp+%9HiRG-Q4ESYwOiISaXASPWucCJ zjHRdmc?>s%oWc>okhLHK^PC;^wnNsyZ_yE=;Civl*^6b)!|EJZi&aPDTjm@@$RFj+ z48ZN2ju4rbQAdsj>kiO*-vXG?jlt4uZEH&w;a;@Dm^B%LSp9e0oXN__FLjkC@Wgho zAej}mKV#N1iqPpCh}wx6kwXE@*2XNVDo`)xa8^^()v_p}HUYwz2!E5NsmRjD31AQi z{aS~7v-?UYmg@qslp;{YoNY%zmKO==tB0m)+@_5Jak2!6`zjFe+{5Z?OmM4FK&mDH z)EJYsnB@nNm=cZ&v1x4r-e*EP;o8?AVPTeM3(9t7gMm?^Dgj2MA32E|;=bFC;_6$T zR7D-Kx`kV=B-x&5wLt}5??dg*Xc{Z zNDw`!l|a!C;L8V#*I3u)ZXR_lI*A00Vk;X}d&3I45)tZ1`A_8iTP=FmIzquVlX~Oc zFKXG0YrKmf(ocMswynUTHz%2{5TU{X0DtwDcSOJfu+94^N}{I#aJ|zHB1e1cY%93l z7YCpZkOVF#6bHDHFfv*sJ)Z*{YVc%Q-DqBl6xV)yaohm2pi z4S4Qg6^m18Z0`l#qX}UkPI4^qM#-fVc8%@96k`iener0_CX_m(yd??)l7~JqT4L77pvhmV>p_ z2PG}w%kV8LOn}j(gsLM~#35?)Cj7R`Yf zJ0T!Bzp*(|!q1M7Q<_lP(%As9g4N+pNmh`q^C9AX^S{1qR(5tUVPSxW$oY0e`0ih#ObuUFcf?H#sE`&!&%$@-eWxA zar|~`1(SPsfH>yyM;j0>LPjsvg*PTqr?RmL5PCpiyxwf-pArDra6ggXsT)n{SB1G~>@-v{9H(Qw0N2kL5;p))@ z_%Fr0<83@0|8sIU35WP%7-auGJbum2zX`V5e!_7Ay@;|;!wvJeCVeu*rwun? zIndlYoL=F)k~os@FD^es_h^r2e}HNLhZv&Pbaq!1itDUm4D3d;%vzoTByBf z7bGgf5W!c=1fn3M?oJ30B=S%Q7qh-&E+zt;3N?6fC{q%aVW(zIJ+69YJyFz(b8 z2#|7$kT=0(DLa^1L}C8e2uir}ib_q#LTzSsiVcge3$Q+Dn6416JmS%a=<}S>jB&S? z9Ci#{UPY(YMZki<>x_Ap4Gz#EnNrl5hn#0J6tktmN?@4FZ;wNYz{YEyRUAY~E6w!F zERN8;gK%!9cCI8sP$o%*ztts&Tt@FQ?w$13h#3?iA|+>p&C?i6o(Pv-=@!hf7?XlQ z5hNF;Qg^USi&pL2ay?l`76qv^Aoe0{#pWOcL_nmrNbsW>ae+NGM%a~<8=^-sDhat+ zj8KnJSUJ2((l)9VfDDB2p_>dM+K6;A7&a^sR!)mS7>~8mnz*nj>Ru&R5h3Xq9r`LEYc%xIVOpwXn8XrJ zjDoU6*;s#b)ROKef8Zl66h0cGbS3#22@T>9r>+y^p|7S$yk*Gb-xdO{G&Y0R$`qfS zorQvwbrB}Rl@|oEbo60jj6t{F4Yv6LnDhd_ftQTJ+b}bl9l@7Mh|3490Ty&CPM6yn z1bv5)C4@-Um;}uXJ<6JaOH81>GPk6QC;cY_A;NdpUn=7$f-=C9g)Vbe8!Q&)M8Lb? zl2&U%rlJfX$0j29GXum^ndSk-z=(iKL*-C=WYzDG?R2)Y=GPysI6FKmA+Ya5z_C zVkTI#LSwZG%pDbS?qEE+<)PrsDv~^^Afc3vuYz4sxn!1f?a4glrlAVPCt zx*!T;utX*S(Js~04-UDg85;u9(nm~7f=rPxzwuiA4dJ)Nd_>MHmr7SmLPQE0N&Vi4VF zzWk!ms^@V>e4%UC+2;HW4jTRU>ce&Hv)z3@vm5IHi4Z_=`PdR=b6<-Bfw zzwgbO_%6qX&9YdOa@Gp}ZD84z4H=Y~onno_FnxO%Zn@5=?nj|+xte}sXKg6h4UI#j zEFz?0@hP))Acv#7!F}}&*1zG_W9P{5<1cg^K7F-ddY%3D+u!z@8z&#%IQ;m=n}*|Pxd-S1Gxhn;k`3-cHn?sx zo!O>84wKf~F|kj#C)p0iui;S4sI_52OfI-Z^LVoGxRRuNET^*!+v|9A_7T%~>+}$# z>IB@`okF{4mUs1X2*fKcZfC|^!MciWg!-|Lqe5hB*Q^UE`|VXcUQLCUkC}~he|rnN zCb7%f8F~>Ur9m${4;M?P07RAqCJ5G07hS-kOR$2ej_KdeS>Z!Jw?Ii(C@_cf1j1Dh zvyp3q^a4Il;Uk%0v2^YM6uLqo3NYrZzGPY^3>MHriaEedC|Q@~;SMgg%%#YGh!|0^ z^9Va}+HCSt?GZs~we&LmiM=#uS~K*06V!wQh;nYxgc-pcMu?fFgmtHu=3Ep&HvD6I zENc#s${2Km9ErHiLNFy0V)CMqti=It6iw9yb^HdXvmqGoT(BVlxcZ9WOu&S>q^N7$ zv7Px_;<2Ait-#!NBDTK3E|zdpAj=F4)d2 z2+U9q3&2I~-~&np!D{A-ZBS7TU$FnVmI>K>$XaGSX`XdqaOELY11w;VB_<+9F*=o= zCM3di)1OKp@+{Uv0)b=nEm?Bl#da~`g)OxQjD{4{!@yw-3-%c+UUOq62&w?4OU3{J zdYX_g8{s&GBVa9M^;$i~fmJ|QAwM+)d5L=XvQ5VeH5ahF>45vjEVeu#kbhHgslCF)M-Ko|*|HDIK_H0tXCH$T=Z$CLQ0o-v=W0Kg{$* z0qB(}GwlQD1z*)9tzpv`Q34V6fObvlr$N)yH4qSic*XJt5tq%Aw_%h_HVE_LOQn_y z+JiFZ%%e_c^)PZWH~^B_qi2j5s`*}6Y2hpy#w?e$?D{8Nom4Apfka~?V)(S2GS_*HL>F8n;)a!yLkZKlzgOx7exfFdZo&syp%t!_g*Y>!LN5>uQ&StK~%)%~P zptGNHLFRU#;4WoVcJ~VsC`=Q*4$hF>;4AF6`vW(XUPV z46l1SiZuP{bauZ?cIV&a4E_J$bMRky4#D3Yo4vOl`w zUYjGl`ZfKNW?y$A+?!N(Udt(nDrmi+DAW+ZZhZcofnmpY-mMf|j3$S@2^`<))iGWP zFQ;ccT$zkN>^YyMsBO|s;C$*ev|7Lmz^mK-ARv}E?VmpMZ2`2BDa}lhSgvvG^w1gpC6oa+N1yqrAF|DB2 zxIB8CkP=XDqPrA9f-P4gVh#uApzGu(>|qvHYac343a{;JZRDya2onH|EUwPgAY9;N zMOzMA)~u3hFh0cIZk_-|6NnxWG?_(KMaH^AJ2fV%9Ka931Rnvezf(kC^ct6Me8;(B z>w(bUSoO>kVRCY|W(8mrW>&tzP;*on5W)+fxah={n$o2_`;e|9xz{1l2JQQKDBPqQ z+$=!E9R4UcmhMY340be6qcpF^V}ypX)$L!Ox!tV5T7&>D&~lu5_>;@NBIoDTawrXE zgi!ka&4Ffmswh&F*Gui7e^_Oe9R)`V6@b-bW zKwClGttXJmtl{Ju6FdHrBCkuTgjI^NQyfhU+A$N?HVkS}bhtW%zYVamdfC9{+wwiC z^4M_Duw|kQ(3yy+awwc;UxraVsaqdU$0_ou0Yl4zo%3d@L;hAgSqum^kuhiw6%KQd zTL52l{pR)yp)`@&Srr_pS(Q@C5s8@5t=9|U_t8^J#Qq93i&m*bm5|8;E_U#tDn%s$ z1uqdoD^!2tGp9yax_yx-K(XnTqRJ%^QAuiN0hK3t()VVK6+^L73Q*ZCuzE(5E8SGx zhtH2m&21S44bn8uNJFH$7&H8>Kt8I7a~mS{WMVJ@d;JlVZzvO&_HRpPH^c-~nwO!d zhxQzySg`8?2aMxd2s|Wdc{?HW(v9glbf6@7T=WgDois}J(9qYMfl!4KQ)@LLUSIx3 zSyWLml+HCeAvB>@A@Y;M%94k5a>LWAB*(l$zx$V0%0MuL@IxR+{<0HgZ@3{vjvBo4 z5L<_#%;pFuTl63sfC7}iVRfUX=cn4w(9mr}CT_DV#TJbs$zZ*!t&~O!Gh>M+BB+lp z2kB8Y7@xU|r-+`eO`LJ3(qfc)yE2{)v8&;42#aHAB@`tYqj)l_Jv)u!{#G<~geSxi zPBjSMpu}fG<@ZIGgGS-2vPullM-8{YpmPAg;Wq5;Y;iDhe{0C$L>vyT|Ml^Lx&gG0 z4}gC2fS(7=#>Rb=k6y?4kb23H$K?m|D8b&LO*@$4XyGNCvMu;cK8F$ zR7E3N$12Aab6;c+_Y;SJ&)1;L7hp{Q$`MyOg0gze18mA+Sj;TXfSi*_xp%$=NIn*o z5L0+fZq0Z9Y`$A*mbre^J409DOz6^LxcqE-PUfe;pa*!(xCCH71Ykd(4ELr-7|ywJ zRBVLr?3GX|HZC#y9yHtU0@n*DbCKkHXMt34uRPxIF5~!dSp14>5VsuXc}Q~c`XWs4 zr_%gBEQnCkLDY|}odH$xaPxI4h zQNrh20KelB&^fg2ag$N6(UN{~883OC;Uup&IT`s{&fhd^uNn>{;z;kA)obu?!hbj@ ziJQ3=@D6{Nj9=ZmcX9QMs>HFzKd#2-Z(1}gP5V!uwwH@WvrO8e!o1C^mLJnYtCM}SRx<0arTm-T)vHPRX|jNm^eL>Xl7+1Mg;ORm75t$v{eB*SpuRS zI=_2d(FVcF^VRud{I(UMI zh!j9K7*SMH!YCO3tXQ2N6UbW}!iTK>tcM_Ydkorwsk7a}GHX>4f#`9=EMO;J5eD&K zk_eVOfJlusqtRaq($N@~H-s@*{*8~QXNic3jmojSv@YsgrD?TD+S^DC$YMuwfv$cPd^Ul`KXsNkh3V*O?XkM4klkJ<|7DTw}YXz^}6T!^q(QN64Z zFgm|Mdo~xE<>;gDb2d>%F(K;WLk-dq6@o2eC@5P&k~A1k2X-s;kUp%$ZC8naw7ARf^o!97Dv zIB)57_l)}-mH-g3X61{486lh$2Ar^)4z!>;SS1U^tU-@f7kptoLGuJ# zUZ4k(55h*xFd4AIWMwFI@R}N7yI79}f#7DMDEJM*FacV}wqE-@Qc3a?xP;$I^8sP| zh7kFiiG=s*FqdD#5KvGIBFUiLTN)Z>1aF8CGl*H%RuICE1w?Ew^3LsOpm=HtW;5%u zIwRD8*pjduCLWsV)`VFW*8&kb3)bKKhEbT`_#|yA1Hd{Ifm~c}6o!rklwlg^9576G zn~n_&Tf$bI^#h(86Q_380eEh5Ze)sJU>M@9N7(y}$x2?GTG|+2QmP9ew+(`6Mf|FH zFwsy^&cWsW+xHFt_XjkVh}yz&>wJ_P9{)J|YcbJ#_2 z?(^DEMRC#Pe2iAG8@e4B;U0XraZ{ zEAfP3LO@;~@@ewUnL>QoAZ+y#W|I7|BD@H*Kv)Ald7FcxW7=Y0AsnR$zXou%Z`=cg zhMcwQAR&IZxB3nKJGlQ-gxsuhiV&c^ya_s;d*v3!e#46GeMzBe_B+QUD^FXm-0NTO zLPz1~6f5ri?cw8%y}|zSrdtA9y#V9dcrSQ|+vtZ|veAzx zIvtUS&Dls8@DCXToK;-gT=$M6({;{Nb~d5u!peT4AMTyKjRzas5J%QlT4MC_-k^33 zph&}k*%>?ptPeN=*?C`q;)~;4cMx(lQ;K4kc}0`gFtX9G1M}CDV)+;PhfHp2rLtfT z<=hop2Fu(1@){ObCp4eCo38I1w0#D9CO*psFlV@D=2VZ`ef_sho@?jFgM}cgMf{-q zUt_bs)V&+XOT+tjm(6$2n^nO3%W1QHb#Zjn99&_yK56|GUpoX@zw}}|zkWQjbF$rZ z#~;VTs^fWY?y=?g4Tlaf+UtyYb|A9Djjh*aBY5wRH%UDQIBJ^JQ2Z0`EX)QoAg%&Y z?pGcZac6pp8gzcS;J5phjbb9*cWO~KTqAopH!GjzCEjkikb-WHfSzmdyF}SAF zV9XC>W|eWFlbyuOjS!0go`gtR_?e!uy#>9qJP`#fjjV0F<5T+a}t9|S6~;71w4g2(DcW0MYxoaJ%WutZ9Lh-ucCT&*rV1b?eLlq8c9 zC@LyKXp4nEnUpmMLPR>L?Xp5`db&2tq8dnKGE1&xq?#ZALs++r;;~6ui56i&Y?0G) z|MG^^C5%|j5-}MOh-oHuypm2dhDK~f)Hz;VAU;iH6$U-Jx~m$X>M2;k5dsdWAf^Lc zST>L$p<+c*S*xOzOJyKCG6vqj=BIbYA*?mAP60tyTrp9 z_4u$hD=;ioM#T7yh(b_wCh#hagVp5l0rdMpi1tkY@}XIxeWQT4tYvK?iTw=~_@Tj} z!WR>okT?hMpUSd{WO-mqGXphR0!Dr7US}jA!K;cXco(Pwb{xVTDfI(0LztY`1OoG- zARbb$K-$_%f!tIk_;8Q>?v0V}ma$ZHrl2^qD~Ali-a)uMr3yje2){TeAi?N-_Pk%- zZWIwr_DK+>3p;*vh&qsmx{%~(4Kx<5!D&Y2!AexegF9`r>Vgk(hZm7OIW&Y+f%E(Pn196Ylc2gPzmgAL_93>mi(G?-<;eAHLtr4Uvzc|o;w2d;q$ObY2N&xN9&4|U;1_5t^CTdhD(P{+IE7&zLuex_Y zKDy<={WC9YObNwMg9Sc%MIF#UDR3Uv=5F%x3x%=WshVjh^|K21V5Rc`Ed!&Ue#5;! zdwa`I;eT(me`KKiogPeKBSTYvfQt)E30#`K>D8*MEZ+R!M!=(nlV+=2YC)BDpRCE3 zm%*Mbb{RV)C@xS7zPtN-iVM`B*HFNKYkRn*XwY=D9?q#!jE9?7n%u=Fc%|s|v8Nv#?VOHxq2mPeUIBp) zxZEC;R^)<<+s*PRSAZOAq<=W!E}a+0Z_bWjS!2~d^#;=iSEt{Pjz_}>GO(LRkR+y1 zVU8xv7Doe)uIzV$}EYnQX>SpoxzBFz5bb@7_iC*D!^!h|&Dswf;5>Le8osz1 zasATh;2&2Lm@oh8kVbeFY^3~?DnnZ7s4mR~7W?tZ&ja$jy4>HH?TAAV z`rP@+Y(#dvlvV*aZ@6s=R(LS*@_n#yj>bea8betHhfhNOH&Wwg={fnd=mO|@Lcd&_$ znQ;;M4OpBI0F8%MpteD=zc?|Ado4^1rHIP_MSf$E^6IS{}EaD0FQ)Y1W< zt3CxQ1QlfYDi-_Mo(u2vCk8fC8dCAAnwyE?XW#*(HA}!bok+(7J$F%=`2s%oS0% ze0|HQF($IDkONN}H1LN32CLA}OQ5;SD_6QAIk*<_{WeEtg1}Iq{ zz^&ECsLiWG1kfY$8!mo)SZ6dj*JN_Qtq2>DC^d+8G8K4rhh)pjCmQR z*51?I!OGJGA0NWcIZS0Ydhi+i5?ppQ522@*m+joqsMUH4n)oVf?e;np^38bBXfbO~ zzDXD+y;zHTB3=yATTic=)VQ1#6SGVM8ZlBWOWczQV*ZjWBzk8dzOzIt&L zB6sm3yblm;9KXvkB+gB&Fd(?zeK^EKd~kL)ygUXr;-cB(nD2A9QmO&%m4%pW$Mp6! zAbCE<69>*tAfMw0u{g@H+~PyLb!dHlrKgT(S1%ekDyNOKX6~+EylH-2Yb|i|^o1(O zIf!ur^XHPChPT{g&-oyvKjy~6-uUc@D`n1gBnT>}Z$+Np(Pd}PR;E2InM%-7U z=dlKVkLQ)~!St}ZGI59wwQy(Rp3b??sO)py=vjhmU6#WA3m-rb&)l*QGF8bG4di&1E`c zt#JZ_Bof$G{>31P#OTSXBxW^2Ma_;02CpP);9^)|BHN1*AT>k400iqK>yjp3lb0b) zk%!nb@=*4yK1!Hjg|t4zx?^3ak$D32%nazlM2-jt9AV6L)_+AzKAL#VrC1XX1}YR| z0(PI6Woz3JQ%%wp+fq2o(j@?8$wLG}vzSQ7tOTIP@(yA+By%P^=^jpQun-UsRIKnMcI)^&@ z*ZAB8V>w*uMI8XT0#Agm6s#S54bM3NjZfg*us>MrmJETn5Rz*ZIbMng=GMIBLl8WI zMc7h-2*3#GA|@ycY^~h>rP~k!E0B!u03m!3*YF#n;Bl%BmbY&Z8rBsQr=SHGcP3J}4MkCsH15Oga87{Ev5ca!k&DC-%0mRG768N;OyL4(1${z80L!{vR|i-^FkY$3 zc3ogD>BCUblElUI5h%)+hPEIU!N?pVGJ;kT5@CWi?QcXu=a>b-%z^f05d$*^aiBwJr>a>OTlU8W26em-khnU~bqwG>DG2d33Zrj$kl@(2ZBe+z~lh%TS>T zdeoy{09)gM2x&JN!f`{-7aET* z9%fZL6=63zAx*yJoubf#m*tiTF!1DlR=wP%9T%Hn8oQ%mbU}jla z_?uFh>C6lDiH0a>q}FEZz+g2KT3jNC8%^HduzQ$*HG#2<@Z+N03=WU1GNdrTyXj*O z*^t`&LpUrHCi5ln$K%QD2c(Ynv!Q&Z^no*>vpt=Ta40H_vd{R5P$SZ3N<{P{DExfo zWt|IJ>*e5!S)O{@1tr+aa{HIzt(-8}7|Pb6}P4 z6^8DSyX2Lu`_Rcb_o|V4@Ts0u>Y!>J2sOOm;t%esM()<1N`p=S3W>E^z|UD+DkOp* zaBCe7uvK98xW}1uuF^|FG2s+yEbQ&|KmdPE&JK;Eky||g#yHpL4CWWzlcX+87++-19q@zcAnc5PG!LfOln;RPoU%M^ z9&&~C3;pfsUT<)9{*s^ilf%O!p#9)zAn!fP4D&n=!^4+=;htWnfISYC!LVD~(<^ zcW&SL-@{?3$5JPBCcUUK z*C>MDkQr3Ag5b9xoc1C^ESG$nP#Fx#XkZpXgzHwO#_Af(93e^U*nfg2c1VV z6-b0gjFp_}D_6VXVkR+!Q>T0grU?oQn8Y>*bO9c>BFt)-c}!7dfUbitU}RT4Fmk{+ zXQZsMrU?F+c>!^zM2o;GXJmjzgcU0Xb*$k|mWXh#*$_3G@(immzoMqn3-wzZYY>VZ zIS1lghHN=IlXgm3hZGiFhyLM9rxypM1GuH>Z-wP?>%u(jJFw-PB=a`}nR&L6S(87u zLyIwUG&tHxS3&(W97lwwPCQ$}(iWUs2#b()kw{7VnVf@!0m~Fi$R1>EvfEgr3ni!D zz|5{{z~q!Jt|8>sw<0gtjaReAyhIm>mdy-Np;($rwepS^(`lcINY*TQ=1Fgw3lQ4! z9*4c4HIO%@FwbCf7TZJ*dXR9zGD}mvM;YOeaSkhRJJ$r+g%1N8;SLl(`VR6%w?O4(UYNXy0CKU-1u_gRmwG`P6o%M2rD2TzFg%I|1m?D1#N5V2PSVCz}2-}Xi+Al)rZGzGmMcFp2U>_a<9VU!8cRmnOUKw~=_!W7W0|JD69f%fs zCniJP%&`C2A|Npp1Pcp86qGJ9CQtGwOFp?WQ@ejoAP$?EON>`+DuizcQGAMQRmiTO zi#1a-mjH2{U=a2gBIvpZy&j14VV$o4{+7kMU@R9!`x`odwRFEF!k3S!+J5(VWTJ%Z zD_%APil;Xf){NdXK_)r=217g8d{*ZZiwy$=QX4`D6Fu{2UhIgJ$mQT3kMSYo@d!UC z2EVN<@)aVC6-51%CS$g?$7=FXX|*AIz81$jFnOz%Se;SKJ^{}#C9TxIEAq&*eVdQ8J2#DaNVrm7y%K&Il}taz~{&+ zTbIiokm{}NY)Q1nO(5Q!Qg{lZ@?>-Mi08Ll0W@>PDFD)lCYPQNeK3g9@|; z;iZ>p_c;;S#}hU`l|$^n*5X9$tO6uj0tU!c{+tow8{GM6 z!^uQJDkSS;=-8v)>c+bFDCU;$#>1rTA;h?n4({YA>R{Z2N=sjdnj8*#$Ep1gTKee+ zmy**jJ?B^4@|->FwZ0usumHdDrkIvnykK^Iuf_Rt^y1p>jdON`2QUv0F(K#t<;)}D z$L$4;3qB5yr?1B6j*{m4i}s*n=gy-DHRrH!wmde8?VAf?4yWS_o6_FV*gJd!u^1Bi zoD?&+gU2krK(UyejJ$R&&P_hB)ko-be52@GokPgkq1^X53+WC-c6eNGP12X}rPz9J zFFpqL+C@S}1=pT#3)4Sg@{n81^uhw-s=+E5z{(Vwhae~)X2J*cIV~XyOSAuKD3i3p z2^B)m20XYXIt56xOU;(lP&1}34$X*q(@D(v zwiI+{^D*R@p&Sa^@S1j2lnewy;aBh*C1_5jbPYpf zEyb7wpkBc?!n)XW$^!-4gQ8%5lb6A7fZ;k>m^=Z)q6q+RjJGW4ydw`Ek{l6G%Mc}i z3P$0e-(*s!Y6-PtykA9a|3g2vQyC<_ zZiSVmA?Cbas4p(=G3&;tidmONO28;#&Z<wj$#Jj;`1Lyhk%^DwX;>95`LWcZ zwX#hVPDt79#ks~*Wm06nh>;y&474>fm>I%yc=gtSL(?MZnja9JF7_L+l_(FESJ!XD zsYolaF4_`+)l!7kb@Y(nWM%~h1h6W9Q#d%#SE8A>6@0mxCaH?1Nk%BB%r8lBMsbWc z6YyzUX$23p{qiaSuCaZVen&@94u9)eW;1xi)=S$jSt=-w`N(n*fq$j5XaY`@;sw_^ zQr&ydIx#K%0E)GzwFFaJ?>CHyLI-qBHEDt|NoM_OdS>znX62pO1i#}5Cwa}i^cd*Y zx`S~oj>MKz^O@{x%PCES-qudwL}YDI`Amo4HN%4m)Wx1*E_?&VEH|~#K2pEpyun7c z1L#{TlrO=nTF}~dyrdYe#^d}EtxErBJ%bFs;h&sm#Knhn56&a*5W$0k@;6vjH+NPR zOuNm)oqfp3Mg7>|0yfRNyoasjuj?$|mY%y-{=zMAnsRl9y_F5(y)`iS3O=#1*lw_Y zTx5>im5yr?P0SpDgwp}@Vrv7oisl$0!TdfM?6U0vLR^3Rm5xSyyPNzSC)8|-_u^m4;n&5h5*azJn5BmY! zATo&1KA#+pFPhu4Q*pODB5{j}=LX!FvG)pRA+533BXO{* zyGZYH;H?+$;RW>R@!`yuidJyFc%P46H|Gbh1=N3RZj7EcGvY2N(zA*u(?f_0z2kP34!$q~AIatFdhdpik7xF8{=glvG#$GIrNg0RV z`qM$B%gy1dJG)P{M}xIG(nV{A-bP<7-`}7&F9Ox)Cj2NJcR;JE>%w8w?d-2UY_B_f z>ttPds^?20Mg26t&b$uo#pg%98@^S+urs~X3??%$mVN*rAUgqJIdZAg0?qWo0?u5+ zU#&&}WUWRlIYG@e%d$o>iFurgd5&c$iHim&&%%sZ44Kjp!Omq$6AGI@wpB~3Z8s6I zG3&HkEr!~b&4h{9h$5$mfNYFXK4ca)icX*v0Xu@zB|-?HZk@MxYSzoTqKA#izQwpY-33YKF~c5p5{ z53&atxMm2p1-QU7ve(fb*=vQS#R#e+i^GTr8Vno{M+4TfS>7q*E{#jfP)PycHeVc0 z{aGZFGv-K>gO!bmunvGx%h5!tXbsJzAjCisqdQE5(WY|!5f0z_kP0ctAL06j@G>5R z{ey7%W*PN`Q~#ELp@mW_GP$^@>gEGGA2ET$AtRKI1}Y8|EnY1Z7ZZVb6BB>{{E@9} zx`G7c?>LrhL3v&ft|%5QLhvB;HA;pqf29$KgXwhuu$7jTVJKd}_#3zlYNWFIJ~2ko z<1rA|Z`mi&KukY49?c&u)SQd#slPosG1fEw_(@;_b~Jl8jJ2x0euP-Y7hrT=qlIF3 zGyyNdnCy0#ah*=0kTi-Ne;*b|;3Eh#gSW)n(p4VK>C>|jA%uXF9pW%1tSnaI64|wo z%}i#1X@?e~wHn7|HrpapLUu$%9gaiOpt@+L4hxX7y^eu6(7LFy8jr=Z^tYlsV}gW& z)9e0HCW;ta8I0Df?CgSEGsABN{elQ^hIedv2}HmA@;L1=SDp!Itt)efQ3l=_@v1Uz-;&M%r}Mt;GmQQL6x-Nwu0Yy%j#7K^(tNi zc?pMkeOPC$twvh^suzlTMUtp;#6uXZcKVnM{K{oH7@bW$`UJCW64RID&c_fCh`Ppm z#dmp1DKG5UYh!aoEu>bMZR@vWQr)b@*5VMBgI*WG3o^uA^%1F8i{tr-&8w{P`ra@K z`z#jDq>|tGA`E3_hTpK{ZwypAPiliru5M<2wZ$5|70&CJY`0lB_i!{(8}+)}h6Jql zjk@jyOv|wxUfMc_S@7(P#>|;Guh+{ly8J1Sx<>c!ZJ-COX~SKBx4r(O4X+~9{~e@- zG7bZ9*Y`-6D_>wGtZm@OLRgFoZuq@LUFJ0Bkb`rv`)cO}PCs}6i+F8)my5Yu2N~r_ zj{pn!YD0{N0}twT1+}*lvKG(H%^>liHy6UVz%;x+h;uJ^4Bc@ekh2T8V0jx~AzyR( z_k$I#6MVS7vbX-E8TOy7|7Y{#$IW*avXVQO85c)!)O_mEJiKLKT|I4Xe)ZAGz5j8m zS>;IG=;B25xyJ1$Q|QLP(5u7IE4<$|oD@95^oYT?*&Bb4Eq4KamCbLF!w`SLGv^l_ z%VGB5?aKn!Xw`jT=+5r&IsU6(#FDE087?iZpAQ$Q5OtM|Nc1NV6nysL6gI&OUoN

5-=K1ItQ|R-vLF;Mm=Z};@0>4~f-p1d;q7ONc{rMq|oe%N4 zaCYzB=<;41c6g+*+#;V$PFo)qTAd*Vw+HfY{BZINT$vtiucf(=XWrh{Z4Uqbs&2mP zI$p+?v$h6wT zTES*^KqK=htGQeW=2NJ|;R3@U6ErfrD%cUu-(XXwPAUtS4FF}mw|Y2>u$(%H%nl#pg#egn`3(G~X{K%gxMi*B$b z1J;<}T811Vfnr54Y!k}e&k~C%PQxxW66Bf*Z@m?i%GV)85cwLDA#IkRTtju~l2Fz> z!3GgIbqR*0PdT0GZmz~&>r8JZgsww~XBI$({H=H{!dtpZWNVEWq845}cwR-g7`3-c zVMz}5veY`+1Z+)$8Z68R=9PIt574%%MvSE2gvth6Is!;mj3a2}H$f{cK!jdECu$H?cp24Fr3iAR&S%q>V$#4uHr?>nKmNKc`9FU#zW+b!yEdX%}GlHBUOuc?-%v(OhYD!A+NJ|ChbPOovgE^jjEI8M@i3}!m^n(HbkrJu(J{;`Ikexjr$&K7=>yL+h z{m<oq^=`8U52JMmb}~M}Q5bvY#nAAV z>kN(SzAUc)wsqR{zQr}N{;bD`e=Au2_gl@%IfwH&@Acq(Jbu}1o#T#f{AwtzV)F_s z@Wsfh3uiox{!0!Ao=-*x&65#!$``{`?3*+1a~^0ryyCKvDHdC8vSaY_g3q}{&v7$a z@%$ndEW-ac-k%p;J;yH@EQ+f$ZaMLEVry)M!%KTT^{0in^LNqQ*c@#(_wF^luX@d4 zUEJL{WNQ!cL45H_n4w1IW`hoT!I8Ts%lEOlA0OZIAf!L{$TU8hUyX1PCwX}0Xsvau z*U>dv&&;QZb#}>_zbRe@f#NsMZsK_4)>p4?PS0K3m0ZvA*UeK|h;QML}Ay3dDS-JBf$$PaDmTR%!K zxj8=kapbIqsG=LaC+rc7`)8aw;Hlli1yhYi3QmUCf9V`2*CGDTc-zhd3JeZwcm^j5k+XQM6+fZvPo zpMT&x9bT&2g*VZZX9~mPy|s-#t}maiaejEFjl6B8ZlmAFPbIbC zs&%G=MlR!Te$(9eBFcdlFV_R+SWyqNDk8otP!*W&n32nVU5sNWbIgYWzfJQy_zbqR=bR;Kwv#V zb4c>C%iwvslm!qYzpa$tw^}H%e7hWZ?9gHX35wcmBzma=0Rn_voDk6gfEWp7p+sqj zE(8Y4!<-LdAx_Q8o=8YBLd0k0fayW;j0CaxZN+Z=QSXN zaxBCyrieoQ0p*13ctlc%$wQyECT+2`J!~@IG@Zj0?;=|8Ny-6yu(%V7tsS6hdo;Qt z*qY}Bqd^F4pm-($CQ2&?T=zGSjUktEMF5eeaf+oF@c_d8cOi2^F}(r;RRvTQSo#&T zQjwVb@_-#2;n_qQriGlCR45_LtUjc;n9ajXr$Sax0u8xnL?=%ZZZL6CVsh&Ar#opMhuN*x4^;?_JHbz<*>W%L(~hZi8^Lgw2k{4 z+OfY`4~L2(H~c9qx=f7_o&-)XryVge<4shsm-iy!ceOd=*gy@&`UN-cw!eL9g!==Ou;`um8;BoesFpWx|& ztCCG<0m4Qi9PAzuq~!=Zo6j^LyAG*o3O7fDQ4p3^DjLFO^_fDb7ixU4Jpk1bikE}( z5ylI`tofcwsBU9BufHj^4yo~>geFpWl@Xk}m!`&0a=akoHw69Ix)(*AfmlAO%Qz1_ zveHZ>Nf6#T`i0Xi!VeGO-UU!CdJrAWXwb$^0yPSfBaF#38ACer0(EEp(VVR(I@&Bb zg%BT;(+V$*XX+K?r63apr6O#J^b!i-lMK58@_U7%EyTML3W#4cBERH4GWkb0!4g9$ zC=ldJBU+}kZxHQ0R&Tz!3i)189MriGs@2%x9qLlF2ZPjP z=#my%D}n}i3wO3t#C(KEGG`1PmfDDpks6`KnpdgZi1Y$qjCEjZDS~63gzDB~YG8jM zcp=OEr(5kgJRH(B0_gcsISvKxZ*6lMdR%2dD#?+&9=7<^KID-#Ono|#+t7S2ExO13 zlrz-c{AIt7S@9`I8W(pM(|2~`jCwD(Q+rp8zBfeXPd3>29)7JGYcK_;_4*jl@#z6| zWakkeot^XnH^s!(D3g1E1cGb2s3S3o%Rs?C-kvyY22Xd_8TYw3W=5`#+qwpqT-yz{ z`c)45ZRsxsP(Qf;8LW)j>yLlB=0BS4L#*gvSFV=)Tk~DBrr(cd{rJtv2uAd#o+4mB zpFFz)k8{M2n`e$kize$|a(WMAHtb^v9`K!~mw1}iGxN}E-7%aFckb&MaP`r@r#{ck zssE1sJUg8ib2;U;P#5nzyx_?Z=p?9fhL`x8sXJ1+&!{blJ%jV}5wv*k%;8HwIR<-s?sj8) zHx4@LQ}y$C+FVR8_%KnOd!K)K@4K(Q_+P*I|NiUuAN}s*r62!(gRS}x{`~*^$A9~O z|N963`?n3un@^SscmQQIA0p%mh!~jeT;Q0Z5HZd(TN7f1jsk z%%kjc3L~&aS}4eqn%FoXje?BDT2uky+JtaH0KSV#5ydCT0Wi)J#X3$Mj7i=`K~SE( zK|zQh?URh9QECX<2Zx9btu*O7Nc4EEp;RWXj=XSxvbk5oHNS`y(BucJ%-RdGXi$ z!-wIIBNseuJ%q&|C`cuRc2hJ+*moraR-h43nAq0@brm`yaJ1+R04`Zbl0{5VrvU)O zVi|4gRsjUTw}Dy`kX>6sswF-wn!xON%+gFP#Cj(3H14}3AIG(;KE{~Q*iJn{BwE|3wvvC4_l+U4-2Q2 zm|1yD0VmthjJ+Uzdf(ql|BiaW_;3OM7#Er3 zPR?0!h&2zl)uPGP&yMd)LxN~IB5y({YNEd<+cTe%tAFyD5wrCo~iT zhc0{*V(8e8{?^1!`&Gdyj*qkjzYa;kFIn<&JL!pmNg&?M-x#o&iWAE42d*bs4R+1v zLD1Whv+nSmB6;a#2hU2A;J=vaSc&Tqp0migh=9dO{hL- zh1I0?xjr)*Y zhGyU7g2qi8Gp~2naVoRcY;AbzvFO|01k<{7?g0u!;KJ_};H6M=7xb*ze8(+)GJOz# z(&BS3a!53M`R&D5U)ck5P%%Ce`n`a9{cWu5-@lI~zH_{{zZD!_H_Uhe9d-R01>$Q@ zTn^#B>fCj>x_jsDAOAB~IcmohHAx;B$9dkpd-?b7`26@j=MgUeNrlqK5KGR+y^Ap% z4DQnA)@(4B$n3l3+rNMJ`jbyS`tJ4rx%0_${`arH$;AFw^CPDvIkJc;T_+qzqgO}6 zhaAUykz<+dE#?@R0GIn_61Jwq$6K>DvVEVcZU0(-ev`b9EYI{2u=HaQMy4 zhcl29$A<7@&bwPkY|9Lv-~Qd~pO|AmcxJXAaXeJNcOSr{X%0`D`ANw_z^&cBJ&jnW zA&;-2W2$|Mf`G?F?D);XVaS#@6jyp$gWEHUN0%qZ+#HEtzV?B%rHN3!P9}o&TLw3d z#$}#&6#3RQ;tI`=84zt81W@`+LjmR6(xk zjv_9yt=VIeiiK{vPZU3BE!iCQHH4>;R^&tC-|kDnomum`sF>W}=4q3iS0v>!CN4wO zLrxu-Y(5H5&(gibq{MV2VTP$HGom<~saKOWb0EU05Ee}S1c;WI1M{|^mZdtegQv^G zk``~Dl>t!0w~6ae6mT-AnYGzx<(q@0nM!D$WKQ%_#)PtK3{nq#M_9o!jtJP9HUY_j z!gWgf^}3#@FR{;$L&De*q=#R8GeLxJdvgEHs@wX|!-l zHcNg3?ihL;Hj5og$lMSt9RhV|K$MQq3TqyzP_ZwDMR?SaE!{@gCKqi@UQXdRNj})M930?O;O83 z4$;_c%z~p*%gwnQj$r{84j|Z9j|E&&d}dxvR0_})wUuW>^MKF+?!crx3{zH}z~3A$ zieyRW(tj~^@^({WYGu*UO?fLh!HHJsGZX>78Z>~I$~i#hL!hJC0&dKS!6#5%%Ao+z zs_Ed4i2WF>qC_D&%@><5MX0_IY9YEGIS`$`6Pla4n(etq%8|cKn*iK_`#@~-VmOpY z&P9C#BF12hwrrNANTLX*06u?Si0112mPSfk?B;<&f+de?u6v zZmmb8$8vRvH?dH*hHpl-c3Cn;_}Z}-TUiBND-tCyK3#jUK&Mu4-V%t_jS#0K1ZZC! z2GNErfRJxq&JC#%!K~O&*MsTXPZ3ohu$}FS#LR9D&X^K~k79`^2-9J?p;;2s z8378KAfYBv@BqVjtxaZnFc7!Zvl~dl@D_X=|0rFpkUY{Py~1( z3i^hRC4jzeim5Zj^jhFK!gjEQVL3ql{7u>Qrp5o}&)jcS>ogA{x{s_T9}-0H4vdMu z#_M64@SP#TC!+WHnDjhig>q_^GhYSyoeNgiRyG6ew*?Tv__^R5>$y~Qu6)7ONP9gC zJaw$Tajo5~@08<@mNHXB_J_-lJ+;|s0S~tEjMpn=06xzi26GL5fA9~+Y>e*S-M+v$ zFETs7Hd~LR^yuw~dt!Q_rZ?N)a9nd|b$xB0Tyv)l+C;nck9nU9+&|S_$=#MVgW!Z3 zoc3Xqk22%$fc|l{{j_gO)p>Z}=~D3#Z9vB;dprl?7HslRg?8@i?WAE(V0WY0JdwTL z8Q%A<7QJZi2h9T8S_|vwxo~xF3@TpLj{d9efP{g9aof|MD_2L8!{#Z#A4h%9M*rki z_x?Y)s|3zXdnb%Hg$HRCfK=g_dEK~ZKEPf0YoNSjmk)m7W`Ndy03$bc?%_lcrp7f8 zzqpHV_qy+G6AC`zPOZ=GiGQ1AU-!p5Zm^Bt_gZ*PtLi-Fyg9~LPoVeZ7d3EpWd!ZYw&R>-=vGp+05Q=>8K>_$ui>J7F>j6+d$&Yh!dfevd49XCM8(Wf7=~4 z?K}|9w-RVkx2y0zr-JM>ynSWO@Zx!QVD&>B2|nlTb3e`@RHgH0XPk-PyardB4!PTW zcc!!a{R{jEHa|>xzt8Q4hW(o7(2aiA+&Fv7BX`RqidXJ2THTD3zxQ+ZK$bnfyslj2 z>}sz9KkuZS(*x|eVA>yI`9B)Inw-v5UHb9rcyzw70|VWxl&RS^j`Sa?PADl}TvO)( zI_6!eCHywmHD=_Ty>$$huXme|OXZNpr{lWg@CtpAP2K@v;&aA0m4FGev(kYlO zU7QSo%vlozY|QNn=2wML3c`9w)#OatECyJ&gggk>YQT4(KMNn3p#!nx@Hb3!Ryy+X z3}qs+o*T*%r!X%TwFvYRLzrq6u|}%am<|ob-)h(1O$KvW$>NJhskN-JvEX{i9)e3T zB6=8`2UZFA&LSko$K<#yNtEn6YuTie^!~ykhp>~}N-(nmK&U*zXw@D5ia`-&m1IL9 zrLxGGW%(6ARFnWgoxsqW^l8c|?Fh}rda;nXy#hS3Ud4IPvpD@NGoOcGkLVvait^?J1*&=CCwn;#7TAPEgX1g7FI4RCvaex^%=0SFtQexm@2(-~+q zuED_4Xh(ou28S9A?LZ_7_}!SfF@shVLnqh>0G0x1RAPjs1yLuenLvhQsK5SJj2Tc? z3W(jBAuAF=NC2TV=4GR32XJADTC%@x^A%J`o(c^Cma2@?jvNZ8EP?F89~~MJMfMGE z3E|5{n2UmRC?vjrM5!mWL4blrv8s8DQxp?F3q8B!Auz{Kj;}Bv#XP`fhbzLyQkvLD z!Fys~!cNtlxpk=%+MEN3*1;Gy9dFMP7^hYcVF^qRCa^0?7^(p(hstyX#t><3IV)nb z!Z(ZXOW;^v+Q2(e$HGKzx{Ol_OT_Ycf#ML=r#NVY#9vr5Qup%bsTQ;3!*zFX`b*K6 z91k>4L0=*K`XTO^j-#zslg$eUnVcP(fc8^S@-o@Ah|<931wXO%V=~aL$%+Jp4wOBQ zqK5Fc?0$q8kgv~K3E}A>gmar46Gq7qVlZ!5Ky?OQ;*K>zSj*aX^zZd#+0fnWun9~+X3b2`Xhftg zeEt50uu7%(s3DQHnNTH^Et7-Q%@cLjdD+uOMj;(h+z=rO1(g1i5c*Fx2kI(HcB5&D z`yq50bAdhJ=>jT^`jcl-#GD_B9eWHBU82SVA%~cZA+-|(ja2)g zt2|Xe^;Ci^2Nl`mW$|M6a}=WZqz+WiTv#vYO+;xt(KFo$F&7jcbK%Ql%pj7E0g;EH zu*v{3Pi6*7ud_GofH>tjA=$7Z#Cf5m>1rK7I6l;CjLGV2OqI)~Q)oB<+ukX}1(t)M zcKqx(tP1uyC9o{HALefHgt{G$a<16jGQY-|Lg@+^{x??V7hKkDmuy39Y`5NMzz{g^ z-lPw*pYrMAv0d+;tjmS;F_T+{N-t{fz+fT!TDrkjjfH9 zeYRBw%?IGLx)*ctKFp5UaXE!U;(al_kAR2Xm)Q|lnoX}28XcbGeoP0Jwv#@7-HRe}Nm1+mq2TdnV&2qvMw_n(p<@;PK>QqPIe* z^oMYmID0#J{^tELzB}cT&MQbhoEIK0A3q!Ap3POx9vowX=fchNldG3pu=(g{`u)pE zT%d1L%F%H7eDusS#=A`)KQg03_F9fclbKT0+2Ai`hZe@eBZx1@=yvmqLh;eq-KV*z zTD7;*vn>Kno}XLzQ1xT`>f{T2@aWp*YFr8iN{ZeaKfY&!GV&aGv!S3vrpEWXIxbC0arUcquF?{ zIi3)c3z;A>MG=8p)vyaS(d~KaN#B6xb3~Z?iazXJ9V@?I(XSJ{zp0Y zfck|>=kCam!_#b!be9RKu>R@OF4XOwwZ{tFtzx@mL2Er3eLp?FeKJ8E>2^rs@sZt; z@$1pSWnC$M?Wdfz;P*w+(|lWpw>^0cYid01_RTKv27ho=XD?3hh~%|NTz7v3{ml-9 z<@AE{5_neP#{2PWiBbK*Q$DTkL^Fqp_%|mG9a6BebW_N&wtCh@l=4B>Q^%FIP z5-tZQ0iqa$yX0_`gq!$@3@$G(4Io1&Yi}kp9m)jLx)dZIvH(OxEs||yE-aNSF$#Y} zSQpk5(-rIn%!yGvJM2RMvz13!daVP9R32DH+o?iS3s@sy2-e$ZG_2y5;8Kl%lxxg7 zZKfJwp8RBIAuOl>nYscYc0nuw;M|H3jabT9aQG$o8$>|&5UPiD@|J3f^WBJwhmn~t_=spVh-`6yi>(7V)hy{-dH^*cF|j235C~y9keHQb zZ-Z8%3J56ZTj2%5brGSGN>7ssRdA(7m?atkf7JxYgR;!uO$q*G89mPn`DbJ8iO5O~Emitq&~$OX~MDlGkTS(0~ORk zj=4Z?p&Jnz{1uEKC{5Or{SL3xvfq{v3=ALgW~~y0n-ZyDF>O#CRN&a?aQtFcwwdmC zBov&A8keTjC0g?P&6BP(X3;wk(R+B8*19|%s#aKC)L#=oSGOQOI%|fY>rg9v4Iuek zLyzKXf->(S{CYr&o{?3wA(bx(g;o$a=|gzh)JSHhzh2oKP|D?leA=fv0(0efWFhvmEpVV0N%ER+wPH;O7CL{#{&h%kt#D7e4nZ43U*q?9wpU!640-WVaj z2t;ax9mEpo+B~F;gzA1z&^4i&8rT6zE9+P)a6gJjF=xd2ye=qCngD?Gwu`+F^(!_# z9C?TUdI(W(PB7 zKu1J%m&;@^_F{w|vw;53ToFnK#ZP*BEl_*G53A(%T^#M;!e)l%@!YugRCF!KU(;8P z|7dwiFomGt^MnJxvEQ$4JZ{$duzv1utZ&u@_PVkOMAua9ouhRZ=mr}d>`}~i0eT(^ zj+6EH-jVh2;O=aPV!o5%Ap#HZHXKysP8Y1aMC9ng?!MOtwVrp{E9>{xv+KgiL)4X2 z87t>19IOE>Ct!TEmv7ehcF-F(r6GOx$4>yD93U0F0h!r_c!f_FAb1CxyfYCH1S~zk zdVVo;UHvz3G5Z2S#=@fWv9cJtk{X8CA(0wQl7jHl->1jZXD z95dj;bCGh+7i?X3^tix2qIBSy#}5|I^|A>u4t3p&buQn)jJk1gkN*~t^3Uugu(718 z!t5!qVe|fNh)&dqRt`K{v&EtP3>_`?Y5mOQ09_i%^vYHrKFqV)0U zi_4?l=c3&XSAPyq{&INv<<0;4D{=>x?cbk5yUU0WrywkK40`4=N z6RJL+?M*=J=`A(Srqg3a+ND0|C)8cVU~S5vZiXw9%ME^LeaL^?3@(~gwiix;)ExKa z{CRUUd2xgzBrcGe9d>3LMlVC`3Uum&FuOaVu>7;<8+fCh)8^zjD#yv_Om{}JFM-W~ z(bX|$t)D%=f~tk`5^qQ7T;taa^#vl=;+{ycxfXtmZqaU$=r!KA-fT`sc0L?E~#kRUoVvH|mpCXm)ec%W1ft0|`Y3BuV$u>Tf=vm)cr#sQx?P=-d z{k4sqY=Cm&A56C3W1e8?5t6t1gI2v*UEXE;v^ZQi<-(l6eGwA62yns(`qwUzF-82Q zSX)B%B~T(0whI-Dh1iWrnH`K!nd;U;*0q58vQVOf1kIP3N?6PcuV7_B)ZI=oP{}RI zZ<>anZUx1c*9(;u9~M^yEv3~ELAxaSe6%i_{FCzLDc6_tUl`CoWXQu6?Mcn;Z2wE0Ek zj52`2-%9u5FYBY6EZbNh!|e>?m!lX?M)2>VR=Y((bTkto8bYMO3SJ6IzzFC=l&SEy zLLnMv$)7;f2+lqjsJyUl8xde1krkTV3_+HHiXgPLA$;#x+U$C?hZGLDrDxp6pw|#I zjlXIW2(?8_a(Py}F=h6|Fy~7_XAwTAx=;+&DbCfc4=W@n<)Nm0R2LDG8q87&s$hsd zN7(1QZr=*;zI@(;0#Q(iqMpLTR(RZ)5T&d1kTp9zHU!X{?GN6d5A`3f2o^@o5u&)o zLkXVAAPS02l#tqrLu;{gxmOgcofJ|2i z3?;&?wLTv!twf3NC9)48GDHNlqv25=yAqa%u&==JepBvB()N=7LL#8aIhV}##Z6l-;g{1OdU>Qq7?dLm$`r)8=Urb0)cfo>2)7xI=C=%7F4c%I1g{g8ifPtrpV5$`?S9jrXnl_$4*ZV$B8Wn-Xh z@(*_49>fj(;$eo3?Vfd9U*7IzR{}-C^9^_zfef=cXq5@*Pm-q}^>R;Bzu8**qh35Q z@uNyzBR?&&q_%;+XgzK{?0V1WGILa?S45NW%Fyy3gmWOa7w`zgD&OA%I?Js>$6(=C z*if}4whG)q&EaFrkh$1N$DMcoaOdv)y87-IZnLW!pW~Oks5$HcTpUa$s35<_r~Tpc zk*p@H;H`7|i}&01b#3qQhvS|~4iJvx6E|M}tBL9UAh!HIhj-_;{GId38|?VHYX9cq zEe1^s%7)LbCdc^143p;m>`W}@3_223e1mLhfdy5XiP)hc$7W*n}ZnoxT>`2FwMtYH9j zm$82I(d?f;Hm@8V!^`HkpXabgb7S!W)@SD+_w0Y%81-&Eql0=k9^SjLb?-*McY_by zz9_}@EoZ6=zbt437tPoX$Y7c&9xd;ww8IRa>vv!sOxesj@DO)K-B#_asG26{C>hsN z%yjD{m@72xY3=AQo~6DIymvPAE?H+e{=JRSfrmIz#YZat!k-xX5=@d`a}!36XLIhW z&mFU!0ul9~_292ZBuy*0`1g9_^*v5lu{Fs|)_kna0HS5Kmcq_LrH54Jbrb z;KHuX;BU33&6b8nQUx_aB+okdha!=J$}-HBhKna6yB@#_>dwwa{3C(GSiSUr>O+<^ zOQ_=>byc8y82Kq@M!*4h~Gvd9Gh-Qm`edDgyV=V^HUJ( zo1pw`AyRBzz2=sLxfD$gVP3`32P!Jr+ED&;0gVzX_=Fve*j+W?L-Z500_hzsd;s>lE{6QAMqYm@;8Q_52}}H@ry*LW zqCln95NL?1fyny*XYV_}qF9!;hhP87%?HcWD}sGf{G}LSwsOv5s+XGm;;7m z&WZ_h&Ixlk=A3gx#q2Q)f4#Fi>}>6LFX!I-h3EOd<|$^kr>Cd8>V2!LduC@SxD<v%wdD60?i8Oe3(s&69EeI@lBjfVS_peCE@`h6!5ry6d{UZpJoDu+nB^} zH1VcP(k4zs7kE&$cwZ1vn$$-V1~HzepR?c_=F`|{9w0<4ARu5;fOwwHi8?g%V%mW9 z6zgjq!B6xTziD5~s(EUuc;6Q;MWD+Xg=2%p65&akw1HBv%z}-0k%tH&PJM_MXp!c2 zG1`bqp$&S4Fkjr@XGFLz0S(jq0c}7~jSCXuj2OT2pnY_C0@GbQF;(0EnPgp{om7Zf z5uFX7q7BSTYwlK82Sn7q=0F8UbLQ zi7q#wMMQ1!rXTvH1)UaG9q2@~5pOxt-0T-zE1)K68rK=fOxWOX&AWJMY5{QJmLqBs z%`wEgpKt+`zR?vYoPjPlP-p43LvbI?TyTKq>JlkOy!=3OZJeESgpxGzz=p29^0~YCPSWBmM-E*3`r| zx6ix?d(?o~9`Qoc$oNEh$5cFB*61D5 z2yaFbn$hEcguocwsnZviCIVc=7mTW$5w=qt6+*9^iiz`;@;$S!JopMiKv05nNQg@9 z6rz1$R0us)GlZUc6GacK^wZYlf z)U3T*pP#^4dxD&_P*$Z{4FCQUb!LZ|yk%0?bJ8+*F&L&d+5SWIc}UM<%2ud~`a!XD zWLOB6#>rB@Hku1}Ht8f<``MkgM`ii)GXEPspQ5%k+-xp1C0S3>nxq3sC`osc-X!rP zC^LX$7|CdoWRj^Qvq=_`EF(c3r${c4sJ563H)(sH~9>KL@!TyQ5jN0LFZgv5N0xln@S2Yq&+?Izl~({>td50EsZ&m(Djmn4B? z7JV;I+iJ9JN8(D-m82I*0?8PXx+GIb%8@i88A4)15=c^rWXXPW;o1>%!RMg45KY_F zw4HX?T-bQjT(CGsIyi1Fn39}7KGL4kw~6Gh6Xrr5l7m!FK00pBNxHC= zW-eG#Ifp<0>tr6ap(M3&_9e0r$yt(RB+p1PNIsB!C0R{^G8;*@lVp74|1oW4SBr_Y6H`CnxC1Kz9B2i zr*&4=pc7eHDD&o9X4dLlSy{Hdva-sNyr=I*3o^5^XrC{YLmjpM$jqwJFDuLBK~`4r zw9KsJ(V1BvslMP!Sy_Kk`3$Nv7acRJB%P1So73l0U9z&44$sPR-Ph zrjy`%u`l}4m-g2u8KT|)HA|DEEwxBYKebyaKd0N@9UqjS3m%1_1ho^w7V#N}w1(MF z`>rI~;VI_hZUiAfMWJQ4{{3l%vad?zhA=a-w;0pVL*XN0DBjU4;7)~cqt0(jh>>dK zKP#jP3F+Hgp%gU@;goo4YG-!XB?v;WrG-$(%0dV#s@dYRB-be~UoqWxDPL*pMdjkO zWmnMlMh=gY>dIf7j}tB@#17P2JwK?Wg@LKYk(Q;rv%*`%G(C2spnx zZF`bnKjgC$=}x6Do@)K?e82R4BpqkuO!g145L$I6eUqr@Gq!T^RLH}AAC~V&-@Es; z5YEO}2(@D^gj5oXJ{Cd(NzXV7p>4c{&?Lb^*hEsbpM~IbRcls8~H^?Qb)jQUN!QrUkeZ_01ZGl%jUl*}i%kqjhhO45-e ziey5me1bDcACmecK_uNs0#s_ufKN&545C}IK};^tx-AAQU@&<CwJ;_FN8nq;jf9*H+^O_|oGJa#TE!UNauqKc~f2f%J01zfs=92Tu|X zO6(dhzGXb8r*Uc)jyDzNu(&%tXfKCFRZh|c-yEsOqdu)1<+N&-`Z3YkTQzffh$|hU zc}I9?PESKkiciRC{m#D8^t@^;rsebq7rOlucPcj3JZ~=t5b~%;x0B|ydN=XOAll05 zUgRjh2;Ur>LuGhe*uQvOc#e*94oZk4t>s(`D%aN0(c}`s;1hC6bhn@Z(R9x%-E=Hb zou2*@+B>I*t8gy8Bk56vQ8`ejn*3w*zkIN(bV_L}Hae%LR_PrT7}~8@4v$p1)+Z>_ z3yl7ThE+~Y#h*jw6rL(ydPVBrFuHR?dZQLrzC{(}K=U3AW1_-x*c!E3tQF76gqr9m zy33f(m4nmJC_q0S2+!%6RIZxh^w^p1;W=!bO6^BKC8cHkoH`@5XQ1Y#SFK_+-u>^p zOJ^ELG(S+yX(K&@#NWq?iqtAGN5i5s0tj*J%3&no*$VF%$Z4~jThNbD=^?P;VL9Zw zM+1sea#&Wa)@imm-Ij!V=E0eq;Ko%8e(AWJ9OKz2DyC~_)Zgur%2OM0Av&PzNA$ji z9AR7SE;51cGy2z#S2_7~jcgU#-6tU-Cs0tUXedbRhBv2$QD_xLqe2etpi(2a{2PW- zsWow;=CQLmQN2?@I6Yx2ry%ldNOO%g!$%ht{XSL+jxwk`iBEl7oJEHi$_GN{m35oRcO6#AvQ; z0?s)&K<(6EKy+yD$Zpv;E#~k*m0IdcPQ8J1^R7MX2gcEzmpO&JdobMrNEdDUC%_%# z@H`%WqpV7;ncp-F%wgr25aBmXIW6uQ7}-BODwyu<%emKd`dh_4#c?I4?ct+l4v$l- zed*UrG35L;At_-bB8P{nXp)EDE9H35aL%5f=I{(^C+##cd(6-r`1ws{*0d=aS!0=% z74bMTE7#l1tgDnCP5F?~l4Zk1>Dj(*{xy+X8m zgA!D#sF?2b2o3QL16oj}6$kBAYw4K~$W&|Y{nV9G(|yTwvulX>R6>++rk}>)JwUo5 zF47MKwOO=6tDlVNs|iUik~}09B>71Skyw!wBPmT{OHzTPGD%gE8YHeHwMgoa)F){~ z;z!b)B#5LPNhnD;Nq3T7B)v)ckPIUkNiv=!nPeu(Y?6f}%Scv|tS8w@vYq4r$sv+s zBqvBNlNgcwceQr_or|}53gs!ado|_D+rF2+^Y&k)JS4h$CxnrtlMyT{2|~&kBf+~~ zK_O|@96@;aS1#eyJg8VqPEJlLC$*EalZ%t9lbe&flZTV1%1NbCsa4J@7nQ5ZP35lg zPg?w1?(E_0>Eh&~a#6cDySTWx zy12QxyLh;Gx;nY4T-C15t}d>wu5PaGt{$$QZcc70H?^Cyn~R&Po12@vn}?gHyOX=h zUG47d?&9w1?&j|9?&0p~;pCz6PhIf)P7=0q>y8Hm|Mc;um(@wD} z@>fQ75S9d^tSmbri%yZ5CFCceguc+mj>L!_TBM&^(Y`!cS?}n$3nb)wlFE`4AhFFS z>V?WfXGqz4OA^0Q8LHX@qWw<(OaG4wCduUh6_!gV?axC(ElmnEkO( zO5N4$7lhM|e`S?z_baQj>Q|O+kzZLh+9d5?LFbc;WIt)E(GC^Tre|aex!6!6e z-0_qwnzUx`K`i!#2eBHB7PYV+0F?@~UFKS%qTll-K1+5gNh9M8%x z1d=Q!aiQ-Kb_Im93D+_8Id5e|mJD}6>;BbF#jOvaQOrmTe~!1> zeyjhNZvR>TCH`oe|9_kR)4=~};QvDnG@w3*lXTN=5s!4$evYFpk`GBDNq6lwp0)`j zNNq@Pd>Bcz_A~b5dngHF9;6B+JPwg!7{pCTSW`tiPkH7yEu(@39U%bZ=6S|PtXh60zE~NM3Er*lc4X=2ad;Ql#z7Nj6S1%*sr9g zzZ2gzq&m<(=nCghI$s3kqYY>u`hoIDI96&qZwLC`g9P=Vj8gy652p2*#h;Vt*ArlE7yf~>BJ3> z%!>+ljiB2+bQe5ayCu+te!qUQbp|IfOMj8T9nT%_);GbgTQ?Qma^9P+zteT1kObLb zYHg9<9M~ZKcSmN|-5|a};%{o#m5Kc4)kOZb8r+so_d3#j7a<8TA&ChQ9)3-GJFDdj zOmts^*=_0fyQWC0rsb2~vDqsfbX-&vX$?zU(t1{Qp3DGRlgLVW z&^<=6xT-8aRF^3`)}2byatam1+Lk94+caxeA>!S6bcOPFt6sV`(W*~&ExKG>GSg-= z3rmIj{q~gN9=%mQYBG|f2H7cjXXr7d+RkX#$!H<&?{`M5MRz`_GnB2+85GuZM(LC} z?TlE9?vx(lRlk4NG-4%s%Xj#hh*Wh*H+t7HUBT_HhcVrm{|yWM<}@;EX-*?5S_FcU z?tZi4s?!pFA|v*O>d8ijuI7KkDk>*>lR@|ZEsdJe+YjVt75C{wfghkUJU~-fKgG(j z_sNg*)STzvM1kld(HRZXb^VZ4|8JODzj~R4wblQ-CdT+7wqC5rp!LZ8ckHZJy{z>f z+LdSV5`*M;RL)YazVUU1#TvBF86fW(rG4lVz0*Vct1f9DSgCm+l;J*u<5cQ+&6A-1 zX)P*uv0_#{CqKwnIq4o4#jdHaXBw!~+TZSLTO$be3p2A~o$2?1bl*f@+Ae9EeeZ;G zZ0|U=D_M|k@`V(#|HF@?K{(4Jq_%x-jvH zYIJ2luc+SGDV`LAHw>U$P_hHu2kyGid-#K9r<>+TwUbgcz7#?U_um#2hP*2%^dyN1}ck=FPE4G_`AJN2Ax0}{mka6>DnvEJU#;!+LJazVojTBmG^pg@^+7!&Hq_D zquenME4TNJ;u*fjH@bBm3NXE1F4Gk>fRn!PK_&Tu@%hVnZ+`=)q?wcXW7Pme8~S`M(wzQZznYhwIp~%X)&m z>R;IW8as3ULj@^+`otdW%!B6Ur~C>}>nfgk>DpzAXEuE`zTt*j&3Ywm&qYV;l3{z2 z7F*HoCY}peix&2+K>2o)r?D0}S97NPhhNJpw&+Uy62%rBxMQN&qFH|~o7~F6W^{2~ z6U9%4?fm{{b=p(*g+FU)fmTH+f0n&1Yw3lz?vy{(;Q?#u^edK>|Lf{y#g@LPk*3(v zUoB@Uw$!klsSahR{-RFytfh^;ao*(NGg(Vl$K<8_Ct-iEmIhxH@9VPqsn}Ah$aKY) zj;s7rv89dks2iMU`=L43A>_%h{R3x0Z=tT%tOw)oq5VnEC$Jvuyy!srKh8g9J&3*N zO8L9WeJuqy4Tv_PzUD+|s>%jm!xb{`^Dk}EivgLKf9!whJrPza}VaxaU+$b7&ly!uC zr8A}!FsA>-)NfhOQfpXK{-hK)*0ZgEf)EK1uveXV^I6X(m$IXL?~=z@&xB3zdsTOjR_xjQ;L?gc8+T%xV$W_Avo6qnbnUgX z=;kDOGVJe~kn&XE;En;T$DM74#a5182iOk62rq_1N_pWeXiI zd$Ar5DGU7!3#-Cc)Ef$jOEBxA(2ZdK}RbIB8RQIP0-l<7$-eHLe5eapY0xv+(wztjEsbj+8$x{S)i4 z!*+}>)w>)~>~Y~!GZcHgR+%KD-H8%f8QCMHj^Mft(qnI`%!!??COa}CqP;bw@(YS!^(n)stgIm+K&;+&!; zOqZ2V)I>%bx3?vZ_kH;znGTjEL;W~*MLe^o!vUtD^NTTVHg*yMuQLt3EUc#dDW_I24fQ$&e>>;&DyE^44dGA9dw4Pp1rl?NyYRK8#L{UR`0$wR<$WTAkHX}ZHc6m3`P~KhnD1VV}Bc`F(?=TKdJ@$%e=-Oh$ z|5Li$XBu)gNBL)2W0{7=Si}De?>m`ksN|n^lt08~Hq%g>+3G}7hE~65H8;JXx@gUIV1q6h>TRMUc)eu&Q>M9%C+#Tztlxc2kS@5pavLvRtEmv|=ei@I!OmqIl zfZH0yUNOyiy#TNMsx@GmD^(sn-}J`;Ma^~C(OOY+Pi$8yYHm``ddrNzdKCpW0e({Rlx7+&mI(ca)^%hWooofZrxk3>G`eIL>{8^@MyAmYE8%DAj%%-| z(FUy^D{6E|%0xwt8tOA_Aja|LcONs2&Rh*%Z&+!|G#Yp#KjlALRF7%&N^|Bv zjixn6oLqUNE7NG3B>2@rJAIf&*X8o0eA|Q-OruSQz>b&NcVQYGzZ~QElKcCaM%Nw% zKJQGnVjAtgvLxlN9a=|GqX!?>SJdd4>Vp+E+I8iVbxn@0Zwb!IlcAnB#RJ=BkpWE8 zy>3De*PpIunzktiK3dm)$~4{18-8p=5nHBd)l%TL*GmVc>G7XUDBr}#l4&}6Ch*zy zOc|zWm!4>6U%0oPlZ}`KnqNgcIneI|vp{)w#6^2=pJx_m-VgR#H02hzKsor&N&9Os3%s6-@^4mb zVix$(6#B2RrwX&cxR$`-vPNf_1(FK^&)+W>W)^VTPT9h^W4o9I;{QbaA5?2Mv%vRC z$S=NfHnYIBC*bL>odXpu(0YrDq6GpzPE)jiq5jnk;EUR+_Am>Sn*+SHI6Qz^px9Hi zGuO{O%mOWpF&`Y+Y$3CN>l*lt8HXk^3&dBor~FdmUNZ~ybHw;K`nDspKz|3=y==fz zW`WmPi1&()`o=79VI%N7H}6Y_{i2F3oUAMv+GRu^;6Gw`TV|O?3*l$Bt_oz9xw;1WjD2s) zEK{`+@=q@x!7Q^n3H8s~Go4wc*GG&mH(!-xmNCyj92I(J6tm2beZa%9n)b{xz3;%me81gl4ztXyPtbpr{?W`bMis!vW|KED%k1cj`lEN%XO;;_bfA1s&vA;D zc}hR+R$t9dwX8v-_cV5-l!YuQ+EbLXXY7Dc?x)y& zGF6h{Pnxfp$t=^T5A@T%$qi^z5o^jHKdmye%>CEk`?EVunPp7V>?ps`n%>MZ^A=&A<=)#v(K2PNo+w%- zb?*#C%M7p9zCxuvB_evjDa(?fy-wx_&MOwG$SiiUa~{f1OMS^K_G|=jwa(gxS!~L4 zlrOpY3$xhFM2x52V*{AQGMb_u%a84t#m!z!#HNK!spPQiJP7>i!FNrUdNTUWEQ(p8+t2W%Z*vASQ+50 zM2fwl#k@DHP_&rGiX=sg8QN=+59Vcs##CSyD?J4GTz7Uqvsiox+WBPTT4u3PwPCOE zS8bTZj*P*)>{5Uiv)K8QaLLWB3NnlBu8MlLIBsVaTXM&V@`o+RWESh$5dPt$pPE^0 zvn$4(z8_XGi>+K*iSk=T#4(F)T?D&tk4$D3JJhfS<)7cwhgob#8u0TZvmCS7s;ilSAg+vkIRIhlAVTI}5>cSVaij!Zjjb12Gu7A9S?WN62VUy!%1t|hbNf+rZSSMOTO zEZKSk@O-<@V`j+>G1IEIDQr z@X*zDD6{0l+18Y=4nDyw*|#11^V_gQX31uv-%D~1VwSA87<#VKZX2^?qd2VRM=UZumCjahi< zKKRKO_dYTU5BvrGzPxV5EWElO_&6*ff?0UTt`d}Q(!2n(@WhhvC*zh0%);?U5f`QI zc4iiyv=jJo+0uboSlEbh#4q#~v+%}}z~LI-1le=~Z2J~^d$X#6qJ^hC zYo}=8?#*w0Q_aduZCDXn(IrE>yZup&c3@Na2HHRFz8AB6g^uv^x6W2&mLJ(3dUH5Xm0AAa0j!VJ9h1&1-`W#?=8FFd zX8E&!qJF2RLz(5%*F(>bQjMAAyFSId<9p~>X8ELX=&#%670mK}i$kv@rNv>E0@ah?#3WDZg;HR}2xUjp0uU z#*Jl&=>8sh@Uy+i5YgZ>WeYJ?DltT)Ee5ai#noYmm^K*mn)gdDGekVSjq?Ut)?kQ8 z-3tER8u5@JV(LKHaqq@&3=s~85&!pU=gJV#D+S~DTANu65jC&CUp1Xtk|82^I_7sf zPnTebxFfEs3|l^dA;Pj2^!#UB6hp+A%@|)|pAJ?;gr#wcA|kF&Ii-jQLp;pd2Rv*! ze2gKYiVE|NIqq#3BHDhdMENsnRAh*V^+Nk|6)40I(PuyG^}1vMhKN5#K+lWjG-ils zUIXLoBAaNAh{jl-A3oNCAtG|S1?9KA^Nt}RZ!p@an$w&i;^0yn%D?peCPT!kL_5l# zTxSnM#E||df2~PRhKLcN(Ax+5H4G7-mt!2a&)0+@;_6!Hq4)D03=#2PfGdv~Lm48X zIza!=>`pR7j0;77i(ZXUM8pD?xa6i(r#upj|+HxYX3rplp}!{ zzbd#dWJp=q73-F*>n1Uzghe`1zVn3^3@LS66q|Et+^R_QP zi6LceNwoh>+*O8@Tpyr^J`3M7q`030{_noEV@TOk0``rZ8^w?kau;#(g|_YtDK|$V zPLALDlp)3CI`sVdPiux0mv~FcZ?=BEB2qdBMk^xa?W8@5NHN4ok;lNpg@_9bDGx+G zR*&~*NEv??|71vMKM3_F2bE_? zxxWK(h_QDohLokVpnsnyYKD~iNzi}m(lHDv?u&r8BC3-NDJ3Q&4*8nZjv=M)9>inY zd|or8w0wq0E@nb3LrQ#Q_>Gmz?=YlHo|K33pY`d%kTUlr#_^2k^9(67!!UnL=pVWZXS8AnDpd~kUuj}y3-2d$mc;97=o+- zH{tk@84N)MEf8-k-(Q#^XkWJLr$v>2j@ zpx5DVm)l4DX*3E!n=BdP>VXOTVU<@^7_#ET_4a|I7cgYKyn}IJ^RJ-{SqCR!TzlN; zB|}!Z`|x{Ti*I1adaVY(F19($kTtvp@~yLaFl3D@2ftzZJ%AyrSqtP>o>Gn>D>)hM zyxQ|NLzd+t$`(3S{fi+hb~NIO&fSkOWaXX#z@0m|j3Mj5cld$2bH6ZTT|5qd^>XTG zhO8d_#dvZ=CPUVa6^PSZ=FDKodR!0VvXz|=L)OX0@Nac|tr)UQ-hoFK9?oaTdTs_k zTxHZ@hOA{L(chpQfr`jF`{IEjvQGEPP(+p?u55bZI>`Nxl^L?0_k|zs{A~b3RyhOERghzBN`7h%YX2>?Dz?lED= z8r2Z(FWbg}Aw{hg@OE8ZjL4uvf(n3|all)};IwiJcg-?)HMea`imRkoEin z)?4mQs>6^~saI~wUs>t}L)Jti=WJmceqnsJCa(iPOsnxtNLsobZ@MY1% z+6-A9J{2hc*!lAeSt*sVZnn2zWrnO%Hi#3Nj%>-0mG%z2*ih(Wwol- zm@X+cW)r|IONMxhvv8#S1IylHh?^JyKin~^6GL47C-7JMbKhl%oA4Fm&fAY|8REV) zM*e|HO&Q_}Jx9MRwr4QJZC?sK|42<{h;!a(O!?;q+AzdL7Qwjbo@~z$=R6MM!h@7& z3~}=h0_QXD)Mbb}-4Ww^p3>QW{5Vyw+<7wrS3mM|lheFTBbB;2^o%#emZd<*F zA+D_>_%&`p6Nb1E;oxcY)9V!xH#B&=BI4fNFRq9&BP%F~r&YqHN)a#~X&YBPQ_w6HcZv#Fg2D_~b_&bB4I1^N?>n^*cjc`fb={ zfbCg^xcGdC4}+V!F~kYs7}ruS-DiliSnovnCS7MR#O=IeNBOJQQ(GD0M#O+$1ILeLh;uFiKOAaRjUle)K3orazj%lu z;#%Z-tBAPMtz0g;U5tK{7ETAtk|7SajfP#mru1Y;oYETn9e=VvLt>V5Manl@+K3_X z?pyG#^qs>Di9NTVo=FeJGbEn+0A9aJ`_7Q)o(R7>XhIOi5rWZQbeL5 z4o5jd4_gwyFeDbP2S5CJ_Be*b5p~d>0{QYXB+mG&8s#^+SBfFA;X3&F105zZBt999 z@zHYCONPWI740ZLwbw+3#O1XqTe#wIm?1IhO##Y}TH1mkF;Kit;cU^4A@SHSQ!3x; z)FXz(X{nx+zxS96Lt??J7%$F_InI!1CdLWXZ9g$2=Dh=+Hc+`RByO1q|2AY}Q-;I^ z2N5T~dYF$P(e@f}c+|8lLt>T9u*=Ql%?ye6#NTBXo7RjW@!Sr?+XohnW=I@hh4Jn$ z-$e|Glf`+{k7w^065C&epKn{=ks)ze3FxOs@1BZCJn(#jA`%liPrXph{?*RRZTg7R z|3(!H1(pM!`2u)rKJ^V2xjUn{y zd6dt)tsq0_*j3=EYU>Jy(5_`LAFSQwGDGN>)38^c3B4FX``f~Aj4r8W2u+!Q{uV7A z#1MLH8sd;mUH34Ansox6XWTx+5ISiR#=9Rct1*Or7>xU8CUmOL5L&Y{{Nx{12Q!4W zzJmGDhE*>aLa(1hJdkzk2t#PC28ffNJ-)#ZTBkS0>nGidGK4zjg3SjGN@EC}SlpiS zr@Z;Z5Zbmh;-WmGzcYl^*$=;JzoiXBXm}Fh2lLL|6%jf*GffepuNucIBD6#21l!$K z4_ko?@??nHikX;eJ?YH-0%d9vyPWXDI)h;%1cG$zE`(b zM6MxjryT`uQ-&lmNu3fMt<$KPk$B^rBwglyWSXEsSxpl3sDk8V(`ge-R z^)LU|p0Xp$4nb@qPlkA|w+sI2QSf4h=#7tHU%xMT8KPBvQGQ~X~7IoORMIqA-58?HEPzAbdaW6vyE$S$eqiDphUnx@&_lB=R~VuzT!i1Qo3@rA zy0o|sP_$$ZhUmgy;17pRn9C4-cQMwJ`(9NsM337Cf7>kCk|FxpG5FgDo^utmZFXq_hW{3`$h;ckMS2RO($4Su5s$&fpqBG-*QhwfVX$;Y&0)Xdj`I|CC z_k9JsM>VU%5N+8W>pxBFTQfw@5wE}Jn>Bp5DGk}TX$ke zPdSS5H}2>kmJA>B^AU#sHnCJgC4eGvbT&#+)f zFC*Uf@@dFjhV%y)FfXq0#gQTXXC=fT^Xk-MNMGnyiSlQy_hd+)+YI{befl9o`uOhP z^(1vahV&h|Fb^H>a+D!Gc@M^&^CR6D(pQS>B(^o~F{D?C0grmNoWqd5tULTtkH80t zNN=0(fg;j<`?Xg@x*^UFE<`+h&A&QB`n@DIq@QyEk7ARSF{Ga|0?t3Ly33H>YcJxZ z(`G&l>4(ljpJPAAF{BUlga2vhu4YKL@x*%LhD{3?(obE3-TmLSWk`Qf5%q-JjbKPW ze+%)zm2MRn(r;&BT&whH97B4Gp=f_A)klW(nZfYSyURE-q)#gdyxnn~%aGos6zq7T z?o5XCOE(eclv*0akUp#>`ZZ+SM27UC$AHf#4U!nr|2P3ZInD7LLwc3nh;w$8D8rDx z=7v4xA3qbxknU)MIKkVeyCTvHwr-<{^cSfO6p=pZYj8xw?M-*Z#UQFzN{0B~&;k0) zv-T=uz(w&oTHbD!i~*MJxZaU|WfEh+odDQ3qe=nBfPl``DBpkJKE{BbD^X9`mtzD!2(H|9RW7!YU*oE*#V$QY1y6#nYdyhDru8C?<2*iQb!7~s$nIGLAxnla#1 zE<4I^bXH&tXx$Ea=zZ0TF(5;n4~Cw(${6s>8S}X14f8VwtO&xqd)S+ni~-Zc-xpjQ zSBxU0As+9 z4CsIO-A#-EZ&DD??;D-T7|=+-{4wTRUdDi;MS=g?5iJxMaCYiaMFyl-v{Pij+k`gj z+#H^rNyDT_me6PYl-VA9EEwv^nDMhH@D}CKfiYu!4UDsgLUuA{>=oAoFaK4SG2`ZY zthXe#Ys#3hY$EVqbKeNYi~*J4SNHFz&6u&r2=PphrllA&s_urLY_#ejW5%|QqW@XH zfiWZBW6UpRTsq2_F*N`nW5)S!XwRS-Wf(K|F0`Zk2CwEaW<2ahcV9#*Bt0u-Bgl$}nbp5TEDJxK$6vjGjr*&+*RN88ecXVBECzN@C1t zumI!xia+);X7n{fJa)E3G-JjrD~w+q^3`I@a7#iQ_{Q~&A~S~NeXhuiB)2z;%rN9d z=MojD{t1H%FlIy+g&$a6xEEtaLpRJfFTX#?m|;B?cyPj8_1ZkWi;$te0F`tj7>i=4;6ByF=jZ`0Y3Yh{$$KJbO80I zC5JO+1kHorxG`cnV@Anah%-l>@nXza@(cN|XU%8KXpo3_Sn!Tx%hyBK56XM#t?E_gG>q*lRv(5238#u#H$=)Z|~QO1}bQRwT~p$8aaE-%DG zj4@?Rpy%PHrx;`I_C=iRG-4xTO!@8T?}s|Y7-Q0X(Vl?ES*H4o>GTof*WRCrj4`9? z!2i#&-p?2_wiU+rjV-wj4=mZf{*>}S2D&Fvj(s2Hg92!`92->ye>GHF=o3n?6N08%^0&e z81+AmEy@^k|1tDAV$c`Hm_+e;Q?t#^GsYxU2ju&#iB@FH(G5LaJb%Qz9(t8eLQsiR9IbP4&GA4c52YY$knZ%fsZyo%GrTG%Zq}>O~QvL~#UyMl> zO~Cg@j;$DzdhfEM`~|OE8Ixk}Vtg#u_X}fEgc0HgxAGMjlkAMK4lrnZTgId+OA)WO zHhRdI)ZZNWXDXR7CiQ3yJ>PKb#hCQ{2kcw1^O2E^Nk{6{pnUg> zUW`csmPuxRNkt|q@ zO{gn05(0(xLKwX{exNW$m?kV1)(d-uQ^HN*h44)i_9g>N=9ugHEf@;2&hG{k73QJT>MBU57s;{fAC<3-fkkH)qp%}fTHtTVZ5VrkmIbb#qb(`Tl( zx!UELk?TUP0=b*zo}Bx9?!tM3^UTllFpoptK6&@%&0`j1w#4k6nUDE2^QY#X7E>&q zS=52?6w;}ASVdMyVde6I|N80orB`m<@c?lfPwBPmeZ%-|kSrUS0)O)KuhgD&;r@8= zgeA+xO{R;#Ph6Zhf9VUmrpIAx~(1k1vf=4aP)j{paus z9Wf5+r=(}+_kZt(@rI{|E?2rEo-jxgEV?0{)};S@uLhl)eNd`hv(@zKQSIuN5C_YW z*eeS&AMw6nS-yZ?tt%I{($HQHT|P&sJTU&TG{Nd!{vahiJc(X3QR{^C4@tjmjqyWF zVZUcv(uLn6FwdXA@9pj}UD}qD>c>{w+qoso>@i;IlBTyqFD+c6SDp%>x986e=`6>g zcpUY1eCx9QhW33Doq1$$X**rz(x>#5rV(|NJd|8?~KI#3TA{K^TYNN6e-xnnvAZbUHxgZX4sw|1xdGb7Y4(1{91nr#d zdAxGA-KCwRod(ZmrW^Wa%ls9qI$uM4%u>pmsixv{3|YS5J;7m$JI}=Tq%F@7PtctV zwf}WY{a$Nf=J_fkuF@o()~4K54aGc4N*YY5G8?}57>u|%J88A{rN@Y|h&y#jqqThd zf_E`~iCXjQZ~KJv6j~cFxcMN1@r$(fp|z_U^8<7$?+MdtG#lulJE`<3X;yN-GK&8B z&@gk6FXCA6kD2(7w087JY_=PgYt35yW;xb7G)bqm*5ex&%Oz#gWf?H>$@4EgIu6=S zF^MioT8mH@8fC4^Ci4Spp8fmfOME4_3(Yu1t!2G^e*x<+vLtFPe?*nRoWIaoi2Z#V zEl+iakk%#*8&w%`KTC(Iw>|X9j%DJjze|Oc*@}?w4@;|wc?Cw?{|YUi$!O|>bp#~2 zmiPG^Cuj3frseaFsZVwOQ>SHH&qo0=f0(W17Yhc~$NX7Jl9nSgPS(?lm$X_wc%#!A z{c%>KY|G}y0% z^Ykr(%27O){nN&Kh!`H#-aUq4TtLKNVK%qKv1eiCzsLBQ45wz%TKej1zh-HNFs=X8 z)4q9+{?jC0%eC)l@}YCKACPH3A$Mt6oTJnJgVO6N%5a{o{h6f(cb5BqN&Eg^CqLHH zk5>B^#&>7}e9Drj{hwhA#NUz1@(JTJ4>y>Wa=WHH6Q4=@ZMXC?*7tkZk-nwu$l1wq zUL%*1<=*~oMX(OVQ%cR#;_ondzMzeVm90hVEQ;2m>r500Vf34F*INCLto1T&(1sE? zDYI7l1O6j4IM=t9vG4MHTHNYcYfz<+{iL?bt<~e|{pHzyTV}1!U#^MQO*O~ptd;s} z$O{=SvaMxN%khEU{6%Z69S-}&=dDQRhDU<6#=V)`kIhF!Yb~DZcLwVw@&s#HEM4tr zFfSl$&G_QDAse6B2c^^;^kmLdJzQz_d5`FlYjbvgv&$3d8x9Y`eKv+kGha~R9Lg$N zk?c+p9!BHVfNCa*)gqXIrt% z)yu>5c&fFcb(i(da{sL1oW;^^_50R+^a(xIz+FF6{I;a$PPIb3M+-RZ1_M`y9#vC`5=XH|*}Ak&r;QpU>-Mf# zbsxqHM5y`pMW}5(9ZigV+gJE{DKQ$nmZF9i3leeI@@C$$>$Lt>&$^3>hRw~6CuP=+OsZv~gPXt(p9!;#stxoqFF{}3p{$PQ4OcN{Wtls`*pCrUfh_JKXMrJ%H-Q((tSN{I= z@Xc=+rx0Q1k2rW^W~t6!(mfv)6V`pb0KVvR`exq7Gp1Gq1FwcVcS{h*Bjk(YC^+r@r(f)2FPZ<$w)#ZjS9Q66 zkgQ(uyZK@}u@3D&t<^K5Honpye>GOWQqL%m$H}nz%45T3spR$GGg-ZWQN9ex@9B0w zS-){s#l8k{HFc!Z@utt^c$SW_T&)r&z zZ?$I^Q8F~o^X@!O07~8sjt(|hpCOd2`ldG3uOGEpt{zn3Xp>6Xie;OW++X&dQi?ac z2R@u&upWp5wd)ByZjkC!Mh{(2m5nSq^w4`j7ET&CgwXTa+P!}P&GEAT-UClOz0h4D&Pn>b>CZmUvq2i39EP|;KN{7QQu0aItBY6U zsG|EJvg4KZFPlC2Fu>tul`FuBeB=VEJ~f;XWk&h(kqfBWnbs~Z+N1B2RvbQB=EGIY z3-o=`@vM(?uAB#MIjT+;8+0D})XXWwc*SPf{A!2^-2X_Z`cn9Cm^>dN*Q#&kV{#3sz8?D2B@L?5 zN7wRWenv!9xt`OUvg3d4K|od4T@9-0kH55&P&KIbq1-$^r#-`a-JNzu$-kZIy*H08 zPSCYV&|WXo%zdQjShrt_j`h(-&zILm$NFfa-(6(KNgA|y* zm!e~gfI!(BQ!XiM)o=87zC#6cq3-nVhBp=*z}E<)SOI%lhJ z|4DE0=v}(0`9_2C6uDNoRQ#5PzuG#qoAgyH#ruu)bLpFQ{S7m7Lv+$}QN_6%MRr`WT1fG^9Gyd#e!iWJH#s_A_{-uq9XbzvIyV{Ph&E-Tvv0<@Dzbb*Lg%;} zH>SyPE}`@0EZZY&ok~P!eAb3}+4F1dNr293%|Bkj{7x?sIxWWp1!6v^pVC)#2$(0{ z$E=?(SYD54*G3i(XYWpFxw|kwueejYK8Y0vL(~caRvZjbOZUHub5D+18aKtcrw+BQ zf_NLSfE5QhYPbBlAA|Y6G^>zM`(|;8sTd!1aiIpar7lk%20y0nQ(H83*-I+p}t3cP-xZP+R6r zhWNY;R3%64s!~(c+4CbAYAx$e6`w1C^0XQtj^OD%$K9}ey62q~57Q2sIXnU~^>7NF*mD5L0!8j#LB5GGhdoIE_D^Eaeuz!U3oPBvdQ2VrF z-QtGp?I|r?`}By+o)78{@{U{CxTpSlk!EL7>Xo?$JQ8uTCKb?1Aa z{AS;2wusZP!XihxaPHY+@ER*DQse-XZyq^Pbd&PP0Vt0vn8yeF()Z0zQ<}{$Z~%JK z_szXR#(g%mLY%-+e(GG@LHIYUuuSiN<@k`tFRQMv8a3+Yy8~8Of5Qrk&Nm+(pK{&# zG{yyv^0Lh?b`jSB7|L(0T=PkTXML1+D&zA_>&Ns^zB={!ddcs}Q9kGFnI75mZW+o? zwyZN(HviK4X7h3$9$!w&=SLFC%e^oyoS)jCowO+T8|mILJ6;lZYEZ78`CPm&GrK&| z6)0coKS6wNoGf1e%7uF~&Wd`F?;w=N?eCxf=cLqmV_$Ao*dc^&dT50EtqszIRPpx` zO1>Orl~GPR6j4sknaYljtB)mLUeF;|zl9iQu|gzA`Tmm+gWv}=b9@=f-MnqF4$M(r zyXkcCy=xrh#w|WtVIIU$UeKZ1O3D^=@v#Qw;Z650fFF})V~9Y2@+TwKj79t`k3fL( zmRn70!q4dY@>Ay~tA7MyUdB=0WP_16c+F8hy=dYFtY>0{=+n+Gh5HIam-eh$wpqW< zMWZl}kY-~PAHOK`=GMxn$QQ>EKX8=isoJ%v=ujBSr?+Xg3VuM}m(MwS^4P{MsGpJ^ z%CkD2Ihc)S8OqnrN{mAss7X4MyWAOcH=8eFd~7*pb+q5eLJfDfko#K+<-WUaSjpEz zB$Su(N_5t)Cu`54LHXBZ0kc@VEuwsEuI2ANrRHe2KzY7l`{uDYiuQ(X>TYx8B^!5e z)Pyk&+m^>Xi>D{mDg_zVL#4Fq`AR+>btw6G7u{o=)Y`oB@%HgR=DKo zw@hx)9P2`wwFt?_*KS&4P>bWht-g;pZt<#gRTI>s@8b(P6fOAaPt0qi*`b8~CJsv+ z&>+oPgbw{nSC<_Jd}4)56Mt*aKWA0%Yw$nvNCxQl9K320{G`5*Z?YjVv33jiLwz4V zz33s=pa|fZqd(5%(p7}--wez8t5c`9sUH>*s$EynLx29Pa-&fXk{tcl zFTSe`{fjBv#}{hY;#Y%uy7*a#{vs{>GcZ1Clh((V@>)?l>yn%o68cRaBo4rQEjww^ zUvJf%A{t&xdo<|ZuuT=huTKH}=0>Mdr1_3?1kiuypsKwgFP>EER=1Cz7=LCL7C)p< zKi9k%{2@=4zRzN`{zQM5_PPdE?mY5KvsGOYtlZ5^Ue?o9kRl?@KFwMRXA7;riEAlY z9p5b8o3#JrWs|PScf@%;XUoR;3YRdxVC7EEmaOMa_n`-@+-c%|;T+iFIp^*m_&3d3 ziY6igTdp;YZVH@e)>3qA=~p!U8u*BnJ2_jnENqenY$~UGX zc+J^jGv|^T{v0cJnz$ch1+k^|@%-?^Sh;)O266w%mRTL^zDtfyy(!|Kv1Mf7j~RAU zp0lOm&e?ml>oa<6aa?%SOp5>IY{^iaewRI;m9gc8>GWKhbt;@g$Ck76gaG(AZOUd# z+eW3{BF>bO#Fj(dx-QqBM`+k`>EMwP)uqCkt;m)itK6SzuTN#~1zX-nuU&|ERWA`+ zYNge!YPh~+=^HokScd3-^bQbG%0w8?SBlfTdxqKP(J!m{roFzS%p!VE0If(GvWWaS ztw_pQbk4Pl=uY&q{BstS`}Art?1B|ZIg2`9n&*eO7b}u- z7Crd9`xD}D&Z5ny*H*(kT7S$rF~CT*zdd*)&7LI|6^Q+-4P^_ud4+~WZM$A+0(@%L zd~_@tI_>UU*j*lRfkjC(8&AS|EN9WT*6ufvuRrFj%9pp#b9>lbf6TF&F{~6eF$;+YWW>ajAU^`FuoT(ZgJ&0`%f5EsJ)Jc{c_2)g=v! zYR$u@X&n|5SQwYQQuns zdGe$Y_$80%z^-q0lOCWw@`w)X`mpZ46?msVM(t>K;Y#i?@VEM7)UI1rVJpS^3OKur z4@54;c#0L(%IW1^xZJsZ5$r1d^3_<%7C5_-3)(M2oXpv^#II~>5+6}KjJriK^# z?3&fK=?tx(*JD@STDxt_p&m+dcEx>aW~9Tpj$IdOz1k}qFS6P7XnWZh`8Xx9YfmZD zG(DVa+0|!6dLOJK>5_(B8{RkTBMRrxIe_@^EH>6|0u5SU+;`0ZkJaJ3xnr*b} z7oV@uKA+uH7m4pz(<@J3{2OJm-@j-}Xzw#u8q-ihX-q>tXI)35mdF+~Yh{>6WjzP$ zDs7ET0l%;!EJcPQ>z1x50>7s}rj^~6_-l3=;spIMt@q)n^*44$94M_KEM2{2Az0V9 z#rwBd2jHyx+H&9~l*fv&oORV^RXhg0Y1YbgtlKeiSg<+ebJoq9Hf%8br?iS7vF=uL z6HE99X%)eGs{eYh&c(Iol;$}o?5VOqx_ZcW{lR}(SrbJoTBSDP)4Ym9Z3x3_iB;9Q?|xATmjD~)$@ z*2NcY?Up@1lCf^n%|zLC86E4c6*yWRaf>!(vredOeG7hGN)qcHe(9`#8B@!;?{5O< z>+x66u&!zCvkhuWm1wpi>l)a$If{5gFA?i{%pQD|&lADA43(vi-gvD!FzjfTCgO8f z_3EK7>19suhW-tb_PVELtxsC*(3R57-=xS>vrm6)lvqmZ&$OZ~XKBs1Pm2L3oTcej zC)XfOz>2nJ{?_>tSla7wwJI3Du%aE_w24E%q7OcUr7g~HFOP9hv(~3$>G_V^UtylE zKQ?}C87A2K!mo0c-tsxwp%~?3MO)6&+xbpB0l)Oe#(C534zn%={AkwtbS!n>eRvk~ zHEVr3md-W1`2c*BRy!n?8e2YFi}6BQ?T}b{bY+>Ie)x*&vU!o=iKxAetzpd&+Ob% zxWBCDsq0{M{A}j_@!-2?r9(z#ejxnft@N=|rVaE;8l06LTyjDS>x{u#>7V##t1QX1 zw7C(#>9w7*NY%D>qOK+K17s*xdSdeXv(P>D&{%1U^%u9Yek`@pt!ZvoaUV59ZKW}( ztD`CR)vw7``uSkhOCo1sr7aeP1Y#YyAzA4+r%pS#^UDXkt+lG^;oAS;ui=;b?Z%jM zCn5h6F=7Aa66eQD>iRFuzVOy7eV;eu*(%m6ap9lVE1gfBeIbl}TWQ#Hs}=Urv}nE7 zcW1r9eH5+t%BF)~q0h+Fv({Ta=0-96Mr7)@93Y-8tT!Q~{XY0nJYyy6?f=CX`$x21 zqf#9ra2}d{nYY2_C9HSNWmoxog^{Uet@p=nozLdd@yOJ()_d2d(mSj_$yF-Ws|vqW z2l`KPm5TMA>wg;fQt^!Suvv8K(`fLNW?!iF&Us(87l5I}jl_CGZl;%k{j#1dtoPLS zo*u~8Aydyiutsn&{h zG+)YEZw2R}vkb}c8qfAuL5uYs$pJ&<8z8@?SZ{8(D?6Z*>Y=gTp-wi3aQ&L0w%!$i z`?sp~NBt79-u#qt2IiWBpF2 zLc;tgNAO4(&ZGLoH01q!a>N3i1}{pU2d|MjILWn&)-J`5nmCrVcIk708IPK>8fPQ9 zR>j(TPBxf|_={(?_1#I8E^PsSc~)Q9WZ6Gj{=O*D+J|ROZwVa`t=(gyZD+hMR3=(A zsNlDAY@<+E`=>EsJ8>U*RvVS--!!fnbdTg(6>ERw^x`UbBwD-v{KLnv|3zzm)6eb` zbOx0P*4lMXl*)#_p)$c*yRLq|3dfUNEAgmd?WIj`e8j#bxmLy6JAX)hDov|smWW!r zPU7!#*w0EFORRlgyKkFt9?{w}KHBdEmuZ#=9yP2zyf|kz-WSj6wS8}WY1kHV1kdUk zlk~h>Eref0vqaR|2kXZ8;(dwM?jLzne(nsFi8~jvv~M{~Gk)yUWapmjUFQ)8p)yf) z;ABwt@z_TPm-u+q3))c)*U7V5XJdBa$N=~cqP6!PQ8S77$-K4e2I;)SzLEyRYLDcf z!!G1eePFG9ze>+i<5x4*KKx4L7hI2eXsq3HzfEJ-j;OWINdJ?}>44hWJJsGY5${(S z#M)=oDmz%COXNk_+B-CU^oKv5So`vYzSm0l^OCi{a{Y0+WF2#X&J&A$W3Ug!QCnw4 z8|oKvujzd~0zZdlt$1sf;#p1n%v!tjeN<9qrKGh>ajsNZp`JOcedv!%=FnT#Gl#Vw z&bIKwy76ouo*A^VRSx_#p6wnJTiku>h4(93yKceW!|;nxS@9UveD;aOkM&{gTMGw# z#eGL*g|+tYAC?`0KgP3NfBwj~;r+pJp6ze?jdyj3$9)&A-7Ux^2;3B{eTAR77veHh zR#H(A*N@5yYwh!WS5?7&AUR>h+C%&oTmj!{){0tt)yFwr;5*OujE@7O+K)&6 zisXb9YwzfIBmjD-#LvC!!`e%CD5!|P%X;Rp_V3p>=1J2kk`q>}{rtqjVOSrY?f#La zf+x*}-y&N3_M?T{aURjyKSvzB2%SM?#bL|o8V@S`iHEhm-L(&RV&*-I|u$ZNI9;{VLzo`C;DL*WK8F{UZ;? z+ONgA+*fo!vG$a_;}e*Alg8TX?cONIU-VSe+Ko;u9xv+{eL-#QOIHssR{RjW2(~X@ z_~G+cxu2FEF8wQ8`$f;g$)f)NYp-e(;!rY9udTCm@Ym7U55mxkb;V~*LZc1?DU`NAk}RJbvWJPr!MSNiQ9=fJg{_I4u6Jb*KQ6i z9|Lt*J+`4P_{DnuP=}-*Z%&y@^Sm#hTTo}^b91bZs19{vf7Ql)5!J!>VRN|;85N^B z^D=w3O!%=I>R^+3X$$;MnqAvk?ye8iq1)awJIYJ*yf5Gu6z{Vr2s*&~0xSHU9GHI{ z@wKQ9rCYb|3w;*VVX^my6lq$8iV>?0iz*)5iu*`%+KM_{&7U#?^Qah+xZV3E)WNWo zg#-32Dn_h2j5WQHg?&h~Yg8RFbsMF_&s5@eq7GB1PPmQrN5zO$hYyD%Pb1HUiV^kv zp$<W5AK%|w-a@kqczkB{s}5Z zM?Pg0o|s(0SvTm?-Ud~cm_R>KF>2I-c>X_!&1u~K^Xi%KCq;GG(zf^>{6LkU4$s|J zZvro*!Kp*l_FW8!A40NgEf(dc%t%kk8y?zyiN@|2btv_se}Bb)P}E`Ir*GTH>E-`w z)S)Q)kNo{N^fjsuJ|_Z$HGT_TRI9_z9#1{7Kb1k$;oJMd|LQA~@=s8Q9Rq@^K+m}$ zsl$_s+nzz6`H_>So87C+Km3QcUFTk`1V2z5hFO;OCGwLvNZmI=a`K{TNbxKxVY`tp zpb-Q)Gw)MS)sUX+QD^(8YP>$+u7mXFp@7Isxf-qmge9N8$m!d zCVJbNVgK_!MV;7ZsweXIKk+_=@59Px+C9Shh^pZ;!a1{yG*5E!ifWvX{LcgT3zaa> z2hvB6gzKJwYUFw)?f_3w2{XHyvj1FvU45v=$;4|LTS)V~Pf@z{mF|UdU6uDK7JIM0 zpZ*9O6jdYTOHr)3G>=Lcs~VmEGmC^yimK6IZMXCA6GYYc-MyVB?1<##71emX=4C$C zpJw~0YMicHdma2zQ8mJRYK(`@i>eW{zkX@#LpFkdYCIV|W(52wHiCd^tp9!57wgaa z6zMiI^17U>EzQ$xA5{&VG6TE9-xgJ)htE$h@JLjRw>|qbL41KqnBr4xY5TEdd|TWX zRKhx%#iR~(k|Kz8&)sv{w*DaVT2Kw=cS)ASKWBZ4;h`hnn|zo38L9G*Y+q)ZPPOap zF7+ZV_~0q38?0&^KiPN!I3f>5H5z>1HB`w@Dys4OOx!HoFZIxM^R-GwD!CO$3>Ny8xd*^ zdqm}r)Q8~`7TxdSe4mV#oY*eAvFa>Wk#1uUj@IN_f5il zPRBMjfu8feNzi`J$`{7T?^lzjASz>hwrx7%QzcJ9R3^iB-!tr6HgbW=E>S_{6*#O z^T8E?p8K!GRc-J5&h%BUO5nbzG9Q9{bP-R9DwCyJ{Y2I)sq%-cLSJt4e*r<~4`&Cq?eoQAvdN}^2D%1Vi z=__2^q*j@jZK2@+(v2T_92vM2cduv+3UP$djQJLw}-lXC@YyB0qRP}6F+lpHY3XyxrOG9%GS<=eK43qfa+&8kSoi$I4~L;LNt3=@#yXSQ z2Z?~7G7&X0s=?lAHk7JN8H+itSZA6Ir7H9LXq*3FM@oK#s7z$VHCi&SrOG9%GA11k z#o#`&5fD_y{z-Zc_A?s+L1oI-j2;Va^1e*DSHDj!ZwWo&eVLtZR*v0k;_r$obF88D zcD!FyE@=cb^8JV4TFV;OlJ`I7%Vep(H?LI->sCT#B0hJL;}Jn+`WBwuPvR8Tm$`BF zRh`74m8A1XgHstpSD(4WzhZrv`Y{(jwEh6@kfEr|t?0`Aa9`9zqcSP?Ot)h{&_Pw^ z>;2!CnB%240$*lcz=Xc_zF;3JgQ!e}9wV<-Qzr3~tTI9AV|KdEZBs zYL>ky3%bYqKA&GMKC{gW`&m?_xqmw!ho6j!?7ntYA79Py{2x?lbgh{UpeLd#r7vDo z9sUWa(I~1^aJ`v1_95^4)EV)5M4ezTC?{lnSm9qv`SZ7g{ zs?VNn1-oD)EU406{VU$shl1}D@yl$U1J(f**$$1Dt}cph@V%^6-@@X9dj=!U6;)}^ zny;_q{EVPV&WDZ^LFYwPI&68F#(j+Mb1V8#N=sdhKf|a}_>lIAmhySO1m0C1i(H`qY2UVr#n~Wp5xJj)_A6J$7!}~Ktl|Bu!Gvf74C9Bf5+tqpq^OEmV z_4)QC=jSumqyF<(FPK((o>X#PHD=Ev)!%>V)!WJs^|1aWhPn^!Up^F${g)4gd74G$ z)k^Z81+_ZZ|K)KL>3CF>S+(kO+rby>PijnxT76sT)e$@r)#`LFo7UhL%_38^>e=SGHK^6cRf%sKN%Oo9HTUm*i%a^rkD^*djqh~VMw;h+sPx563XLmaf1;wys@2+E z3+|zRgVdNZbziU9R|RVI-}enphzEHeYNNlMU6*>WJKl$yVSVgobWwR}UR0~YDW^We z??pwKRjd1c31x9Ts3@~)wYN*@v$%dzV^Y*A&ua20>?_`fTCl32^|DmiKU8OtsanN7 zm|Yj|OH`{>&8&LhdPt2)QLC3L=6ab+^GZI5sMQYJKd%r^v5^|oYL{NktFQ~+hpImN z&hxqlpc_g)h^Uo8);KM^FD_D()av`W8|C0vh-zhO->e7rA*nHuNbOafJ!^X0jQa_@ zLq++b=guAh<+WZLx;h{Fm{!*f`;d*)E`B&{nP;A)OYEKXp$?_|*6Vix>n06Ot#Y%H zO=N#ndVy6d!_T+YtB+@Vs1{9aR2!}+evzV94bJSChW(%(8nxQEuktkbpL9^Q%E~bc z1*bGatyX;w6#LV>z4|p-tqyJ&)kW=3YJLT3RrYf(-Yu7pi<7NSWmBRwQ%2gpX+ol6P4Lg%#&J}qGHdz=Y20D&68S~ zqGAW*-xPx*G^V zpS+kV(mX2Vtcp36yHpeBN2Pp@mQI<}GA13MVp*=4$KZ&QT9~3@JB*+DVE^+z*Q#dP z!t5$2kW$;nSHxU&J{?=_b^n}#HNF)dqD_hGV75Y!J z+Em3nJw5K@{7k+{RgYs@Tia~YJZIJxolKH=9e4c z`Y|{aYieYtiSHN{+uf|jH>^Jyii&k;vhW%7Up+J`mSb}x9s5=@)GGElMZcZBW)eRT z73&Z`OZLu9yHy&IQRj+GAQ3Lqr zq?V_s-l=UbDkJ|)v+Pv$3cs~~Cg;1P4ggm5W~DDZgZoZud5Y?lIk>tIJkn&@)v8z8 zsX-a&AscBz^}e6qnl4SNMAfr2-ZBg4=Y6+;+e7|Ns}8?NRJ|w5u8qKcMhCzZi|*GD zY5vyRxcJ0>4s!nqIsj(PirJQOFMLRD*8X?nyB?W`_a&;H@12(iaDSEj7ksxM^Xh)- z{h;ci)eHJ84NmoJ-3;Gg{V`b88|kq&M&s`>s%L8D69EpYheq{IY(Dc8+@XW2 zp83|-YjFLVp;o=)-KuOatC_?PMD+$w{N0Q@UafjHz0(b_ZtQ^SwW?zCRsFt{mn7B8 z+2P=f_=_J4#%dp5(x`;4N(W_4eCdMwUSg=fukGKeC-vL>Q}v`erqsc}s$Myd%krst zUvJ2%RbOg$f}WGwqM~}&Tl`pNF3pRo*R9%}P~_i4)f;TE;}Glu9Sp4M1y6dejdeo@ z1FL#3t#V#N_egD#M66Ifhue2`us)==sHooNa+~w8o;2%ERj<{lfNpiAc_qI_RIiVR zmi##zqUyE#db&EeL$m$`*VpcX>dkH5t3UiD-q$PkxRs{g8hXq7dgB7ab>_&=9n-8o zRlQx=)9k=;QT2k7Umpa=(ZOJSwv5Hb@cVvHy^9H(hl7vkV94*0O9uUj{CR>|?ZsCvsn&FVnUMb+E3;#VmCF7NAgjc8qU*f3l_ zIv7~hGry5G0qb8?t}QC6H+af}&9Ea;^&V!&G{${L2Lr2m)fyQ)*-G>3+M-7F7S(;4 z2>Vs?Yee;W&h8|C-wN;RSsM4~HZvHy!A7i5z5OdjH^w~g>pfYfHDFRr_-Uf*B_*~U zB2BAA)w5lg`we*#bTE9}^7X!JN5jv9NBQ2F>)coV+%}r^r>eK8Qb;%47v9(Fo$0#$ z7@)OM_FrCs$tkkhoiVg;6~d=gsXEze`a)eP3<%`%^Tk zcWd+80@i<{s@L$)nECJrG(-0FYI?Ul@~66u+owi9$%el;i8&7JQ{QU8*0ks-%tAr7_S4-ZyOb)w-zJT-+B@8&y;?L#u-d=U4K2+bTmPEB|+Q0PLEL zh@q11tsXtVekQe1MJ1bFpBoQe@V;SCa+|SDE%0|mm7Gz2%Ms|0sFK!e|9HcXLI*}< zMJ>lXE2nZ$$+?%tUWfm~M#NCb6RG}kU4Zuumxb1kI_`)2#ruZaRtyN7DEptZ3h{D< zrzcc$mZ6>n;s{YC3rZD!!}Xv8gH_2b0}pLQK7scQA7(%DbDsyh5LI%lna&xkr`&BZ`s@R!hmQK854 zO;TR3DOB?4qq0-5Ptbu8l>Twm#(le+*}7G>n!L|36#Ja_4L{qTGCh(d`FCw|*l+S+R5CX$-B5{J6_s4+cYlW_Z>jMO+a9TNwPIIu z^=a@ARmt0?=9Lh?XogxPQ?u$G>l^oTx6> zo9LYUZ%*!decO@nAB6FCzU{?C++T4R&P~4zew7&NeqAx2NRRuMkBQg*t)5c9sGy$b zFVroKJT^K@Ugh-cD&-SjKt1iQwBG~%(yGY8_(C_R=NOZj#;9BIK4$HLDJy~(!7g|o zGj`g%%#hp)2c*$NfcT39FuV7kbu!ost@^qMnNmH2jPGgU%9? zPu#c;>X}`~dMNZ3oh7V#_O4d@5#n)L6`|_6{qeXb@LQC8B2iDzDXNX|$Iw~As%P}4 z0mtB9p|gZW)=*DNt@{3`gYZ7)jPjLhk7@)xN$0UX=9v6$tM!^ zjK5zm0qcp*lBjWU4|W*$jD~tzUhnW2c8bmt*LozfhI-ZwI@lI}m(*|-^{iGmt3B+3 z_c04fZQkG!20ang)A3tT1FSzfOGrL(Ryx%4bD8YL@I!bXbFA6eaa*UtUZnmtPCdt# zDZYe#LTb2*dhQ+5@Dt`m^&DliOa5LYS{0${Svxf1IpRN)Fn zspZfUbe4P?l$Fz=$w&{V=ZpT8s=|*#XUVqPeGcTUCv{Y?EmRz3Hn1k`RRpGW$S#>cF4b+X-$C0GYC6!lzky>VIW zH}%k{=hL5el3-tSQ1yH?*XE$&593v$o>3DI9mTpaLsrjvc^}(jpR)t%xx>?BJN73x zB=u}_c*b?uBR>r1w!eILHT)A{MD!S?6Daz_IL6s+LJ!z`iJ|UWM#qd&y-|)*@|ZND zC$Ig>SH&y5uX_H%?`zZ2;lGJ$dvR!HWd~`V)Swl$HH@i07#u;z%<7*ro~#KEo&>e6 zdw>7A($YLSW?JnI*yMNqr4`in*)OY3^18`2XcEykj(hi`j_-djabI{}HFV{jW{c(L ztwgo8b8*xL7tk@os_o%^q59B&QEg8@izq~VC#vnYhM@z%eNux~)OKLpbwlLMX!VAw zZTjA`)8W^NYP%rhiy`*8sJ1WFva0ZKImyyl{Wnh#qQteL#;+SbyGEojT%t zz4dX(tI+BVRok4|pXBdJ6xFu>n(dw8*NJMoYk&SvaA}J{!ZKC+L{jVOy=% zscq&rptj%lnTBFN^S-L%x2CI~dt-e>wQZib#2UOq$BaRir)Qai)fz!hfNuvcE0Aps4NP@Luh)Z|R_F>u!HEANHsjYPB7fuYC=1pE8KrE`G7e zl*#MjWm#<>dTE8SI*%8iwn>|xwxHLKIeAEGt6!~Zt&)DPv)j3{^|P^0gt4GXaKj$( zGfRx5|Jq^QXw`^UTggu-kr^OopjD&mEl!+N)K=Q(oNs$^sBUoC@8B9bept0lbesHH zOPUweHZiuGHLg=sTL;@WwwOo9539Dr!b}RmBXs;6A7H$BbGe1rp|(p~ITgciU$qt;CpqZfNr4UXP|#+ zr$u!)-5Xn`y@&s-!Ce|k^P<`go4w!)@)GFyp_u`w?VldM<^6z;A69J-sJ8VBO}j|bnyOL5Lo5CEr9Ap;Y&`su?EeUA+q~*@ zAKXVaGqB|P#G0jcpRxuwrNODKZHtQY$h5Ra<^1WVbC2d84v+X$IR*QQ3`K2KuZzcM zbXy9;kQu0#H!T0*18?=|$!}1#-K^iS6Q`eQwM}XiGL-u567WJ)=SSNjhhYEkzHnl!Z=G$Mv7Wpy>|lF& zXN8r}6H%R4w9+1lxJ*>%WV_4d%%ypBGG)zbbt)vt?gG^L$63Sa&`;hMexd#KSoRP2 z8|Y-BnFpw|>6O>F5dYHZR)$vZYEb9*pN4(}*F<#=xiq92;xcqHvFdEqW^#MHUvx4> zChy8_dOi9R)Vc6%?=Zymq^3_Y4^Zcy9n$4K2U^{t>O7%RW?kqNt!`0u?piP_rJ6La zt!)gYc{cL^b@p1hWf0N+3)GtZypApshWryEquny>CqL~M%^THFkM)1RVUpT>`*|9TKux~|metR#! z4eXcqg@?^<=H1sE+!WQ>W!K`aI9^oeei3oa;Xk93iB)IMPd=%#|1a0{6?JYN`1mxq zLu&epI(z@U=m>$J)h(*dS*s$va9>DGe^I4MFDlG{I-g87oCZBq^0q{s%O~79fV``c zwYN(X4BLktvO4ShSpU0(|17C<^>LMa;Ft2l zEi_kJZ!uc9XQ+%s@=xMsz3IpsM-Y+><$}9^Q0$1QSI*?Pj7)f z^FDRRrKRtU=0pEQ)js?_vOfHIQMFCNKFHtegib70wJq}EDuN57CqYr|Q@d}xg+D`j z5){>*Ipxx0tQ)PiQPnPA>7M*~*P?1q|EYTv`%}pm6V<*rXlGUUZ|KA#`C_8lRklYu zVxQ1zTeZ5|`#`my%?onEb+Q=@sCL;ZtzW|)d7t`a2Uq?4W7xN%YNzQeGl#vi84RfQ zz|RxUK!13j`t7}MC)UcJ$AV6*p0jf*>>bnB52_u$>v&tNKkrk!>K< zBF?Px9S)9&svTuCR{p*}bYiipt=-VJBY1~SELOE|tvlWc?~C*#D60M7n1kGxh)yh4 zwa*Ry{ss9oT5Y4M9Xn^*1lTF9wo%ot6R@B@_P>%ZCaQhiW2!OsgOV>MsvTq)vt@;-I!@}x|Y*|_hbY8Rhe zT2ZZ6oN8OW==lcz3lVR{1 z`SEsT_ay&e9AC%*h7DW3Hh_+aBO)w82YJpCL;X3!Y)=Jy%)eD$s)MMjg1pL0`9^hB zkgB|1yC0iyJ|$00R9?q&(hyv~lBXsrAA0on64((s&q$tnSQu2^G+^*o*eRPify)0V zv$PC2!u#eun$>HR^c^}Ns(iWR(!Q`4-Zw9p@YL%~U0e@3&sdcoW*oEzI!1abwm%*i zce)-_J|QjVGxj;}n;(Av>~?-v#QVH&ZW3mBw*MQPM^t(HFVkCLf1>k@Re6Uy*Ve!f zMCTc+@?oCqvSD|mr$SNrKg*VX!#?DF^XWfz!b>+m-hfsGsmhy& z=cK1XQF-tD((d4_lBXsrzpuVeKj;ISIf2S|ZT@Pqg*4Cm=4m>Ai~eX~|0{WFqVkp- zl1Jb=*~|%4e$f)G?pPmD<$FJ|n+$)R^i+_{2~@t!)G)0|(mXoPg57F|>bV@qh04E) zP0+*p<$d!gqsm(L@9d>{QRTPwwU0tvEUNsTVFQOkH_&;;s=U!TJrlfNQRV;GjWWY| zNKb{L^8PNzTjBkZo(e_fJ+zB&gTJ&YNLBv*;qmhKDAB4QRr&7~hL?pOqU5QG$~SMk z*$w-j&745xr<#OYVP7eEYNGPm-kFDy|Kxr1q4hhBF?PVZ@xFOX#P$|C{h^bh%IB>b z7?1T4RenO~J^A|((RoHQCs6q_HT%E7{^5P|;*%?URf(`8Hgl3=YW3py{wPCp%nK?X zd~cTr{2ys>Dj#FkRsJ3jyuhmbxP+ir;E_BSmA5|ev^=hd3`OM&Y+{Qv9FgP+nG>BK z?nN_mer>|~Yld3olde6E$9+@=QTcVFYnKspURL?zt}T3PD+kLzLFK(Rb#Pa|FXdH9 z<(o~dB*)9jBz}qrGi&gyHR4lYIJdudZU`TDkRujcO7{^15Aq6m?&DuRK3*q1A)@+A z`$z5x5!GMvuhdmas{ZTqt*&E#(kdlY|KtSA^;ic{{nMw;S&wy7^5#VS|0Y~8ZYIsM znHi}6yUhb-J>h-)a>-}18n_`oB|RY|GXwQc-8%au_M52wt&Hw}!{0?m9jpG;wCh%Y z4x^*ab4vQ&v5r=4p#F)LPN~pOHZueDck-?$*9CYV-~P+>u-6aZSBUDrV%pD>@ZUuB z@9;gXin%n8jyhKTb1U?S!F@zWUHtu=*@ITO4u$%!@UaNT`z1Xgiu#wHT2Kz>;eGrU zX#<1yUy-I&=%{1W|L*hQewC$pTBW4wKgsOzKpSaZ$(s}PuNvNIpQ$uYdO}EM2I{Y~ zwemPQUQkylsrui3;T{2;fHiH4jG(aO>y!3F>e1;Hn>VfcNpY_5GXi`6YCl_wn}(yPp&(e?PCN{w;pr ziN-qcKK>uO9^2oX!*!ygj#d9JVIA$^r=g>cRsT*;8}5O>M|wgO^&j#$xI6Tg^n@tt z-@T222i`BOQd0HbvsmvFIIHB%iTc<4UHMOaX`WUosrvgkFH40V#b#!p{&o5hGKhJ>-4-389NZ79PX<64gJ>B4Qjkj*hyhiSG8Q*A~B_ z{@EWswY8GwmApAo|0C{}r{IV1K7R1MaXVZN$$BoRfAE;pMnunf^)EIsAkP(Q^5&}V zTV7b7czAxrLmEf0>hB&szO}9NcjdvT|K27W6S2ZX^zM85Wg-*eeRFLxCQm0@^DoA~6a_@>lg)=X6;pVGI z?}|bNtBE6%pwE0vkUn+hCB2j2GOeakDjcg(c_a9%8SBPoen5pD3qAf~ z9ndMrQlU)GQ=XXTV}e#j>mphiL$5?CtZ5j~6Z{gXU}G@lzPU87a-d7#4gp%+7-+k8y0V%pqKX%oOrJ|^h!J;)`{9{mX-6((2>^+CKZQo;7b`Z(AH zIt5uOSX^6>i0eV8AkF-M3gPifzrfx_DpXJTYY%^q^sXpWxE<&>7kWagsgw$5yT`_2 zAEHx`rNSuxw0F3_q<2N3f{uUo*DBIHt)@~cSp4|073*^N$#*CC0~DTFsQKNR`z+Uk4OcJb~E+NrFoGGF=q{a<9(r1 zkfp+x()~X}_s}WGQsMCe)n(`{>0ME%kbdj89^z11O{G+rdcL^{uAlU-C{(bDf0By0 zfL2poZxAYIyIqy*O-jC=P~oRXMmyXWHuD22m{h)23%bf?en5rzeaq#~_v2%NIEx+2 zLT*C;MJnWHue^i(Mysim3W;qk$AUZP6kPmbv%Yyq_<2yF^@ZA%8cFl$6nyBF)TsP3 z*T@*N;4xETJmlx6L@IonkT@3amsV3L6|QM}Re;^GnV$k1_h_>mZ$J2H(%`62CB5nh zjla!MA#%NywW6yE6&&*_ykK-&Lj_xlTN~ND6Qx36i+)#Nr<$Rrf=yS4Wb)j+(! z#}I28CYU8#T1v96Fd4d-EqFp!OJbVl>!74IOdp7nVA4Axl$n`HBi+Pb2HGJRh z!#az!h`-Y281#*gA*!dei_w1wyGExYON(lCR*l4ZlHMDI7U%vBh=;w1v{*T&NbVCu zrz1;?wCio#nM=o$-W!D$&ph`=HIe3(Isu`Dew9+W*#CSCac0=T_qRSGAIN5qK#Q!r zc9Sioc|L|H_2b!0PjBpVkrs(*Ki}c}A}ykN#meurm&EC?7+_oNemx;TIe!krvU%2GxWPh_o1H8q@`U7oCnA zEy5qDuun+ujY5llft#x#e}zs*mKGaUdY-8y&6D06g%-Mce=Ko5w7N@a(bnC#0K8D@ z1cVk2QGfKYACx))p+&C^gOc!mN$(BGAb}RC@n0^%PWc!jKilwCtXw}Oy*COi0;3ic zl$MScX%XPPI1V}>(jucM*8}tDbX>Q=yq9C=aS6o+-zMCRoHh-90y-VrxhS+Kw>Pjg z;z2%!Fv}Ud{;DbVl}L-fPRp$j$4P^u#qk2`by#N%mKH~nYjx82{|qf0fBZ_oeI!Gn zMX#8Vv#_4(p`pdYzzWe=H##US+78e&$NSO@H7#lwHP&O!BfU&$;j3DC2KLSl(4rvI z#~bk@HzZoDHAs{{FOi#9j zlHMldpZS<$k*9B)3hC0ciu4F66dAQKdM4IWsY6uL21T+X40B+0GLC zP^8GN#*J=*Q|RDiDPry3bqV5DKIT|C#%A}&QhL(7NRfKO$}NCSqJxvA$Tr`p!Qd&a zHdBg>Y1hOXeknRQNgcxWJ}A=2Vr%!R(!5fKAQYML=$QQd-E1Zb6uEZ$;B>^Te9V!U zW-?{saQK;g%n{XV!LiB4;G;;9l(mgtz|SE)LJCC|ru<53EX|{XvwTAA9DkQDVW5bA zWYCJ%8ve9RGj?9I4Oc31~K<``zWc1+@D>}Qc8>#b8v zVAts2WGV8>Z}efzixio7z40WR2OXR&MY46i%is4wdV~~;eD7Wyfqjk+PE?yIMZVXV z=K;T+R+}kBjGjK6gn4vuvJ|nLS*Qr6w!&K~Bks>czR2h!z7b%jPUuHPgO&T0UGF=F$Z7>d+; zQ>~Wb|0@)!_Garm>=X6SP^8?i&_hZbq)_B*4c{6vAL(zX)D-!+>n7q^X2=vt+S*C} z-WFzFrpU=InV%8&aYLfWdW(0-8g6P{G&J2dp!3#$a3^ByhXx+7YjHTY$8=82I{DeY_o#Diq6z3+exMi)VWtk z(zK+#5W~2z6er z&~J>mjLmd`I{lxRmWK|rnJ!RgtA9cc){T!{Qr6o0t+;|d1azjd)Y0+lItzLzQfK6% zks(^ryht5WnMTopYXZCAW0#57*F?Qr4*x`?PQl(srC~4VOl7Ha%3#q%=)XuE z=c?ZCpfjY$Nukb%Cw}>`FOfQ@yBB5PJWBn8P{%CTZvyU@NS%WRO{YNz*i09w)4_4? z7{sS+rVG^hxpq%VMQNUoT^0szdpM{8xWmUT{-a(+R-TM~B~m9KeY7|B1Fe2j>e%Qd z*+YNOnVLK>=~{AOLiRymg`V- zUZti^~gZp#wiW2!vAE3Or3;QE-ta(tKKaX^TM4d7%qN8!Y z_~G1U@wx^Uuoq!0m^mPI4fcPDvCv{O`~-B?>R7JNr}Y(j96D=5k0vKrO`IR8P)G7( z<(?~rI;A(}$bXiPbz&OWzB5V1xkT!C`5lYKeMe_4OPyAu_Me14i`031rdN6RMIv?d zyy6<+zL1_PhC2N%`hiEZ>Q1Q>`$*qsunTn7 zvecPoJU9dAM`!K6`jgCV2Ue>C>Wpf%C=dMQW1VaH?mKLrK|e+6%n9rL4*6Sj)>>}3 z{X)k-?K-G4^-}N~*e@UJ?6~qSe%C>qADy)*Qk{Q=4kh1{Dw=iPSOo`Lqk%5vfxtx8-c;Fgk0SH{P>Y zs;`8DIwS3TeBlrCvCguz)ivC%Xh*(!d+N>C@EV8CT_X`YXD3iby7)^&hB zh}6mbWBd+0;$t1>s)xoOnTPv=&RUi_p+D`y@P7GN=XCdwHg(OxYg%=u)Uhr6)eU;Z z$2tcOE)LPY1iMh`D}*|KJ{{i={ZZ;GggWb@E7>~WcqU^8>Ud>d@WJ)788cAlw7KgE z=mQ_?1f&mN)n^9Qf%IG{)aibDaSyDgNFDd#1s|}_(OJ80bk7LKyd%k=PWY@PIhaRh z?Qz$BJ8dl-e`L5;XyIz`yghiw$2u8Z22AWx(@>fhsk1-(Tb0U~mwT@640`6?c0jc^ z&-L153@w9omIg2X~;EB#BU zll1rf4&-MvLrtAcb?fP2KP!V!XGd!8ae+rNbv}goV!wV zPv*}rAF#00t4G%2dGN~B5XVjIDKtN;zX#{vl#=|qe^N?%9$e}SE=eh=A64oM{+n>D zMz7-5Z9%E0R()E*Z{cGjFTb^)@(w}IMM{;OQ`Q>$laGxapV@oI^b|nI$3}WyPLZz% zgZt3DPovy`%Hy{0Yfi}Z#ml!`F1s*ZJ_T>z9)&HL$&#QmaO0F+YudKK2k@k(8XP-@g5 z%jsALJ~lddskC#uAJ8#2BL_+?SYebUO{>_994O^3C zLDhTUEFT-?{yApddNbl>HX{d0{a3wNJ=}MZQU>9DETCiP3?_9Q%UPh*_K!cDaXq9r zOrg{{r{Hkt3_634xF@y^jB0KJO6BSK&1@vi(=Gr?srbUlkFc+37XYPHy9Obz5HBco z9YU%0R^5hTJxOoaHSKbH38lJ*C9Q}5#%AO|sSVy2b-)*N2D6m1S$nz`^p=l}+=m}E z)Y=H1ijG>k4UL?CXW`wPeW($areL$y<&`>fKuykuIT~2LTB*4Fq^Iq1M9ts z4cVVPdVIwQ%=58Pn~d1BT`u6PNU3svE;b@@6Hh6(wb8v`?-(qlrVkqHh4(8DhEgq$ z&25RegA9dI8<*u4!7kK8L#e@9<>FW#QA#a;8DC5|tNsl&rJfCR%uu+A*9fJ;in`x} zy)#3ml=F%jn$N$|ye3g9_)vC*l5y76kdHI|6k^?kF{ZX#P-!6#DIfB7LEB6414|6` zbCmyzv2g6a=_SQe0=;V7^^;GH&gR}7+tzvgcg<7K%XLlkTG$I8W0jp_)9-s1_$}yc zrkOp^>(h{hOR?W*SHh@`Q&gZ=*u3QKcwZvD!uH+Kf_k2<8`0UE>sPnq zqmIqmfnF<5-%kQ(Ne>&z?15e*cir?bm*#0#0;SioQ~!lR_h?rFrB}48Um)(UQcoiE zDoS3`2XO&9n^}6Tw)I@nMw(|cd!X0uiXS_#POtuO`%sC;DE=;&;g~MMCete zd93`orfg;p^r~j%(hD4CGkc)d!8+g5G0(?Xh6~o+b(X&ms06)KUB<(H`50@G_O}-L zrNLixHfy^n^eWeAoIchKoy|Wz+PZ)5e#!v!(%z~s>z-0iBJ}DPSZIfR!pB$!;Souu zn<`23BE34b&X7M31f9(+y*_>QSb}vE>1A0wjo)i-O&W85#&!=}(H;Oyho z_`9?#fzs=-_HsEdht6h}Ue&F1%0efVdJ>^mr>Rc9&=ZkfSKCI7!F95kJE-2Q>XR{Jx#ij-Ng>M7D zNDo^LqjJBVxG(=y?$3q>rgy+ckzS^zzO4ygczR7Oa{PjQB@K>VCySDH;(9PxdVOxW zEfs!?JQ#YdbItKrxS-H$uJ7e;3`aEd8hU1JPxx|P= zA0X4KSahD5K7O=WbjfB5lsLB!W5LuekAdCjo%!K7i(NpVCR z-MPg*jgNRIqhtcF+kSdG$7>faM+*KqOkj<_%A*jCLbUCCLe)hN)e)vO%( z``|>{U70_#CgN_=gQw6gIR9f-6KS50)x!2UhFiSA{Ss++Ex3FrbWfz+{vx+t*#GF* zW@#6)$=d*V0Mdh}(5_zoboqI6(u1eaE~4I<)wnOTdxFyLyIWx|tiMP*hvmbkL$8!N z7NOnHRa4UNe$lZ_>R5z!V|Mwi!oH&26E}8DGY0Ky*Ic|1cEn~BLA!m4+E-zBe5_Vv z-2P>y&CpMgb`K`@9R{Jg^s{)0%nEgtPILx0e*&C+he-O}>sd!u8UrQN6o zyX5{f(u1eaZmfNu_c%W~wprReEZF@V>qfgLDDB4mFp|Gdfp$+&+FAG6RJEFPyi&&^ zwCnn6>0Q{bQpX~+>t%Ob7wf=g6hXVnmQxPE-wD<<`iOI@v)l4sjA-Fddd8fI<{Hb9c!Pq6#9&gZI*Uk5$-ji!=wjK zq1}s+FEO|;BJF-$ztI4B4|Hs!dxFw#>zk#QVMlzdcC~GUey!8+f0Q~FpzADtQnxAxAYijyCJ6z_zNV^|LZ|BQg5NLO^(tP=It3=uzdYhQA0b&hnJf{XEZ}i zyX^1FS3-xCL1^doCsFSIQRY=L?UvvFx*z+29iZK$o6tp;ST7F)J^bktj9Q`w@gMtmL+zesAe9U(( zc+j(c(=pG-eEW-zwYZoGK^G}F^O0{|*eN>ESqkceG}?x^0G;S81rs+t+y;9gJ%kDc zhqp{G2YcaTK8NMW4PG^cKGSXvO2MS8ok7S;ixjMtRnHLfq=!(U;F_S5wV_K&U5!xi z&Bk(%&82xZV+jh{*Sq%>aV{V8J(xIU#L0t-px}|+xt~yf;bXon9yVix&oq$c zMG8*ybeW6&DN@jE_O(J>Cpytt3T6k_cftCI6rAp4av1xK^bjf(e0t=3YusPbL#R;D z)pzC*#2vJogHo_{=A*jsCuuharQo=LN3)?DN?namFeasSS@4w0SV|N$Tk`HQ-mg+u zBNXfveO3Nmcs}MU-7vYLK@9YhkNHx%{0+vOZyp{(EF9hgh@IY{n8492qp^7P!F2e7%>f zwt9RJ?@Od$AIBLd&82yff^`kzZL!XBC;G>ParIwKA2hY-$I<$>rH8=2(21V3HF9!| zl$OU%7Tv7$<*MFg%=0ndq30L(uiS#`7b$pY)rjHPCn5#=SUPqGucg6JQ2Sd(Q(O-Q zOTi(o>*eotm0w^eIA!NX2PPiWP_W4{zlE?!}%P3XYiRmxO(y3_`(X z1Cv)_J((d>u>ba;!6Lsv!E4j%ex=v1`5lRZ9eykyi2cKl1r~ zCC19gO}L(a(@~{T>Uk_3rRUR`jHp6K=|o(N*uJN3R8mdkH%PA|$%ulE4r|>$KvzXN zw%IoR9rOns_ADJUy;~i?JUZ-O-ih(wmHpZtbUf1D>l)UX&4_}I1tWi*fG+Vd;><_o zLiftg%Zqdzn`hh&`F4?x_kX>~g59CRo~7dmyZxtaqCRA7If@p^KmTJ zlk_?&bZnaQKz@#oj}dES9kD5@kNb`edzOwa0Xt9P`f1k*rDLk~E*IQirJhIVxb0<{ z3j3e*I+Bbi=;-O4G#L40+I2$dcwu?SMeK8>o=51oCuKzrxX;IkWmc80kyZ#l93A#7 z9aE<^xhqYpL^}RD8a;&&P<>!{H2X|*=Z?4)_xbwcSFzaeKm=9zk)Ovm8mI`a3z(5@3o$Im+_XF+e- zj40^1u3hv3=pLI91s#(->d4Qz^D*L=C3_#HRl;@hG2+|TwXr%(>;v+ zFVgYE_9?|JrFo^ESKSJ9TtDfCT#x2s#JN2>Z!5C}>w^w^mW~Dsnumecq}NfQx9x#H#O=c{9)R4Lh0zHzp@hSU8(00I&SpUnU()NC z^mpU3&n-d60!zQ8*w5&&PpG`9?qIETu5M2+t+J?JxE|}w$B2C_-}hVk6Py+4xTE+{ zQ47oqbj&WZsT8h98XO&Ux?Dd3|5F|e9bb>Quo8MshC;`_F4`xtf7C-m$BpF*XR}u6{#b#C&YD)V@dP0Du zW~tT}s>0s+n6k~b8H4(6!L^9g?A5*2DCnC=%^5e+PQedX>WGAz6+U(Bj^pu!0I4H} zYyvf#SalqP_2Fa6v3Zlap3lJg^D*W9U;R^i-$h(6QZw({wK9lXMQS!VY#@J*F`f`$ zsky+%EeYqx69P1|3TnR83Mz&8OQdGknRi}cpO9Wng__m1hgXMP(=HfF&G+$L`ocfK z69OzXpWltO#Qi0`nhG`dgw-j7^V2REO3g=!UX`G?Y-SbI^gH%j{+a+Bs{X=cHFtq2|3W%~i-}YPw+5)NC_h;5_(Oq*qg+=J^*-<-be2 zV9GTj)T}%B^mf>(Qb#1z?09otbLbwMSp_v)eY*S(eiWNo1vQiB8nwmy;$zB99m|hj z8xFe@sktd;$4+pBcEM0;8qJw_89Ih11pa)AFYKFjtUaii(A}mf@(*}IU|n>(1_5Wi zB0$YjHCma2r+iF#Vtco#(b3Yhigv+JYQ_W^g=0UnnN?8pRD5%Ic{RRh363YdnhG_GH!ixBy^k2tb&?TyFA#8^NZB1SE}9W|D<_7rkpqZO|2R$ zao_QT087mu1=n7JMotPX*?mYBX#-9xxTHvLCur1^W{7ko)GvD_H5wLGwW@% zzwIcl-N+{f_QJ=M*=2kuroMxJB~tTi_41?Cc}|X+>%+UhkmFqGS6FJMj@q>y_9734 znl5en=3^g{p-|JpeR(}5?$b~+v#7Ne@`rR#Y95*w{DX^6)zq9H6nV;$9xVS$sCn=B z`HS#3*a2$h1@_zsJLQH%O`UyD9umKTeI@5W*(wi~iCmVBcssG#+O&Vns|=5yFe{lK zmVQJ1ygBJL6)7uy{;j%Oho`J`zclqrma$%s*mTB?l_%}jfp@f9hf-Fje&^oi(!5C7NjENR$2#DN2%VP8PSwlT z2?1qIC!0oM9q>d%R+DJghc|JDH`_^uKxmQ?!J~nM~c(`soUpb#I zP_|VK_fGKJMarI=Z&nw2$j7E#XKwT9(++xzCn8wN2CjbU3+|9!Q-!i^#;w~cKkpz= zw!q^1A)FsiM6i^N7}GW!{3X4n3T5By1tl51NAG}{aHtkfq)7Yeuutzp? z3(8j6Y}^l=nc{2=J7zVX1WQ?GpBs*_Yjv-whO#E_{mq~ocp`$O>_WFm#o!d_HB~4(JELzq_+7MHhf;RY z%L{XGf0g+s;{5BOcA*OX*#LD^>KzCOZ!;A7KGF|&$iyCP3Q zdQBC|F6``QioYvTw%nKO`uMvdWq+luum-R3L_~Y5$J#GK?3#eGsn3r%fG>C=qHCB! zS?!{PAJAK+en}|nc57r;*eM^Ix*L=&uHFee6)CG@+ix$9#}g4OWuyD_pNn}s5y4W{ z<;%<|xR0dQRH1C?nZG;Yevw{Ng|bC664Iq<6`qJ-Df`syVi4XJ?be}`t=jy`0pu%` z`X!-ki@kB&e(HEV)HbY!N*|}rlO_QI!1MZFtKOcA)RO&MOZzmFC&ZFzB1&;Xe}V%*VK`YHUlHu@^eS$GGR_x3qdC ze;xwu9$FLR5&-%x_x+m(dlczwQhsSN>g;&ZgQaiTr{l_iYkZ7bV9|C~#{}#Lk-n|+ z2Wr7jz>^*7m|O=s!)As--?5$b z!?2!s(u1XMm#@=*!T;f7+;U&uzHD+2>%+&mztTE4c-j{GU!?E6rU@r-yhz`eHeFXj zXYiy)r>R@tQ2I`;+x{okN2!|{EC78SBDU!w9^_+O9ot61gA1{5MfzUxb9RGYC(_qC zBJDZ+G(73S()Y{&T@{YUlO8O6?;l^auc0(A(zo)!_VL)~q*qpNIdt>pe{`A?`6`VQ0elfS2)%u9AG$GEjCt?}4dvkINWziRqk zUwJ&koSj$G*V}#OTIfGBWcm)Qm^!H%Gl?HT-|3G12SFdWA<=ibWhEQvCqJC68Y{z4rNCP&qQMK~-E29}~CAFT2|FBJ^LRvisMW8L)TK)2mSVQ`n(n@JmH1YgOsl z7wbm5nJATYHox|erd3M4mQZ;{t!j&Lem3(AD&Ht=<%#>qW}ZRiAqUUo;y&^*vE9(6 zWvjF`k>>FP3rpoP>$3H+4kDHJ`Nsd2rd1-9Zv+q%p3o4tP+5aBg;bY=g+fubH+rWR8p1(5G92)Jm3l3q zaz;~^%g|eq%CkM%$j=wBnP*V>M8w_*@SV*(gUUswH8#P2;A7%>O}l=rHyirT$HXyh zhOe{L!@d%!tfN)E5#BHDW};MXQ}DtU`v*_3gvNT#lIpcypmJEih)8gmkBP7N9h#k6 zjJSi3iLE0VoYjv(9z&#ZmmafcAkQgMdEAWC55X5cCayfN@`Ua6p~K~*Cstc`|5@^m*zz( zw@V)O5PB$5d6NF;@$g$DMUlIiw8IX~+c~yVRLga>udDOA9F;#_@%#>d9)qQFt*7xX zX*|wS`NPgCf3Obfp`mj9z2>Kt<7rIn?LOEhHc^)zjepftP8@5m16@)Ep>o4DkIEoU zWQI)Tzu_y5M9zZBZQc*_c3=*cf0U@aYHoAQ=TR^(yR4u5(bJrnpg+3h+lAJq|HJ;P z{@!Oifm4#!()WP>lh)Ec`6sQVdAavkq4l-_*7D~S(C#Wq>*;L+$0JW7(t4S(WnJjM zNb8KhW#>W9*$g&ly=Z!a0Ms3#nG7~)?GXKIIO23ZhOSb5+VeZNpbxaWiqd+J%fsu? zTang{(-U8Svm&j#ji|60_X|(pu(Yn;y~k$g7@oj+voY1Wx_01d&{|d5Vlnm=A48ki zym{j%Z*!5>cYd4R!wYxM$!^hA!0)Gd1{>6R}X?^G7 z^)j#*k=9lYvkY)OcmgLTC4R%bFU~%o_3?c}WhdfwEjSM=wd=U*&z z1Fh@)ooEM+@G2uZP&qe2BXdSIjV>2crZY90P3at;>eyh<+nipx^=%~we z@E1?ue9F~Ly&50g2ecmZELZ+K6Fh;_aI2es@kW;~pmnbyI$nr3`53xOk2@|+uH$|2 zG4!|@osQcTHk0OQca`>wr)Hq_2eX56UxY~OQWNaY!CvqL4omAl`hJ1Zw2Jf|E3_UJ zztkD~jr1NXv<`~-_6{7R-Bpy<#j}SwVc*j3DoX1{I^WXZ7b$gOLTmG_rR98rQYR*~ zu3Nln9sEo-gAH1@w>KJ&eaL38LF-jo=38LDd<=ae?%0bBo6V(pk=AL;TNq>ii?mKy z{Ob_>3Os?M#L(@M*KeBB8uuMf;Pl_#C@-_fCO_!h{Tf{uYZBCWSJJ}7@44(+a* zA7nhNX8Dg->?|K&Ao*dQ*4+tj0PvHfO}ha}TFO8UYX*fld`TKnYx z{hz(}famh-|HrRk@0q=_M;RHRkjTu4jEH0uSy`d7Qj%muLsCj9GE>TE$V#QmB%@(f z5*1qbo%4Qs=gp`4{@lO&zW?9v|NnUWPd&ZPzRq>7bDihwoa>zP)|DdjBv=7kr^+1G z{pEf|?*PP}F-V<~1~x$s6)MaqaX1yFYpVtOgcz@mPFCGxfMk z6Rgk0V{wyh8To5kpkBR{-I*lgCt&+o%I+fqyR32G4|f)2z8$@;v4gr7vis_-pJ-q| zym&0$I^$g2tqt#MDZ7W!AF+r12HaV6vGzsvJO5mI$nF_JD>lORyp-LeC0uZ*N5P#% zq#1I^?mKZYdRPx|XAwEOXR=CrLp~+0onBz~u&uR(b={;@U?g_m_jn`WTw>BHFcP~D zR9k$51w=5ou@4S+SyjFzoDftXJWcNEPNEPh=$!EwRyC1Bqc7W}9 z@mRdIW`l4mJM2FfkHw`mkDe$BgX6NL?Edbh_hn(=4|f)&9MAocNSHBW8D{RVE7fXJI|f> zb}N8CX%!fW-H}%&eXt)RtpX#l`?&-Ycv)oO`!tc=e`>_g!~BVBr;83z{v@)y;`VoY zVR^`B$RWGe{-jBVdL!IfM9%KJcZXNN@+=;U%az_x6mo#|wv^qSo{VmU{IZnYl^&m6 z2I~{L-G!wS}h40fuc7G@DV*=ax z!uM$+yMNwqXbRgi`3yN^cSngLJE(Up9*Z{~p5py{4)&jm$KtmS7-J_rU_)8T?%l1P z_N>5vDZ6t&yD*>x{Nc``g|YbQ6>Z$l2J*G@06`_>>OS*V9EW%u)xj>WJ%i^t-j z+g8fD?}Y6Wm__WK;IrW|)RUnhXLmUaj}RvKfccK`I?y!H~lCb0Vv@unW~c>|aZvb&qh7Fprn znao=NyEC2gjez>?Vly)3`Dl|d1fUJmXzlk&O zko7Cx9H@r(MOu|cV*L!?-Gp=FNvqOGte+}XKP(F3FMJm#vOeW*F~a!|aK{um>r)Mc zxIkz(dy04Bx#Hju#e>>6s+=_!@*mo?Seq9}!S|N`D8w*GpNy zMf0Q|?62UCDbfr)Wc@Yn>~~@Qi^ud~^j`dSB5)kNl=WYpd{7LZ@8U6iw8ZnIdJ5QX zm$JTa{uCE{AD6Oz+&-}&I9`A|rpQ@;rBp=#?*-N`8ae(5zAwbJ-wUj7xWju4 z>anC%X(ZOa<9PQI?3ajZzcV>5uA@kTtp8;=!58*p3*W`(SwEaJ4fab5-^Gcnzm;Z) z(9Fqa;34a8iyLT#{R8<7JY@Y(sYx!7FBXsK-@R-cOKODg-BQ-q%ND7Hwoc> z{+Hq2ID-i|{#wfV-lkj2ARoaUQ{=4w^z4K+92YEQ{ZiS-*0B8$*M2XszMAsevv7WZ zxb}O2_32qWF~~2ZRcR#FSE!RJhW!}aF-6Y$1wQvRV7p!TE>2{9N4q^v@VPH#{lm+& zFT>|TJ_8R~-)J_c7PhO!V|u42Cl22v%=0fE(%Y$M*a^qK#I@gC%G&bMY>@TYz9&V(aoplDJxBC(qO2w4_ob{qlI(FF z^59a|x7Cm4gggs(Op&vG1^UYmj%(nKDRS07ygPE6Ebu3;{a#@GP$Rh_SU<$I-wUiy zFV!Lr+XHD;8j1DS4jbHo&x^Dwjl}v+FKD%4zp?OLoXGl?Jko^UeIuWNhpaz#U}OO5 z;p8*$koEaKtn`QNeDRpRyLG1Lr4i)8#bbKzXEsr+JW&5x%KGEJfvT_`;EpMB)?dyv zI0W;DJEq!i?EU3LBiOq!?>yx|?qy2&ycUn?SIXMj$a1Lw|D~+29B{=1=D(EnDSbaH zL%j=_MXdiMI8Fxk|IjRq=>^_=$=EjbdmK&1`mH9%8)3ghG^8>8apg~{mh4b(nK!># z|H_7YCGfs}n|ap1V!g@)>XQqG$ofZlH%Tn^M+mI{Ez_z1IniKAEBNJcY+bW z#}l;yN^fp{*se&c#@8rXiwXZWEuK)3e8aaf+ka{w<-zKprL1=a)H2`1DL{C*-si56-DqeAt@p4Lrymp`--XJC{WGzQdhh z9%8nF>Uo&z|% ztY~wU4t(wl-{Xne03yX)3(G@3!w!u0GpP$IeY6>M)ywSiIz3w5Z+!ku7nl^mSmhuXkIZ22kZ4|jsS*Li$I zJm*AWf{$`QY;dare7=k404RNjjCXXx`Ol@=fR%L7WrAL@L>nlTIqM7GM_?9d16*6m z(&2TXA=d^XQILb6C69HGLhYdzWr&HM<3$c-MVRnP&QnW*qB=9>pT;zV#bXne%myCtABH_TLxB zR7AYgS)O$KuqmI`_<6<;cN(@%yS%pVXMU=j{I8*SbSCUUMhArv$fj-#oPUlKf|QE zQ1CgZ#`<%UO$IK!>BI1kpAup%sojlN`d(vEO!2oHe|_K&3O3ZJFfqd(7q3goZMc?p zXmj_JbXYO|{7Xi_Xsc-6EYEo9-_AT0!u(#3v(mg$a7!Of zoyYJ^_Jy_Ap6xHvxedATR_nFL zQYyv2jqdDh?>CXf>6I@HF`kI6AGZqajiimo);ti{q?M1ieqmlw@>qi!lcPLw(>3(m zyG=tJH^OVRa9g!}*_-vHals$fPp>?C3e%UX`c%7XvQ;MD)A@5TJ9dG61&?OfX2HI7 z%yT!(HsK?>wg&QPdrVB4FS(~CU%@JN_EXxg|K8ED+tTt%pfD!APO0;2mV-?^zxjU(@kTIm?!;-?B1U5rH3>y-HF~Q&o@fTMz39AS?`~Mt9<2h z9*dsT6Se(#S>;|gRx8bJYS%2I#((X|!P?V?Sex#xuzPRw?$n3vx!~iNi-)Leb3qPl zcBlMVXEtFpipi!7ZA3EnlzVN@>PpPE!PsuBU6%3P?sG4Ga*NT^YJA;v6RV+1xI=^| z+Z!M4Sq%NI$#kAKy=eC1I=+NCH|)%Px&0o~){!TTRO?tDx8rX!#uW}a=P8A?r+BTq z$b!dkZrP&7vw3nG%geDRHv+MUG`93#_)ItYp5u` zCa9u9Bj3lqQoScHs6GZ$NdCEU`==uYzkcl7ymd8(4e`jCJTvL*s;^DkoPJLgzr`_5 z!+0^GV6|6oUf!xy>^RH4^J~rB#eP^1R8}T2VIQJAlz)|IY9xzj)Tc%oZ*7Xdj7nO=RMo& zZJ1bfSV1FS!_d%}@RR;YT}-{P#xO{(i;mu;`L&Ww7XHIqMI%}Cm8RbXEwdWdK`gym zrtwA0ko-|Tr7rZP1vb{0cR7h7$Mn@V{>03bGF+Vcli+3CVr|q4uAH~sQ<&vB@kl|{ zz>^+g*WSf&I%6ZVH119D9}?)O3v{zKKf({WYn1zDfAXX8KC;t`}5l!UWqU2 zq8*3)a(ys;4d-KuDV>&6Eh1Wxm)_uNjf1gY4o1x3%ED(AzuwUvLB-l5j@>_Nt( zSE>83IY9}L^<4>Hc&VKFGh%<>b0#06u!1$xMJ)q^d!MbuAFruGFtMhOcI)1k|9X*t zxuCSLDJIA{*8H@V%Fk@e)V40IHK2*3?M`hIt%{M3BModMV!EV_p zA}KZ1>1J#%cZk= z=e!o<4AQwGO`|xS7qCZA*CQS8b3{H?s7uOyI_Av1^FuFYVvLB%uNz1#$2 z&0E!0vaBG@@6oZs8n1l(rL}aAB%4^?-3wiJ&K&-Py;*PlR_Mw*>(e6}l$L4OV_xbX zvmU<)7O7#Re;+?}A3xks%}uvAWms9uZ;Uz>#WmDtH)zVQyPZ61XGe8q2j(a>^W2GB zWpd-m`(HX4Uf}z!PKFJJ$<*esH7DuQao~zq*;ZPv+>DpM%wfp#4Z+HoEZ;^Kk7Z5N zQNR9GJdQu04>E6-_-t~YqAhCXiv*sz=0O0xkkrMDXIGHfq=T68%+ARUZejj6mxVJF z11YepKXVVqe|`KtGbdwe#8LwnIdQv9V=LXyw>S5GvtmlehV@*oHtyGyKTs#XVtp42 z#-OR98}v(du!Uymb&G@FB(c8TS=g=$XP> zxXZ>`=jO5Qla^xK&4%JXu;}NWJeTa=^mo6zw%e!N1yjDuoa8I%b-I>$ulbvVr?|{x zZEf+}BCl01r)!V+G2qMnPb+AwvSZK72%!u>!)=fuppR~HH{b?3I<0i;4pjQ8Q zL%;5=IXfl1=zz)k1K%m0?}}8}cDmpgb~MxPsfvpio9O9jQ;s|I7)M$ux6RJp16jvD z8+`nwkN5T*d!;8_l4)|ufoj4v3){c{()DLNUzc?XETjBt&W-*2sX4k)S~$VxVZ84l zo-O!^hrgcu5afvVP zwtQf(@rIMy`I;%~62IQTEJpZUX(+=UZ+^(wymqS+wvq8CMQ--}SIc#c-fnL^fNSNX zpFLQ%V`EISk9kbnQ|!^ZJg%$K#4Nsx%^djrH;6ZKv_93Vf}8 z@Z{*Hs$5zj-q%>kPs~lNR>oH1zAKA}*`y_~txp%#TDCQ(={7P?b$I^@{YigCR=8YjIa2w07P3 z7RfdJ0?tySC$Si6^x2Ni&?p9>7X@keS(SuZE%At05|ULT2>d|2w@W5A;;~?$GQ}G-EQqt^T0QW*69|c+J)bL&A-A% z?iLnou7*u8h5vjq{B~S#Tn@7?O~6OElI!*{v<*}9?#1(tt?@h*c3G{d^UCPC*Ns>u zebdL5AC}+S^M#M_m}y|LVIlk8$sCZX?0<4~a$hoT)8M2a@@c2=uhJarl$jS;=sE60 z-lHFR6p}1@8D|YJiMe~PlQ$GOUXS42Zsd9yKd7`Lnz2>f*wNjPFFWP~rZ#yi<9U#! zX7PLVeY}_rHX-6rD?Y}TdOgo^o6NH+{8;oGPs;?Omo=4dc{T^mU;)ZEtb_d8IiJek z`&|8&-NQs!B4l{=lbDc^147x*~~sR1q<)l5w=o@ zHhLm;DkmIg!lsThB<+8+?#zwYBiS)mU1kr$&cd|}#jbW-30%?UZK)~)!O1div~{HWLS&9$Y`iN!`&e5FLPP=WkIRRGBK>)5>yv0I9(|J{3BZ*QQLSz6`86@I)EA z$Zg^#o^AfAyiQ0>y(kVhFX(tDP^NwKyBCjK+UHj6MfDyLlLvEaT$aDCy6T{dNu9X6 zGk@3Pv@_4ppTW*&@!PB(A2-KeWLeg|W4n{-0QR#x*0#@wzgutbZ8Pq*7FbG3yWPy2 z<5L=SQYBx~uH#Hhd9P;p*4H(j75-YoGKB?bRbF>nmg(hFa$Wjqpc7`^G(?pIGD0oH(Pr>D^xJb1-Aa=zuxBoS2-oMeuig za(eBaYKzMk;%~->1WvEOWsWP#DRx`lpiXUfa+FKJmiKHC{mSujtNP23L8EaR%=38d zE~~Lx`5LDQ&5)>dxX4vp^y-~wchN{mSJ@{S7_YTwOvR(X)5>!PV?OzFVlu+IUNT{$ z4snmxs6|F^#y^>!852npeM{Nt>OA)(53{E;66a&Nd4nSK-JrYHa!fLBT}R4L)$(k6 z-A=z5PyDt>b8qek*BcvOqxuqxl~`KY?vl~v2d3$wQHq0G9cCaOPYDw_w$dWh;MudxVMT*1Rcww86yncAuC+!x{`di)Y35)3LQWxMdIRPQx?y zXO`eYnlFQlEXqDe_G0_oiX~M)$nV?Q{zBU3kTE7lJ$%U~lxKx}MN&_^e;!^8iTR#y)n$KrAp3*QoiF@Z)f|&X*=xv$d3Y>jscN~?GqK|Q}^(VAAj2F>%AX# z8%a!Os6%kN33>Jw&&tsQR$8od6g#lIXr6;+SHBf_Z<>h?q3Xg9E5ClGWxXO{e4Sgf z(`t6SZ7Pa!uBW;~;>=C0()wWR!x41fY=eA!j+3pMMb0NYz9Zw|?75>F0;OyMi~>F{$p46(+$L8@FrM`n8z4IU9{_*G{eg7;JI*EUezA@eb^)isNy z4l~V~aX&Y8YixXO-OUIcHEhF7;~FI~hk@a_r#5aG3HZ6D^9oWHMK?$1wAlo}k7h*D z3`+>>9Q|hF;qkJ#R}Y&F)Y3wpY>-UwUz=LCJsVegFzdGJ`7WU~i6@z4Vumq?r;jWg z`#%l-^1nuZxOo#MTiVb2W43h7NYFN(`?8wZ{u7o{WxyCTYJgDu~&{5J*SW>O4W z*zH>ss5o6PeTugoMvMj0n;dQo(;jZd&#IdDQSUxt{gN|fl=?h9ULwmdcrH=1mouO7 zi28Lu>;_vw_G3=D^eR@P^+_KF@sumo!<~C<_9!s5t1#FI;<;9ehxF_F>Lj{)rq|?0 zz)u_GX32Dt&JuU>68G1|?x@v^-RBQ2b{mc?cCS!eUj>R2y@mui-xb+MWiiiET+NU$!e+dFNr#+%y z$u;~?g(9-j14VSU_~|Um>)p0yeVvonJrvn_N`GYIkt0=<#+rL|xltr$)@J)_xdPcZ zemY(&Cp2pLozV)O&{I=aE*^a~d;?AHm}=u-F%ilz8nPWNW<|dq`AWmvE|81#^!rE( z*q|i^iV4C-gU^sQTch1M`RFl*Ih(SLNinb9c0Q@!K#!I_z2hw+vg%@2rfGC2w;7ss zV64IO-P|Ll+H=__va-?jY?J(NCL*=tyK!+roDyxfQOeme=A0zkqE|S$eghgms`p3{}^G8hV_b+kf->0?R@_YjAWYvC`f2v`Oj>jY9l!O{;(s}G{XGB=u#&dJW zwZ>!7zJ!3xqkUYQB5`x8&E$sAT_+n}bA4D@5L9;VZShNabi;o2?%qq`T`Add{AXMD zq9=PAEH;~;Hk*pS>Z;e!jrP6Uw<*0vXhMC;=+2QUDbz-cKlex74xh%r9-o2_0jT$i zw-#aL_YYSjTRl8=`6=r9E3q}7Ei64qqkX?nqX@chuVRCZ?ehv&BcrX78eXVF_e`kV zibQFfGiQ`b8f($8D_M^;*L-`yx$gBn-x*%?r;98!38H&wZyYlkI$1^SMQ zl}qd|`qK?& zp~>bV^lkUv5KEo5A2(V&SJ}U2L^E4>+ud0Xhly^`iz>3WMDar+Mv)xvZLisG-o{sa z3BCWNEb*!7=bMjgRQOqsQKO{_%Xj0i3UeZ^>AI%xHbVQWyf#vt>fV@T%U<>4$r+UX z=JsOj+?DZ~Wk+vRZ9~yxu6rFH)VE#>M!>x7b?ll>!f2qj=-SLI$z`i%bnZOc(-G04gqp5DRPr)%P0PpHjw5{sBT?b@;j^vh67;22 z6IR&l=tt8AiZ8rfzvs~lbWczEd0F(fz}AEwj?G#YHEC9rheJ?FcjZF%E$=rDd=8U( zFVl{$zWd75G_d0D?>jsCe$hwl5JjWR;3r(Z7ns>BtSKce^$ ztqx~D5N>p=E7Dham*QOk)Z4yG^u^1PW}zc!{me>tw85|0`{^d9n%vQ7wd(LYXb&Zt zbN@^Di;-0&x;GiPP-_E}v9$OF^V_JcZEv4CqSB|=CHL_^YvX&Z+}MMcp||Ixc^< zgHp7E5FIMR;Fm~MPMa|MJi2bCJG|^iw*e`nA zy*@KLqcdS8S`xQs)1z$fxoO;Z-Q$(v=vhxrNv$S>xyjf}%gBl@6x;68<9bJ zJSj*LJy3D@7_!V?{*^(^p6iyoQPFoYrBQsQnq4nSHw#~Wg5GS|;-`?pEH2rYq|zQI zjEWvoOxPD}HKtJUMfc&=o#?gUnSz&_Mx?8+6d%`YuR$Ld-!Xl-N>L-k(`?P9S{`)H zaqL`WRCQw;zCEE?WgDvX^Q`!% zGt;(Kl?#+5e6>XnhE~vAvPz)3CN7>`I8lIFB?O5ZaB#&YsFjN`mNTGIg)L)tFZkVk zDF=O?4Va^)Jcne8nB8U{Zg7gH=(~tAKRX`&Bz{&)YqYzg#)}GNuU-8@NA6DZ#$3yU zKuSZDd-4#oWx0>82&;teGpY93Uk#VJA&Ejc$SlsC1R znqe-zc&c|lnnnF3*E&d+_Gv_=W6ve&#f{U7V<+h_boLHK(?8I-OJYW)5hXG@o80tGfCIHlH2v@Xeer)e}U19XVdl1 z+~4$Pi#k!({DX#X%-M@iGFN_>T_J&Xa^`k-i*je~Z@YRq;e^?Vl5C{X_V-cSg&l8w8KANR{U+;zWDoNoszH8ql3yNA9b&n$rV)9ZWa zi_;S$-7=DOGP~paRT8;Tb7bYklu7@P;Eh)w#}qrEu2J9Vr<54mThF&eUUa;PYK`x0 zc%gl`_JH1i{Lpt+bmQn2^=IaJH*z-jRyv2;pxbgw3~JwUyk*R9&)c)?8k$M-^x77t zj|C&|Yn$kf(W4KVFuMuMoi8qkhwM+>ZH5Nz#|wQtYLe)S+Iq$rFQ7YD)*PjXdZE5! zBgK)MhLk8?@wtfdl{wA*Z3pEBGB%(Gc7Ecc?s7GrxOG|W?7CBEj_!vyG|j&=a&82NitV0B2_UVMT>3-2dSsoiqOhsL~aORoaVfNx;oTpp>3?1F8h`$&UXptdVP$ZP&NGRojQ&`O^2Qx^6kQMB}T~J^C+ETf2m7 zCn`Cga_v8Q@hZJE>a()YcAw14nys0B3~6S8C{4v_AHKd;5*mu6mc+&26* zRAu$nkPp3M$F$$VWMoUF@HHuZzU}DPm}RDz(*?!%QqPs%W>=urD%W3khEqDHhSkrl zxWs|xVZs?lU(`=caovgxYqm$N@hkT}C54uK6}a5brCW@0V4hu*_K(JIwKjY(lV(CQ zbPt#XD$L65Xt;g(!gWhjhh1!Wa@E>zI_f(u5`-_KG)HYXyVE7kG>Wccvu&qF>Ey3v zSZI#lukdc^^nPfBKGCqfFJ|l=@UGz?L!)LUdM~!~n@!aCYhbX=UFeDt)N z%gWeBqcSF4)LZ`Pz>a&>-_zEuiHbI#qC9_~lKJd_c3+M8ag?xW2SJ}!&; z?Nhm;(ybwM*wFm+snHNL;iWFygJfFCJl>0U@8q_lY7ad>g#B>0%Nws#dp;tD9@NzH z;0}u%YxgdvWEAy9J&Zddoot0<=nTaTp5;G8qs#`G&z!K@LbY*^+;)0FRQN()+q!)t z;|9X%G_(JRKChMUeT|7gE6-2LIi9rWthK_B*P73I~@-u(i$T&R`Tu%R98`gMo0 z4QvPHwxT0?A`XR*j<*cQ^Are%mZ7YVzJ4=x{`E#-N?wlr5ewQeqo^jlr;@Mwi{|w6 z8Ee#d|E%7Kb>8-C<8+f7*XE&SOhU4(!FkoYIwfvfc+jD5T84r*vvC$s9rYg`m@`Ek z4sC0DZ?naYrpQ#JPy0Oj&5FNvQ^;yH_m5B8-%C=U(>q0jhmTVoyfcCy8C29qmDhW3 zdcE3EV(d-qs8hrV6tkwz#?r7Zh zl+3#Cr8}pOqI6OMjDFIUH*mFE{kkgSD3!X>&cW|#mfM%NJu(teMGYFXYMfU882s_R zK`Zm>0d(o_#TV1xr>5JNkEh8qu*USPL^I7w;u#!HP=slkr(ZZ8j;_BTaP8?trM@lQ zMU8~jFHo<8sgFeVq$h+7U81mkAc@vW?9qDP9=?)e+tWI^vfXI#I7hSC5grYm)$6C# zM4C~fWls>>Df3!p{K$`*c473T19E@#i|dWH19G>&(0Zb+0Xh@I<5d?LbN1x=n?67v z7&J~LFT3eJQ=hl9o|zXdvkzBR$rQa?721K_I=&4R89x=Rdth|zL+26v@~vAa=ZU#w z+E%Mzt9sR%9f9m<#$N5og!(I+M)uT}uCuU1qqih|Uiod)jyLJSLYv$Q(d1L7Z3SK) zk1E)I)#~~z16np4SuoIY>lJtJaj9)u7U=fb{;sW5!`#m8x0JqZ%t2>n6LNpJCi_*! zUKgq?rb4S`eJ#K53o?1{et!6!qanKMPSYi|Ov^sE$ipsTK^Z7sp4D&x{X%!ne6s4q zVFaypI}}D=Q}pD}MJ4a5JRNj$X4v*vDPQzXQ|-829m#0G%%^XWB2SU?5U#~*GHgsFYbLmU7|29r9gYWqP-V=9>=`~@e@43 zbB8kLYmN;1_J-W+3&#j<-qG%S3>R1nL^-oOtLH*rO=T3GjdgW`8hR3bbzjDs3 z7+H2n6kU3Jn!#9UrI12#lj8b@rY|K8=xDon2fJXQ;9Pu_)9R=EsASW{rc;;fha#@+ z3ZcT?(2^?CnOUasb6F>;I2!S4^wXrdlQZp<=mWu6hONt&pu84Z-UWd1eJ z0o5OO#4q_OjIj70b1g|PMP--qE|=67OrfJv?>lyu8NGSt{CodSca=nghV;*|R_MhV zLvcpayD|Pe>YDN%SJ2C08`Tacm0J!K;Rk!QX;GFgVSTSa@uB`RdPD6=CMXA!XQ$q? z!v5zq9YRfkS?J>{D3z@4lMnT$2d$iFaCD9>rr}2Fz00eS{*mCO_2}J`+tppm{hw{P zx6GBjIt?vaTS>iU@Mx@^e~+f!=yx=csv}mPxoeYJy{CBjsx_#hA>Gh2_G6NDYPDlI zItgGt=8p!20pJh#2mcbB1dn+iLg;*?1wo7tGXyC}Spy5S6T%2KVlXkze2U+Z3BE8g z;Xff0VtV)&CM1BE3fiHQ7z;}1BDe{$iEd~mPf18ah(<_COhWL4(Fn<*AonN6Ciua; zh!#Q;un&r0gb)vcp+XE^hjSDfph#}LiCX+R1Q9tFyqga!geI=m1N*WltJMyHv{f0O zpTqTuM{l`0!F7`{cV4lE}yu>Hm(U?jPXi>K+*A7XbXg9_TVAqzs@IpcJ5eUP3#>$PzcnpCl*#CY%)S zAITs8VfufR@4xC#dxHtt0Z?Ome^7w4yN|!O zi>td4DXj{r{cR8S^mcO(&^NPkR(J9C_H%U!@(VDsQgYS@hIA=)_fS{wV3@z6lBchK zaFDZyr*{xY^@j+GFz4Ul2>kU90Db`Jc?ssx(?yRUlytXf57_t2mK7uO)?K-Zn_J}wJY_&bX7c9%eReZ(qAarJf=@MK*A zJY9T)oLyXDUqH%HQPIaUFwoO?hx0D?Fny%Wg&B$30qS_~0$8>)BRa0X%b@1t5(F+8 zpz0au92yw-`@$rDb$D54PtZ_|%&ZigNzoQs0V(2q`Q~qpVwNFk-r|mhy3**Rl(94{JRUS z`qPD0{oz7?eFmhP*#6g`e^3e#^RID9+4%f5=pSSQV*WKQDI4#<2K|F z+GOXm|Ch>4%1lwwJrs5s|8a)@q#ycoE&OZ6BvsLpJQkD4@0(Hb-x&bA8sgpjuIKsg zX~~mTTpataEkjW;zO|cdt{xy@uPwQ_oS{xZ<26RayR>U13 z0^lS-F~CEBHvm5YIE-156#yFm0ss;KasX}vGy}W^_y&+;&Wb!X`+FGA$MOCb;x5fE z6Qp6Y{3~LB-+u!zZQ-ohEq^dQPa@U(K9eIF|)9;v2$=P*;SWFf=kYG2LipZeh8}%6hYnt)2ZAm+h`@?jAdK zdV1~h_VM-e-yIMb6dbZAG%Oqzo>&jEaJwsVb~h`M3(!i25;8lvKgnLXB)o3k{d8W6 z1c3MKAS==jFbeP!fbI}0A^@No&x%9>oB}8TXaX1lz>csY>j3-##sCf_up*@Z4*`Y% z%t4&YD@3T|u_7m~62So^MDF!}jwc|^$qH74yOI_80wlzGGCRrdKb4Q>^Cr&+`hEPx zirCKl8Q(zI7XT^{jsnO6NC3bzra&edupv1Bl>n^(?*MQ^Hbelx6(ALW(TEMX3eW>E z1%QA!?8a<}C6GG-A^}bS6aX{;yatd2>1+U60DM52^8gh9hd{dLKo)}&u8quyxH&IL z?xm6x7s_|@arMmEkYM2cJCO2vKYu+a^DKzhtpM`QydB1?1NMgiPssd8>6iUY8Ad2P@E%|SU=rXv04bk8$zB^!#{i_T&t!IzA8xx44(kZ|vs$tt zA|UT%fIcIW^QZm{s<6-2d6rd49z3^RF71n~aGjz~)V$ZQ|^cCf6Yy=w< z9;f(*eaFD_Aj~K-NNQ@CnI~msoui_rRb8uP^W|9!1qZjPnvHE_USUzS$}6_G_+weW z>lRk6nYo+0{>*^_lMa3ngR;|`BG&Z%c+H3CjVm-bB)G8|#GlK?&fVi`;4~3U_R3l|fG0saBGlSbxbJBCsaxic(cu~<&@lx4S z%TjKjlcQjuKygKSISO7XdOYeH2oljYrC5On(X7Fl@io+|X=Ev5=2*FD}jJ8aP!kGqcx?7IzpA7!@xzMm`xGgEkVsdNf~vCHQI+I zR7!>lgeFo%-NE^AW-wj-+c+)XONoObdf=%1%BUCtT#=cdLX1|BQkGJWf=MPSk4lU} zj6#Y=nI4Twj#i^xMn@BMN*#Blp;cH$DT4D+qB^T7Oep0k=%{I_b%Z4;7-&^-4N6{W z3I=KmBehkGtEgAf&_zpZGLxc_vEk?7qBWxsWM0q2Lrq6zNFzxbOs~C8hDwu?j>?h> zrDVY=qY}0Y8q&~3U3OZoPfth1$gWOJrz%Im5_NBln-zm0E!_rvK0_KS0rhC=4Rivy zfsq={L<7FgP}4=L@4WQ8MDR)GWuhMNQEK()e<1h|1t# zu%+dRiiR5sEQee^k8kFckBkYS9>icz4Vnezduw!_Y}~f|0FYkKm5S3RC52Cn}gp9mdf$2xjFdd zVX52}HaiEuV=R^Ts>kPG375(!&mD8{oP?zk5u*D6&pG*vRCTWcd!3pm+|$>rxbB4b8c&4mD}#=thOtM>}b#Qiggxfh8(5h8S74b@$(eT_pH>G z!Kd%d+$=<^i)viI3jWYeY)Z;JS(pE5GI1HD0E-u$?&Ipbp0jbu_Jdwp4v9ZaZVUJGTd8`aY{18^-8+BYcE?uy_i*r+;l}0@^FH@?BGdrW%9&$KD*_{?;nJvqgbCybKZkpxAbxGu8OJn%z%8JX@9_#Fi z+6LoWXGh;VibVwWUEQ6NXtL_kd+nlpW=_hSRMC-+0ln?VHHCt`)^gDm4{u-Aee2W6Pj>~Z6yTVDx&hIoSXF2J@44e4x={X{3Sdwz$73OpnU z7}~W&N!t4GdXkuuc={Lky<04u5TTdp9U-NqaUv7+NDr(rgp#z0A<<}t7q;*FexDD) z^G51cR$V9o^2NJrD?-7GwKiqjfE_p>MzQlvaFq@CU4{AW2oK*&9M};`gM3V{Nut~$ zgJgev+{{^yk>8Tr{G#0WYB5e5GeUMy0L z5WES0;I~0|`+x08h;g7leCOw@is(82ck}pW5H3HtpcgX#!`{{K2&;leXoU2^oc^8s=%~vV(P#jWbOQ*eT=T%a}Iv>lS<6>za+jxm5`SvBhOB&3= zUnP?sH6Nr+`xdIM9X&*UTlG10Qg*-D4vHraKR-OVJX&98pWm;n`Tl_Ft*I&-k6K*= z->U~U{jpP9S-mw*(6eWhaZ*3r+kEn#$V zuks3f<;O6nn^6Em35Oa~)WFaH{g#%N4j6iRdIkmtMqrqjn3$QFS%6_>Wn}|{4`3F( zH=ynazcmS^8~g*z2zWQ4VUZ+C(iSNlUE=A2q(_I4I)7 zet72t%(x+O>4dX<*#cx#eNIFoya=ghAnjvv%e{W{6;QqApPvw*Zz1>xSs^P-Mm~R} zHP3%UYo36Q$f#=xt%11Mp245;>0tY_|DWRir+zP% z{rlJ2OIH5Npl%+r{xvUccA_5)Us_gPP=^OW8WT{4{|Ur%{`=Qa0OcnyV;H!1ayPVp z`X~3&1g?FSgB@`O^1p#ZP@bjv;Nbd8{YdUUaP3A=#th!SLiZ2;J%8}~Q`&##N1iXa z{hy`ze=@9&=dbrpN#;id+M_)Hc^SgU+-bl*3+mAZq=6u}ll=atWi;rImR|EdC@0D9 z2yB-C7eKuK1jgXLN$JV$B)|V@`2^hK(rYr3`Tgmh$^D>R3G_`zKpF^gJIU{VTHXL< zT6)b%&|i@JTtGR63Ck_cBZ#oW*1h}JXlXcVuhkI|jgVa-BQLSfNnA==PEwN)XE}%j ze*11OJA&?GM>qjw0WP0rM-u)CSTfj=|F>i9AIfO<2S4)ra9$GbKkts7m#OpeB9uAo z$cjt-~KE zJ-z=?Re=?n${PY)c96c8NXc;^5HDpHAO-BZ00>fp1F2ojfm~U$0K>H$$WGt~<(Lf- zyqh_Y^#GEF?$YV2FP+B4y4PQ0|@}Kfy} zh~9a-_InP*?;uRIZfy=uJFUG>y!;AG6cYE%ZbPXYy@xz z_%IKDJKzVf3An9+g#IuN1S!r^3B#i77o!-sA>ji8X^S1Z3)f;cToT*atq)& zzyyF=ASbc`pc;4DBXKm$MzzyttQFef4bz!k!YP=s+Ji~y$k}=O9FP!U00_9YJOKEQD1kp}^LBM04FN#XIe3!So_T+G3Km2h@DBqr0szK| z1QMcsc5W^TNQeg@&uAbaT7f+V$b$gjKLSr%gJ=f!I3N!LzOGb5D>dS z=tUIl4I#Evm8J+v2`I&a1eFdFK(Qc25GjG|f8Dz?Ftf?N>U-Y*eCPkalgrC{xVd?D zv*np*+B4!8EXyo!;+HHxLwpvc9hp5$ly;scid$HIfhZ1T`9-4mEz2(v#jPyAOne3T zPkEL9iEptiHw6@>US=~Bx3N5*C{AK|0a29qET8xe@}E+`|HR!a7ZQt5o@X&pl>EEI z_mKY-jtO{_{6dx&q14MPZA)+$`K82VYW^dlxQG05;tJ$HMP`9pr7o`_igK@p&xj)b zDeL*4DD7_`ioA{PjYLuMn}{O+DWCH{QEI**iu|X1$^S&D|B5I|&1T|WHNTH2E++py z@i_9IA_u2M`L`#DBL69;_@5~4{6Rd8{HOfM|HK94&k)5;ET1LX_}&l{J5d}@z9F%V zn(ssuTejd{(8MlCiMuON9L#bzqS%a@45AA&Sk5G7q11OLigIdq52DDRN9idMd$Zh& z*bn9TWD`Xj?OaC`lUTlMiev2 zk0OdvFXt187pWOT6kC#ikSMlbc^pyviJCm3*p&PvqS&1_Clf`t>fDe(g}{~=LykzYpqNX;)NigNzP3ZfXq0Qh5~*pB=sL{Zvb zNfgg<9+cdWb&Z-YBZ@K9uO*6d|EP6DQC_!CiR)3?*+3NKc+p1UCN=*#Q5?*=>T%JHglqPULyR-)L5{5GPvmHc+1_yx;5h~in6cM^}IJkJwE zv4s5ZL~#+zCy8PRHK&N;Z1R5)#kW~LO%(H4K0`c*q3};VplcajVSgYpFwn~`AlM#NWMGq zDmCASD0U%#HL;(X&n6B*c^z_y!%&|8?L@IL^>+{_s`**OHR^I1QIz+3E%9rl8TS^V z_&dwx#8yGxHd_;KLizj*B~HU~YVxgoelUm6C&$V?%JOEGMOiK7oYvf?Vd;!pJRCvQJlx}WTH5qHFw#QJL9PE1zw z&4{hkypz~o&37PXsCgH$r<%W#*jLS8M-=68Zy<`Y+@C1Q@&KYJ%L9p`JhxkjqAU+4 zin2U}D9UmU@h+5qJAyb$&F2zFV;}NzGvf#GCYB!}K8$y;{0Q+;%wzd6;^Uah@)N{y zIG*J^;&hbuXAtM0JnmfLOKSdQ;sQ0FPb^gPMZ|?_ei3o8nqNv3&9u3kxB{ikkBOh) zGL}~o#WO6gCW_M!x!s=HA zk3>=Wx}S-^pw$0LJc4ss{*5S}WBC+Ol;?0(a+pW{Jh2idv3!9j%JXUJVBLt5`AoJT zijMtmcT1x9D$D6ak*|hwEm8b|f15)bg?zP@T;c>gL4GoE3bN>4)|kEp6R0UC{(^_e zD=B<#(MjG(9D>rf^c#!2W#0OBG!-K?!P}&(!oPcYo$s-;@sX0PChEj8q zcuLJHsf;x+pU*}zQIzFWqA1I*0=@?*?OaV1yFTQ0Uqif4&F2wcQ5;4yERKqA2gni^O6zUqTdDQeR3G z7qk2>QT&kQ_lV*emft6eQoo2O9%FeiQQXe*5~6s7<)uV1pLOsD#7|M4&u2tYJ{#+a z;wtJnPfHOu@jGrLeu46Pl@qt3{M+rsUFz~_;-4rrHmwOVCC8WyF(M#=p&vGbwA(E_vd9l;`id z-=qvd$>$I!py&7c#iTf}A^C>H?)W|RqvXZYe#l*o**Fky!y$O-C)T7SV0~16jlZj$;Ovfv*J$6*>Ocb+JdlSX0RIetAeX$>2hu7ncsso7PK-F7_;$YPw zL~$q%!#h;(B96p+aSV>dhw)K-0>`W75oh9)_!_=}Z{a&wjHUPqp1|Kx`)M6?Q_#Sm zSwT+)%?XOGjIC5GCZ#QQ#0*ptuSv{K9G{q9aHc>RVNyDby!>yajdK2c6i?u3EK59< zcp=d(w{G?Pwa#z7rnToESa}cddVCNks4C6e z&6I@1ga!$=1Xt`0o>N@C4?G13s1D;1e8(t zMYoK+twh$Cbtl9uiaQ;*D1q-R<_DORa&+NBX# zcL&>HZ&XU!mAFd!mSmRFBf37o&4a*OV@bGAUmooy}S8`B!@#=*4bP zvJKzkg2-qL+W56!~zXiAh#s-cJ91l}rIt1}vhC?FQzyXmW81|B#0AC$!M91@CLMrZl734%mUJrVbkdom^Pt!* z_DIkBpKBj$A8*gIKW)#qm)R9Z6GyV6xg*8V+R?_**U`^0#4*$Hq+_n5X=7L8K8PG@^(7iWg^HRn8MK2$m{IOn!r z(7LSkq1N{FA?Xv+x1_t_<~Hs&WqsX!l|o11jKW!k&Z3S*X~k*9Ti}+`TS`Zk-c#D` zV#Y;lReY7R>iVi1tAD4+YuI0a{; z+)m?t{1q#{F)61|`PQV|ggN*KD&NtE;!QXY=i>r=Q}rEU0T$!C_&%<{PgGYCKgIPJ zw2j{j!!QEtVhko>1MG;Mu^YOu2li6!L(InO@ty4^r3k;=VN$+EH;(?zqSCe-h_N8?>mO7h7sFhd+dN+R5OU;+*XX2@X}AM zDYd58Y88~1G$W~H{g(9~fr?ajs;gi@L0VO-DjEC9c<<7WIosUde7kwLd4jp3$X%qw zo0MQQtA-P!u^z@?9Ja;w*a16ZFU-b)sKk-OOFyhbMq(~Lfq6Is=i)0k4;SF2AE!x4 zyMoUl%6dp)y^)L+QM~@B)VZ=wpE`Z(WY@W=&do5W&fq#Zb!OC=Rp+TXbL%Xsv!u?4 zb(YszU8fAz)mdL>Q=RQ~cGj^**NLtd9Tz<)dT{g;(RtCYMCYZxn3|tjkXn@be(Ivs zWvRh{!~so$rXN`p(yrIDqzOJhpyrAnvFSq1U?w}aNuKs;{qqZ6qp7x0%rxzhNlDH2z)E>K;Vf$cVN@{E$R=h zKeYbv`XlSF0mW9&7H6w(v)g*wuC(>FW!nbW2Er{c7>3x!+45{3*p}Hovu&__Yuj$y zW7}stZaZoF!}h1G%GT7;)NyC&oowA){&CT_KaKgS$~TO+Q2Ut}HaTo+*i&J1!k!6x zF03f5By3UGlCY&=ABL?6TN$=KY-8BxVPA%A4l9R>utQ;o!+sAt7v>Jr{(Vk;`YpT* zN8m_&8t39O_#D2B`B;pmu)we&E{lo^vxY^7H4AGI<_t>@YZKNstX)`#u#RC}pj%j0 zSnsea!>$hN8+IKG47)Y#HW(iEP*`5tD`~H#ok~+$ZEF?e4048m!`a-~!P(iVT(PFF za;+C-8Tas|NQM1h8W$KATN}p&pQnjDj?%HWi<^pQ{bZaHOL3&R5`1G9OCyJGK-`cTg$A%qW?x@_6 z?_TX*=icc4!o9`)jeCcCw|np9F=YhbS9D@Ww8vhd8@uvxr_VZxf8c4XL^o%!Rd%TCQrQh$m6?^@D|=M-s_a*p>3q@ox^sbS(krG#Y#%M=bBx!c z=b)afXVf1TZTH6kI7A*Na8%&bTGMJ>9o#=S7p^nkXkL`?bwYX4H%Z?mRU{ou`Z?(+ z`~hc^&Lx%Ach?WMN7&2lC+sbpsm@oNuR4{gs49C^s%M++BA+|4g0Y3zneU3IY%wXR z=)_)l*H#nfshE_Mhv z7yf;CMfjoc!{Nune-A$yuDwPAzT)mmVXG{tJUJ7{f6d>tQU$V_WQioiGEh z#B977e;DOe4%}~>&Fc}nFSa7~m)H}r=VB}3-SJ8tlTsU_u^TSJU-1N2HDv+aKC5Yz_-EM;K$&@!N-D+2cHkVz@7qkur(}ab>jCo#^Y7*h@UQwSt#29=Hr8)Hx+-jy+J7FP?>xt}C*hVTi`5cs zX=Z6*aaz(XZ7gjq9W7nqR?A?^5X(@@XqXIBEwe07TjpAxwY*??#qz3U9?Z8au)Jk? z+frmHvAl0tVJU|oE;5Y_w}#gVZx-GnykmG*xH)`K_+ZEh9~zzqQ)~F_uL@tKe%=pw zKI^q2m=G<(8c`>rQ$$y|7H*8VDPl;((1^z(CPYk)croJDh{X|0BR+{(1!WN%B21Ca z$n?lIk*>&IkrN{)MNWZfk+ULaN4^#L4irW%id+)8G;$S`MSd2!0rp1jk7N@$$`#c+ z>Z+)Fqh>_Sj#8{GtQnAP?Qb1q9ccUp&A@3-b!AGA)iJ`L5!5C66CPG0n!Xl)zT zRQKMPQ85!@CdRxPlOOYI%ZbwzDfO(1|^M4lCh~aCc3=M`lolBRmKSo{_Z$orEN8=^Nbh1 zvX!f2h6>wn>X_kzZM1!~eFDt2&$9{u5w%plO0nWO3Du@M^cWZoMb$&AHz9Q?@Ap7e`hS&Dy>yo5ZlhJoo!i@cX4X# zFzIhE(BH~FAzA0jej(|5z3ckfZ;wHt;;$FqFZl6-T#IrqKQT#*k;o{@P=ZmX~m6B%c2gxewQF3)jPRTYA54Or?l`U#q?=SZ3Kh0M6pPsl_d1>FN!`;c9=^o@BUgNlg?M8*|XKaeiuoHH{ zu9$&+F&hVC9-f!Tx_lnyqwL307<=20X^0dTX5m$sjSpfTzK;3$DQ?0oDBD}Fqqc8d z+pn%|*J%64w0-Q_{&j8Ny0%|k+o!JWPuKRPYx~iaVD@2!@Z7LJ=HOkJhf{GnF2W`F zG5(Y7Oc_7r;6s>)^RWW;<2c34IvnlT5%cgPT#ai`VcjiSv8MHX4*4fA59eVSZp6>= z2h{fSYU80C#zHbq%EM<+PH?;uwPOj|aRh}iXd}#vWPFZ`a4{-T>_@?5RVT40=Hf(@ zeSM!{1xX zLop8*;%B%XH>&2Io{*O zzuF(siv125s+mMF9}BSr%khBfA)=@xc(oAIv6E^xQM?sr;_IriU;8X7NtgHUr?Ko( z9YdUh3fq9e7>Z`Jpc8wk*0eo2lfRQ~NU@>;-z!{X^Qzdr`-`;wL$bf;1S(GNev(Va zrA9Dj9?A2;+b|bbV>!z44&@%+2XtX~yd59LaX1HE_j`|{DWiCwFJm}ir*Rk)%JmG7iimQ+V(*%xymK9v+`f>C;FefU$tyI+hDjG_u_fH zfOU7U&kV1?c6cpj6$l7j8AD_*NQ_aNJiScXUN zul7qRKhriUzj%*}$}v#!e~&YIk2C(e`%P-z9=N<6uqkqLWO?K+@Ad#2!TRk_O=`Ace_)H&%Y&)D7bZy5XA<6Fi%<2%Q@phtW^ zZHg~%lYYh_+T+QY4FAq}to&{F+n4&p%Z({=?UroE@AquKyA|duG6g~qgn$_=5CKt8 z8=|2e#6mnILVd7-0~$dSNQUOn5>lZRw1zg&7TTLSm^wih=msvxf*#Ndt~6a`x*GaI zKe!HVfc`K52Er{c7>2-5xE=0$ zw9m934!|M!5q^SS;0PRrU&=OLi6|{yn0UZOnKt_NoAPaiKRd6-*g?<6g1-uZjFkp1x zX!dMBz@DCm10QGK_FQ;3@aw?xz^#GX0(S)Nf<3Sg_QL@<1V6%0@CzJ)qi{U%RN!ei z3zdNv0-d#n)EWx6!<}#!+zt1@eJ~0}!vpXjJPeP*<1h{;z(jZwo`N~>3_J%fz)SE7 zyauns0(b-7f_I=0ilG$VgN3jdmcobd5v+huU=^%^b?_N%fKBiPd<9?E+EQyPd<)xP z2ke49un+db0XPId!cXuE9D$>79DavWa2n3QIjDq-;0F2f27>&AfEg?h0Z~vJqM;td zLOdiweXxN88bK3ChUU-`QlS-i_Rn!_<|4;tHb7%>v&eb0Z@3Ds zhQ81bu7ew(KMa6@a0?8EAutqfhdbdexEt<)`(PA|h6mt5co-gq$6*{yfQc{}rowcX z2~WaPFbAH2=ir3~uQyl#Z@^pd4irK$l)`(k5EjEy_z*sV74QkHf;F%fK85wL5k7}6 zVKZ!jt?(^ugB`F7_P{>a4+r27{0Kk6FK`5o!g2T=PQht71LvR;E`qy3h|S7zDZ4G% z=Crl9xi~f@$E9*?ciD11$D^j(=GtDe<=YBvi#*4sKGN-T+Nkbx+F`4({iN=5I%5lC z4^yNgioHzH>`!XtxB@zQ_A7OD^iuaLO<}*1lVgBG;m(xdDI;Jc+zX@Oft0Z+52ZYw zG7%=jRG1Dk;YoN3=D;)X9J~N8!7K0@ybcTC4R{OQfkG&TQg{y*!eUqoAHqkl0zQFN zum;w`r?4J2!sqZMY=$kc6~2XSumg6%9@q!_;Q$sfCK)3}4!w?t>x5J%q7u*f^zX|058ES z@ESBKY*g3;lA$@Ygj8r%*t)O{w1xK25jsOx$bd}f4n3haTm@G{U+4$d!41$K2EahL z1qQ=F2IlRTRes#Y|Ayo&Uh1!!dWQi)#$hT4)ACFc3&dfar*7P z{`^_L-8Y9n>$m$p=g+e3r)~e48G~EYakzFq>!N__$Ef67vd3T`XGY3-V{(j1JBFk` ze)KuVi$-&Vs29gEo=6#&BKx|% z`>})6{n(v34m+Ry*HzAX`StS8vadS4Ae{Zv6AI?Bk6QLm=fVs)S@=ic*}`*$a^BZr z$SuibukpT;Q|vRowR8wuC+r_q&+*cKUi@vJX!|kz?Vs?sA41!X*0z7;IG}8YePa5= zRA&0r^nAec0p8=!a_r7~Zm<6M-CrHMlVf((kJD|ccARd^{~o6^9Haiv9;fU1w~o{O zyT|C1+W+-qa{uI5ocB0fR$PyNcr5N;ABTHbcfP3i*jrmi2ZtQf`a8$7T#?E zoQomHvD|)-W7W-%$(LhUXE>H+xpXY+evW0yajZFoa|+Ku<-c*PejvM{&@uL6i@gm>jL<+}$^v8$%9S4&0!Tu}9d%_s^irVpy|MGE< zV~qFl|Mu&t{@vpo+L(O3X}xI^d zCYmOhroc4Qbkj`JlcuLl&zPPwJ#Tu^^s;H5>2=crQ@-gOftHfjCHjBxnG3Xb6p=DKvu?kOFDogmmZ>&^e%M zK(~NQ=pN83;7aHN*TA)q8<-n71|9-A-`sfq<($B0Jm;I225y0G0>2B~3AaO?Vp$pa@FfU3ecB!4miYmcerP7*@h+D66%$)~B!@ zHp1udC2WRr_y)d%?XVMe!(R9vD&Qde0Egjc_!WMGV{iga!XNM_oQ3mn0jfX=QsmH0 zP%Yr{wV+Vo6o@xD2;JD1<>cM1mFSKwXG|IN)N+21y`4c4!EVp(!+j7LWpI-~>*8Z*T>) zgAULMxLI|!Wb9} z55Xhw7(4;vArB_O6qp7xU>3}Vr(rHU3(v!R$cH!KZ76^uD1mq3eOLrb-~(6&%i&{K z39F$D*1>160XD%G@D+RwX&+se!2FKt8oP)MNk6o!uzlYmcR$F43@*kuo6~7 z8LWfPU;}J|FW@Wq8p`1t_zt$iPS_25;d`ingKz|n!bxy(mT4CBfL?GV^nq*OTF8d$ z;YPR#ZiYc{E8GS-FbwX1;V=S5!o83OlVA!=gBdUjX2a7k7oLUZ;YD~EUWJC7>Dm~Y zLNjOqazG;u(%}kd2OXdjbb)T*f-L9(z2Hjd1J}T{kPX+vjc^m(41?fSxD9e(7~BEF zVFZkXd*Oa~6ds3hh2snJU=mD$Y49XG1#{t9coANPS79E^hkSSw-i87wf|A1b3l|lB zP`C`1!^f}^Rzn%AgU<>#7Jd%pupM^7X^`_fw6T{bvOO{tI+*SB`y@BhyAt@lzl74vZg zeuqaflzs6I?1KF<7iXee2P@}m9Yi@#D~bK>a(>oLDCcF(ML8eq3spH^O6HW%_OIB@ z-t${Ch@xCiG`ZHyTF(Tn3R(kegVqIo7POwLm^KDknIq!r;31ytjJ7j3#QxwPf`1DB zIr!J$qrtU9YIA8);M@4cvLIe=}JEEnT3SrH7@rnO5!iR<59zHRAGS?uj3|}4onc+I5gIt58aO2I+ z5nUp>Mf8a16>()mpNPH@17KLh?GY0rCP#b$TOz)T*bX}*_D7tII2F-4vUTJYk)0xY zM_wDb7(R$x87b#k#YM$OwT1T35xPZXM2(J`5;ZMqdel=uBp(>m=(_);aL1^>ypu=poT{V(Z1GbN$-&%)c-u_JPM@Nt{AlQp<0Jz6RU z%)QsWaSgHV$M^cThdAf`F=Vr@+I3!0K`cFJDl50lM$+^nyu-_=J*Ab`V9DEkF>jSmxVP9X#^$O^FJ?5QTz1L&j zM;wFxuFK5&hW8bf-8?RK#(VKW)hR^rHC%vi;6nV{*L8C~Z##USb9lwy@Hm!puI_g1 z!MdjpYS)L#bp&z^ft)9P7L`lavuXWjKdxuHJz_Z5vbk7)+y=U@tLM53`Mw>&KQWj0 zNX}dLecvwq8{Pxo^V$8qpYmR2<3QAY0?mQu&d|;5GGArB#(XX7uSu-8R-4Pzxpcnr ztg`}HS9J?>u|}HA`lyVnvgNw$ufhUY4|M|IdV*St-Iz!jQ%*5`jD<*l?6K^CIBo-!?z)$dN;%|w^ z;G}2W(K6|Zq_&lz0&_mAFK7VOIZF0 zmtzW_Nm1)xrBBuR)el)-gVMiBzbZ<I5?rDB32`;9Rh9mBF-m{?39eR^{x%&uV;AhE`Yus?9~a^>ReLPsMQo}n z{c;+1Q1$JfyVCE9`u66pbwMl4mPU&Sw3TCxh`xaZozNRjd{2Lwc`uYzia)vTz_)u$1~oZ{@*a(4q&`3 zBknHdO!JlIzHoWmJ<&7nUdy<9Gh7~T2YANYnLhJ7Tpn*v@{G6FddAzEm~~=v*yZuI z$ur*0uw+;=8FTmbjJI#PG~O<=lrhrY%s5*{+S*v#Rl`_&qGznVCVUNJ?G52yam~MX zoNe-qvojfI_XY3xdZK51{f#=l{=4Jq(NW{0@@g1Yr&^uXbZdKSM{6f*SH{>kt7B~M z*m{z6s&yJ8?57xAYvb#>jIZ11#@8RleiXYZb_0CwcdXqoJ~_Twd^&XG8id${I7Z!x z2{*ybp0RgM!Z5fq;ogJ?E{(%iC9Ls`#RD0OUy;}@u~T9f$O7-Ud}898iSHy9B^D=^ zddBC66Ms(pCGjX6Py9VGC80j4BxAckJGzsHtb{ z{#mtg`*n=lCoyhcTD+`yMe)kwRqFUXt)z8HdP&<$y*}bg=#yZ@3>yZ_J>d#-*cJBrSZLsWzlzDoDQr%GB68!pp519rlq|OIXjyIDaLs!ZoNL@3&#S(h0k&W)QXUzib1%r@DyvA<8y^wq0Oj9n=t;U^2>l zmaSM1wa1Pq+Y2&(Qx}wN1W~pP#P3ksKG?&uY$J&J?Sl@idurM~xQYBg)V32|Wcg)$ z1)Z#ewC#niEXy{7e!HQBKQF>1xD-Fcf3h8+&CBxw>orlfA++s?KmNsbgzxr)zwL&* zw*KpRT>htRFDQH-dSEsVM1_N@wIDbyKF&M0LrGi?`FrK>_|dK(&0$;XZoD5yq4tx< z_v5dwLoTL9u`aMyFpoe^$%GQ^-%6DGVJ%UXGBe@|Wu>xO$qBwY_(brz;Ifdu7cG{`) zF?Di2+xi9OI&7YPWqQB#>zMN}FMVeE;`BA?_JZaGhYOAuIE&gAl^5+QQi{!qjvL#_RA#T#qt$rp$9X3QyoUlzBHZupD>c0sI5C`FgVHBL`wR z?n3QzT*l}1b9Bl53|{~ zFc5FSp_s?G_C*=ne&e0*G3Q(FJdfHuimn691A!;lW)N&cS+)(dANhXO^qtawpAK7& zd%jCD|I_e@Q9j?H=9j)h^4+=oz1ijWdn2FszxteO&rkcjYM+gV7W zpU=TS)*-&11Ap(kzxP#pKdZez>ijp-uY4cBKVIQ8dJ7K5p;*Q|P8UPmA=>XReNv96 zZ<*oH_a)L_NR9LZW6|IDUHjZ>?dkhc={KdXJV9S6{iO7fx%81O=oh6wlsU3?^E{3H zoXq`Ny`S^fw@II-J+?%x|9X(;{IB}2|I_``-|3g`U%wr?_?ep@deXh_gZ<_upAW$-C%fK$vdXbuiz z*5wG#yvy~OcX?#+eZk*>5)u(|Cgfa*Gqjs$AF9m3oC_D>A?9g30S|{i96km9i1;I- zUu3_?L6Nsa){3eVRTnx%b&Z+|49P>Os#XQfUjVz6tH)_#nMDl~l>ytMmA5K1=Jh}PH z&3886)BHm7i_OoaTu7;6Zra?`+*BnkEzOzMFYS8fc{Vu%ncum#v#!&|9M4W?SMbj7 zTK6IiF4LKzEQk%pLBIawoVG-8Rqs&t2Uaa0?8EAeRe$2 z#J)mYz-Lt~SJmblmw6vW8{-`@LsgqkP(IV*36%Ze+C1{FSU9eY?=hAU`}0{Jh||>1 zeQ){-nYaBDT#2i3ExH)HbjPvyAg;$vxLI`%QQU|7u>)h}t|((>@oLr?{csR!>k(O( zY*1ax_Tdi9WbD%&wXypOmc=ggTivAZqTdpGFgB3+c@L^)unpD=U&VPiAK%1G`nH}Z z#{)*=8TI&ro3WCp%~Sk)EBY1GwhLqqeX%`brOwzx^(DqkvQO~fD6cZduQ+`-<6FFR z-v#P>qVKrN_q^jA-a{GJmhoIY^YEx20=ds_hTUbq5!zH;QPsDqUzL1bw9iN?H!Yc^ ze)r{j7tEHA)n3mYn--gv0G-l0r@3G-Bsr5jpLzMbx2{UBx*6p28R3p_YoCMWN^^Rj z!JdAnb5v)(izI_(HeJvW@oqTulJ#Zh4g3<5*JO~@$ zC-?=9z)?63Q<_g{J`HBTESL>X!(4b4o`)CVW!MFK;370iYn0XmlA$@=1UJJVxE0J! zvyZPwV@uw0uy>T6QKd$`)(JzU5u-Wt7-=wpc8a~Zs39}=mA&4Ah;E71GC%g z?h5if&4JrN`eG&wvDb%A5SSJyr}m|VK?KwX2e{}*JntFbPuWhH zq{XGhrzJvzG+SB=&$^*wT9>qpv`pykSx?;PSx?Bip%VTR>ji6740iyKdjL4AdRFzS zx~l4$s%+2w00vYIsT%6DjtF5bA?t~7ccgAz(Z$`xJrwTHuOt3nUO)WrdVzHv+tUB> z^@7@gH)F=eTR_HoofuVhf!7#0G1;h`!ppd)Cu5#*jCr17%oExu44T7;XcoLq1Iq(cT2QR=&uoHH}1!$PoFs(5(g=UZi z{b2wMgj*n#Q592xJHvtLWStx;VT@JR83S>U0QDh*dtt;@#a6Y2_RtYJLs!UvOy~~1 zVIbTBgMnKNaj%Tdo^jF;MoGg!Ucx+!cYZ`|JfrQmadLcE#%VGS>@k@KR{d$LG*((d z7p0qW8|1(fFdp7i3KVu`DeuDj%3|dMSPt6#P1Xgh5BSD&kCP(-N8vd4IeC!T;-_$r zlRsfntx2`yzCFRgq1-{q5*!&E#r=}L5B>!%23H0D8S-aH72C=)LT7}YG@mk`H&>c} zXFm2^ORnXV<-BD=_=NC&5&a?_1-T!JDJn238tO&GMkPeqqOO2;&;eFQt&iFm^;Oi@ zQCp(qzA5wBA32wK|L=>rFJ@2do>-Z?>U-vk9-25ZactrfiK}d@Y-?<5ZJTXdm~(!= z?I`okUu4et9`+vg>)|o`6ZTp5+4g+T{ZmYiKu546)M0kig&0R1Q@%8EG=XGj4zg0~ zpOViH}FZX1r#XVW-IHS2Ei{06dd$M#>@5wT^^)szs zRPV8fR&__!@T$8(Chf66lDmPs8@S+BxDB-XwOml| z*P`8@57*p5XSs&;>uJGOf7?{Y73GEPC?`@1OLFeXO1mw>3S-|r?|yWQ&^qFAX~ZwK?A zVq4XIL~)erOrlt<`YBOVb~3)+#rT;qaVNYF$Kpdc8K>ejlx=;{%|1r)-QT<}B8qA3 zM-(sJFYClVyPr$M?`-GbVf?@GJ}wI9+sd|n9>-w6mVNfR{_suZ9c2#vmh@TDKS|$I zsrF6hEaxoJ7fGKYeM5($4n-F(Ubv`zFDl#z+&^;9BCWlBd}b?9dmpv;FR!+Ddr+>w zo`Bj<9@~1_d4O{MpL||q`>ERbX6iqXb7pIr!`$~gS~+fVlKmFi{x0qDv_8Lxo_--{ zef+=b+oiwu*MG|Wq4fQW)~85+qV*Mr+=twir+7_JTlZ+|99h>a25lW9>k(Oh`1Z?j z^uf~CUJY{{&pTdqyynPjSmE@1Xmj`xp0bm)?7M zjsMgA$EEMP`UV!TjxL7xVG(%Oxlaexw5~nR8rHjRJ?n6^b$kok9H-dall7^sQA5u< z_3=jI8ok=+gGPHA9W<<8mo-`5q^!xhCTE%`P491-+f>%KYmz~rWL^7D*0bJqYkStN-u0`tKE25L^auA3+}G@9_fhU`rmZXGE&WffFZa^#9Kin* z>&<_g|`VfAAh-&u9O2p64C` z`#t9Yo>|yq1 z_U86hFa$Wc!>RCDl4}K;dH$xk-M3y}yPjWa;<>s?tIE1cW9<7k%%Y~IA8zw|>4)9$ zp8Ipl-AOMcSqQ6Db3Rnf*Y>H`^4#6j&`v0~%aZ$l*YVA(?NqDhHd|6(El<7N9Lu*I zdH#}%_Gw?s%fU1K?tpT~_PCvNu1N0Ac?eA=;+6Zya4b*oKI%&29|*C8jq;FFIgC;sA- zuS-s>@_9b>$cYDio=*%pv15?&abwAe>EXtF967O-oSv$C-{Z-Nb3EZVQ%-uuxF~(Z>N5H&`+Mvr@np% z`5i&?tJTL?cqJ^ItF^z2`dvYZp3jNCo!#Vj2lcO(-$Q;+(3WcL?&-)U}|K0APcJIHDdRf=`wlDp>+-Ti*y`02) zP^Q>vTCT-W0mz-G%Rct&T?MI%Embd%lwa0b%Jg)csvU2uGBgI;u=OWUM zR^QmCUTeRpPy5>IBR%Rc`{mb1#u_!fKGIG!uTXNH_UyfntL0nx$*20{d8(fN$LUH} z==0Fqj+SrZYD-Qu=G(bCkP~|w`pJ&2&g4X6eHT|Za-zxeInmnBaAlGcTN?6icNRG@ z!jSLo>Pb#)?D_s`?eub8Nlr9A?p3a<$%)4NHLh#PiS;Ao{nFa$=emxZXso~9bt5^^ znD6fzKu$E~Z*~nLCmQp&xCWCGyBVH8Jpeh;SUbH4|Cl?PBiB4blpWxH0DRR zMv@b!8h+nvp@_ElN9Deu|4%jhv{@Pa}UPIZ>aV zL4G*7;r2%i{p~FBcaazM^|Q&3AUDEZ%k%nZkNY(Fdi2BfENOd&iZBI6=r=BH0lDw#Y+~>*PLr&D^UnGAoxqI!`dfs>K`Mga2KJucz{#Ek#lN0s% zdE`ft6ZQG|khH?Qf)hGWDXq{m)%rQY#wk zzjC?VQ>Yd7^E)QbB0Z(ZA{6^-@VU2gX@YDInhPSg(zGsTGa&^5@BQz3S^@GUKQf zjrH-FZuj%FE$ZtNGwV|;8tWTS{{pq5zTTeMkXq4L--!AbsTKA0O)`_I6^-@HsDFuC zQD5I8GljniRebB^&))4seZ5sl%XHGVsBhn@w5FApXk&_>NJ6$s~XhSsCyJ%+~^`gE$E3*f+qOraw^{-Pa z>g#)FUPY~FtnWkpd}_tZ^;YGY%xkF?_4QVzAN32U6))E-3S(PpMSZ>G*~3GvsIR|1 z^G0e#V}1WjxBCrhMScB%%z@O3#`;0jze%mAufH|(Hflv<{SfNkqE?i8@B3m^hGq`U ze4CtBudVMG>CuL$ulN35?@%x5ANP*T;na%r4gKw1ne4fuw!r?Tp})O5^B!tNWBd0~ zUr4>EZ~y+xTxvyQ{b=fos1^0~jC`mSjrH>9VroTw{n*Ths1=R%4`;gFCDe-g`bRS# zr&cu9KS6ydwW7X$d}bcCqOpD=_3u(E>gy+GPNi1-%>_o)?)&u1a^%cvLi&u4MwQfftG{Rh;4M6IZ= zUzWL?TG3d)g8Jpuiu(FbGFMS68tYe6zk*s(UtgBFj#|-J|0(q!Q!DE0*Jo~|Ry5Xc zqW%+VMScAjnO{*W8uObow~!N!?UZLS+NKRr-_AFg-%%?X>$g$Aids=$zaw)OwW6_p zH}$Kj6{TM559@FQ$h#l1hhf|<|9%a%qP~4TAG9s{=J$~k_3eB6&obH&jqQ9-tysN& zEw!Stem}LMZ@z+@SiPNfv>_VXIY6!Gn?FcS^vxe4CsuF&Q`#1#ef@g>Gjdw}<@L)C zv?2Po|06lkH-DI%=$rqEoLK#F*Ymid@%R0iTG2QE3pufRI~!<2G`90AwW4qS2szO= z{~I~6dixt`Tl9V1Hjxv3^PiK`+PVCC9;I#3_i>Mr6RWrL1?`B&-|INFqA`CW^CUU( zs9`_AsZ6#)Y2!PaIKkir5i^lptsTHf&Z>Cl>)}NtP^v$0oCsuFgYuXTv z?VO`ltX{u`+M4pF;LG{Tum5@45UaOSPCKIUaVx16jrj|iRpi8ep4aoz_bAit-bx!= z?Rh@0vnhamz-VlTW3A*w-+UlB(VM^2K7am(_C@3KtVOL@z5ZKjMPq#swPF*`exA#( zPcS*LdOP3IhA8dmpU*aOTD^CFzPJDMW_=$wgto=%?QEwV(YO5_y7u*tyOX?l`S-Fa zp;=+H9Tu=l&2uUO`-ZHFh5T;&<-E5Ys}hmL-VkcFdhhFCRid(L(@yPxJwEN!A-~st zIj^--mv;72tJQ1m#AL*V;*>o$smD>b3W!ewK}PMC0pYr+z>6 z;^o&tQ5t47rdBl8H=(|QT2Wu0oYkCK(OBPt`UBL8`udctG-^d-eJkn@QY-4~TW7VQ zRy5XMLH!|WMSXp{tPa$Q#`=!b|3Iy%ukW1Im0HnQ-;MeosTKA0uBJL*Z zO1g)StT|=#CtnW+x&(wBCu%(vE0++{dUtMZKth z{_G*AR`kt}BPSa3b;2MScBCS+7tl8tY%B{v5TUzJ6ZT zd}>8w{Q~OGQ!DE0-^hB4TG3elHuaU%iu(G3tRiYfV|_987pN8W^`%+wQ7anj->3c} zwW7X$QPvV_MPvO^>Z_;~_4OZSeMGHjtY1#On_5v{|8dqzYDHuHD(V%7SAG4OthLmN z#`<;Co2VD{^`B*JpjI^2Z=^neT2WvBdDfTIipKh{s1Kx8)YpHVRZgvFtlvs~Eow!5 z{kK`$s1=R%+o=ztR@B$i!&567>*deE)QbB0U0Hjm6^-?Kvl!)2E9&c6yHhI~>*dd( z)XI;sem}Kh_4y|#Xly4vLAN7~Wn z>-fn>`^g7$PEAei)b&$ePnXx$M|>lA?xOekT+RoP6RSVZ7}^mHuY)_7TG2NjLQbsS zPAqMRzOO?ZInmgDC~b(<>*J{vjrC?~Mc;fFIng(7At(Cg!^w%>y#D=4;4wwx<3>;` zRd)<;n*RDzWBvno=wJ)+dvz>2ZTOFGcjNZ$`c7 zn{V!?ou~agpOb!m-xjnZ`nKPaoamc>#!vex)QZ0KbN$q(QY-q_r;!tV^UwQfrxmrL zZ@rV8=$mg%PV~*ElM{XOZODnf`Lljr&)57s&nsv{^lj&YpLW_(EBe;ABPaUiFZyYx z%1^#MZHT_@yy55hzv(B>Bv3Vep6hsef$HtQ<)@u!KlL4Wj`HLCJUfyXee(r=+VAA2 zozCP%-*)Qyc|KjJ6@BZwk`sONFZg*r-KeeU_sZ~-caax;+s`B?`sTC9iN5*nh_E3*9*1$;ejfLI zKlvGc@=yB7&+?Nm@{=#|lP~m>pW`S0qM!Ure)8A)d42l%$!Gh?U#H7+syzF9LzNK4 z?Y_Q-d~H6PH`I_fQ-5O(`P$_B*N~UK_NE&0(x(onAuoN~%{Am>Xn$Z0`8e`}eDc2M z!^`K;`}%Q!;?s_71IXv{R$X2@Z@*r5&qvntKWSgf$NI^~`^hKz$=COjxB29? z_wg4KXN~Kgrc4*PQR&`Mf{(=lb>ce_a3H!_(_~&wcJWb7tnu z)?Rqr;xhnqoMO>;=$Zdth|jU%35VJ@>(X;c|U^oUT3hgK^fo^*jK^SwFC6cNk~AN6!)%XT4X?G8mWh z__~$%th|0Oe$F)Z_S62|%77smPC55~>)j^7`~`Tb!q zZk^W;fN_4m9gOQ(!@d6aCfpNA!wdd(D zF6Z(6@Qj{#bin=E!@rY=fN+Ab8de@&x@|V0p`D4 z{(B8P-qvW6>Guv)7udgBS*|>jl?(wgJ-v;AczXm=RHn;E*@9|-13ST$J+`cI4 zlj@U7-k*!20jU8g_kNXooGFTU&G7vOIMW4Hw<~`98HSgOpEiW&jvxE*DnDn7=WcIN zbbRXgRPs6(MT1gK3 zoOS%n1dOwOU(W|%oOS%94~+BuK^W)zLojZoc^rGV=c6#r?;nG4zCRA*e4hd1e18JQ zS)bW+7L2q0RL^H%ob_jWJ`dxp<0mX&obR(?+}_^rJ9-hu`TZOi=le@AZiTsS_=yG> z=l64A+`C@?3XI#{JD*o!TyM|k!MJbCabD~B28{Dx?tB>crRnj#IWW%e--2}Vn#`!)BHmq&^#Br{NbACS@HoR>?qQ3!tL)%>wJ_0_XZJ&hS2*0uI#;VtQeEy&I z#78G^&VRWhVI$i%P8??xob&t9FwXZeFwXa}u(53iB#u80&iVb#FwXb!FwXZ0FwXah zu!(J(C0_0%IOq41VVv(%V4Uw$VN=_lnmGO~aL(^k-5gK@s!2D`28*@@%d4(I&- z4%i)SPe}B4!a2Xc3&y#=68Dq4VVvt4SLpA9ajvt!AIACq0F3i} zI&6B|hURvCgX2F4=luR580Y)LFwXZ!V4Uxd!Z_Ez!^?dP#<~6j{BaoP`YQMg80R{# z!xJ!$p8q&%bqe22jwSnVstw6oiGtn&E&@JG+T&~o;%!TF&;H*#`Sp&~ohgd*pbHc-;6OI)8(l{kmlC>sImg#J?5f zmOP(Vacs_>Pc`&hvY&^Rvwd~6T(W-+Eob`_S}xhYj+V210a`BEzk!yseHty7?B}E9 zY@b2PCHptga<;F5mP__;q2+8}6D^nQ-$u*Xz7|?8*)KrL*}f1hm+Tj!xm1^u^ZIk(666QJd6 z&wu|8E$8;TmvlkP*?!Lw{H!Nh&h7C$5n8TS;^Ubg(EiZ2+-o(dd@xmu_ec&^PeK11TF&iHD>(x#XZthJuSd(d z{n;hwqUCIV9{S(Wa&CV?$wg>6+h2@+16t1Q@s$X)obCDVf1u^u{<4xQ&~mmPQi7kh zLd&^5o|#9>*`ELY7h2BtSEJ>U{YJE$?XN-0CHue8a<;z~Etl;7LCe{GC|WMr|BIHh z{dH)$WWNb5XZvAjxn%zzTF&;@qvewQX0)8`hoj|^ebf%08DO@*0WFv8tD)s=KLRb6 z?5m^YY=0wKF4?Eha<;z-Etl*I&~mmPiIz+DX|$Z}N1^4CeFiOO`_X8*WM2a|;lQ7QrSuoD` zr(m4#Ps2FhpMi0{KMUi0e-6g^{ydEH{RJ53`)nBJ`-?En_c<`m_m^N?&iQ?+&%!yrUAx{G#`)d^#`)e9#`)e1#`)eH#`(StjPtz(jPt!EjPtz}jPre4 z80ULy80UK%80ULi80ULC80UL?80UL2jPtz%jPt!CjPrdv80UK@80Y);FwXZKV4Uxr zVVv(f!Z_b|f^oj@4C8#?1;+WlD~$7fHyG#p?l8{xJz$*gU0|H=d%`&1_kwZ0?+xR8 z-v`F|-WA5>y(oIQWZH?H&k#=jF6a-L_%Gtsue^U2^Z?1XM_CTbIOo~M8OI=C=2-+7*~ zzfzLF1+LE%{<`q_!ru~}f2rqHJrBdz;s14>`TpXnK0g1vUsmNA`~{@&b!#cSmGHL0 z+X?r7?ytd6-k{{q{Wbib`#%@fsLJ^_{rvkIUoX7>ggk@4pA@>ix$9FDEeKx!qG*xu z#lqhWdK4`co`1buZC+o?w%QLh{fArahg9|ax@YMA6MDPg+ixZ+4qmqmjuX0`e7%Go zXJ{ek<3Pyyb5Ei3tO3WX**u=`70%ZB^;@nFeR<&e*nWiRKiO(O()6EteFi@nZLd$Z z>VBKSUj?(C`}%Tx{wtm1G{gzU{)OzVtwB6%Hhw>SBE zY+QAHl0K%8uiw~zabB+LYlY|EzM51WKfa#poZh|8>xFOdoPP%`bbI6D|LOIyZxsHI z@J+%u3$NBIe~Wp$;_H^zX5u=JH`h7k9LK%RY2h_IPd+|06)(5BdA-zfj^n;wY75Wb zt|{j2v98ne{X+6Ov=HZWtGN#On-PRk2BpIr@1&z z3*oJ{_@n0fx8CA-JPDppEAjf>R(KoXGtA4)-`-iq+lupPFT8{2@#91O_;~!p```Hb z=e_O!uCxDsq@6hb_QIbr=f9nIKJm-lev8jGedjH1um4WM^XFe&bw0VXw1G(h-3;YSJ|B>Wi9azMl@`d_M!m`F80Y)NFwXZ&V4Uxl!Z_bAgK@rJ4&!{k0>=411jhM(C5-d^Dj4Vc)iBQYYhax3 z*TOj8hr&4DuY+;E4})>OUk~Ga9}eSuzX8ViJ_5%1ej|*_x%+jHZg}4HLHrj!4!FKQ z{Gos!0Dm~(2f`l-_(AYT1Ku6}SipP09}jp>_>6#;z@G?sFZj%Wm%^V6co}?Fz{}w` z1-t_ORKP3YPY1j={F#6s41YG@hrpi;cpv!l0Y4P}LcsgNX9v6={KbIxhtCQ4Vepp% zJ^=o5zz>Jd4fsI#D*-kK%5%6Q+^8jhu~|C_+TvL#lo}rWFgcSDaQ|402l2!BcV%fjahe`Sm3Kd%{?9*K`lIQwzV zsPrfpXMJ>fG>o%8COrnmSs$Ao3*)ShOOJzb^ujIApYY?5+&}ah`1&0`c8QJum(M@* z>!!F-=QzCn$^HY=-|6+qm%H3Nznwpy6~^xt$GO+@L z=X1Z;=kNb>QrF>U4mf-Nd^vSJjI+KVH5SHMeE=R2RN z;C$yZ5}faRMuPL5&q#1N&p#gGwN>>|LegIo{!`UalKtzVe?$0u;cp6mOZeNu7YJV{ ze39^XgfAApMEJYH-xI!6`1`__3I9O&hr)jruisyUeVrKM`JC{5_X7;(S(! z<9sUoGvO`0E6)Eb(SI%c8{yvy|4#V#j>pf7F2gzg5bzJ+KL-3m_^O2G zKF<6p;WeX=(60{oa`?{y{}}#Dz(0Yn3HS>5+JJuw|25#B!PfT@L#}x zM87%UtKd<4zBkIfABpd8eBV+n;MH*a>H$x|Qvol47XK-&zuGPP+v&ZqW=T; z_4x77JP$KOZvU4`!}d_Upcgzqo>0O1?O?LAQR2MO;kyod0f!b^nr5?(62 z49@wlmvYfp2p=Q<-dv^Vdka5U_#wjk2tQPKU*Y|P_jf#gJNgy(g~I|~9gmX(0=^FY z;Q>Dm{lI|p{rC|9PoqCF;J=|iD&Xtkg983L{OEwM#{0Wt0{#>H*nn@qagGZ(9~+Jj z_@D3-0{$2L#DMc{?xcYK4Idovf8Zwv{9pJf0bhgLb!xyjp+7C)|G`fW_*xw2jDT-O ze`dfVe2#clz&GMJX9v6*`f~zan8J4-2E1BzeD`6%`MK%&AxD2fz-y=Q-G>2hj{D(7 z0q5U1y*S|f8>g29JdKxoX}|}h@ZE=wXQGe9_Z6=Qdi?xzy0ivfGG`y(YL?==7;${g zecrXOv<_O%_I1%;ftGXo`lSufa<*@Xeh6C5?X#s#&~mnKivCKpoZB}qZGo1veM|IL zq2=6u+tM~@Ior2Ie>Gap?c0}jK+D;_Bl>I5a&F(LbO*GY?K`8t7A@!YJC*K&mb3k? z=!c@^+w4CkxpdW#jbNjxf{n2u^KMehiXgRk(yz~gP zob8W9e-m2H?FW?}gO;=XvFJyl<=p=G(i72gwm%8|D72j0pImw>TF&;Tp&yNwbNe$& z&qB-D{%rJP&~k2nZt3}GIon@=ek@wf?Jp|51TAO#OVN)*%enpKr9;qiw!aen&1gBd zzq<5Vw4CjSq92czbNgYX!_jiKzXAOOw4B@DSUM6dXZum;C!*!teoW~&v|LGTdi-(w z&FCkgoz%Xp%Hzk+38j(N9LtxyPScdMjGa_S4W$LCd-Q?WK33tqvhOwap}8gIorR7{vNcP+rMA>0b0)XAELh(E$8;jOFu!& z*?tB3`_OW3|5@qhXgS+|f&PB9oZEj@`VCsn_TQp^04?YC-i~v|nb%h~=C^z+biZhu+X6=*rz4?+JLT8@4Ean4m`SCzdE z^ZRSct|@yX;6uxXmdy|Nu(DxgZzjAb8eTTM?5%`H(TK7?Gw$PUd_Fvnw*x(Wf#dPV zWB82bAiV#r`Y-P7V!tTR-&8gV*M@tfj@ysM^?3*VJMEvU^7!~;%Wg)?+3PkQ{bKZ- zd;KStO-9SvehT^}XgRmPrED5n&i1#Ve-|z1_IH%sg_g7Z-RR##%eno%W%r}yZ2tiI zrD!>~f3WOfw4Ci9LH|Bl&g~y7n}L?I{S)Yyq2=8E$+D-=a<+dO{Re0{w|}`k=XwRP$7{q`;NAEW)a z{je&J?>`I57NOw{*toy&~mn4ihc!J&h3|#eTbH`{YU6OMa#MU$7L(f za<=~z{by)7w_jQI1zOJbU!q@$mUH{B%f3a++5S89pQGj6{)e(vXgS;eg#HV(oZJ6g zwgxR{`?cu5M9aDTy0Z0XIotn^{wuVc+y7Da7h3KI^ZMP0{%f>fxBtn!e*Y=kgqE|% z{}25)=sEZJQF-;6_)0it`&2ou8d}cn)8#eLa<;FD{yVgs+ZUGCLCe{`F8c4$a&BM0 zya8Iy_6^bhfR=OnY_D?b1&XZr)u|B9A#`|jmE(Q>vgLB9?y=k}%LbNj=~k3h@W{z&v2&~k1+sQehTob8WA z{|8#m?T;@%5iMu?lhFT(mUH`)%TGni+5R;2f1%~v9$y7P%h{g)z7Z{F`!msU$^LJ& zobAs-%O(4N&~ohK_fuz=ekr^fyjt;V#xI9ghgUD2Z+r+m1y2>fUFGrn)2qs_sflOdu@?2+>u_!P zbug~4@nPk|Vca5foEyq-gmFuZ-&8&d#w|5Ix_m5*`@s0P^6@Zkx$z0*lVIG6Dvz)K zQ@eNhy8T(Xt8lEm5Y5Z1r2F|(H|2B9H80Y#O@R~5r^}FD;V4Qp1?kT^o zCY}|-#+vJPfBAG6H^KOWJiZQ3mE-%YaE^t= zQ_OXE23{LpyLh4T=iqhVb&6M->-++|F1&8>m&RX&*Mrw9{>J!A@cQuj#orsB3on8f z6|XY>D!c)_LGjO39^XG-D}SRVzV98|Y_8k<^0#1Imm>H2zg@l%#;rBSSya9l#{Fh| zN%?y)ZiDfq<;!5)U&cQu{|LtYV|;n}Cot~6Dvz)KigJ9P7;aa?;xnqwGxpEmjo^)n z?==27JPYUC``efB#xTzHui;H#oa^7ho5DEPzlS%2ajyReZw}+=@%8_y{O6i@FODrW z*ZG(7wJJHX})c zpwnyI{&bim2bHz?0iv#_x=sUnUx8J>@%gBy_esA>K z!8!MOcCFa2*5=LJEOXtuRU820Y=2-ycNk~AM@0#Yv***R0>4WhFSAqecjh{j!MBHR zU%ZWZd#`}+0NRRjj2{Z$3BFTtq49q3o#8td*EN0^ zd>8mG#YI&fzx^IwaYQYAyoViX9n8$Y_@SQyvH9Ot--6JT5u<0n=OhH=e} zpImV&jB9E9w2CueTx;WJR-6sv+Esac|2(GxzY8C?YuDlq#=E5OyYS(hd!5fizdMX` zeIN9Dz&O`0K;H${rFds^{Qc1H3FqAYBJ_K~IM)wAzc-9?{Sx&1z&O{tqwfmiT)zzc zzA(=967>7QIM=U0-woES_(602W$5>Zb8dfS#nlxDz&O{ht+=k@Kp5xx^%XZ%90cQB zzp-LuMRyqI`sj+W6+K{_>o-?SsOSmfT%S}irJ@AJxqeHlO1W2EaJi->O(paX5^l$Jc)m&L3Z2fLdRS^T*>%!r#UD<2woyz7*$=?%!tg9Fd<6K`~v7zE*80Y$*6&ou~fpPTs`u~IT z$Kxv0`X-z|zN0YVn{od5j>3djt6T*?BjKsaU*KmZJYBgCepbS3RBnKuo$y+f8{y|{ z@%Z}JuEh5fLOJ*L)`g!3<6N&_*`V@#SbTfq<20&lTzLVUbNi;1%_}bq^erk|R9;l& zoz3H9tIF1+E_V9({bsw$;zE3#6w4hy_70WX!8q%kDtCZ!);kyO2;*`dzaDqO`Q!V7 zs$MeBpTB>;QH0-53yuGAZ`XFI?NW7H;qxyn-0xj=yW->Q<{c;JQM8BW@%7xZa_`DZ zo$C;L*UJ4WFN1UL`RreLVCCh3zI$c&$}6h8i@DA{D|?L^;`H(LEU&CA#N#byuUqfJ zgJGQYLn;r2an}15_JeUbkFRrooIk$nvFat~uN!{>!1#~rg9|IKzc%0}SDsusG~lOJo<8b2$K&gHR^>T`c)P=L$B+Ho z%JX5I^$RL5f^pU_F1!TBH=;{OHwCLI4pIOiT`ROP73n*#lq%5kGcIv!uo36+xy@q84P zJ5KDAE2qLZ>$g-+gK^exE4&@XS-+$5E*O{d`1;>nNy+`g^N;=Bsxs;Kc^+T?`zxnc zj&g2S>f51l_N;yeFN zQzJY({g!v0yiI)n^7fY7o4XE0Rc?>N^KT%Wzxz4#cevu?FBC6#k@s@re(|XME#S*- zhU4?UMy1_4xYmcjAN`zYa3!ybpzX{_Y;%d5*PlM(+h~7yW(j zcEz{%!$8m1%W~1LaC=<%_&A@rj_bzP319c_dGdP3{pVgE`l66u$noqVV;v6@{;VEph#8Md9mTOI-h2 z;`-MT*Pnl-2;U>`T>sqbr55hL_~;?v{CW1t0dESQ5^(&d@SMP=Y~rA_Rhx)K2H8QD(ruIj=L`2 zZ&r&oRlQx@UG)-pe~ypypE!OrI(+<;=egq-MVZ>{?@1iLMsItZ+{ahd@O$I%J?75s zjn9+6mjhon+;SZ2_X~eui^s?3>wUU&{MZ}ge)6E>@$vb4Kk!}MTaIJ>;e_+|d*FNh zws?Ge-hUnq9G}1617FA7(#O}omUn&Pm&@PRf$zHB(#KxA>N?LzIDd}^z8)%krtl}X zcXNiO;j%XdXZJPyYT>Giu=Wc#x0Jvr*M~&f0SPoa23D?UV3~Ea!e4TP@1< zPbK=5>8jt5fvf8F_;|l|cNph-wTQoG2H#!n9Vfp3KeNT-*B2juo~=@Q9R8jed{^|A zKE7Q|s&3cwp7YVIDgFy*kHg>hf{*jJ^zqB(r3>WKqInSbi}B0F>nnc!wj7;%neoew`$2e3&53(lgxC*-zvOs)eOgz&+?T8V3-2ef zx2f{ETbv&cG{S#v>zpUwkHmzXdMKI3wk&E!X_At)%aX8Lm80R`ao?HUsTpy4AT^Q&3M7-ShV4UlG zzrPg5xz68H@ji@mosVP7V4UmxIP(J-N6$ST@^NWO?c8#nd)&)CUVVs`V;?`xOvQP| z|Il-fx4Ay&GpnBGN%~X5pKVlxIPRz6`o#aZ$G=_pou233??lnvp2x?(SNQ#&$H$4! z1@CLc`Og(TM>xI*D|p@JHFABrb3U%;&*u%(KeW~UEz>`;)qbHl|Hro4FE;&*U|$r? z6h3Qo{+8u$Z&CD&=${Mr?)~sRb9-k8kK^{sOh0F<{YR#Md8_>=rhjFt{nzGt&f9AL zh3Q}4YQNf?PyXt!F+Sfpj{ElbmgD)?R}?J}zDW3D;ra7fXU-?@zZ-v79A~NU{Biy? z$I1KO#+QlXd?-ABoK5C9d5^O0b<2AT;oBNtE?({@!t=*Znd9U=WBgNboRz}!$EjtG zllMBtzYxdyN_hS_ZOw7=UM#%6@P@)03vXup8*!fB3I9R(D&hItwVipndEY_!PQrIJ zzFNH8Uxcp}zE1dh;TweiDSV^n$@k0n=^MOrkH2p6Pwo5_YK!-?x#w@A=wHusAJ5bX zJ|Ei%e-$G1d@Q{UK4Oah$$wl_BMLnq%RcmcEIssmEIssmEIssmEZu)THoje(oa^SE z=Vsy64vue?`|)0lC?z~CyoT^v!fOk!E4;q&20<^18jT6vuA-=k@aDo>3U4jEo$wCA zI|=VBeCKz96#h-n zYedV^-`BxU9b>uogSq=*4Ltw<1B|o2sqjY_XZ@q}Di~*ddHN?9XT3@7)iBQbC+VMI zob?syUtpZ|Pt$8)ob}JrYhj%AmFZt$ob}Js>tLMqFVerkI5Gd$q_Ftuc zhjG@wNpFC0)@#@P1IAhZHvK1zv;J3|jWEvocj>=job~V1|G+rw$JPE9###R%y$Qxy zKfm^WFwXksI#FHxG%04iQSE9l&N`og_z2Ijar`(fzMlO3^UGnJ>wMntV;JW;pZEI& z#<|X~U#x&}uJiZMehTAU=hsg^gK@6&>z*rNoa=o4_H!8LI=>F`1&ni@Uv>Ku#<|Yt zIlqE&uJd`zuVI|){QCGeFwS*;{p(v8=Q_V`{vC{Sy$Jq2jB}m8U;PId=Q_X6{v(WY zonL2P1>;=j?_>W7#<|YtrB}l^*PFn9hHN^D5}0V|G&sTzfly`7#sG&L-Nk^jE_^t z>GO`y=bXOLmfk&|J+kq2;IZ;Y&pq#o|4G$z-}tw>x#g};-W$R>-y6X=-?K2z_r@^J z_a-pT_ogt;_hvB8_vSFp_ibRD?=4`Q?=4}R@2y~*@7uyS-&?~t-`l`A-`m1C-`l}B z-`m4D--}_K?;T*A?;T;B@7uvR-#fuL-?xWxzV86zeD4h7eBTkq`Mwj3^L=L+=ld=& z&i7qmobS8AINx`NalY>X<9zP|<9y!}#`(S%jPreO80Y&wFwXa`FwXaVVVv*#!8qT$ z!8qUdhjG3i0ONc=5XSj_5RA*Y`}MV7@z~7q@yhjeFwXbiV4UykVVv*3!#LkJz&PLk zfN{S63FCbK3r5ytucH;nWB9~kHRzc9}CO)$>)|6rW&n_-;qQ3rgahWTC%#`#_y z#`&ItalRM8IN#GS&i4$A^SuU)^SvgF^Su^~^Suzp`Cc2w`CbRc`Cb>s`Cbpk`CcE! z`Cdc|cmo*c_YGm3?~P!b?^zh$-WI*&h|afa=!P3alV(pxa9HM;@F&jp6y_qeYw4G z3@+KXN6Xp16fKwRi_vnnFGI^E`wnP1+w(7palZ2}S8%@bFOpULRWk25{0k&p^5s6} z{2iA3>#H2+$R+!Z9qtp?+Ydy`CHsBRa<)GLEtl-~L(AFzNVHtC?}nDM{ZVMS zWWPUJ&h~@Qa>@Pxw4CjaM$0Ap1JQD}KL#zA><>cA+5T9xT(a+umb3kFXt`wH11)F! zX zv|O?ufR?lU1!%cse>hss_7|e%lKnumob4|{%O(3G&~moF7%i9Vk3`Ga{t~oYvOfwf zXZuUha>;%WTF&;Dq2-eO(P%l_Uyhbb_Q#;*Y<~q>F4-T8mb3j3v|O@34lQTalQ|OalT&<<9r_u<9xpX#`!)1#`%6DjPv~_80Y&) z80Y&a80Y(F80Y&K80Y&~7)OtP{`$JAw{*U>=bT@k^1trW27S(pqG3+&`t`zx3%^15 z2;nyhze)H=;iH6)7CuJ!SmEP@-z02l2!BcV%fjahe?|DK!siKpP5A4=-w-}u_?yDt68^UE z1;Q5!UnKk;;fsYY5&o|5_k=GM{=V>K!aorHq41A{FBkr?@K1!V5dNv~&xEfO{<-ik zgnudgE8$-Y|3>(?!oL&#z3?A||0sNw@SlXQ7XGvFUxcp_zE=3J!q*A^P564@zYE_W z{14%O3ja&^M&W-8|3~=0!Z!*3PxxlxQN7&9{`~*lkLRliuP!_#yg+zbct&^);WdTV z5?(00w(vT_>k6+YyuR=v;SGd06y8X9R(NCKO@ucU-b{FN;oAsrA-tvVR>HRx-dcDY z;cbPt6W(5UvG5MUI||=Ucqif83*SL_XW=^v-%0q+!gmqAtMJ`~?=E}~;a!C9DSR*C zdkf!3cvs>33g1t7H{tsWKS20_!VePOU3d@SJ%yJD?(P3GXlbFyRA)A1-{L@FRpDDf}qUbH8u4w)uUt{CFexdp_;=nbUL6PbNM0 zd$*FF`@J_wkAJQ?|4|OUcM8Xkf3Dg0-0!FT|2q!PGjtq!jp)k=|Hpj)d>fp6@c4Mb zHGY0|kaykU-{<2$KNkDZqCdv-;C05!O55 z`vXlsc&mL6)1R`{zL)7w+iG8K`ZKoL_cr}mTkZRp{+zA${Y-z}R{H^_zhJBV5vIRr ztNkF;U$WKySkqs&)&2z2U$NDGu<5T9{nf(r_x{t&ajq5pb;9$(?!oL&#z3?A||0sNw@SlXQ7XGvF zUxcp_zE=3J!q*A^P564@zYE_W{14%O3ja&^M&W-8|3~=0!Z!*3PxxlxQT_1SX*J>1 zg{OoU2u};o2(KZ$rtn(A3x(GfUdQp`EuTjhMRgr_AK&T;uP?kvcmv@Lg*OtO72a5Q z6X8vTHxu4m_%^~@2yZF8mGEtaw-(+;cw6D^gtr%7EWCs8j>5MS-bwiO!gmnfS@@2^ zcM`s{@Lh!ODttHLy9?h#co*S&3g64~(C_!)+dF@MNHu&8?0-LG?Ed#9#=f_A{Mh&L zJoj>IMqP#PD||oU-Gqm}-;%d$f6*Tx{6OIc2@icACV#(K_nYnequg~Uib{l+39l4> zi15C`4--C6_))@-5q`YzlZ2ll{B+@Gy&Jxs=L^3?_z>aO3Lh?fr0{XVCkek*_?^P< z6aJ9!$A!-dIR8%2bHe9(o_l*Qiss*JUoWx0EqsyiCBl~q|3LV1;VXo%6#k{>@!Rh= z!oL^3O8C#h*9!kl_y*yB3I9jxKf4%VG!fy~hLimlsZ*u*A-+#jM9O*hfZ;j7$lYj~Gx~+E+f3oJgg-6(*$Ls}zaV_h(r`ad z^lylMf$(?5ao+D2ez_kA|41DFGvQwe|6xM-<^Cl4U;Bmo-^6h?2>(m?Kf?bLUhVzx z`D7-By;lFQ*A{&};SGdm-wz+Zx#(L8Z!Nr?IDRMLJM|Br&n}|hQ}la_zMJsw6T>gJ zM0lC-N^$%{`-hL;PaJ1}IL=YxI7f^A#NhFZqLYKiDT+=Ne!B28g`X|_T;b;nzfkza z!Y>tm`NYumFN&@devRfqwrC}#|j@We3I~~!lwzpL-^go?+bdpXu9x+`-iT7 zz35TlkBj5X6#cA#^Yi_ug+CiOKKmDh&j}o-8v2)ozao5|@YjXUe?NTP7K;8I;Y)!nYOPRvf>*=sODEep&c- z?JSPJt2oXc!uJy1Rd}~$;q&PsyyCEM-&^=0!VeYRPxxWN4;Oxf@S}tuE&N#F#|uAE z_+a6u2tQ5u8N$yJe$KM+>+3?%Uo8Am;g^f!Up*=Oa)*lJ3==+F_z2-Q2_GeVjPP;7 z#|xh*eDb95b+|?J(}dqHj&qmrd&P0?Ulu-}hsALo6UUh$e5UYO0k8TvZBp`l=+6b5 z-w!-Ha2)y^;V%n+Mfg17uM3|q{4L=NgfE(uybiowON1{K{(U%;PRFUsbY`+d&t z?~@;g-~G(-_dn;%ITF&-av|O^Eh?cYc^z`P< zT(W<)-R8}l?Hl9RT(X~pV{^7|f|g76lhJavZ;F;n_EXSuwr_@(OZHRIa<*@dmP_`x zpyh184O%YQ--?#AeG9Z)vY&>QvwcgnT(Z9nEob{yXt`v6J6g{6+oI)?{T*mI+qXu` zCHp(ka<*@SmP__`q2+Af7A=?T??%hnz8zXF+24bfvweHCT(Z9xEob{;v|O^k4=rc= z4rsY#e?MBz_8rl3$^HSfob9(m%O(5iXgS+=Ldzxl2hno2-ySWO>>onQ*?tGKT(W-{ zEob}AXt`wn2wKkeJEG;1{iA3(+wX*yOZJbUma<<A-$2XR zz9(8P+0RGI*}eoVm+ap}%h|pcS}xhYg_g5@DOxVszm1l&eHmIV*)KrL*}fbtm+Tj! z@REw4Ckxq2-eOGPIoS`=jNO{Re0{+aHFOOZFe4qUyhcu{Xn!_vi}$@XZs`2a>@P^w4CjaM9U@n6=*rzABC1n_Mf8V z*vIc@S$+l!^(%#cF8mAOUkd+9_}9X}5&o^?`S(+UaLc&lb@&cFXRq7QXt`wnJzCE8 z$DrkWKNiL%kMjeL!P(;+hn7qBKceMqe>_?)*{?#&+5QByT(bWOEob`^(Q?UtHCm26 ze~%xpHM{s9dz_PS3@&+`pK%P%_P?h$Z|0KyZNa|>Js8L4lKn3@HfN82GFmR#uR+VP zcRwy#3*&tM6&4>Se}6j#$L5m#I`o`9pHtCt$^JLAob69T%O(5uXgS-Tj+RUIzoX@B ze+F7E*>6D0+5SwlT(bWIEob|)&~nNCPqduv&qm87`@hg~wm%0gm+Uv9LeJUrxehIt>+ zUSD{T@CL#g3U4GlE4;Dr;Z;{Bd3~CQzNzqL!kY`}VKg zeN1)?jI%yAI~K-SAD0~m6c}fHYIZ7&vwlnV78qy!*6gh?&ib_MG#F?7w(M;%&id`y+hLsbJF<7cIO}(2 z?}TyI@5WgmiZ)*sG34CAanl6?fmS${P9D2%iISoSd(XZ`W)<1o(pjO+{;XZ?xn z6EM#D%0 z7-#*}?5i-&`n>Es7-#*p>}xR2`s>-(VVw0hvTwjR>+`eoVVw0hvv0yU>u+V>f^pX0 z&b|%ftS`tefN|CrW*5RZ>x;6BV4U@LvhTn+>x;9CVVw0P*(ET}`n%b8VVw2%vhTq- z>r1msVVw2%v+u(=>&vpsV4U?2vLC=W>mO!6gmKnC%6K3Dv5SBl>MeMfQsx#+(T z{-yA*gnupk8{yvy|4#V#!haC{qwrP2e-gf0_|L+B5xz$FTH(J6Unl%G;p>I}E_{RV zKZO4&{4e1fh5s%5AL0KB-z5A$;hTj=jpIj$+~3uW-;SyYuP!_#yg+zbct*JY_kZK_ ztRecE!fOdH6kc0+9pQC_*Are}c#-f1!YhJ*2LXS#Q1p$2XN5Nw-b8p);mw3M7ru?~ z7Q$N!ZzX(N;jM+Y5#CmKJK^nx7Ypwoyrb~#gm)6Yz3?4`cNV^*@STM3EPNN?y9(b; z`0m2@5Z*=jp2GJMzPIpwgm)FbukihZcN4z9@B@S&DEuJd{@;&|AD_F6zK8Ih!b^nr z5?(62OnABQ3gMN)dka5U_#wjk2tQP~|9dRs>)%)O{e<@yewgq9!VecdQ1}tTr{ekB z_#gN4mm@`gl<+~qj~0H6@MDD^C;WKfCkQ`L_({SC3qM)-DZ)<`ewy&pg`Xk(OyOq< zKU?@Y!p{|cp78U9Um*NK;TH+NSokHvFBN{7@XLi?A$*AND}`Sr{A%IX2)|bNP~q1J zA13^I;lqXBAbf=I8-?E_e5CME!bb}qBYdpzBgDu5<3xY6@bSVY2%ji?lJLpGrwE@a z{1)N23ZEw2|9#-``_0=#f4lHIgx@LrF5!0zzeo7J!tWD)zwifyPZ$26@P~vyEc_AS zj|zWG_~XK72!BHOOyS#$x34EfKTG&i!k-rYjPPfLKPUWo;V%fEE&N5{bA-Pn{4eow z-OHk%EBqDVuL_?h{59dP3x7lSeBo~je@pn=!WRf%D14FdcZ4?*_w&V~ZzB37qJLNT zd%~9re_!}A;U5V9Q20l}mka+`_$R_w2>(?0X7Tp&ndnyv|6KSN!oL*$mGG~HeDyDefn~iGIEC--T}w{)g~Ch5sdd zqwv3l|0Dcg;hTj2Cw#N;s7d^+g#EgS|MyVhk0+~%zPj*~@B-oH@1!_i=S_<~BfN(2 zn!;-dFBD!|cpc$&h1U~aUwD!52ErQ(ZzMb`ys_{m!kY?jCcL@uZG^WF-ctCs!dnY( zBfPEfcEZ~WFBaZGct_#e3GXC)d*M3>?<{;r;X4W6S@O5weQ zA1wS3;eCW3D!i}oe!}|;KTP-l;fD(!DEtWFM+!em_#oj&3qMBqvBHlNe!TD#gr6w< zB;kXFpCbG;;b#axQ}|iJ&lY~J@biUVDEuPNbKkdL6kR;<;(4w+gS@T;d@J+&<0LkA8USRU|U@QQ#BA2@tqWxz)a95JwWz;7CO)4+oR zK5F2ofrkWq%)l`N`#8>zxA`*<;|AtGK5vMRhcnSO_-%Cd=eYSzm~lVe>vLW$n&@2r z`1>jg@Qs(vyq}MY`xe3{iQ`NbK1KLc;kO9C)pPtw#HbMG|Eu@&fbn&hHqc(T*l!np zr|`Rl-z)rn;nRgbB>WNKj|rb4e5UYO!k-rYtnlZB&lWyM_{+jy5k61&>%!*?e@plR z;fsVX7XGgArNWm9|4{hypch4-I3B;fr}60VX_NfhMc&iGGtpMJQIB`jvw}v=nI5rqA$ep!=4g-f$&W9r8s`rQ=%^ro{7E^#}9i-^aa8*(bwYm zVNZ#^KzJtlMjSuvDbW`Q&qUvf{AUqR&FODDfl;{hDXQCg( z@xz`HeSz>y^rJX_*fY^8(T6<~{UrLZXQI`j4|^v1S@dDgM8AkW?3rkd=);~8eSz>y zv{oEH>?zR~2+u^nisOepCHey4nP{Cje%Mo@FA$!IeiO$JdnQ^h`mm=&Um!da{Vt9l z_Dr-v^kGklzCd^;`a>K)>?zR~2+u@+isOep6a6LnuxFx;q7QpY^aa8*(cj|uVb4VW zh(7F@=wH!?JrivT_L=Cvpr?grqRpZYdnSsShCeP1dnT$T`mm=&kI#UE$4N!igI*v! z6Qzy_pMTghQCjq2&qOsuANEXCOY~vSM72d9_Dob)^kL6L^+g}{l;|^2196ylntI|CTb$Qnec6dw-mmu@HWER2`?7jQFtfeI|$!V_|C$26~4Rh zF2eT`zK`&Ig?AHvfbfHa_Yhtpyi|C(@Jita3-2SmukilD2M8Z1{7B)0gdZdPIN>J< zKS}t>!cP@`y6`iFpDp}c;pYp#Q252dFBN{d@FBvl5`K;Fp~8m=A1-`^@SB8>57N1(oy*RS30^$96$ViCmn^~@1&#f`<-+&RUAM3{vsWP-(RGozr=CE zkN@fDZ_)oNJp8zg=LbX|emqY{;m7lI6n?zIS9OmJKR#3!9)7${M+Kq}KMtp(@Z)ew zeBPH5pZBGrOz`EVqVVHbDyk{^mci?sinbNrMtJyfEEN@sm)l7kX9wXs3J*VCrJ_3G z<%S4d@AEl!4;y4q9PZl12Kb4A# z#QpFNah$t^-y{4!;SURcRQTh6_EjK0 zek+K=_p1VNzbc5rug8K&y&l`#8v3}XAZjnXW6(3v_QC6ri8>44N%$_pcN4yc@I8g^ zExfDn{e^3gNwlA0qrv;r)akCj4;W;kTnqbcE=S5`MJsV}qU+ z_s_Jrf2O1G{WBef@1N-?eE&>G;rnMg3g17|QTYCuj>7lPbQHdSrlauvGaZHRpXn%k z|4c{W`)4`|-#^n)`2LxW!uQW~6uy6^qwxJR9fj|o=_q{vOh@7SXF3YsKhsh8{+Sl{ z&vX>Nf2O1G{WBef@1H4g{wZ<(DRKTOasDasehr`Z1>as$(Fx-Hb@=m?f++lXNDdFkpieR6Pt_pf8x>oqGpr@l7gx?hORHS@#u*dUe zK~D)!M-xOJ_H;Bk*r%ggf}RqN=jDQZI=VCHDdFkp9?^$A9o--7)6s)LPYF*)kBDCR zkoOo{ByS zdRqD?!9Ep*Jso`}dgWh;{_CKpqwj*AihdNnTKF2_>xBO<{7>P33*QuUJYPL6{Qe~s z^i&jf{3J)PPe-+c*AZSn==lDtpr@mT!W#>3CcK64ZH2cLUMze&;X4T5N%*e9_Yl69 z@UFtU2|rMH58=IpmkaMLypQmHK~IUtw^XDa#|8wCgO9_5o{9zq9UpH89UtEYJsk}e zu6p%yPZh^GL-^Uk&l7%O&{N|4Q&IT+W=ekCa!K&`>FDyHXQC^GUnBgwPePAxnP`OY z(Za_IpCbG=;dcvvU|RS*9}@kGo@enn1b?3HbUBr z>mT@B_p4h-l8_`xk|arzBuNqy5|SiILXsp&k|ZQak|arzBuSDaAxV-XNs=TlJ=h{p0r=Dc+&n;#gq0M6i?cJrg+kRqvA>X&lOMFA54AzI=#KWE1tC9 zr1X>Y|Apd7`^}0c?Y~q!X}?ABr2SWl%j2Z5*s8d^eG3cq_Qm!EV4%;J0)jeUu>A_< zc9+X7AgJ>K+tcS!0im83Nk8g&B#-9;LOq`4{wW~R$7i|x9or)*h`#s^f++B*}_W)pS|Ij*{&)L^`;B#c8!{@vh|C#Y$ zNVmnWq{HWcNVmoBq{HWNNQZBXXhUt6D@@XDVUz9(mvnd^kaSms$I{D%YIU2G(e2#{6_#6%C4&(6mS7dveaaR;4+dGU~jN6RE=Y5#|X@|cPBiqCK zyrjFL1nCar7UMSKt~iG2pLSQ2WZKgX-)6(Kr`;8$nD(^0;y9)~?eIPe+1_T{6{X4c z4&xT%Hsh{1p6Q=%af@;IyAHA+SDZ+?!??w`&A2NnF#XeRG3{-}U2ziAKkXLN z-e%kt6`B5Nx0v=e7RCsX>T(Qf1ktjPrJpmw;6ZEDNO&gTTFYKariqTrhnSu zacl|dai+t#D^6wlr`=-O+l;%SN*j88Xm>?brakShsK&IX-4)fD_NvS0gOO05=O$j` z*z9)U^Dp^4E7G1<%I9Sf^Smq)^z*Vv(9c67ah9@N`FuHIo?l1W^Xp{&>F2YN_IwuG z7l3D$>gx~kd2XaV&rRA>uRmZvHI?;;e}yap@EywY+{8~x{UrkK=fuk}UY7CinErP% z{ypQn82^Fs-HIpshkm^!(q5mDuMb4b>jsffUmr-?tFI>{w>$lMPNcmK0J~ZkaJ#H^ z&W2kIOMd@(;_IARU|6|+C_D!YE7w19YJ*|r`g-SFFsxiZI;sPPmFpXv^T4oj{WGU7 z7*?)tbj}CE%Jt8kdSF<&zR9@&3@g{aaO#6$<@#plLNKgc|I%pyhL!7EoQuG)a(%1Q z5DY8Vqv&EVtX%)vX#|Fq>%YdAfMMnOHm5NdR<3{JTndJj>(@k0z_4P9ICH($=6pSk$!y@+ZTu97TaHu=^1;M z{ma)^BklFor2W&%c`CnWHG~1BR9M7`!bQ zR@w`IUk!$p_JZK;z_8L@2>co_th5&fZx4o*_9EaNz_8MO6nIB4th5&ezZMKD?Zv=5 zfnlY+IQVs7SZO~RyfYY9+Dm|64~CWYW5BzBVWqt!_zhrK=*jV~zV4mmd2{7>PL6-| z_3)&93#EP1f6J8P`I7W=rQ-7bA{OcQ-SBl_*-tFe@23>W?rwv1oRcss_gVZ1%#9Tdm!Fvjies5rh3kG`ca zy&t|}d@JK$Gro=SZy4Xs__vJjVEjAAcQXDxPx_|R9^~) zrTQ{3EY+8TVX3|n3`_M@U|6cJ2E$T)4H%Z{Yr(KoUk8Sz`g$-d)i;1)slE{mOZ81) zSgLOZ!%}?<7?$c=!LU@{28N~jb}%f}cYtB3z7q^f^<7|Cs_zEFQhg5?mg;-KuvFg% zh9zCT4kpj*uJ9TL7M|~E?*@jYdUr4^)q8+psooO|OZ8r0SgQ8{!&1F37?$e&z_3*B z4~C`s05B}o2ZCX#J_rm;^}%3Rst*CfQhg{Gmg>X6uv8xohNb!lFf7$af?=sX3Jgp2 zF<@A#j|Ib0eH<8;>f^z%RG$EbrTRoLEY&B0VW~bD3`_MXU|6b81;bK(8W@)9)4{M* zp80K-!KAQ+bFhrqB@ z&qE{JD3zCorM9=guvB-zuvGWJuv8DguvCx0uv9MqhNXHTFf7%JfMKa#6bwuC;$T>+ zmjJ_3y(Acx>ZQQ2R4)yNrFt1KEY-__VX0mo3`_M2U|6bG1jACj5*U{1mBFx7uL6dp zdQ~ti)vJMFsa_onOZ6IHSgO|q!&1E#7?$d_!LU@X1BN9%eLpxK&N(dgd|UvArTRiJ zEY%l*VX3|t3`_MTU|6az1;bK(85ox8%fYZzUkQe#`YJFi)mMXIslEmbOZBy2SgNlB z!%}@c7?$cAz_3)`2!^HlCNM13H-llRz6A_R^{rr7s&50sQhhrZmg+meuvFg(hNb#0 zFf7$~gJG$@2MkN~yIcBER6huYrTQT-EY#HuvBjehNXHdFf7$u zgJG%O77R=Ec3@blw+F*gy(1Ww>Yc!_RPPLirFs`IEY-V$VX58?3`_OyU|6d60K-zf zCm5FMy}+M!%}?^7?$dT!LX#K@28B4@Q4gb zJ?|_qEY%$_EY&?QEY$-rEY%}0EY%BuVX0mS3`_MQU|6ab1;bLkI2e}dCBU#$FA0XF zdMPk0)k}k6sa^&QOZ9SKSgMx?!&1Eh7?$c4!LU@X1cs%0WiTw&tAJstUKI>W^=e>P zs#gcYQoRNkmg+UZuvD)FhNXIKFf7&UfMKa#7Ys}FdSF$#yd&WcgLfi)6nJOCzlHVeLilKC-<9y<(7qet zW1xL^!XE+eLHJnko`gRN-iz>Y;C%@1Q4H=~2!9OP_appq@cx8P03Sg36W{|0e-eBU z;gi4z6W$Y!=OKhohW0}Ve;Rxk;rRDp!wH`P?MD#)4ERXGFDwABX%YS`v>!wGez@Jo z5}ad=~h0!e0WP zLHKO&nS{R#K8x@<;Ij#z3qB|1`2G=Qs7%gme&YL9l;iUP>GPl`ESb+)pAUwm`T{U4 z)fa+cslEsdOZCNISgJ1p!%}@I7?$eGz_3(b4u+-rN-!+dSAk)vz8VZm^)+Bvs;>pZ zQhgm5mg?)luvFgwhNb#OFf7$Kfnlk>84OGHEnrxxZw13reH$2->f6DvRNn!HrTR`V zEY){`VX3|w3`_MrU|6c}1;bK(9~hSE`@yhOKLCcM`av)()enJTsh)>MxN#gji-1zw zTVPnKJ78FP5h?R4)pKrFwBNEY(YZVX0mc3`_M= zU|7=ed0CQgb0H70lEI=ZIR9^^&rTQW;EY%l-VX3|Z3`_N;U|6az z1H)2%IT)7eE5Wc-Uj>Gx`f4yN)z^SwslFBrOZ9bNSgNlF!%}?%7?$cA!LU@{1cs&h zW-u((w}4@(z7-5h^=)8Ss&5CwQhf&)mg+mfuvFg#hNb#$Ff7&gfMKb=7Ys}FePCFs z?+3$D{Qwx2>IcEFR6hiUrFtG1mg>gIkZ@3{TVPnKJ78FP5h?R4)pKrFwBNEY(YZVX0mc3=16}zu{+ZFxQfw#PM%T6T?e%`FE#j|FBrv zOJALxBkfuJIH!HNBewrEr+o#bJ^W|k%x505{h##~t|a~)6h5ZRdS0FnABu$k)U32W zJJUY#N~!*_pTw&$p6&nhV(Io-|1#sAwm#=%maF^M#qb76yjps>DX+nJcDd&={h!bH zcbVlT+nep@yqxy6nB~@CJloF&IsMc-V*3ko+GqRuDQEv={g)hX!1UipaXd!kanj^C zc!dT^9_Lwa3WlY6GcYXGn}cDg-VzK;^;Te5s<#HiQoSu0mg?=muvBjkhNXH(Ff7$O zfnllM84OGHE?`)ycLl>zy&D*o>fOPxRPOL!%}@X7?$+(`wOMuJ^>4JJME>x zuv9MthNXHrFf7%}gJG#&0Srs^ieOl(R|3OQy)qb<>Q%t7RIdt#rFu0mEY+)nVX0mN z3`_NzU|6cx0>e_hHW-%bb-=JxuM38ydOa{K)$4;{sonq#OZA3eSgJPy!&1F57?$cy zz_3(r3WlY6GcYXGn}cDg-VzK;^;Te5s<#HiLQn2z@qCa!7cuuczvbM&jJP^!pO=UC z*Lc4v??+OedHfKNPChEmg=*>uvDK7hNb!(Ff7&Q zf?=sX4-8B7`CwS8F95?*eIXc@>WjdzR9_5+rTP*uEY+8SVX3|h3`_OpU|6cJ1jAB& z6&RN4tHH3Or|);#!a0hip1zy&D*o z>fOPxRPOL!%}@X7?$cIz_3&w35KQmC@?J5$ADp}J{Am1^>JWWs*eZ5LigaF zM7SheoC(Yk$GOMyICCjNqo)KcxK6;qf)*u-`(PA-;nQld1>Fo_!h>uF}_1_ zPkgFu*TskLe^rHf zWZF;Ge0|2JXnu6&`@pAaz9G|on&zKne7fcvGd@G}&oe$#^Gz9_rTG^bpRM`kjL*^h z%Z$&}d`rgXX}&e%^EHn$`(c6RUuW7c)cn^NZ>dF^Z_Bh_tob(?U!wUnndL6ke0!$- zGR<$vv|p}yUS@q(YF;$cepSB9`*%FL;9ftY={&?E5nlTz9KW|`E#dgRJ?jX^@9kMn zIDT)>2Ey@sdo~h|-`lf^aQxn$&4lCk_G}>>zqe;A;rP8h+X%<+?b%K^es9kX!tr~1 zb`p-?+p~*s{5y@^gyZ-2>>(V#w`VWm_`N;*2*>a3*-torZ_fe3@q2p?5{}>7bBJ*K z-kvDV}A9M4~(Ol-6r@73xKy#UIk>)br3TQ6#t&rw2-->81 z^R1}nGT(}8F7vH~<}%+(YA*Awl;$$uN^36jt&HX}-^yt&^R2w*GT$m_F7vIT<}%+Z zX)g1vvgR`1s%S3rt*YiS->PXY^R2q(GT&n9&T7+Z1)g~PCtq$RsZ*>XB ze5*$|=39NjF~=GZjycwlaLlnrgkz31CLD9D3E`MyO$o;wYeqQcSaZTL$66AOIo67B z%(2#lV~({Y9CNH4;h1CX3CA4kNI2$LC&DquIunjL)+OahUdbHmnwg^KSU1gOj&;{u z=2#ESWsddKT;^CW&1H`D(Ol+OU(IEX_0wGDSbxoBjt$UU=GZ{ZWsVKfT;|wd&1H@a z(Ol-(P|anI4bxoa*l^8dj*ZY<=GaKhWsZ%~T;|vq&1H^_)m-M-IL&2_jn`b}*o1sf z=h(!I>Nz%vaLlpEgkz3PAslmTD&d%8(+I~Ln@%`BPMbkE=G#odG2dnpj`=p5aLl(k zgk!$VB^>i@9^sg8^9jd%TR=GG+d{%I-xd*$`L>vF%(o?kW4iMvr zaLk7TgkwG&Bph?#5aD>f=Mj$QyKze9;eYCNHUqxn2LASka6I1~!ts3f2*>k1ARN#4 zh;Tfw3lNUybs@s>ye>jGp4UYQ$Md>4;dow`ARN!@l7!=VU5ao#uS*k-=XDvv@w_gV z^4#;fd}d1eyq2#oRnT0X?-ezd=X)j1<>Qvhn#;#6RWz6RP*rpJxTTur@^MRb&1Ig{ z&|Kz8P0i)wmRg$2$1Sxrm-$mibD2MNHJABQPji_+^);7|TN-FCbE%=`GM5@@E_11| z<}#O>XfAW9spc}5nrSX`sk!Ddms;j~I+t2yRL`Z>gkvtXB^-099pU)6r9I)8R~-q* z$1R-*#~kZSIObRv!ZF9X5{^06jd0Ad?u28G^&lK`tS904xTP22m~VXu$9(HcIObbF z!ZF|a6OQ>dfN;#WfrMke4I&)#Z7|`OZ$k*jd>cwQ=G(B8=jPk+%oIJ}MrbZ`Y^3Hg z$3|%`b8L*}GRMYhE^}#|<}#PYYc6wXg61-pCTcEoX_DqLmnLg2b7_j^GMA=mF7s!a z<}!b#YcBI=hUPMVW@;|;XO`wNe`aeg^Jh-Jr*md*M)jPTM>yupe8Mqj77&g(vygDi znMH(S&MYPzb7l$Qm@`WW$DCP4IOfc9!ZBx75{@~uig3)C)r4cttRWn8W-Z~EGwTS) zoLNse=FA4dF=sXsjybc5aLk#_gk#QZAslmNE8&H+$abT$K0qxIOaxO!ZA1Mr93w`>Sw0txzRv#`TVP)<}yzj zX)g1mvF0*QnrJTbq^aie`ByW|WzIC$T;@zm&1KHC(p=_DYt3cOwAEZb&S|H)%%Ap} z%f~q#HJ6WbI%zJSe|6Se=2#caWsY^#T;^Ce&1H^t*IedU56xwc_0(MESTD`x^RGVn zp3b+v8P)TxAK{p9{Rzi>8$dYb+d#rG-v$wm`8Jqv%(o$gW4;X~9P@1$;h1m33CDaJ zK{)2yNWwASMiGwrHimG_x3Pp{zKtUs^KCrgm~RsZ$9$VeIOf|V!ZF__6OQ>dg>cNb zsf1&`O(PuhZFI2%(vZyW4`Sn9P@22;h1mx2*-TePdMh=0m3og4ib*}c8GAyw>-iz z-;7i7HqO0WFyAb~G2a}*G2cAGG2a5hG2bG>G2aRhj`>!IaLl(NDbLNfqM0drz7^M8 z=35EPWxkcvT;^LT&1Jro)?DUW8O>$BmD612TY1f8zE#j%=37P0WxiF?T;^M4&1Jq- z(Ol+RRn29-RnuJNTXoH4zSYoN=37n8WxmzYT;^MC&1JsT(Ol+RUCm{_)ze(&TYb%C zzBS1AbiOsrXzqE0f1lZiaLl*Hgk!!nAsq9qDdCuJ%?QVQYfd=kTT8+*-&zrl`PQ0n z%(u3LW4^T`9P_O`;h1k73CDcvL^$SKXTmYxx)6@})|GI~w{C=EzI7)Y^Q{Nrm~TA^ z$9(HWIOba)!ZF|a5{~)SFXg%U);}{v&$j`Z%X}NCxy-jgn#+6}thvm$A)3p48>+d? zw_%#gd>gL0%(oGm%X}NDxy-jwn#+6}qq)qtv6{<#8>hL?F-)zD?F#=Gzp_Wxh?-T;|&}&1Jq#*Iee?49#V}&CK_7zRk+0o^P`W$9$VZIOf}2 z!ZF|G5svvbpK#2#1%zY1EhHTCZ4u#^Z;J`Xd|N^|=G#)jG2fOEj`_BnaLl)rgk!#~ zA{_H=HQ|_VYY4}DTT3|R+d9HA-_{e3`L=;@%(sn%W4>)79P@26;h1k*Ql6V{TQgJi zeA}kE%(v~D%Y56Rxy-knn#+9KrMb+v-I~jM+oQS6x4oLneA}nF%(wlT%X~Yaxy-kN zn#+7Uq`AzuJk4dk8C8ytNdBQQ@DiDtOR*`Vb zw@QR#zEvh1^Q{Wum~T}H$9$_sIObb*!ZF`!5RUm)lW@$pT7+Z1)g~PCtq$RsZ*>XB ze5*$|=39NjG2a>xj``M*aLl(xDNoKX`FH7!GgI_@YofW#x2BrQd~2q;%(v#6%Y198 zxy-j#n#+7^t+~v%wwlX)Yp1!)xAvOLeCw#W%(qUO%Y5ssxy-jNn#+9as=3U!Zko${ z>#n)Xw;r0yeCw&X%(q^e%Y5sjxy-k|n#+9ar@73x{+i2t8<6kmd>fciJ>Lcqj`=p2 zaLl(Mgk!!9B^>i@7~z<2!wJWH8$mec+epGO-$oIR`8I}d%(t^KClem~S%($9$VfIOf|d!ZF`w6Yi9T z$Nz-8;B!-+)+&j2!0(s}4-e7@#oB5c1v^B~iHq2^)67ik`4e6i+n#+PV5#=_+; z)x1Ea{W8r9W_-Ekg)+WU^THWlrFoHzuh#sijIYtWXvWuSUN*De)@fcW(|*0?#WTJ^ z^Rb!zuu=1)GwnBNJ}A?Ev*sl-?YC%tOvblrUNYm`G%uFfuI>4b|7R9I9er%3J>F)y zxz_~NXGf-Wu3rYelkleCy9mD=d^h3E!1oY-1^8aVn}hEoyao7v!YhIwAiO2`LBg*D zKSX$WIDhj9Zw2j*(=xj)=W_v9fm?*P26qT=1MU&t7Caz)G-d59G=52M&W!~1%T;^>} z&1K%!(p=_kZOvug*3n$%ZC%Y}-qzDx=52k=W!^T>T;^><&1K#;(p=_kW6fpWHql(> zZBxx<-Zs-*=52G$W!|>bT;^@7d{2&N%+=Nz)pNBi;h3xK2*+G)PdMgkN5U~zI}wh# z+L>_7)h>i%u689HbF~}cn5*3h$6W0}IOb|k!ZBBS5staqhj7f*zJz10_9Gl~wLjsQ zs{;tfTpdU_=IS8AF;@o@j=4I7aLm=Agk!D_BOLQ{IN_L|BM8U*97#Cl=P1H4KgSS` z`8hV_x%oLRGeytO@tVv0oS?bP&xxAL{G6n@%+JZ1%lw?8xy;Y0n#=s0rn$_|>6**@ zoT0hQ&zYLb{G6q^%+J}H%lw?9xy;YGn#=s0r@73}`I^i8T%ftk&xM-H{9L5D%+JM| z%lurTxy;X{n#=rLmhb8OT%J)qKUWft`MHX4%+J+?V}7n79P@K6;h3N62*>T;^v<&1HU;(p=_eY0YJR zmeE}1XF1JfewNo<=4S=XWqwxFT;^vb&1HU8)?DUi70qRSR@GeQXEn`bepc69=4TDf zWq#Jo_jG>N%BY^7wF$@ktV1~FXI;WEKkE^W`B|TE%+CgdV}3Ry9P_gg;h3L|3CH|w zLOAAUQ^GMnn-Pxr*_?39&z6K^ezqbU^RqSKn4fJ4$NX$ZIOb=2!ZANP5{~)ViEzx% z&V*xrb|D<|vn%14pWO(@{OnFR=4TJWF+Y0}j``V(aLms>DbLN%zL_a{e)iK`=4XG+ zWquCOT;}IM&1HTL(p=`}V9jNI4$)lZ=TOaMeh$-I=I3zDWqyv(T;}IU&1HU$(p=`} z7|msVj@4Y|=Qzz}eva2%=H~>>WqwZ7T;}H_&1HU0)?DW26wPIRPSsrI=d^rJ=jZf{ z>iIc?aLmt{gkyfrA{_H`HsP3`a|p-$oJ%<7=RCqOKj#yU`MH2_%+G~{V}33o9P@KA z;h3LG2*>y-ff3zjCR5_A%}EGk$>agNz?yJdbhXbb384 z#vR5z#skJ9#tSfBi18wf7iGLS<0Tj`$#^No=caCVJm2O2mn;uwJeEAJvR(lUOZAFi zSgKb7!&1F67?$c)z_3)W3WlY6H83pItAk;wUIPqE^_pN_y)hV;>P^6~RBsA~rFt_kEY+KXVX58{3`_M^ zU|6cR2E$UlEf|*S?ZB{9Zx4p0dPguU)jNSUF`eRIdkyrFwlZEY%x;VX58_3`_M!U|6a*2E$Ul2^f~@O~J5K zZw7{?dUG%=)mwsLson|$7k@4dhufX`RjF)D-DC5N#FTr?8 z#>+5Xj`8x0cgZBVejNUEbaHg&9=A6z?YlC5in3h%f4G4tEN*O<+kfIWF@7`Sw=jMy z%@sW&=W_&E;k1;-h@h2Id z%=j~mPh|Np2X+zZM)k^L7G^80B!X)f2Nv*z;i3tcpqpI_*zx%~V>H_hee z7rJXMKflmJbNTs&o|?pUC(m#wRmAh4HD3 zPh)&K<1-ka$@nbBXEQ#B@wtr8V|+g23m9L>_#(y^GrolJrHn7j@uI>PUlPY5`d5s9 zop$)+WSP&&@wr^~ulY*GSLLh^zIGlk0p_y)!|GQNrN&5Unhd@JMI zq-*B%~ma_T#r=a*IeSE?Jm3w^3_J!d| zMDn=e+f@6q`Rxme@6w*?e`n!ro~M_)q(I73{d`}V_Fats!1x19|GSy?KQdmo5WPNo znD#$0zBlcu{WGjM-T%+&_No1|kMUm^-_Q83j2~cpJF`E3W7;2N{CCC=G5!bRd5qu7 ztY=N;b`;0c`~Q*jahuxz=P><@W!f9*{!_PmnS%6k&2;zd`mq^z7J_ z|EtpbGvyxBkI#6(c$oJ5?JX>#C#n5YSQKQu2;;>VFTwb+jF)D-EaT-#_ryuZr?xA9 ze|n;#;>F><9VVyVrOwMM3omlw>zuHg#L4OQ0Et&({1nD3GhT)9(-^PH`00#SWBd%p zt22Hk<7Y8mlku||uf=$6#?NJ3&FAFys>8Ie%lP?>*JJzw#_KcwNcwzF9ajyQ_7^eU zknxKd&&iF{avL%2FJZhfZg{t)92Gd_y((Tu;y+~191+CRehSjHb^d>rHB8GoGdCm5f|_>+uJ zV*Dw_Co}#u<5L)ahViM4Kg;+u#-}s>JmWJMe}VCtjK9ttSF@P*vl)Mx@i~mY!uYF< zzsC4{#$RWA0po8lzL4=Z8DGTsTZ}Je{B6eHVSFj$?=rrO@%I>C&iD$(S2F%S__>Uq$9P@F>oI--#){94AZW4trt*E4Z4^8K1%U3yi4g z<4YKShw-J1zsvYC#@}OnIpZrBf1mMHjDNuRYQ{fgd=29tF}{}Zj~QRb_$Q2i%J^rD zf6n+O#=l^EGvi+}{uSd}8Q;eEH;iv*dt3- z_Zct6?4N*XA2A*?UV!m}j2B|O2;)T=FUELr#*b$F7{*I7UW)P3j33W<8OF;pegfm= z89$Nn3XGq`ctyrfX1o&Pl^H*k@hXg;#`x)sS7ZDP#;Y@aCgW!@em3K^7(a*c+Kks> z{5;0bXS^Qc^%=jA@dk`v#CSu-FJ`Zy|s+u@MR2OH88saQbQ=Bbo ziE~74ajvK%&J%UT`J$eCtWK0O=^M6`jO&qO-VO zbP+d*uHr_~P2429i5cvegk&xz^cc`-x0AZCge#VqlXm@QrwbHpoRu6R|<6R(N+;&rh= zydf5fH^n0HmRKy_7E8oCVySpnEEDgEW#0O%v_)x47ABnZ%W3f(rBG!vf z#Rl=2*eE_1o5UAlv-nbM5nqX|;%l)@d?U7tZ^aJro!BY97rVp{Vz>BF>=8eSz2axF zPy8bGi(kb7@tZg(eiw(tA0iL_{N6B3!!m5cFKNx4b&d0ldd3Ated9u-fpL-1(74!W zWL#o2HZC=q7?&AMjmwQ@#uY|$qlMAZxYB54TxGO2+8Aw(tBrQXH83WJ_C^Pzqj9a# z$+*twY+P@2F>Wxr8aEoWD#u|?rmDF`hA|8qXTjjOUE$#`DGu;{{`;@uD%yc*&S;yll)d zUNPnxuNw1=*Npkb>&61(4P&A4rm@I)%UEo@Z7ea~F_s$d8q19LjOE4(W2N!FvC8x@r~^~R^h2IDhhqw%@1$@s$9Y8O+tYDsGRy0pGE19R5mCaMlD&}cs8>6aux>?OU!>n$eY1S~$GHaS=o3+ey%-ZI; zW*zf9v#xo*S5C%)G*E4nvFlp{04H zSrt+Y2b`h~*fN=pm#xgJ%+_Wbv#oh`#@m_KnC;CDX2+aX*jH7NJ7>C=?D(~2C-XY9 zvw6MQ#k|4nYTjseGjB4xn>WMtE@ls?TcCQHx0=1o-iL=?W}p1=-sWvE^fhlc`DYf4bFg`@ImEor9BSSV*W?o96dWIxX_I(wxF*-4 zi}`>#9ERk#oP+xPpgF>P$Q)@t4Etl0IU2vlON=oeF~^#Zn&ZsJGS|_r664Ls%?aic z=0x*J&`D5FnUl?@%_-(H=2Y`pbDH^_Io*8ToMFCT&NN>%XPGaVv(1;yIneSIbFTTS zInR8}oNvBvE->FP7n*OHi_Evo#pc`Q67wB%srjzC%zV#WZmuv_n(v#d%n!`f=7;7Q z^CNSu`LVgq{KQ;uerj$oKQlL)pPQS^FU-y6m*y7pD|4&)wYkmw#@udxYwj?=Gk2Qb zo4d>(%-!aX<{tAWbFcZcxzGH?+;9GB9x#7151PN5hs;0BJX2VPWm=YHTaM*gjfH3V zR$zrzWW`nitDsfLDr^<8jR8)vPnD>eiW74eKncrggSe%R0xZZJle?vCgyV zTIXB!tP8CA)`eCB>msY6b+OgRy2NU1U1~M4F0-0ims`!OE3D>L3#+AdrPa#1%4%)3 zvD#W!TkWiCtoBw1tD|+T)ycZf>TF$ab+K-+x>`3{-K?9e?$*s#59=1Ir**5<%j#|Q zv2L^aTDM#MtUIj!)}7V>>n>}cb+p1T5np5thcPi*4x$+ z>m6&U^{%zdde2&Jt*};F?^~;^53JSJht?YFBWtbov9-?n#9D8CYHhGSvo>0vTbrye ztj*S!))wn4YpeCOwaxm*+HQSo?XbSHc3R(CyR09q-PVuR9_uG-ul2LF&-%sMZ~bZ= zuzs@+TEAO|tUs(gcwlGPrfu1_?bxpE*}fgvp&i+=UBE7A7qSc6MeL*OqINO6xP7!; z!al|>X&-BsvX8S%+sE5w?6P(_`vkkZeWG2#KFO|VpKMpMPq8c8r`lEQ)9kAD>2@{y z47<90rd`86%dTmkZP$XY#yiKZZJ%q`vCp&X+UMK#>`y#uceX-rhzQk^9 zUurk8FSDE4m)p(kE9~ZW3%jL#rQOQD%5H79vD?~L+wJUY?DqIYdyr}!?Cj9dzSiz! zUuSo=ueZC{H`rb68|`lPO?G$tX1j-di`~<{)$V2Yw)@z(*?sNX?SA$hc7OX$dw_kH zJ6nh#J4$r|b-G1Jl zVZUI{v|r3zNBdWYSxU>7;QDO)WqXeOiY=El*M8NWXTN68&)MtAo_^idhfplA7aU%< zH}d;mXuoMMvfs+T{;#g#|7^R(_S^Q7BdpUq_EOlIci|XG4^P4ElU&EWG0UEnY0>2H z5!>6uHK(3`$t_xDFN5jwR-}Im@gB5TeuTCA^ZkRzHLhjy8_yJZf1a4Ul$sbGqkrD- z|Hi2+Y-)IpIrHRsxYE{#F=pO~4r^m!Is5 zzuNzi+sm_9_Mdydk}OlbeClvACg)gsUYVqc;H5kRlRo4TyV_2khyLBm5A8KMeSKuF zh3g;N>r%SHUTuE@*H^>kYVDkny4qf!ncBukGC{8Cr}hT>Gkc@`xxLB$!rq+e6{p}M zf-i}ogWUq2yZzaKYqJ_|W3>(5>S)QOI-R?&_Jr5v5tPh<*S5f#f0bEsk{PI5GixHm z*Y-Bp{$xI!B7eTgKNh#!-@^Eg{O!l-)b`^t@%5?i?CdbwieQ~Oz*&rQ;quk(N5>zW zJGo5O{z#Y9_V3K>-|r9aW$bYajAw6u6#T(;NiNrC#?Ug8@_+5Jf3SCF_RWvV6l(ji zN4ds$jO2Hc8lkpd&XH_Ma(SdJ&z&o2o$STry4<69`)9Ww*8#UPSr?olw_iU7QmYQ5 z*sGkEzWqDc_!u_*BiqX*%4Pn~AOE-&`u6XEZO)$mxQ;*Bdo#Du&-T8<+n-(Y74|Ro z{`_0;SNi~r%Rc{X`$t=CjO>yBUH<&pesQnou3xgxW$!}#2Gb8_+TdKM_&XWPS~8Y9 ze|Ec*F>HrFzh|~y?tQ#|$o?a9Cg$0~F&yglH}UOv_J=zD<+kG^`DBP-Ec+wV4{wEJ z?UKu6?X`)0!>PGz@XuevWH|wjXE~I2 z${$WPTsA%q$qp^d750ga9AxVb#3Q;dVO#jE!CwE`4=!Kc8re1Z>w87*A=?*je}(+* zKgl@>wjZ}nz5Vf(Eg>p8a=;e29o%htvQx>CYyQ`^R&CwJ7!9v~j<#^0riW9U^fjC= z+u^eZIVOKo>!+V{c-G;3Sd|@oj~FYPJEvy0CAlT!zN?aNr{#}T%^y2Ge=NJFR6^>C|w}a%wtfJGGp1;JO^}%&+d$cJR5-df0lr%=RXaK>7RJ{M#?NcUocB z$+z?J$Li*fou5CJ?gz)~9iAdYdOzZL?w*y?v$sOh8YS7bzkiwC-oHC`fm1*KHo4Hb z5Vo>GW=pe=@K)Gc>EYbs^mJ}@dO5wFKF)1UU*~qGpL2)P-?`Hn;N0a5bnbQrIrlh&oqL@j&V9~M z=YD6H^MEtldC(c*JmidY9(G1Kqn$C%BhFaoQD>a<7@XVVoyVOC&J)hW|I@>h&Lrol z|5d|e=l|BgofH56JbT}Z>6W`5x%aaA^xW3}-nHy9|C#>(tiQjyJ=wngy>0t<=E^Sf zpV=S(&N`^`;F`!+Fw^%Ha-4efK^gXu-8OXz$@Sz}bNW)wDStohOmUuZraI3$)12p= z>CW@c4Ce)Brt_jR%X!I}?Y!*Fab9ueIftz_nhU<3TLJBzO%~tz*+5l=&W%*a@IN@JL{ZJob}G9&Iac*XQT7Ev&s3w z+3bAjY;nGFwmM%s+njHl?asH(4(B^(r}MqD%lX0C?fmHMaei|4IzK!6oL`*%nfsGp zodeEq&OzsQ=TPSQ1AlVAnCyf9UjBCv{C5xhcMtsE+5`BU^~h`ND~~XaJO{SGXRq?- zUu*xLZYA#t|8Q_nmz}=%I`aJ=&W+Er*4ufh=O#%XYTxvAoIfw~dO|W^_L80>S8@LA z9Jyn;Ez{SMZIjQ~bLYxlOV9B?IX(>jmNETI7`F+Z&!z_{I3;($*NfyWn*8k(zHTPp zy`!F=X17j=^sC*;czU}0ivaoi0Xw;dYdg#o?lAL6{r@xj>`3p?{xeIFy`;B3IYw{_ z?(=_QIS)cF*=76_J^oXDWY;P?_V@ZuUJXl*V%&#vJXr!>OH57~4zIKI&b;%CZIbt* z@W`W%wZF4|*nf6@yr%zTr~k8K$&&t^UXyw8lG-QV9ZtjabCQ2|{I9Ja-g5F5#dX6> z`fKz5o!0U=%1uTg*28Oe`1&3G&K=~@CV%6U$$!gxS@-; zSLDW-%fEBmwK2BX1>9s=1>Hh!VYi5Tlv~s-<`#F4c1yT<%;OYl|0jD}UM8=Lr!R5u zX7@afrKieqoLlA>_3r1tzO~tVb$MpsW1Zw39{Tm|f4%R&)^bZGYm>CaOS#AM9R0Cd zvJd~-68P5Xvn|;V$?c7o((tc-@F!V%2lD+r*+wply8ZdJIP#S2CvW7rdW>7rJ=QJd z9tT5d_jtFAE0>=9F6)+aPjJh-C%P5fliZ5#$!;b06t}W_s$0c9&8_O5?pAZpaI3p# zx;5Oh+?wv$ZY}p5x3+t(TgN@mt?Qoe)^jg#>$?}a4cv>|hVI2~BlnWbG8(&=x=q~d zo{(e7a+6DW6nC%(!4aJdXxZ`AFfBbK`vBj+$PV(It<{I$<4SrL;xf0Xd%4@py~1to zws2dzSGuj-tK8OZ8@H`{wcF0U#%=F*a67u!x}DtX+|KUxZWs3kx2t=j+s(bn?e5;} z_Hb`;d%Cx}z1-ezANMx5uY0@O&%ML#@80PSaPM*lx_7&S+=KIo2cA96>!54)q>(e4=c5qGTns5{Pm%pLDO?oM!@a3{J?x|7_e+{y0K?iBYK zcdGlWJI#I0o$fyG&TwCFXSy%Cv)q^5+3w5k9QPG>uKTJx&wb6E@4oIXaNlqjx^KFR z+_&7t?%VDX_Z@es`>wmpea~I)u5een@4Ktq58T!6hwd8pBX_O)vAfRw#9i-x>TYm9 zb2qx5yPMoE+|BNn?iTkecdPrgyUqQ^-R^$t?r^_zce>xZyWAh#-R_U>9``4AuluvR z&;7;S@BZo@aDQ_Ty1%=J+&|nrS9peJdX|TOeByeZ=X-${dXX1<1w46M%3C@4EwgL5 zSUkG<^}P$d2Hr(pL+@g*k#~vL*t^tg;$7x7^)C0Cc~^MN zy%t_e?@F(gca_)LYvZ-`uJ+n_*Ldx{4qiv^TCbCLo!8mB-s|Gs;C1zG^tyRBdELF6 zy&m2zUQh2*L+#_4RJ|`gwPF{k=QA0p4BSK<{pEkav$a*t^#o;@#&B_3rnE zc@KEQy$8J!-b3Cre^9`(j~k9p(0$Gr*O6W&DcNpF((lsDOX+MD7% z<4yIR^`?2xdDFe;y&2vM-c0XBZPIpLhns) zk@uFj*n8Vs;=SW7_1^WCdGC44y%pX{?|pBT_kp+C`_NnCedMk6KK9mmpLpxNPrVJ^ zXWmBdb8nOPg}2%J(%a&F-iV>_5BO|2L45U zL;qsGk$;Kb*uT_o;$P-B^)L6EW%|3qZ|=A7Tl!b}tuoiI@>~0D{I>qpemnmfzdcw7 zsE$zALUr=5^E><3`(6AS{I33uemDOnzq^04-^0Jf@9E#__wsxDef-<}zW(igKmQKD zzkjDcz`x5M=-=%Rg87BG#~ksko^N0HP`@{SP{NetC{s{jef29AgKgu8NkMSSz z$NG=@_6>K@t^Uh`p^2){OA1X{`39}{{?@h|Dr$3 zf61TizwFQPU-9Squfo#i`LFr&{nz~k{u};6|4o09|CYblf7@T;zvC~3>F@f>{P+Cj z{tADk|GvM<|G;1Ef9S9AKl0aR*7ak5o&Sly-v89!0M|eBH~OFZoBS{Q&Hk7E7XK@M ztN*pX&Hu*V?tkm=@W1nS`rl{f+vWe@?}o7-{XPCq{$BrQf1m%0zu*5A?12B9e=vXe z{qUG@5BY!idASCC=^@@pC!Vf za8LxM9TgOX-_79D?|~==W5t8h;S>C_hob|0O|3+5Oi&Vg$r&2MHQDZ1W!ga>zNR1< zM5*AopmcD2kS{Q$Oi(t!+qqnDLQp<9F{lun6jTgO4k`tw1eJqRgDSykLDk^&pjvQ7 zP(3&^s1ckM)C|rJY6a&6wPDV4gF3-^LEYf|pk8o6P(QdZXb@Z!Gz>1z^ooDMlshyE za<3ox8m=AxgQ<`A?n(N~dwRTvpH##l8I!H>z5~ayj}Q31ampn@o!^bBqddIi0MKEZ84UubcA&@Z?n=pWn}3<&U3XE@vy z3=HlL1_k%P^v0RLRvjFSw(bpv1os6)gZqPF!2`kY;K5)-@K7)^csLjpj1I;Gj|5|b zM}u*}W5M{~@nAymL@+UUGME%R6-*AE4yFXp1XF`&gK5EY!SvwyU`FslFf({Dm=(Mf z%nn`-<^=LJ(pQ4HhsXYKUWMf+uS&o<3+wk9)cl|+e4m3HUJn*z+F(1mrs?0J5fN$^gv^ziNQZm z`ry-G!{I5PrFu)2r*6ZNU}NxkunAgz5p2%yQLfvU!It2wU~BMour2r|*dBZv>n3Him7S8@BLDhjcHX}-mL!QjPkPC@Q}NcpL1vY_%-&v7bLaTyuKoFa4K56gsQ!*byXVfpaHutIoJSTQ^~tQ4LSRt`@MtAwY8 zRm0Q6YT+4S_3+HFMtD|OGdw%26`m8;4$lqigy)5I!}G&>;RRv+@WQY`cv09eyf|zW zUJ^DAFAbZ7mxWEk%fn{j6=CzRMc6XDGHexI6}ArBgl)sC!*<~{Vf(N{*fG2|>=a%X zb`Gx(yM#A{UBes0ZsAQ~_weShM|exvGrTqI74{DMgtvu#!`s7t;T>WB@Xl~Rcvm=31L5%S!Ei+QP&hJtI2;v@4#$L#gk!@;!*Stb;rQ_J za6c5+aY@+B>|4B*GOTGN}mNDsY3UYHNDJiE@`&mxOZI|1dJ`LA>@)4HYRN14{ z{!RCs9?xCAeobvF`_8Hy%U(~m5w{=5^>sm)mEA|leN%ENr)2MGvMpt=>FfHq>?PY5 z_LJ=k*Ch+6(mKQ>Gr zw`K43rO4~aRBD^EbI@b|sYhJ@UC#f`JwP87|GeTo&OhJlKh-03=5U6~OwKsG%)OSL zBB$oAWBRPtdyNuXjsL7gGA&tyBVXbi z=~l9b?EPnUEb05NT;j9GztWy+`}cdGdj0=r&%tA?>EZL?jPQkUX82+_D|{)O9ljjS z31124hOdV6!q>w2;p^do@QrX`_-42$d@EcWz8x+J-wBt7?}p35_rm4jig0E4ez+?9 zAY2`O7_JFF3fG1ohwH*m!u8>&;fCs9QMKrdsCsl}R3kbosu`Uf)r!uEYDecr zb)xg4y3zSjz376desp2fAi5}O7+oASiY|#7N0&xTqRXPD(dALI=!&R$)FNsbT^Y5C zu8LYmZKAf()ls|Xny7u$A?g@i8+D4Vi#kWwM_r;DqOQ@6QMc%(sC#sC)FZkj>KWY{ z^@@5&eWKf^k6h1 zN*_lLMI)n!qfwE3*I;xsCiNvC$$KlFgJX|GW1~l-anWPZ_~`LyLi9v5F?uqZ6g?G9 zj-HODM9)N1qi3UO(R0!C==o?y^g=WLaD3bpYQ&n;p-N;k${+H5_x4clDBKc!@`~ysj?g0rqdg^;?OT zh0$Lhgh=Lj^WT^|nfE_=xhQpB=I)EPqQ%kM(URz$Xle9rv@CirS{|*4Rz~kftD+C0 z)zOF1n&_iwZS--pF8U-|AAK5ah(3!pMxRHUqA#M&(U;Md=&NXJ^mVi?`X<^QeH-nF zzKeE7-$%QmAEMpSkI|m!r)Y2VbF?q|CE6eT8Xbs!iw;J=M~9+6qP$4NMr_7bY{yRQ z#$N2lK^(?W9LELXf^ng^a9kulDlQrqi;Krc$0g!p;*#;PajE#YxO9AcTqZ6Xmy1t` z%f~0i72=cPit)*DrTCP%a(rrBB|a^#8lN6li_eIw$7jYh;M zxOLnnZW~`6w~Mcd+s7T^j`6i|r}(-yZji?}+=ycg6$ayW)ZI-SMFKo_KJ4Z#*QvFCH4-9}kNkh=<1y#v|f~ z;*s&g@u+xoJSKi59vkQ0a{t;j?kG#f+(Tn^Ln$DWI)$Ir(z z;uqqX@r&`S_@#Ju{Bk@eekGn8zZ%brUyJ9*ug44GH{ylyoADx;@2z-o{C2z~ekWd< z-^=*;-FR92Uc5YB5wDEjk5|PX#H-^E<2CU|@!I&~RJ&vi_{(*f*0|R`iPy)U#v9_# z;*Ig=@x)XwoASrMh&RVy##`dA;;r%5@&9A*D&V6!p8vbsyO5APcLx#(ash(71cJM} zySux)mI4Ke6)$d0DNvxeI}|AHu7y(IKku&Biy{U3Z~M#bXJ)@UJG(nOyHD=PzNPtG*>F3Ka0l+h{qQ8XKOTT5#S^yHn-ho!;W$o!Ch69ua2jWD7M~0wz~MYD zcwb|RF%I6+pCZOE5tncoS8x^Aa2+>r6HkUG$5Y_JcnF>nPlbo#sqr*;T09-@!o%?N zcm_Np9*$?iGvitCtavs&JDvm2iRZ#|<9YDBcs@KoUH~tMN8p9GM%i|UBig+cwGF}C*idVy{<2CS_crCm(UI(v>*Td`M4e*9|BfK%* z1aFEr!<*wR@RoQhyfxkiZ;Q9X+v6SZj(8{VPG`I`-UaW9cf-5mJ@B4*FT6M22k(pb z!~5d{@PY3!25e1e@p1Tg zd;&fZ{{o+ce~C}Vr{GiZukdO3bbJQ>H9ixch0n(4fPd!V-{AA``S=2SA-)J-j4#2z z#h2pC@a6ald?mgLUyZN9*W&B&_4s#q6#hNF0sjHth;PC-<3HkC@U8eZ{3m=nz60Nh z@4|QEd+^wAYVXDO;rsCe_(A*-ei%Q3AH|R1$MF;RN&FOk8vhwTgP+CE;lJSL@eBAx z{1Sc{zk*-Iui@A68~9E97JeJQgWtvP;rHS zL@pvXk%!1jqBv24C`pteN)u&>vP3zeJW+wDNK_&! z6IF<+L^Yy1QG=*S)FNsVb%?q|J)%C*fM`fGA{rA-h^9m{qB+rmXi2mpS`%%EwnRIk zJ<);aNOU4P6J3a|L^q;4(Szto^dfo_eTcq9KcYV|fEY*&A_fyfh@r$VVmL8^7)gvG zMiXO*vBWrHJTZZoNPIy|BEBRh6H|z(#8<>LVmdK{_?nnW%pztJbBMXbH^e+*KCysU zNGu{26HAD1iKWCcVmYybSV^oRRugN8wZuANJ@Fk8MSM?eAbub=5}Sz4#E--lVk@zY z_=(s~>>zd$yNKPy9%3)CkJwKfAPy3Th{MDY;wW*9I8K}(P7Y)CdD8}MYi6PMg|0^Ew zoK1h|dHBca$326Od42KYQ?cKFOQ^<5e_XjsDBmkj=>5F7J&9k^gX~H6B72j4$i8Gh zvOhV197ql#2a`j{q2w@fI5~nGNsb~%lVixS-ywEauK$nze?she zmOUo7Y>fVnVeC=xeF^M+Q0%XhSzj5;fbAf6lDo*=9$#Zv+$QW7Oo3Z+sSrBeoFQpu>~R0=AX3ZYU`si;sYHI;@+OQoYK z+Fg{33Zv3f8K{g@4O=*siONi6p|VohsO(e@DkqhT%1z~=@>2Pz{8Ry|AQeFsq6$-y zR1vBuRg5Z5m7q#erKr+W8LBK*jw(-8pej<8sLE6osw!2Ds!r9QYEreR+Eg8?E>(}J zPc@(#QjMs_R1>Nx)r@LRwV+y3zENsLwT4t1pte*ysy)?#>KHB8r}sA0iRw&sp}JDt zpg-NI9#l_=qX+N07rwh8UP*MVx8Z|-zIp6Dhw-jn9sU2j^#n{Hd=WGH?`^ey$~^Bq zh3IWt*s}anwv$j@Ry%JC)*IuoZL_x9`1Y^S^?ZA?gi&;xJo^Ic>kpQ6PrUU<*Z&ag zd*9ph_L0@rhm`lM!Q!jMiw?%S_K@X;dXB%oeyPWco;E)B9I)QZ_r8tqeHY#nd)7sY zzK8Z zx8z-vCjIuUlfVmPV{nC-n-H<%SYGttfL-Xf?>U=-c%o|FV&CgPYs|3QiG_$ z)DUVYHH;cgji5$Sqo~o;7-}pvjv7x*pe9mZP?M-Hsmat7YAW>=HI151&7i)fW>T}L z+0-0rF7*vHkD3oh=mpe5Y7w=VT0(tGEv1%G%c&LAN@^9gnp#6y=Oo_WT527&p8Af8 zqQ0j#P(M%`sZG>o>PKn|wUydN{X}i2c2GO1UDR%B54D%tNA0H$PzR|))M4rfb(A_r z9j8uEC#h4^Y3gU{40V<|NBu&br!G(zsY}#l>I!w0x<*~6ZcsO=Thwjp4t1BhN8P6$ zP!Fj`)MM%i^^|%>{YpKjUQoYLFR9`$@kE+O z55zA`oG*!fiQfb9OB3hIzuPbITc5BralGUAjrad_?0D7(eVxc#`tyH7$lDSu|Cbp4 zhdltx|0Rb1VGjhrtDJ#!5RKCWP111Fl%{EhW@(P*X@M4LiI!=FR%wmaX@fTDWOQ;m z1szO>&?)IubSRygPD7`q)6p(Ej80Eypfl3pbS648orTUyXQQ*zIp~~pE;=`zht5mq zqw~`R=z??vU5GABN76;;qI5C3I9-A+NtdEa(`D$gbUC^_U4gDhSE4J^Rp_d8HM%-o zgRV)}qHEK2=(==0x<1{2ZV2`?q8rmq=%#ctx;fo~Zb`SIThndmwsbqXJ>7xsNOz(; z(_QGUbT_&?-GlB)_o92#edxY)Ke|6XfF4K>q6gDM=%Ms5dN@6T9!Za)N7G~IvGh24 zJUxM)NPj_3qQ9gk(^KfF^jGvWdOAIW{+gai&!T73bLhGBH}pJuKD_|iSV%9T7t>4V zZ|SA-&y`MfnAEXb_hv_5qQTiBtoIXLHq)*YO>7VH{^jZ2G{R{N@Jbi(_ zNME8a(^u%L^fmfAeS^M9-=c5Rcj&wHJ^DWVfPP3nq94;w=%@5E`d9in{eu3Deo6mM zzoK8$Z)l9MF?I$q4#vs&F-aJICV)xG1TsMk&JYaAP>l7Y4-K4gBbMP9o)H+4kr++6V7B}GBa72 ztV}j0JClRS$>d^kGkKW2Og<(*Q-CSRL@rZh8yF2j^% z$}#1c3QR=^m6*y*6{advjj7JmU}`cZQ;Vt1)M4r}^_cpMb&PAkG-MhvjhQA)Q>Gcy z9BgR8v}9T_G}D@C!?b1EG3}WSOh={@)0yeQbY;4MP2It&9!yWB7t@>R!!S%Z-IwXd z^k=%l$P8cxf|Y~7&cVzOW+?b%7&Dw1!Hi@^fpnZwKl>lNl3=)pYb!+eIK7cdK%Ma*Jm z3G*$plv&0sXI3yP-Tm?&zdtr!#jIx5Fl(80%zEa#1Z$3BzGpTtKQJ4kX>4LPGe0t0 zn61n<<|k%5vxC{m>|%B^dzihm3dCk0GFxJM}S;RV6C+o*1Vg1dG|R9o%dtEw zup%q5GOMsEtFbz3uqK;~P0prZgV_)^C7X&3WmB_h*tBdq*2RXg>DdfyMmC(y#Aar* zuvyt`Y<4yWo0HAO=4SJ-dD(nyezpKxkd0sqv4z=4wg_94EyfmSORy!`Qfz7Vfa6`^ zU87irEz6c;%d-{OifkpeGFyeM%2s2mvo+Y7Y%R7nTZgU7)?@3l4cLZkBepTygl)<; zW1F)r*p_T7wl&*^ZOgV}+p`_mj%+8kGuwsj%64PBvpv|JY%jJq$ojB-*?w$)b^tq& z9mEc1hpBi+?0j|syO3SPE@qdo-?B^DW$bcx1-p`6#ja-8uxr_M?0WV)Hj4e8 z-N639Ze%yHo7o@PE$miy8~YQxo!!CiWOuQ<**)xDb|1T+J-{Ah53z^YBkWQ37<-&O z!JcGKv8UOe*)!}}_8j{Qd!D_(UWC3~VlT5-*sJU{_Bwlmy~*BUZ?kvUyX-yoKKp=u z$Ub5pvrpKk>@)UP_Bs24{f&Le{?5K)U$bvmjI(ie4sj07$@y_fIDamHOUeauK^)Ez z9LZ4}%`qIyaU9PHoXAO>%qg78X`Id(oXI8Ql5;7zU@nA9$)(~#xzt=5E-jaib8%r@ zdM*Q(kqhTCahbU+Tvjd{mz~SO<>Yd4xw$-CUM?S(pDVx>+Ho-MJoIPp%i&o9n~%<@#~` zxdGfjZV)$^8^R6chH=BW5!^^_6gQe1!;R&}apSoO+(hmRZW8w;H<_ElP36Agrg77` z8Qj<0Ol}r8o14SU<-XzOar3za+(K>#4kaqBr} zjI|@Z&kjY8zvH5~@3{@!58Os>6StZBk=w#;<+gD@aof2a+)i#6x0~C;?dA4y`?&+$ zLGBQDm^;E9<&JU3xf9$;?i6>L`OAao4#U+)eHl zcbmJz-R16a_qhk$L+%mxn0vxK<(_fBa@LmloO{9j#=Ycz=U#EIxi=if+ju*Vcn9y~ z{rDujKOev+yc0LE6lh4I_yzKeX zQ^GtqpNG%O=i~G91^9w|1Yd|R9K*(#PaSzz9rnalUL;?HFUl8#P@FHpm*h+FrTH>^ zS-u=!p0B`Hz8Y`6hgzMl!PopF)Z(pBo3F#yg%<1a_4x*TL%tE;m~X;2 z<(u)%`4)Ujz7^k^Z^O6c+wtxB4xrbO@5FcJyZmWmSH2tHonPqa0g|44FTOY5hwsbx zm%dg|t z^WX7N{P+9@{s(>|zlq<>|HyCQxANQgpZM+k4t^)Ui{H)f;rH_U`2G9={vdydKg=KD zkMhU(&1%!e^giuH* zEJO-LgrY(*p}0^&C@GW@N(*I#vO+nbyih@?C{z+E3sr=wLN%efP(!FG)Dmh7b%eS? zJ)yqPKxim55*iClgr-6>p}EjPXeqQ3S_^H2wn96hz0g7ED0C7!3tfb+LN}qi&_n1c z^b&dteT2S3KcT-cKo}?t5(W!HgrUMPVYo0t7%7YrMhjzvvBEfEyf8tSD10GI6225B z3sZ!t!dJpHVY)Cw_*$4L%o1h`bA-9VH^Mw&zOX=8C@c~d3rmD=g{8tWVY#qESShR$ zRtsx{wZb}Kz3`n7C44Vz5PlFg3Y&z@o-x3F6t)Okg>Ax5!gjZ0hp zeZqd>fN)SaBpi09j|fMFV}F!AE}Rff3a5nA!q37PkewCI3BL&Eg$u$(;nE*EG4~*s z1uIxvzx7M#nUjQb5-MTI-?x-U?_Jt&v(M9-y9*!1-qxG{Zl2F&^CX?HK*+B7SUXdRD7`+lo(~9)0`~+BX*Qp|~FVR1)7e_~L8#e?|7! zw{ee${(AZME&2A5=w!bt{FUIX^?j{KNYA^M@Yd&x6Dko~9`_5iucLiwHn#T1q1h(w3z6#c{`qQ4j*CKUt4AQ2Y{krXMB z78#KhIgu9yQ4}Rn78OwyHBlE0(G-)3$;A|6uoxny6jOEcAOEDZH4g83zBZefUCbfo6myBW#XMqOF`t-UEFcyXBg8^tVKGuH zA{G^kiN(bdVo9--SXwM2mKDp1<;4nOMX{1tS*#*f6|0HWMemr`5NnFH#M)vVv94H8 ztS>eY8;Xs@#$pq(sn|?xF18R`imk-fVjHoo*iLLOb`U#?oy5*!7qP3@P3$iA5POQf z#NNL8V!R3T@!s{?>5V_W#`mki+g{ALo}-W0SL`SD7YB#~#X;g=afmon90t7~E{+gK zilfBQ;uvwPI8GcdP7o)GUx<^$FU84_KSi7>ekD#5r;9VhuOU5CoCSQgI7gf-ek0Bk z=Zg!(h2kP{vA6`}--=7cW#V#ig}72&C9W3Nh-<}l;(GBrF-rU%@;8V-h#SRC;%4zj zaSNokirawy6MpaK-A|F#S7v^@sfC1ydqu|uZh>i8{$pz zmUvsdBiJcyl^8R8SPiem*B@0rc59x#_;^g)tduYB^Pt!k#h;GudCLx; zo$>3(FZGr3_A4O9{E5F*T&d3!)8oAJ=*#M5(R~^pw)EqZe=6C` zKfV#{`4Y0%IxE%5ZhbS`>kaQ-!5e$!9}-)&TVJR1{qsB-Pyc;3SZKNZ-5r}Echo@q<^V&TtVoTpAZM|dYZQnOLVtUV;?~C83=1-;LY1R8C zwkQ6Ox~=lQo_WywmG|}cuax(dwvgAOF{Qp#%ocp|ef3Kh3h?r!P>=Ke3 zl2h`Nl1TnifRt1Ul!7E&A|z6xBwAu5R^lXH5+qTQBw11Qf4WOlvTrD^mg-1#rFv3*se#l`Y80#e=reSowg!1S1+u5t1XM(b!2~6Tk++;Px~GnuMa(Oe09Yqd;j_^&A2sKD@ASG%>w@}=bf2?t8<34J~ZspzSm}4BDPoishwzqfJ`hFU1 zCGVJ8eQqbUhja(2qjb`}8by4zct^X2&GJ*+F?zpt-=%fYJ+tE0?)lgI`R9MkS>W}N zHS^xUDl~B73Eb<6J&ud+S!u1|k}h8%bSWn-?!hg?Dy(^p4gf}9`E|*)Wttm ze(`uOzHKhLUp@OjYptc&7J9As# zt&gw&v2_yDf87J0Yur8c_KPw4mX-ab0n$KeP_&Wv&Gq*|UVE*X^w)dsE9tH8bHxd7 z_13Y=+VVZUjyv-`Hoj~A$Gn;xJ>~1;+tBCfx79b#vDq3YZ?MjQtn;?$_Pncq(RDuu z{%ifWEb;WJu+v&g(Gz_8Iq&MP7#3gutTR>5s82vK+y5LrOsw;59*F7xPnU{+yZ3DK z*1xg0+4OJ98!QcxhDyVv;nE0cq%=wzEsc@JO5>#Q(gbOu^o2A@`cj%KO_8QbUrE!X z>Cz18YiXu5OPVdsk>*O@Nb{um(gJCrv`AVkEs?&JmP*T{<Cn=%rE9Sq!gghF1ye&P?o;YrfC5e6(A6p~- z^mg|V(3%Z9;N1P7W2gHJ-r8Thah`-*E(hJ@`F|eYtcb6F3H9dxLms==9uLNreoQ*% zmSoj$dCw!Yj-Z~koIU@5KHLK_*IeEu?Q)-)e2l&EmEGe$(tD2*(w|3+*k?SxHO79gIs}Ejlq(&an^Q z$H%S1EAeiv-fhWy*62webnJ%fc>Hdw6_3{Qs53^oeKeKjjRFx&QX`)%xdZ`N->8>$aNMO7y^|^+5FbpD?y&u{DR^ey;LqElaG;@<2k@ zwY=U*=;#$!9{XL@K+F?j%j2fKZN-g0?%0^{eEV7sN=x-4Chu1eRW>(UMBrgTfX zE!~msO82Du(gW$C^hkOvJ&~SD&!k_a=h6%5H|eGHyYxzWExnO2*(Td%Bs*lM>?bFY z{pA2TsT^}Ay({INy??E*|HM-7w-x-|T3%0CZ*BOl#{V72+p;J2-pTRqrQ*gh_5c3V zl%OfFZt9evxOYxGwgk$7^4qMk)_6}0|2rOtJs19Wdj9Y4Y3#B46zRBL`4qJ#){*Fe zL=XHEJYbz4SOHeF`vXN!V_4kn|F1|sUr#;Oyt{s{)v-0b^29jN1Bo8+dBD4!{N4ZT z{+{-{y?fi0A&*X6dwgXr6gS^redpA1bw;|9k&m+@}Tqeu>9lA7b_U_wT>1`K~(_-u#68@b{1Q$Fvx?7vBE8e;l`t z|7A%^Ih7nLr~an{r<_a9E$5N*%K7B{ zasj!Z93dBy3(Jvm5xJ;bOfD{$kW0#?kXy>FCA1LZ;TV0nl@@e^J`HXy4J_qSv+%ixrhH4jE#Hyv%J<~^@&oyy{78N*Karox&*Wd_=kg2rH~FRf zyZlOiEx(a5#irO5q&O6(;-@50{FMMDsS>CJDY!x?q(UjQ!YHi5DZC;mq9Q4>qA04O zDY{}P7-lNTl;lbZKo}ORgeWPMR7$9lT1lg%1z9@9rGzQzl?+NoB^=V3l*~#NC99H6 z$*$y3aw@r$+)5rLuaZy6uM|)UDiKN{rLYpI6j6#Q#gyVo38kb`N+}H`%P3`)a!PqX z6_kofC8e@b1#nd$>sL*wuGCO!Dz%i_N*$2ZRq84Al?F;frIFHDX`(b$nkmhd7D`K{ zmC{;iqqJ4pDeaXGN=K!W(pl-EbXB@3-IX3nPoy2?Wsovh8KMkT zhAG395z0tqlrmZwql{I?DdUw1%0%T0Ws>rxGFh3TOjW*8rYX~v8OqnnOl6ibTbZNG zRlZT?Df5*D%0gw4vRGN7e5))~mMP1X70OCwm9kn{qpVfdDeIN*lqltUWrOmAvQgQj zY*v0$wkTVbZOTu|c4ddMQ`x2LR`w`+m3_*7<$!WfIiws`jwnZ!W6E*mgmO|jrJPoN zR?aAAm2=83%6a92a#6XYTvo0qSCwnZb>)U~Q@N$wR_-Wwm3zv4<$>~0d89m6o+wY1 zXUeb2bLEBdoAOfmU3sOvR^BL>YE$hhQXQ&O^;46m{%U}lR1H*vR9q!gQl(T{WmHz> zR9+QSQI%9#Ra8~gR9!VxQ%$BOS5v6LYKWRrO{Ipasns-US~Z>OQp42rY6dl<8m?wi zGpkwDtZFtjyP8AIspe91t9jJCYCbi;T0kwRMyQ3iL@lZoQ;Vx5)RJl`wX|AB zEvuGO%c~XCifSdbvRXy0s#a60t2NY`YAv<4T1Ty`)>G@N4b+BeBek*GL~W`zQ=6+T z)Rt;1wYAztZL79Z+p8Vaj%p{hv)V=Ns&-Smt3A}7YA?07+DGlH_EY<-1Jr@)Aa$@h zL>;OQQ-`Y~)RF2ab+kH09jlI0$Ey?6iRu^XB=t*mvN}bbs(z(TQ>Uvl)UVZ<>MV7( zI!B$WexuG)=c^0Uh3X=8vARV4R$ZztQTY$9x>wz&?pF_}2h~IBVfBc5R6V91S5K%X z)l=$e^=I{rdR9HB{-T~&FQ^yQOX_9yih5POre0TXs5jMH>TUIodRM)t-d7)}57kHN zWA%ypRDGuYsyTC6lifJ~@t|85#IkkPNpO!@P*8;SpTA&uB;ToZl z8l}-1qp=#N@tUBCnxx5^qN$pu>6)QkQ%xCuu zw8mN!t*O>bYp%7>T57Gd)><2_t=3L!uXWHmYMr#s&}J8{tJY2HuJzD*YQ41HS|6>i z)=%rN4bTQ^gS5fg5N)V7OdGC^&_-&bw9(oaXmhMKP8+XH&?ahMXp^)rwaMBPZL0Q_ zHcgwZ&CtHqW@@vv+1ea!uJ(;KPn)kT&=zWow8h#I?OSarw7E=MuC35kYOA!>+8S-G zwoY5GeWyif-)kGRAGD3yCT+9!qqarcs%_JL(za_mpv|4yE^W8AN879I)AnlzG;1y$ z)DCHfwIkY5?U;64JE5J_PHCT7_F>m&K33cNU*==$d7tv$w!HBkc)~2<;K}tq-xL3{ za@Lr5e!lkKb%o1Y;-4Cy)_&H`XlJ!^+ArF9?SghuyQI~yUDmE>SG8-}b?t_BQ@f?z z*6wI`wR_rq?Sb}Cd!#+qo@h_CXYN`0PwiQvUGL)o@3jg~9DTjSm^r?bRjV&$Ax}EG z^nF@R)cGfPAinz>pGM~CUCfyh^Z7YTBmU=mJ!Nay;(sRClbgsBJ@ENG@G;whwP$?S z?_=tJTUqaR_qOc|gEz<%fB$X|#GC`Z`_&%gOUKmnrasU2FFx#>1c}-n4_Nas<~Ja$ zl=lvmC;s$v^;hk=_Cotjd#U}dz0zK5Z!}D|>2@9I4&ABy=}B~dJwQ*Y2kJpOt`j<` zQ#!3PI;(R!uM4`UOS-Hpx~glst{b|kC)1PbDfD1HL{F)w(nIysdKx{go=$h^VS0K! zgPu_j*E8vv^(=Z;J)53g&!OkkbLrmwl@(uf?E7;apuLW`!RrO%h5xS}C${VJKyE#co>$MOV_1H@fL>6K z&^+>&lUQ{op7uQSZCG}EzX}yeIRxhWQ*DL51^-6kWy^3B{uclXra#ndb&j7-( zg23Y|4|jb2D34(kL8r1?+iQhY-t+U6$FN9{c-qG>t9`4yrBNQZw|rH=aohLUk6|(G zkJ-M*ez?yBHF*19L-+OHYCmTAxc&dbV2I-S@59RbYWf_gBv$gR)t1FPfllg0pC6&^iy|!LQudCP7>+22lhI%8t zv2J~7%Tv0E-c)a)5q%*^ojZx`Xv2JeX>49 zpQ?YQPt&LCGxV?Znffe!wmwIntAC@<)9338^o9B&eX+ho|5jhBFVmOnEA*B6Dt)!S zMqjJ1)7R_Y=~4Rk`Ud?6eWSif->mJ94-e<{ z3;IR(zBPW+F6o!`EBaOantolsq2JVR>9_Se`d$5=eqVo}Khz)TkM$?|Q~jC#tNvVn zq5r18)PL7s>96%SI%e1myMYXc;WYe=B!<5cU?epHjUWRzysZ%iX;21jFa~RI25$&| z=6EzjLy9Hwmh{AQQeTLflbCuR5BR+IS5fS}j5i&(zOqsZ4Dt7#N?Dw|f ztKBP!9s4vALY@-d;{HNCE^IJ^xPqjC$UEYz5X@B$)jgGxq-g)3n$ByH+ z{>R9o*Zr~eTji}$`zQYr@>TS?^yzWwc+WiImwx)z z6KnK&z`IxZr^g9xLvOR&D&y;^r-p>nR!-bjeEJ`XVz(E)ysw@3rSa!`b$z+f`{&7x zo@ZfinfP-*{r)qd{mH*u{&)K+_9*`KbbQZ?N3YGdB19tkH+mrYSpF6K_xCxbm4AQx zi7kFA4_G7lsp?9sC(#4{Ru9D7qs2`9Tl-Wh0RE_h*{Ju zW)?R~m?h0pW@)pGS=KCPmNzSy70pU!WwVM|)vRV#H*1(R&01z{vyNGwscY6V>zfVC zhGrwPvDw6IYBn>Qn=Q}~cj`9M^7@p zG$)%=%&F#A<`ia{Io+ILer?V)XPL9jIp$pR8*`pH-&|lWG#8nR%_Ziy=2CN+IfMC{ zS#GW{SDLHL)#e&=t+~!zZ+>S+nctfm%$dv&=0(8^^N@MiJYpU-kD15K6Xr?tlzG~m$IN4XHqV%6&2#22=6UmidC|ON zUN+}5SIn#CHS@Z8!@OzUGH;uA%)912^S=4Od}uy0ADd6ir{**BL-#d_`u{u+{4HB}7>~MQx_%D0lU31i*P7MF69{6K!*f2YWFbC#@RWg%c z{#XE(6kZGt!f=ehNQ}a0jKNrp!+1=gmPGUtO8aMs{}o$ zj8(y^V%4zfSPiTuRtu|*)xqjw^|1O_1FRv|2y2Wr!J1;tu;y3`tR>b8YYkUYO_d;)8FLVdlk z-tpAbCmvbc9`(igVg0cI*g$L$HW(X%4TbG1H8$+evcs_v*hp;DpXt%q7;LONH4Yn( zO#n0z`vRMUeThxRreIUCudr#@bZiFp^`9j^ehWyf!RvvUf6gASBz8OtoBd85Uzs@{ zpzAATp}BvKZ|phs4K@#(A1ya#xr9;+qLqkUV`4hd1Bo6;^gyBq5uL=Pl-AkhPf9!T^+q6hwo9$1JiO1!rFKkR`Iy?dCb|6lUJ;t#%i z{9o#QVqX>~d}H5pOTRtbxbNt`p>Eww?}k~o#gjqsN69b;vL5V6hS|^$kP1cN@Tf=O zWLezY%CT@k3_}-y+t5Yec613iLYIO2qbtCZqN~_7ctj!;UB@Cp_Xcnqx(VElZUOg4 zx8ZRG4BY{4LwAAO(LLb)=sxxU9$!G{p|!#SdIa2t9s{?dC%_SU3fvz(!=A%K4H$X> z+=hMwZbvVH`=j6CAqN|Jg}sLUb%X~TFjODX^&!7Ll&%jN^})J^U|D0xYYcgfA+IsW zn?t%eq?<#!Ii%Zwep`^W1zB5=wRNZcQD=~K23cp2bq2lefV)Fn5IkqI)dyY>^Fs)b zAE4C82`Dux1X@XK4S^SeashzsfRdmf!SxubY)uy;1!^BRY0MDf>3opL8v0gYuY*juLw5Q z24n{mis}Li1r&tp0}4WwAl1;;33w$)H3k%fn%XLZN1EF@1FsD2w*=$>R25q4Z0icVI#5?zcQ^7!Z{PtO8|q=}0lWtIvKOEvfPzpT zKwzueM3QMQe?!dP$H4&Z%YX7#a^f|1(|yahb! z(*m}(64*W%jTZ2@PYc*~gU|t(u|Y7-LFf>Sa}anu2pxgef}n>%=or)=gwBFDPS}12 zeir(23Xly@5c(OAm5ZUXfH3qEXq>lQ0iMe4Ko@PHz;{6Evh5o1tG1$`eI4dx5X_+< zbQ9)~)n00J8`?{aZrL2@uI)DP^|l~%A9@pnT7X7j@Z)3XPY-x_sRvdR+I|Xk*w7@~ zX6Om*7hn!yXgf$=+O7iM0rU!51WSS5*q#F~h>^%hAoLiPJ3hEk&sFP zhz389D8SwvcyUMt0;0h}65;l?z+nX^L;|8=-jRs5cK}`iQY;`E{6!+(-WhmhNQr=G zn0Kj=Z0`uX5|C=|1YEZlgI<~TX28F;1=~~F3&Okz1)HqxJQSq?OG42J@K`$gGvLR8 z!t76hmj#Us(0&q>9y}Xv&j7qS$TQo!18?lgWBEEClckx9;1ce zzwwasEn1IFf_N#K415`y3Vb=5349-#2Yf$T1pENn13b!I*GjYstww9mTC@(WN8h3E z(FU{;Z9<#Tk7x_phJHfZ(GIi|?LxbuHh(BN5BOfR5^7irwBB8l4J0x{R)(>*yxBjqalRXe*k6o};cSlc0Z%1E8f5%A2XvbK`c*jJ?B*$dORL3;O4985zY{y*3JjVjZBF7TP zQpa+~O2=x)TE}`vlw*Tqqhqt$L0q7jm;EzP0^WX`8bP;?3TRim7fh0&@1>_G4$oiw} z;1_>%({Tg%ZO2XEVL*4mAO0v9wC{tr9Dw}MLqL`+5IqJIh(aOt6nyB9es!b+{sR2y zk6t=nIo>#IPULjLTE_v-Kqu}bowSp6@=nnyJ5{IdG@Z$v!OoPPnfODX8kaMte2-Gpa8R}QjFVwG+UooIizsi0k+;JyJck7KLe0WiyB6wB&G`zpT|oRnTY+wDq;yS6=*gQNJfzB$x=Y&fvN!21S&wT zCEJr7fI5+@$bLZm$<+|AhPXY%6F_G=&}^XjKudteL79|PdI}4(h1tVkGK4w9{KAri z`G*CBB@GJ<3kt)-h%hpY3ZuiAFgA<}-QLc_ws&QcH9hwLNvG5dsl$~xhDwn=~jfC7PVAQ~taP#&OsK>2|pfJ)9kL>;D% zP)Dg_)N$$rb&@(oou+=K&QRy5U#Roc1?nPoiMmW(p{`QbsO!`X>LzuIx=r1o?o#)t z`_u#KA@zuQOg*8VQqQPgspr%S>Nn~Y^_qG^6~uPHgT#fgNNfxo2V+0Lo5EfWs{oJh zR)$A+%h}7@?RqV{^<@?vzQ9t!UfEvPUf-ooBfpJ8upAFR)`x3{;mceHo1ce4+$ zFR^L*IQw`wj-*63qdxEm*vliM0q_Vc7&!q20}4k;0EMHSutyItt^+R&8i7V5;Dtd0 z2NVn_9Fc&+0fizOP$-}C6BD!I`Vcri!?8_j?hgBDW)3I^nlLIL>$f})bUxN(U%C9FH@i zFrzu}gJ5?C<38|`K;cG9Hws3X0R@9B9AyO*4(Ff2D7(=Lcs9d85A4v>YT+#s`Z z!%$v8VW>2e&u_E_UK;Ev2q+1lU{nZDFr1~rQQ2q%ybPp@0fvViS?qDUIoq_dK+DUSAjbF0t$vRVGQ**x&p5X{Tm3#2FM=`1_Z}cuwp16=r!mL z2NaB|L29JY4R|%s7!AlD&S;XLu|{{`HQ-EoywL-A4bYtk2>J_lPXZK%dn%R z@Lsqb*`c?~4fvA>2tz9YVSobAYCr*i5Lye!D&dFLLka8OS}=-&xgQKD0Bryi00^Ot zfDqaPZErSi0p9~{Zvlh>3P9Tc1pq>5J0OI% zWXMluP6mzSW@^Y!0dL4-C>XE}g#fmplz<~pD!>sa)QkfCDA3LbTH!z$K_eU}Bjkny zO@;i7=2XxKhqvf4lnLIX$53X#Hk1Xh4P^x!fwBRPK-taBpuZWk3xHNZpaPNIaI z3z#z@ry#sxkD&;7%N|380K>i?Fzma{?Vz(AG|EAag<+oss2t>3DI26Z}p zpeUFDkw8%}2O@!@U=~CIMZr9X1cLu35C;Gy1quWT0>Xg^AQC8~gLoOl>!Ge4kY~kFusva@gE@VUhy1;eYyCUK z&01XA21GErm5zrE#UO>Hp#sf_NS_QNk zXbsR>ApeX389ifUU9-;wWQ~z^<=z?-Yb>nsw0xfvax=pG35OY!8nDGPyLlEj&+6vc z+&sIR=Wz3!z>&LL#y^o&heu}B<3Uzk9%R+$K~|j}WYz0IR^1+C)$c*xHY^L%fQ9M7 zzVu*Sdgw`d=t+9$NqXo>dgw`d=t+9e$>`>muGMdgTl!YtEpC;u`fqWooaF+qijKH*e?W?cKbC zn|E~cPHx`W&AYgHS2yqG=H1=A2k=87heM8p91S@Zay;Zj$jOjXA*Vxr4mlHYHsoB$ zFCphcE`(eRsfb+)xg2sONPip?tysJO1;u8Nl{o`fZaZdbEcPgQ`))qGx) ztd*ixj#_PMwXb!i_SM=CYrn4Dp-zuFed|1_^SVx8UAAtyy7lXJth=qQe}ildN;b&Q zI7{Q9jVCw$rg2o`3yrTfe$d3fY3ioQS~h6ez2)kbds`lBd8Q@ViffgnRm*m1J7nvS zyF>mCMLSgQP_x6g9R_tA-tlawYn`OdX6MwMGj<-?c|zwdU3zy})@5^-dtJ_Sz0~z_ z*IeDd>%OUbryhHIyy(%f*YjT4`j+oIqVLAOwthkVn0}f2_3byj-{tVW?v>rLRBC<2E4NwP^>L2O7tlrFRwxeSm(!db9DxuNt3Q0`I!6uAn7`zlbNKshZ? z?yF>ha8vG6jdF;JS~Y49#Hy&MC{{+|iHe((4He*Hc3?99&2%+Abi(`=flyJ+H~ zDT`(<>a%p*(zu-7IU{rCFJHO*1ODH%Rc_bZez`Mqx97T7Wv)88%D4LF>aA;ztvR)3 z-P#`O-dcBS-TUiiY{=RWx$*SIa~tn%OxiSTQ~IW{o8I2^!={Iubou-9KhH1A-?(}6 z=0ltJZwYKE+w$VpW?P$YjomtE>sQ;R?O3p5-;N7A-q`WZj`w%`vE%O@)plBTM(u33 zv**s_ovU_k+qr9Jb$fhDmy}-i&GtUf7m^?a`oRDg2!mlL42KcmhS87#lY(rqPr*~{ zTkO;DG&~c}#B=Z*+!aR&$rk%|wsY(&>^^)Lj>2&`38&x;oP!H+39i6v;D@*1I=mNT zi~R%q0p)*$Kf(bVz@OvKu^0aY58!9`1^$MUlp@SJ^Fg!Te8_y*Y%nWkqxqQmxcP+n zq`A<1%KW_9WDemw9iq))eC2Z$b5--x=4$3=%;9FUxw?6TIo&+c>^6@wk2a4nXPC#D zUonp}k2g;+Pc%<5XPPIQrF*h|gGdDN4G{>2{nR}amHveKCU`{m;G-sQam~+hQ%J!SM6`G9@p# zJt0YM8-m;8g4;ub+Z|J6dgvG#j?0vxW4sI-22 z`}UhVY|Wg+;H$~z{4Q-C{E@E=9Uxy7wuDyD8rtyt*begdviuBz{BeLYIG%DO_jv43H|YcIEdZNF##*8ZLSd;5L+ z58QnoaOe5OUY?NaGEm3+_Q#YVx4!4t-NCJg_Rs8l_<2m4K@?E_L;EpW^N>2d_5)ZE z`)S8fa@-)te(ZyT>^VpHn7V!B|Bih=>V6|wgOA$3W8WeCknka`Jc5r<{$p|;wtp4e zZxBbQ_bO?J$*GY31GRrkna1Gu7woxCjwgG53YPw4zapOada#8@$#aob{6N`92_Mr( z#|h8TkKfa4C)qAx-zoCQyb4?L3}x#L;tXw(XMG`X^y8U^GlrOx*Vzo#aJeGh5DH^F>mq{xvgqh}N$LwRQ3Gs_N9iVS6s z$gL;iQ;u&i|74!8Idi%1yI@~^&#~W{p2{xa^z-v=YOPA74dX7X2pn=eX{oCt;|`FpgVm^v&xy~*fYSA&5g z$5k7BVZ4Dm^E1Pr@7rNkCM8USi-wLI(~&P)>B!f$bmH&uPJDu)6XlKI^B4xwpKN9F zm-$4v$kURGhA|v7hJ9mrPBa_Kuum|QPmX4DAC}=ogC%-`utd9I9E|3uiEz;{fp7vP zPvF=m+b4pt6U0sg)8y6!aWa^CGPosk%B>pprqZq_J`wDXalx=pFzgl#rv!URp?73& z$s>&aYx@NKHIrL{ec>8$=cn5k1dxv_5 zdq;RjdPjN3c*lCjc_(-mdKY__c$ayXdsli_dDnQ?d0+Kz@NV*M_HOmQpuMOy&>Cqk zX-%|dS_{powbI&X?X>pV%UVaRv(`oHruEQzX??W5T9THc_0tAu1GT~0P;Iz2LK~@# z(#B|GwQ<@6ZIU)yo2$)I7ibH$#o7{WnU<@q(_Ym!X`8jJ+IDTHwp-h)?b9^vfHq#g zL%&R);yb0C(avcXv`g9*?KSNUEl*pmUe|7F?`xlEpK8Bp^>wX$$-Y(kU6$RJJ(j(e z0?R&&*P>bWTMk$bT6~s6mcy1KmZO$qmgANamXnr3%PGrg%NfgA%Q?$=%LU6t%O%TY z%N5I2%WIa`EpJ%-mNzYLS*}^GTW(lxTHdzYvblMV5~&A6q`L z1T3FgKC^soDYleYN-brUJC-jjcP(F9$}L}6zP5a0xo7#-@}1>-%YDlamLDxYS-R&| zjJ}@x{>0`9U!^1sT-LAqz~4rUoxWtj%S+EMdz7o!UP8m32RFUX(B` zMHyFnN|U8^b|en{e$qQj+{3`^C`=#hPJ#rbhLX<^1Ub0;nOV8wgu zeogM=xfOnmra#V}yEbQZ(~ZlM=ZtwcrsITd z6T>_Mr;VE#K5tll)AX4eUrKy#=ywwuuUR=fe#EmGOE)%3e#SLwh-GxbnEe@FjW>Dj zPMfvhy__#{?rwZJ<@`j|b$fEY=g%d3HxAggbH}DJZ%??lFlx^FxmTxb&P-i-HKFC= zw#gyM-eDi6e>``|;5riuN4HzkXXA&%;*zfR8don~vxdzYHEYeR{+lk( zt-kcpiq>oYUi(x2>3Q;Z#yaQdZwC zNm)tPlK)KZ*Kc#byZuh4#t(E2Odr&6@CU;_9X59O;o*U_gb|@5(ndJa{UamY$?pE6 zYLDqZ=JSbfPpp%v&2UaRI%TrQG_4)WiPxR}`SgnEJ!iztxHa?m>^XA>&+D)tdGSw+ zXD#cveChHlE8br5!-^FvTd$e2cJsRF>*sF#d{Y&^Ou1*EXK~L!d~xrRdsrt78|rngORnr@hGn|he)^Xa4deA1{spFFD1{`%yoPoDbht!O^r!!OnPI|8f>;aUPPU!6CFw2MZb^De;;q0Qa+mGrKpbR~aR(z}t~jd(ZW-H6K`=t;hw~+xE7T|=I(7@D-mdcTkbMDE^liPAk z(xXPd0O3+2mMCg+-*gEEvUGLH;px*S#%=cSyLvIH4E*_J&aYnHR~Nhte6Zp%>ik8FvYr*c-xV`V63nygEPau&++=SvlJ~=~~Q}&1~L5A|mm-i}}E?e^CSUJbyxfgZ{ zh6d39%Xu!hI+HtwttY+Pj4hd0*3Oen%IHqaAleYhtHB_;lP1qX=IUWG2Xpt}n*EQ%y$1~zyT$8orRB4PLQPA~AT)MhSWC$~#RmPBulG>X}|JSxql;H)ueAG-7n+{afChn{kJV z;eOGA(R4DFt+@NPVGehsH|4!k&Z2Jgk{p?O@B+LD4NUb!1D-W^tn5_TxiY@8OJ&!} zZk63DdsOzU>{Z#jvQK3~W#7ug%B0HV%9Ki1WxvY)l>;hMD+g8%svO2wXwDZ)8ZB+~ z7w>=UweRD-jsx~Dc^~K--Y1anPHg5qb9YQkO!Jr)F^(8#Ov{*7F|A|T#I%iR7ZV%P zKBhy=%Q0~=9b-DhbdHIS=@Qd5rdv$+m>w}bV|vB(j_DJV5Ysm%F(xS{IVL5>71J-K zf6RcG)R=)WgJK5942c;UGc0C!Oj^u{nDm&DF?;xO#Y5sKU!u5A9Ej4txHfuS^!n&m zqj$ad_KUhe!(v^a5j2LEzy?jADKrB+#6WXs0S<6NOK1hHp$)W!b`T5gp#!`OanKPu zL1&1EF3=UaL3ii@J)sx$hCYx0eIWp!!e{U~6hjG=LK)nFFW@eG3FYt=d=1~gJ@^*B zgYV%!`~W|~Pw)VKhF{l!OAMM+xFVL@3 zQ}x622lQ@zx_+yEn|_QwTYpgR)3*vJWvjJ`*jToj&{Q^EKSMv8tA)%p&Y(~ui+cG2j9YX@IBmz zAK*v$2_C@D@Jq20*bL`k>%ZRjuVYG-Km%z0ue+5gfes)&pv)LJS8U|CCqDMCFX6K# zM#hZvpiT6ZiM}$?S0?((L|>WcD-(TXqOVN!m5IJG(N`w=%0yq8=qnR_WumW4^p%Oe zGSOEi`pQIKndmDMePyDrO!SqBzB18QCi==mUzzAD6Mbc(uS@}Fi78+&sU1iwtsNKv z=`a%9FbYP)7|4LJ@CuBB@h|}Ie)d zaRf#|>c1Y2CxY~(f1UZSC;#gy_#DXN&j0HRSRQxrU(2{WUdAVt#OPY-*BRCu^7sta zMn03c*|5d1)v(R5-LQjCF6}l{7zzwtgJ#%o2&t#9_p9N3!vn)dhMx@|8;T8W1NBPU z23`Owya@H70W^e0&=_6<8#IBY&K}YBW zol7Q~cAF-d6rSetx1; zFzg%**M|?_6Ik7O7cqtH6n^BpjPi@RJM&(nBp#9pdx|;s zUOX*0X&*57R?0Q|^8>$JUXTzqI(l-S$WkE`(fUPt$^JYpJTO-T9 zPx(2;Y>yw-0OZx{XNw*u-?G*fn5UoOS=TVE4@Fk_MirX0s}`6`myEXmZ5_?4DK4Z zUh>A^z~F~$)heqTY#Nd>d||7!Ay+XTYC39OJ8+LNo>0udx^<~`yF=e_%^@d+--8*o$G&S&9N&4_{!@q33 zW%xV8KN{oET9s;^>Id5#i~z(}$)XPcKSO9N9Ba zwXFTf$LSd(7mq9__H$W1_nRXRjBMI=tb4wDyL*OveB1G(dIz2={&Li#(LF{#*DkbO zdE50#8>8I4J7wI?aTkcKD(x1iQj!q(vov9R-@wJ%#H1P zX3oo;k$E_?Blm|clM}g8bs)by^Cqk~$EsFYeCkg7E1nf!$>(fwN^UUIjgVIh6N&9u~X!&EyI^SK{Js zQfEfp87pUWjoTR4FQ67D%={A9FYOoD0&kXnKdV{Cp0oST?jM+2(m$Y;{y1CTsXy&` zYtD^14LS`7WEFofcUPx-bHC*K+Xn=emW`aJ>wImVetwtvj?SrphKy#n(t&J!S~`0{ zD!<=0EckQ5Kz^4D(nOVI9!^=MEd?L%Ruu6P~g{KA$3}|Ho1EAP>SDPb#c{$Rh{_FShRXX;DeG8fgeip*8aTKylzCG zX<6-ct=E6DKCpi7`afQ+k(VC$vux#t4L#0m7#WyR@^z0lHl7&z!X|g1ONl%1eCfoV z(|g|8G>W-;qUWf<#^Of#qj)`bH@`)%Mw_+GuW$Zm^Six92kd2W+y376Ztov@kJ+BV z@AF5!GXlnvj6k*0jKI3m{(b)0Vchx5&YC;N21MyWabE1Gb5*=1UKei&zqlr@iyPvm zcw5{O?}&HBd*ZfuUwj}w6h-1A@v#VqPsL~Ab5SfxM5&mm6FR+4(V29ix~Ftib=7p? zy6U$t?L|wAZrR%Rt)eX`O(GAn3>C$y>-Dq8g?iJm5-9%lcZi>#Mo35Lso1+2ip8|!WQ zrh2=+x!$37>Rakt>)Ytt>F3)P*gERF>bvWE>U--G^pk88Y?-!&x~011x)r)LxNn~)>9^=lXvef&`a}A|`Xl*v752Gv4^pzv6r#8v5zsq*w>h7Ofn`LQ;aTS zKVyI6GGmT$ZREPh^^vbe=0);B!N^UK`H`C=w?uA@+!nb#a!2IO$X$`UBlkq^jVy@V z7wL`EBKJogh&&kSi#!y0IPys3(a2+w$0JWfo{TJvJQaC5@=WB}$a9hBBQHf>j=U0? z>6`4E;+yL8_@?=$`)2rN`eylN`{wxO`sVrO`xf}Jd<%Vxe2abAz9qh;zGc1~-*Vpy z-%4Muj}J5Y*7(-?*7?@^UiIbqHuyIBHu>^>n|)h+TYcMn+kHEHJAJ!+yM23ndwm7I zeLk;G^X>N?@E!E|e2098eMfvpeaC#qeJ6Y;eTBYLzSF)lzO%k_zVp5dzKgy~zRSKV zzDJfnEPq=5vOKo@ZRzP1s!lbiifU9%YKR)DhN(}fRn)5L(`q&K88uurtJPJD8ll!u zYpKtv&#Sf7I%-`tN{v?QsV}HjwZ7UwZKyU<8>=s=HnoY`RBfi()flz8+Cp`xPPL`l zN^PySQQNBR)L6B>+ChC;jZ-_Soz%{1yxK+Ws&-Smt3A}7YA?07+DA=L`>Kg*lA5fh zs4lgi+Fu=@rm6$gLF!<2h&ogqrVdxr)DddBI#PA3qtwyr7&Sv3tG=R+Q^%_l)QRdO zHB+6ePEn_-9+gKr>I`+JI!~Le&Q(X+=Bo?TEOnu}NL{REt4q~oYL2>GU7@a2bJbPq zTiP0Rt-4-)Rn1d3s2kNyYQDNz-J)()x2fCJ9qLYXm%3ZsqwZA;)P1T~)ztm!0rjBj zQxBFX$UOm#x*JXNP^ zooaQ$>r}53QKx2|T6Lah9o#ysfLo8XZ|k$_ZDUrvZOUr5%~|KRC2QQaWnJ42tY_Pa zb!@w`hHX#QuT5a>+T=PeR;*2Bt=b{1Qk%vKwQg3Y&0uBP@vKUl$%?cd)}o!sDztN1 zfi{b^XR}#%HitE5b6Ib8Eo;r@1y`AsYs_v5t}k0A`9AYKeW2_)s z$m+3YSvmG1tHxes#aMrxw^%9mW}REC5PO@|VT)K5Ho%Io#jFK;hgD$9SpoJQtH0i7 z-PZ@K`C7qxua8*g^)YL_{{L5V{oiZ1{@*LKHsgQA+SHY+u*x-9{(CE# zl~rA=tlGcsaQ*}9f3KrDDY#1M|ExtS*BX6P_mAL;qJP%?>;G9THLLFby_)KiwNn4j zN~!;6HPt6;ss7(9r5^o%R#KI#q@JjIkyTUWnyDvQAN3aNqsrA$-w3XM`T^^q%C%7C zDyMSY({k21m8+iqQui?{oc>n#cUC%`9JL^7ZPdD`^--@z^ z^|n0QMq9pZi*1{2hi#W_kFCJwwe7bZv>mb?u^qFWuoc=)+s@j~+b-HJ+pgMPxA|>v z*{<7e+HTq2wcWOTU@NkHYzx>vvlZJ)ZFg*UZRNJ_Z9mw4vF!@q9lj@gZ+JoYzHo22 z7QR3HK={FMU-+T$!{JB5kA@!$KOTM}{A74x_^I&I;b+3nhMx;RAATYHV)&)-%i&kT zuZF)C{(ATu;r{S9!>@(E6CUU7+96_9l1}y~$pex4$>lJIFi4JItHrP4~LJ zqrDm4SG?oBS>8q7Z0}NUj(3GO*Sp%g*1O)D=iTVd_ipiS^KSRn)2v#3t)bRfvuRB= zyB4E0*Bn|)t+m!xi`6=4aat!WUhArL*LrHbwFE6uOV(Uke=SuTqz%!AX=z%z=GI1Q z8QLq_cx|GVsZG|VXj3(hHcgwZ&Cq6Qv$Q$dd@V~`q-ASMwUydhZN28#HfS5Qd~J)i zP1~XE()MTtnpfMe9n}i8)7n|>ymnE$tXQT0r|%d!+rL{i)e~ zF}~)$7Cwj1>1*k0?Q7#}>ucwW^|kkP@V)Gd^L6xf@^$vb`?~nL`nvhL`+E3#`g-|# z`}+72e0_b1z9ipC&E@Oo>+c)jOZ5%(4e|}~4fPH44fmz_M)=ZwBYke)C|`z8{H6O# z{}*4%@t5&0(_faqBL1rJSIxhk{VTe%UgZmw*2)(v>sL0YY*^W-vT@~0mA1+zl}#&~ zRoW|KDw|ifsB~01D_d5!s%%}^rm}6NE+BYcSqFOH%Z2#+MHeuF2|^$g!r&>W0#)H@ zs0PnKIGCY2SRevwKuvfSYQb~xJk*9rP@xXgg(!%Idhh~R;YFwq4d56YhZArT3gHx- zhBI&$&cS)O02kpBT!t%f6<&ka;SKP^oA4G~gX?euZo=De3*Lcu;XSww@52Z1A!PFY z_hgs?Q^5n%U^>iznJ^1x!(5mL^I-vG!9rLBiy<49z*1NSIj|g7KrXC;)vyNE!a7(F zd9VSp$|wi2VF@gQ99Rx3U?t?jDp(C`U@feJ_3$d>!3Nj}n;;)H!xq>I+h9BFfSs@l zcEcXn3k9$byr98;H~73{JpFD1=jR8qUC3I0xr>?>~$8%@@KVSPa>) z1eU@w$bsds0#-sUtb*0B2G+tlSP!p49&CV(unF>EGi-sauno4u4%i91U^nc6y-)!A zzzZ7ehXZgBd~gU3!x1gK0}0R< z5+MnaAq8B}5BkFZNQHqg2nNFt7z)E+c&U?njFWqelY5MldyJENjFWqelY5MldyJEN zjFWqelY5MldyJENjFWqelY5MldyJENjFWqelY5MldyJENjFWqelY5MldyJENjFWqe zlY5MldyJENjFWqelY5MldyJENjFWqelY5MldyJENjFWqelY5MldyJFkkWQXMI(ZK1 zEt=2ljo35oWIe8B0&<(mn59kTKpf~h^ z1n3KikOaw)0xswe10WR!!XOw7LtrQjgW-?{BOo0{f*VG`Xcz+-Fcw~caWEbxz(kk? znJ^iqz*O+SG?)%EU?$9h*)Rv@!aSG{3m^*?!Xj7<*{}qb!ZOH#<*)))LN2U=)vyNE z!a7(FuR91N*=W2jC$1;1C>vqi_t4!wEPEg>VW^!x=aW z=imZdg3E9PuEJ~ZI=lgI!dq|+uEPzu32(zKcn98v_uw|X4r$M6XR;8XYv zK8Io`fl?@gJMabEg)gBTzJjme8@LDG!gugJ+=n0FNB9XIz|ZgtJcJ7P6@G)?;Su}+ zf5Knz82*NTpb|t0*E8tB016l(1VSMUo`R>L8axBxV20{ofe5Gx&q6JD4xWeF5D9gl zE<`~z)Ponm3NJ!^XaEhN5j2LEzy?jADKrB+#6WXs0S<6NOK1hHp$)WySZEI&APzc0 zC+H0E&=q<@FX#<@AOZS9A|yi!xS${OhXIfZ17Q#hh9NK%hQV-1gAtGpBf$-$U^I+@ z3>XWqz&IEW6JR1tf=rkUQ(!81U>Zz^888!O!EBfVb73CLhXs%Y3tA)%p&Y(~ui+cG z2j9YX@IBmzAK*v$2_C@D@C!VI3iuU%gWus1`~iQ$U+@_IhJTI;-L$4g>KLt zdO%O;1-+pUBtTzCgd|9Y6mUU5=nn%R6$Zi}7z{&TC=3Jn`X~)XKst;BH;jVOFa|PU zEW85aU_4BKi7*K=VKPjCso;TWFdb&VOqd0;VGhiNc`zRqKo%^7MX(sMVF@gSWsn2Q zVFj#&Tv!FGVGXQ>b+8^@g*?~*8(|aV!)Dk5TVWe)haIpJcEN7g1ACzW_Q8HQ00+Sb zhu|8E!359S9PQw{E3+LcGT!4#k2`G=xUb7+wM!G=Zkj z4D1jC&7lQ2pe3||*3bspLOY0s_Rs-dhB)X5ouD(sLl@`@-Jm=4fS%9`dP5&bfJ8`w zWJm!Q^n?B|08(Kf41&Qh1ct&e7!GML0@7h5xM38GhB1%>%!dV#1q)#jEQV}Y0!v{TK-t3+rG#yb5`+ z0XD)W$cN3a1-8OA*bX~jC+vdVum|=+0qg@WXs{m+f)5VCVK@Ru;TRl;6L1m=;S`*P zGjJBp!Fjj<7vU0IhAVItUW3=c4{yR-a1E}*4Y&z!^I1ayI?#gwLLdyPKvj4es=+f5 z4rZth7KnfvP!pboTJRh^549l@)Uvj`W^T)C=C-_MZp&-tw!CI;%WLMg0TYBkD1^aN zPz9>O(@+hbfp9QGb+AAL)PS1sEYyPM;CZMGk)T2ys0&dLT@uUd;aFY|$MSkOme<3v zydI9_^>8e&hhup?9LwwBSY8ju@)@#NUK7XinmCr%#Id|4j^#6CvAiyh<#ll^uZv@O zT^!5n;#fYd5X)=h*g!Yv4m}_dk{}sUzy#6{!S+f*;n|zP88z`oI!g0k2;~ml8znNkGGNT zd7u+6;(0&o#BJgQ6*`ec-1@6d9Ks%~R8sCEoe0Q$xB^G~p%YQqV%b%@)1bFOQ2LUGA@`7m+V>9FE5kwe%tr`)ljPRU99w7Z0!t$8vlDt{Nvq5l$ms zuIh!2xTB6}L) k#W2q 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

&?R5xwIj;Hq^^@ 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 float: + return self.reported_logs.get(raw_log_hash, 0.0) + + def is_deduplication_enabled(self) -> bool: + return asbool(os.environ.get("_DD_APPSEC_DEDUPLICATION_ENABLED", "true")) + + def __call__(self, *args, **kwargs): + result = None + if self.is_deduplication_enabled() is False: + result = self.func(*args, **kwargs) + else: + raw_log_hash = hash("".join([str(arg) for arg in args])) + last_reported_timestamp = self.get_last_time_reported(raw_log_hash) + if time.time() > last_reported_timestamp: + result = self.func(*args, **kwargs) + self.reported_logs[raw_log_hash] = time.time() + self._time_lapse + return result diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_handlers.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_handlers.py new file mode 100644 index 0000000..32efb83 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_handlers.py @@ -0,0 +1,377 @@ +import functools +import io +import json + +import xmltodict + +from ddtrace.appsec._constants import SPAN_DATA_NAMES +from ddtrace.appsec._iast._patch import if_iast_taint_returned_object_for +from ddtrace.appsec._iast._patch import if_iast_taint_yield_tuple_for +from ddtrace.appsec._iast._utils import _is_iast_enabled +from ddtrace.contrib import trace_utils +from ddtrace.ext import SpanTypes +from ddtrace.internal import core +from ddtrace.internal.constants import HTTP_REQUEST_BLOCKED +from ddtrace.internal.logger import get_logger +from ddtrace.internal.utils.http import parse_form_multipart +from ddtrace.settings.asm import config as asm_config +from ddtrace.vendor.wrapt import when_imported +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w + + +log = get_logger(__name__) +_BODY_METHODS = {"POST", "PUT", "DELETE", "PATCH"} + + +def _get_content_length(environ): + content_length = environ.get("CONTENT_LENGTH") + transfer_encoding = environ.get("HTTP_TRANSFER_ENCODING") + + if transfer_encoding == "chunked" or content_length is None: + return None + + try: + return max(0, int(content_length)) + except ValueError: + return 0 + + +# set_http_meta + + +def _on_set_http_meta( + span, + request_ip, + raw_uri, + route, + method, + request_headers, + request_cookies, + parsed_query, + request_path_params, + request_body, + status_code, + response_headers, + response_cookies, +): + if _is_iast_enabled(): + from ddtrace.appsec._iast.taint_sinks.insecure_cookie import asm_check_cookies + + if response_cookies: + asm_check_cookies(response_cookies) + + if asm_config._asm_enabled and span.span_type == SpanTypes.WEB: + # avoid circular import + from ddtrace.appsec._asm_request_context import set_waf_address + + status_code = str(status_code) if status_code is not None else None + + addresses = [ + (SPAN_DATA_NAMES.REQUEST_HTTP_IP, request_ip), + (SPAN_DATA_NAMES.REQUEST_URI_RAW, raw_uri), + (SPAN_DATA_NAMES.REQUEST_ROUTE, route), + (SPAN_DATA_NAMES.REQUEST_METHOD, method), + (SPAN_DATA_NAMES.REQUEST_HEADERS_NO_COOKIES, request_headers), + (SPAN_DATA_NAMES.REQUEST_COOKIES, request_cookies), + (SPAN_DATA_NAMES.REQUEST_QUERY, parsed_query), + (SPAN_DATA_NAMES.REQUEST_PATH_PARAMS, request_path_params), + (SPAN_DATA_NAMES.REQUEST_BODY, request_body), + (SPAN_DATA_NAMES.RESPONSE_STATUS, status_code), + (SPAN_DATA_NAMES.RESPONSE_HEADERS_NO_COOKIES, response_headers), + ] + for k, v in addresses: + if v is not None: + set_waf_address(k, v, span) + + +core.on("set_http_meta_for_asm", _on_set_http_meta) + + +# ASGI + + +async def _on_asgi_request_parse_body(receive, headers): + if asm_config._asm_enabled: + data_received = await receive() + body = data_received.get("body", b"") + + async def receive(): + return data_received + + content_type = headers.get("content-type") or headers.get("Content-Type") + try: + if content_type in ("application/json", "text/json"): + if body is None or body == b"": + req_body = None + else: + req_body = json.loads(body.decode()) + elif content_type in ("application/xml", "text/xml"): + req_body = xmltodict.parse(body) + elif content_type == "text/plain": + req_body = None + else: + req_body = parse_form_multipart(body.decode(), headers) or None + return receive, req_body + except BaseException: + return receive, None + + return receive, None + + +# FLASK + + +def _on_request_span_modifier( + ctx, flask_config, request, environ, _HAS_JSON_MIXIN, flask_version, flask_version_str, exception_type +): + req_body = None + if asm_config._asm_enabled and request.method in _BODY_METHODS: + content_type = request.content_type + wsgi_input = environ.get("wsgi.input", "") + + # Copy wsgi input if not seekable + if wsgi_input: + try: + seekable = wsgi_input.seekable() + except AttributeError: + seekable = False + if not seekable: + # https://gist.github.com/mitsuhiko/5721547 + # Provide wsgi.input as an end-of-file terminated stream. + # In that case wsgi.input_terminated is set to True + # and an app is required to read to the end of the file and disregard CONTENT_LENGTH for reading. + if environ.get("wsgi.input_terminated"): + body = wsgi_input.read() + else: + content_length = _get_content_length(environ) + body = wsgi_input.read(content_length) if content_length else b"" + environ["wsgi.input"] = io.BytesIO(body) + + try: + if content_type in ("application/json", "text/json"): + if _HAS_JSON_MIXIN and hasattr(request, "json") and request.json: + req_body = request.json + elif request.data is None or request.data == b"": + req_body = None + else: + req_body = json.loads(request.data.decode("UTF-8")) + elif content_type in ("application/xml", "text/xml"): + req_body = xmltodict.parse(request.get_data()) + elif hasattr(request, "form"): + req_body = request.form.to_dict() + else: + # no raw body + req_body = None + except ( + exception_type, + AttributeError, + RuntimeError, + TypeError, + ValueError, + json.JSONDecodeError, + xmltodict.expat.ExpatError, + xmltodict.ParsingInterrupted, + ): + log.debug("Failed to parse request body", exc_info=True) + finally: + # Reset wsgi input to the beginning + if wsgi_input: + if seekable: + wsgi_input.seek(0) + else: + environ["wsgi.input"] = io.BytesIO(body) + return req_body + + +def _on_request_init(wrapped, instance, args, kwargs): + wrapped(*args, **kwargs) + if _is_iast_enabled(): + try: + from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_source + from ddtrace.appsec._iast._taint_tracking import OriginType + from ddtrace.appsec._iast._taint_tracking import taint_pyobject + from ddtrace.appsec._iast.processor import AppSecIastSpanProcessor + + _set_metric_iast_instrumented_source(OriginType.PATH) + _set_metric_iast_instrumented_source(OriginType.QUERY) + + if not AppSecIastSpanProcessor.is_span_analyzed(): + return + + # TODO: instance.query_string = ?? + instance.query_string = taint_pyobject( + pyobject=instance.query_string, + source_name=OriginType.QUERY, + source_value=instance.query_string, + source_origin=OriginType.QUERY, + ) + instance.path = taint_pyobject( + pyobject=instance.path, + source_name=OriginType.PATH, + source_value=instance.path, + source_origin=OriginType.PATH, + ) + except Exception: + log.debug("Unexpected exception while tainting pyobject", exc_info=True) + + +def _on_flask_patch(flask_version): + if _is_iast_enabled(): + try: + from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_source + from ddtrace.appsec._iast._taint_tracking import OriginType + + _w( + "werkzeug.datastructures", + "Headers.items", + functools.partial(if_iast_taint_yield_tuple_for, (OriginType.HEADER_NAME, OriginType.HEADER)), + ) + _set_metric_iast_instrumented_source(OriginType.HEADER_NAME) + _set_metric_iast_instrumented_source(OriginType.HEADER) + + _w( + "werkzeug.datastructures", + "ImmutableMultiDict.__getitem__", + functools.partial(if_iast_taint_returned_object_for, OriginType.PARAMETER), + ) + _set_metric_iast_instrumented_source(OriginType.PARAMETER) + + _w( + "werkzeug.datastructures", + "EnvironHeaders.__getitem__", + functools.partial(if_iast_taint_returned_object_for, OriginType.HEADER), + ) + _set_metric_iast_instrumented_source(OriginType.HEADER) + + _w("werkzeug.wrappers.request", "Request.__init__", _on_request_init) + _w( + "werkzeug.wrappers.request", + "Request.get_data", + functools.partial(if_iast_taint_returned_object_for, OriginType.BODY), + ) + _set_metric_iast_instrumented_source(OriginType.BODY) + + if flask_version < (2, 0, 0): + _w( + "werkzeug._internal", + "_DictAccessorProperty.__get__", + functools.partial(if_iast_taint_returned_object_for, OriginType.QUERY), + ) + _set_metric_iast_instrumented_source(OriginType.QUERY) + except Exception: + log.debug("Unexpected exception while patch IAST functions", exc_info=True) + + +def _on_flask_blocked_request(_): + core.set_item(HTTP_REQUEST_BLOCKED, True) + + +def _on_django_func_wrapped(fn_args, fn_kwargs, first_arg_expected_type, *_): + # If IAST is enabled and we're wrapping a Django view call, taint the kwargs (view's + # path parameters) + if _is_iast_enabled() and fn_args and isinstance(fn_args[0], first_arg_expected_type): + from ddtrace.appsec._iast._taint_tracking import OriginType # noqa: F401 + from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + from ddtrace.appsec._iast._taint_tracking import taint_pyobject + from ddtrace.appsec._iast._taint_utils import taint_structure + from ddtrace.appsec._iast.processor import AppSecIastSpanProcessor + + if not AppSecIastSpanProcessor.is_span_analyzed(): + return + + http_req = fn_args[0] + + http_req.COOKIES = taint_structure(http_req.COOKIES, OriginType.COOKIE_NAME, OriginType.COOKIE) + http_req.GET = taint_structure(http_req.GET, OriginType.PARAMETER_NAME, OriginType.PARAMETER) + http_req.POST = taint_structure(http_req.POST, OriginType.BODY, OriginType.BODY) + if not is_pyobject_tainted(getattr(http_req, "_body", None)): + http_req._body = taint_pyobject( + http_req.body, + source_name="body", + source_value=http_req.body, + source_origin=OriginType.BODY, + ) + + http_req.headers = taint_structure(http_req.headers, OriginType.HEADER_NAME, OriginType.HEADER) + http_req.path = taint_pyobject( + http_req.path, source_name="path", source_value=http_req.path, source_origin=OriginType.PATH + ) + http_req.path_info = taint_pyobject( + http_req.path_info, + source_name="path", + source_value=http_req.path, + source_origin=OriginType.PATH, + ) + http_req.environ["PATH_INFO"] = taint_pyobject( + http_req.environ["PATH_INFO"], + source_name="path", + source_value=http_req.path, + source_origin=OriginType.PATH, + ) + http_req.META = taint_structure(http_req.META, OriginType.HEADER_NAME, OriginType.HEADER) + if fn_kwargs: + try: + for k, v in fn_kwargs.items(): + fn_kwargs[k] = taint_pyobject( + v, source_name=k, source_value=v, source_origin=OriginType.PATH_PARAMETER + ) + except Exception: + log.debug("IAST: Unexpected exception while tainting path parameters", exc_info=True) + + +def _on_wsgi_environ(wrapped, _instance, args, kwargs): + if _is_iast_enabled(): + if not args: + return wrapped(*args, **kwargs) + + from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_source + from ddtrace.appsec._iast._taint_tracking import OriginType # noqa: F401 + from ddtrace.appsec._iast._taint_utils import taint_structure + from ddtrace.appsec._iast.processor import AppSecIastSpanProcessor + + _set_metric_iast_instrumented_source(OriginType.HEADER_NAME) + _set_metric_iast_instrumented_source(OriginType.HEADER) + # we instrument those sources on _on_django_func_wrapped + _set_metric_iast_instrumented_source(OriginType.PATH_PARAMETER) + _set_metric_iast_instrumented_source(OriginType.PATH) + _set_metric_iast_instrumented_source(OriginType.COOKIE) + _set_metric_iast_instrumented_source(OriginType.COOKIE_NAME) + _set_metric_iast_instrumented_source(OriginType.PARAMETER) + _set_metric_iast_instrumented_source(OriginType.PARAMETER_NAME) + _set_metric_iast_instrumented_source(OriginType.BODY) + + if not AppSecIastSpanProcessor.is_span_analyzed(): + return wrapped(*args, **kwargs) + + return wrapped(*((taint_structure(args[0], OriginType.HEADER_NAME, OriginType.HEADER),) + args[1:]), **kwargs) + + return wrapped(*args, **kwargs) + + +def _on_django_patch(): + try: + from ddtrace.appsec._iast._taint_tracking import OriginType # noqa: F401 + + when_imported("django.http.request")( + lambda m: trace_utils.wrap( + m, + "QueryDict.__getitem__", + functools.partial(if_iast_taint_returned_object_for, OriginType.PARAMETER), + ) + ) + except Exception: + log.debug("Unexpected exception while patch IAST functions", exc_info=True) + + +def listen(): + core.on("flask.request_call_modifier", _on_request_span_modifier, "request_body") + core.on("flask.request_init", _on_request_init) + core.on("flask.blocked_request_callable", _on_flask_blocked_request) + + +core.on("django.func.wrapped", _on_django_func_wrapped) +core.on("django.wsgi_environ", _on_wsgi_environ, "wrapped_result") +core.on("django.patch", _on_django_patch) +core.on("flask.patch", _on_flask_patch) + +core.on("asgi.request.parse.body", _on_asgi_request_parse_body, "await_receive_and_body") diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/__init__.py new file mode 100644 index 0000000..ddbdd58 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/__init__.py @@ -0,0 +1,74 @@ +"""IAST (interactive application security testing) analyzes code for security vulnerabilities. + +To add new vulnerabilities analyzers (Taint sink) we should update `IAST_PATCH` in +`ddtrace/appsec/iast/_patch_modules.py` + +Create new file with the same name: `ddtrace/appsec/iast/taint_sinks/[my_new_vulnerability].py` + +Then, implement the `patch()` function and its wrappers. + +In order to have the better performance, the Overhead control engine (OCE) helps us to control the overhead of our +wrapped functions. We should create a class that inherit from `ddtrace.appsec._iast.taint_sinks._base.VulnerabilityBase` +and register with `ddtrace.appsec._iast.oce`. + +@oce.register +class MyVulnerability(VulnerabilityBase): + vulnerability_type = "MyVulnerability" + evidence_type = "kind_of_Vulnerability" + +Before that, we should decorate our wrappers with `wrap` method and +report the vulnerabilities with `report` method. OCE will manage the number of requests, number of vulnerabilities +to reduce the overhead. + +@WeakHash.wrap +def wrapped_function(wrapped, instance, args, kwargs): + # type: (Callable, str, Any, Any, Any) -> Any + WeakHash.report( + evidence_value=evidence, + ) + return wrapped(*args, **kwargs) +""" # noqa: RST201, RST213, RST210 +import inspect +import sys + +from ddtrace.internal.logger import get_logger + +from ._overhead_control_engine import OverheadControl +from ._utils import _is_iast_enabled + + +log = get_logger(__name__) + +oce = OverheadControl() + + +def ddtrace_iast_flask_patch(): + """ + Patch the code inside the Flask main app source code file (typically "app.py") so + IAST/Custom Code propagation works also for the functions and methods defined inside it. + This must be called on the top level or inside the `if __name__ == "__main__"` + and must be before the `app.run()` call. It also requires `DD_IAST_ENABLED` to be + activated. + """ + if not _is_iast_enabled(): + return + + from ._ast.ast_patching import astpatch_module + + module_name = inspect.currentframe().f_back.f_globals["__name__"] + module = sys.modules[module_name] + try: + module_path, patched_ast = astpatch_module(module, remove_flask_run=True) + except Exception: + log.debug("Unexpected exception while AST patching", exc_info=True) + return + + compiled_code = compile(patched_ast, module_path, "exec") + exec(compiled_code, module.__dict__) # nosec B102 + sys.modules[module_name] = compiled_code + + +__all__ = [ + "oce", + "ddtrace_iast_flask_patch", +] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_ast/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_ast/__init__.py new file mode 100644 index 0000000..e5a0d9b --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_ast/__init__.py @@ -0,0 +1 @@ +#!/usr/bin/env python3 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_ast/ast_patching.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_ast/ast_patching.py new file mode 100644 index 0000000..9eb516a --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_ast/ast_patching.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 + +import ast +import codecs +import os +import re +from sys import builtin_module_names +from types import ModuleType +from typing import TYPE_CHECKING # noqa:F401 +from typing import Tuple + + +if TYPE_CHECKING: + from typing import Optional # noqa:F401 + +from ddtrace.appsec._constants import IAST +from ddtrace.appsec._python_info.stdlib import _stdlib_for_python_version +from ddtrace.internal.logger import get_logger +from ddtrace.internal.module import origin + +from .visitor import AstVisitor + + +# Prefixes for modules where IAST patching is allowed +IAST_ALLOWLIST = ("tests.appsec.iast",) # type: tuple[str, ...] +IAST_DENYLIST = ("ddtrace", "pkg_resources") # type: tuple[str, ...] + + +if IAST.PATCH_MODULES in os.environ: + IAST_ALLOWLIST += tuple(os.environ[IAST.PATCH_MODULES].split(IAST.SEP_MODULES)) + +if IAST.DENY_MODULES in os.environ: + IAST_DENYLIST += tuple(os.environ[IAST.DENY_MODULES].split(IAST.SEP_MODULES)) + + +ENCODING = "" + +log = get_logger(__name__) + + +def get_encoding(module_path): # type: (str) -> str + """ + First tries to detect the encoding for the file, + otherwise, returns global encoding default + """ + global ENCODING + if not ENCODING: + try: + ENCODING = codecs.lookup("utf-8-sig").name + except LookupError: + ENCODING = codecs.lookup("utf-8").name + return ENCODING + + +try: + import importlib.metadata as il_md +except ImportError: + import importlib_metadata as il_md # type: ignore[no-redef] + + +def _build_installed_package_names_list(): # type: (...) -> set[str] + return { + ilmd_d.metadata["name"] for ilmd_d in il_md.distributions() if ilmd_d is not None and ilmd_d.files is not None + } + + +_NOT_PATCH_MODULE_NAMES = ( + _build_installed_package_names_list() | _stdlib_for_python_version() | set(builtin_module_names) +) + + +def _in_python_stdlib_or_third_party(module_name): # type: (str) -> bool + return module_name.split(".")[0].lower() in [x.lower() for x in _NOT_PATCH_MODULE_NAMES] + + +def _should_iast_patch(module_name): # type: (str) -> bool + """ + select if module_name should be patch from the longuest prefix that match in allow or deny list. + if a prefix is in both list, deny is selected. + """ + max_allow = max((len(prefix) for prefix in IAST_ALLOWLIST if module_name.startswith(prefix)), default=-1) + max_deny = max((len(prefix) for prefix in IAST_DENYLIST if module_name.startswith(prefix)), default=-1) + diff = max_allow - max_deny + return diff > 0 or (diff == 0 and not _in_python_stdlib_or_third_party(module_name)) + + +def visit_ast( + source_text, # type: str + module_path, # type: str + module_name="", # type: str +): # type: (...) -> Optional[str] + parsed_ast = ast.parse(source_text, module_path) + + visitor = AstVisitor( + filename=module_path, + module_name=module_name, + ) + modified_ast = visitor.visit(parsed_ast) + + if not visitor.ast_modified: + return None + + ast.fix_missing_locations(modified_ast) + return modified_ast + + +_FLASK_INSTANCE_REGEXP = re.compile(r"(\S*)\s*=.*Flask\(.*") + + +def _remove_flask_run(text): # type (str) -> str + """ + Find and remove flask app.run() call. This is used for patching + the app.py file and exec'ing to replace the module without creating + a new instance. + """ + flask_instance_name = re.search(_FLASK_INSTANCE_REGEXP, text) + groups = flask_instance_name.groups() + if not groups: + return text + + instance_name = groups[-1] + new_text = re.sub(instance_name + r"\.run\(.*\)", "pass", text) + return new_text + + +def astpatch_module(module: ModuleType, remove_flask_run: bool = False) -> Tuple[str, str]: + module_name = module.__name__ + module_path = str(origin(module)) + try: + if os.stat(module_path).st_size == 0: + # Don't patch empty files like __init__.py + log.debug("empty file: %s", module_path) + return "", "" + except OSError: + log.debug("astpatch_source couldn't find the file: %s", module_path, exc_info=True) + return "", "" + + # Get the file extension, if it's dll, os, pyd, dyn, dynlib: return + # If its pyc or pyo, change to .py and check that the file exists. If not, + # return with warning. + _, module_ext = os.path.splitext(module_path) + + if module_ext.lower() not in {".pyo", ".pyc", ".pyw", ".py"}: + # Probably native or built-in module + log.debug("extension not supported: %s for: %s", module_ext, module_path) + return "", "" + + with open(module_path, "r", encoding=get_encoding(module_path)) as source_file: + try: + source_text = source_file.read() + except UnicodeDecodeError: + log.debug("unicode decode error for file: %s", module_path, exc_info=True) + return "", "" + + if len(source_text.strip()) == 0: + # Don't patch empty files like __init__.py + log.debug("empty file: %s", module_path) + return "", "" + + if remove_flask_run: + source_text = _remove_flask_run(source_text) + + new_source = visit_ast( + source_text, + module_path, + module_name=module_name, + ) + if new_source is None: + log.debug("file not ast patched: %s", module_path) + return "", "" + + return module_path, new_source diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_ast/visitor.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_ast/visitor.py new file mode 100644 index 0000000..d7a4b08 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_ast/visitor.py @@ -0,0 +1,759 @@ +#!/usr/bin/env python3 +from _ast import Expr +from _ast import ImportFrom +import ast +import copy +import sys +from typing import Any # noqa:F401 +from typing import List # noqa:F401 +from typing import Set # noqa:F401 + +from .._metrics import _set_metric_iast_instrumented_propagation +from ..constants import DEFAULT_PATH_TRAVERSAL_FUNCTIONS +from ..constants import DEFAULT_WEAK_RANDOMNESS_FUNCTIONS + + +PY3 = sys.version_info[0] >= 3 +PY30_37 = sys.version_info >= (3, 0, 0) and sys.version_info < (3, 8, 0) +PY38_PLUS = sys.version_info >= (3, 8, 0) +PY39_PLUS = sys.version_info >= (3, 9, 0) + +CODE_TYPE_FIRST_PARTY = "first_party" +CODE_TYPE_DD = "datadog" +CODE_TYPE_SITE_PACKAGES = "site_packages" +CODE_TYPE_STDLIB = "stdlib" +TAINT_SINK_FUNCTION_REPLACEMENT = "ddtrace_taint_sinks.ast_function" + + +def _mark_avoid_convert_recursively(node): + if node is not None: + node.avoid_convert = True + for child in ast.iter_child_nodes(node): + _mark_avoid_convert_recursively(child) + + +class AstVisitor(ast.NodeTransformer): + def __init__( + self, + filename="", + module_name="", + ): + # Offset caused by inserted lines. Will be adjusted in visit_Generic + self._aspects_spec = { + "definitions_module": "ddtrace.appsec._iast._taint_tracking.aspects", + "alias_module": "ddtrace_aspects", + "functions": { + "str": "ddtrace_aspects.str_aspect", + "bytes": "ddtrace_aspects.bytes_aspect", + "bytearray": "ddtrace_aspects.bytearray_aspect", + "ddtrace_iast_flask_patch": "ddtrace_aspects.empty_func", # To avoid recursion + }, + "stringalike_methods": { + "decode": "ddtrace_aspects.decode_aspect", + "join": "ddtrace_aspects.join_aspect", + "encode": "ddtrace_aspects.encode_aspect", + "extend": "ddtrace_aspects.bytearray_extend_aspect", + "upper": "ddtrace_aspects.upper_aspect", + "lower": "ddtrace_aspects.lower_aspect", + "replace": "ddtrace_aspects.replace_aspect", + "swapcase": "ddtrace_aspects.swapcase_aspect", + "title": "ddtrace_aspects.title_aspect", + "capitalize": "ddtrace_aspects.capitalize_aspect", + "casefold": "ddtrace_aspects.casefold_aspect", + "translate": "ddtrace_aspects.translate_aspect", + "format": "ddtrace_aspects.format_aspect", + "format_map": "ddtrace_aspects.format_map_aspect", + "zfill": "ddtrace_aspects.zfill_aspect", + "ljust": "ddtrace_aspects.ljust_aspect", + }, + # Replacement function for indexes and ranges + "slices": { + "index": "ddtrace_aspects.index_aspect", + "slice": "ddtrace_aspects.slice_aspect", + }, + # Replacement functions for modules + "module_functions": { + # "BytesIO": "ddtrace_aspects.stringio_aspect", + # "StringIO": "ddtrace_aspects.stringio_aspect", + # "format": "ddtrace_aspects.format_aspect", + # "format_map": "ddtrace_aspects.format_map_aspect", + }, + "operators": { + ast.Add: "ddtrace_aspects.add_aspect", + "FORMAT_VALUE": "ddtrace_aspects.format_value_aspect", + ast.Mod: "ddtrace_aspects.modulo_aspect", + "BUILD_STRING": "ddtrace_aspects.build_string_aspect", + }, + "excluded_from_patching": { + # Key: module being patched + # Value: dict with more info + "django.utils.formats": { + # Key: called functions that won't be patched. E.g.: for this module + # not a single call for format on any function will be patched. + # + # Value: function definitions. E.g.: we won't patch any Call node inside + # the iter_format_modules(). If we, for example, had 'foo': ('bar', 'baz') + # it would mean that we wouldn't patch any call to foo() done inside the + # bar() or baz() function definitions. + "format": ("",), + "": ("iter_format_modules",), + }, + "django.utils.log": { + "": ("",), + }, + "django.utils.html": {"": ("format_html", "format_html_join")}, + }, + # This is a set since all functions will be replaced by taint_sink_functions + "taint_sinks": { + "weak_randomness": DEFAULT_WEAK_RANDOMNESS_FUNCTIONS, + "path_traversal": DEFAULT_PATH_TRAVERSAL_FUNCTIONS, + "other": { + "load", + "run", + "path", + "exit", + "sleep", + "socket", + }, + # These explicitly WON'T be replaced by taint_sink_function: + "disabled": { + "__new__", + "__init__", + "__dir__", + "__repr__", + "super", + }, + }, + } + self._sinkpoints_spec = { + "definitions_module": "ddtrace.appsec._iast.taint_sinks", + "alias_module": "ddtrace_taint_sinks", + "functions": { + "open": "ddtrace_taint_sinks.open_path_traversal", + }, + } + self._sinkpoints_functions = self._sinkpoints_spec["functions"] + self.ast_modified = False + self.filename = filename + self.module_name = module_name + + self._aspect_index = self._aspects_spec["slices"]["index"] + self._aspect_slice = self._aspects_spec["slices"]["slice"] + self._aspect_functions = self._aspects_spec["functions"] + self._aspect_operators = self._aspects_spec["operators"] + self._aspect_methods = self._aspects_spec["stringalike_methods"] + self._aspect_modules = self._aspects_spec["module_functions"] + self._aspect_format_value = self._aspects_spec["operators"]["FORMAT_VALUE"] + self._aspect_build_string = self._aspects_spec["operators"]["BUILD_STRING"] + self.excluded_functions = self._aspects_spec["excluded_from_patching"].get(self.module_name, {}) + + # Sink points + self._taint_sink_replace_any = self._merge_taint_sinks( + self._aspects_spec["taint_sinks"]["other"], + self._aspects_spec["taint_sinks"]["weak_randomness"], + *[functions for module, functions in self._aspects_spec["taint_sinks"]["path_traversal"].items()], + ) + self._taint_sink_replace_disabled = self._aspects_spec["taint_sinks"]["disabled"] + + self.dont_patch_these_functionsdefs = set() + for _, v in self.excluded_functions.items(): + if v: + for i in v: + self.dont_patch_these_functionsdefs.add(i) + + # This will be enabled when we find a module and function where we avoid doing + # replacements and enabled again on all the others + self.replacements_disabled_for_functiondef = False + + self.codetype = CODE_TYPE_FIRST_PARTY + if "ast/tests/fixtures" in self.filename: + self.codetype = CODE_TYPE_FIRST_PARTY + elif "ddtrace" in self.filename and ("site-packages" in self.filename or "dist-packages" in self.filename): + self.codetype = CODE_TYPE_DD + elif "site-packages" in self.filename or "dist-packages" in self.filename: + self.codetype = CODE_TYPE_SITE_PACKAGES + elif "lib/python" in self.filename: + self.codetype = CODE_TYPE_STDLIB + + @staticmethod + def _merge_taint_sinks(*args_functions: Set[str]) -> Set[str]: + merged_set = set() + + for functions in args_functions: + merged_set.update(functions) + + return merged_set + + def _is_string_node(self, node): # type: (Any) -> bool + if PY30_37 and isinstance(node, ast.Bytes): + return True + + if PY3 and (isinstance(node, ast.Constant) and isinstance(node.value, (str, bytes, bytearray))): + return True + + return False + + def _is_numeric_node(self, node): # type: (Any) -> bool + if PY30_37 and isinstance(node, ast.Num): + return True + + if PY38_PLUS and (isinstance(node, ast.Constant) and isinstance(node.value, (int, float))): + return True + + return False + + def _is_node_constant_or_binop(self, node): # type: (Any) -> bool + return self._is_string_node(node) or self._is_numeric_node(node) or isinstance(node, ast.BinOp) + + def _is_call_excluded(self, func_name_node): # type: (str) -> bool + if not self.excluded_functions: + return False + excluded_for_caller = self.excluded_functions.get(func_name_node, tuple()) + self.excluded_functions.get( + "", tuple() + ) + return "" in excluded_for_caller or self._current_function_name in excluded_for_caller + + def _is_string_format_with_literals(self, call_node): + # type: (ast.Call) -> bool + return ( + self._is_string_node(call_node.func.value) # type: ignore[attr-defined] + and call_node.func.attr == "format" # type: ignore[attr-defined] + and all(map(self._is_node_constant_or_binop, call_node.args)) + and all(map(lambda x: self._is_node_constant_or_binop(x.value), call_node.keywords)) + ) + + def _get_function_name(self, call_node, is_function): # type: (ast.Call, bool) -> str + if is_function: + return call_node.func.id # type: ignore[attr-defined] + # If the call is to a method + elif type(call_node.func) == ast.Name: + return call_node.func.id + + return call_node.func.attr # type: ignore[attr-defined] + + def _should_replace_with_taint_sink(self, call_node, is_function): # type: (ast.Call, bool) -> bool + function_name = self._get_function_name(call_node, is_function) + + if function_name in self._taint_sink_replace_disabled: + return False + + return any(allowed in function_name for allowed in self._taint_sink_replace_any) + + def _add_original_function_as_arg(self, call_node, is_function): # type: (ast.Call, bool) -> Any + """ + Creates the arguments for the original function + """ + function_name = self._get_function_name(call_node, is_function) + function_name_arg = ( + self._name_node(call_node, function_name, ctx=ast.Load()) if is_function else copy.copy(call_node.func) + ) + + # Arguments for stack info change from: + # my_function(self, *args, **kwargs) + # to: + # _add_original_function_as_arg(function_name=my_function, self, *args, **kwargs) + new_args = [ + function_name_arg, + ] + call_node.args + + return new_args + + def _node(self, type_, pos_from_node, **kwargs): + # type: (Any, Any, Any) -> Any + """ + Abstract some basic differences in node structure between versions + """ + + # Some nodes (like Module) dont have position + lineno = getattr(pos_from_node, "lineno", 1) + col_offset = getattr(pos_from_node, "col_offset", 0) + + if PY30_37: + # No end_lineno or end_pos_offset + return type_(lineno=lineno, col_offset=col_offset, **kwargs) + + # Py38+ + end_lineno = getattr(pos_from_node, "end_lineno", 1) + end_col_offset = getattr(pos_from_node, "end_col_offset", 0) + + return type_( + lineno=lineno, end_lineno=end_lineno, col_offset=col_offset, end_col_offset=end_col_offset, **kwargs + ) + + def _name_node(self, from_node, _id, ctx=ast.Load()): # noqa: B008 + # type: (Any, str, Any) -> ast.Name + return self._node( + ast.Name, + from_node, + id=_id, + ctx=ctx, + ) + + def _attr_node(self, from_node, attr, ctx=ast.Load()): # noqa: B008 + # type: (Any, str, Any) -> ast.Name + attr_attr = "" + name_attr = "" + if attr: + aspect_split = attr.split(".") + if len(aspect_split) > 1: + attr_attr = aspect_split[1] + name_attr = aspect_split[0] + + name_node = self._name_node(from_node, name_attr, ctx=ctx) + return self._node(ast.Attribute, from_node, attr=attr_attr, ctx=ctx, value=name_node) + + def _assign_node(self, from_node, targets, value): # type: (Any, List[Any], Any) -> Any + return self._node( + ast.Assign, + from_node, + targets=targets, + value=value, + type_comment=None, + ) + + def find_insert_position(self, module_node): # type: (ast.Module) -> int + insert_position = 0 + from_future_import_found = False + import_found = False + + # Check all nodes that are "from __future__ import...", as we must insert after them. + # + # Caveat: + # - body_node.lineno doesn't work because a large docstring changes the lineno + # but not the position in the nodes (i.e. this can happen: lineno==52, position==2) + # TODO: Test and implement cases with docstrings before future imports, etc. + for body_node in module_node.body: + insert_position += 1 + if isinstance(body_node, ImportFrom) and body_node.module == "__future__": + import_found = True + from_future_import_found = True + # As soon as we start a non-futuristic import we can stop looking + elif isinstance(body_node, ImportFrom): + import_found = True + elif isinstance(body_node, Expr) and not import_found: + continue + elif from_future_import_found: + insert_position -= 1 + break + else: + break + + if not from_future_import_found: + # No futuristic import found, reset the position to 0 + insert_position = 0 + + return insert_position + + def _none_constant(self, from_node, ctx=ast.Load()): # noqa: B008 + # type: (Any, Any) -> Any + if PY30_37: + return ast.NameConstant(lineno=from_node.lineno, col_offset=from_node.col_offset, value=None) + + # 3.8+ + return ast.Constant( + lineno=from_node.lineno, + col_offset=from_node.col_offset, + end_lineno=from_node.end_lineno, + end_col_offset=from_node.end_col_offset, + value=None, + kind=None, + ) + + def _int_constant(self, from_node, value): + return ast.Constant( + lineno=from_node.lineno, + col_offset=from_node.col_offset, + end_lineno=getattr(from_node, "end_lineno", from_node.lineno), + end_col_offset=from_node.col_offset + 1, + value=value, + kind=None, + ) + + def _call_node(self, from_node, func, args): # type: (Any, Any, List[Any]) -> Any + return self._node(ast.Call, from_node, func=func, args=args, keywords=[]) + + def visit_Module(self, module_node): + # type: (ast.Module) -> Any + """ + Insert the import statement for the replacements module + """ + insert_position = self.find_insert_position(module_node) + + definitions_module = self._aspects_spec["definitions_module"] + replacements_import = self._node( + ast.Import, + module_node, + names=[ + ast.alias( + lineno=1, + col_offset=0, + name=definitions_module, + asname=self._aspects_spec["alias_module"], + ) + ], + ) + module_node.body.insert(insert_position, replacements_import) + + definitions_module = self._sinkpoints_spec["definitions_module"] + replacements_import = self._node( + ast.Import, + module_node, + names=[ + ast.alias( + lineno=1, + col_offset=0, + name=definitions_module, + asname=self._sinkpoints_spec["alias_module"], + ) + ], + ) + module_node.body.insert(insert_position, replacements_import) + # Must be called here instead of the start so the line offset is already + # processed + self.generic_visit(module_node) + return module_node + + def visit_FunctionDef(self, def_node): + # type: (ast.FunctionDef) -> Any + """ + Special case for some tests which would enter in a patching + loop otherwise when visiting the check functions + """ + self.replacements_disabled_for_functiondef = def_node.name in self.dont_patch_these_functionsdefs + + if hasattr(def_node.args, "vararg") and def_node.args.vararg: + if def_node.args.vararg.annotation: + _mark_avoid_convert_recursively(def_node.args.vararg.annotation) + + if hasattr(def_node.args, "kwarg") and def_node.args.kwarg: + if def_node.args.kwarg.annotation: + _mark_avoid_convert_recursively(def_node.args.kwarg.annotation) + + if hasattr(def_node, "returns"): + _mark_avoid_convert_recursively(def_node.returns) + + for i in def_node.args.args: + if hasattr(i, "annotation"): + _mark_avoid_convert_recursively(i.annotation) + + if hasattr(def_node.args, "kwonlyargs"): + for i in def_node.args.kwonlyargs: + if hasattr(i, "annotation"): + _mark_avoid_convert_recursively(i.annotation) + + if hasattr(def_node.args, "posonlyargs"): + for i in def_node.args.posonlyargs: + if hasattr(i, "annotation"): + _mark_avoid_convert_recursively(i.annotation) + + self.generic_visit(def_node) + self._current_function_name = None + + return def_node + + def visit_Call(self, call_node): # type: (ast.Call) -> Any + """ + Replace a call or method + """ + self.generic_visit(call_node) + func_member = call_node.func + call_modified = False + if self.replacements_disabled_for_functiondef: + return call_node + + if isinstance(func_member, ast.Name) and func_member.id: + # Normal function call with func=Name(...), just change the name + func_name_node = func_member.id + aspect = self._aspect_functions.get(func_name_node) + if aspect: + # Send 0 as flag_added_args value + call_node.args.insert(0, self._int_constant(call_node, 0)) + # Insert original function name as first parameter + call_node.args = self._add_original_function_as_arg(call_node, True) + # Substitute function call + call_node.func = self._attr_node(call_node, aspect) + self.ast_modified = call_modified = True + else: + sink_point = self._sinkpoints_functions.get(func_name_node) + if sink_point: + call_node.func = self._attr_node(call_node, sink_point) + self.ast_modified = call_modified = True + # Call [attr] -> Attribute [value]-> Attribute [value]-> Attribute + # a.b.c.method() + # replaced_method(a.b.c) + elif isinstance(func_member, ast.Attribute): + # Method call: + method_name = func_member.attr + + if self._is_call_excluded(method_name): + # Early return if method is excluded + return call_node + + if self._is_string_format_with_literals(call_node): + return call_node + + aspect = self._aspect_methods.get(method_name) + + if aspect: + # Move the Attribute.value to 'args' + new_arg = func_member.value + call_node.args.insert(0, new_arg) + # Send 1 as flag_added_args value + call_node.args.insert(0, self._int_constant(call_node, 1)) + + # Insert None as first parameter instead of a.b.c.method + # to avoid unexpected side effects such as a.b.read(4).method + call_node.args.insert(0, self._none_constant(call_node)) + + # Create a new Name node for the replacement and set it as node.func + call_node.func = self._attr_node(call_node, aspect) + self.ast_modified = call_modified = True + + elif hasattr(func_member.value, "id") or hasattr(func_member.value, "attr"): + aspect = self._aspect_modules.get(method_name, None) + if aspect: + # Send 0 as flag_added_args value + call_node.args.insert(0, self._int_constant(call_node, 0)) + # Move the Function to 'args' + call_node.args.insert(0, call_node.func) + + # Create a new Name node for the replacement and set it as node.func + call_node.func = self._attr_node(call_node, aspect) + self.ast_modified = call_modified = True + + if self.codetype == CODE_TYPE_FIRST_PARTY: + # Function replacement case + if isinstance(call_node.func, ast.Name): + aspect = self._should_replace_with_taint_sink(call_node, True) + if aspect: + # Send 0 as flag_added_args value + call_node.args.insert(0, self._int_constant(call_node, 0)) + call_node.args = self._add_original_function_as_arg(call_node, False) + call_node.func = self._attr_node(call_node, TAINT_SINK_FUNCTION_REPLACEMENT) + self.ast_modified = call_modified = True + + # Method replacement case + elif isinstance(call_node.func, ast.Attribute): + aspect = self._should_replace_with_taint_sink(call_node, False) + if aspect: + # Send 0 as flag_added_args value + call_node.args.insert(0, self._int_constant(call_node, 0)) + # Create a new Name node for the replacement and set it as node.func + call_node.args = self._add_original_function_as_arg(call_node, False) + call_node.func = self._attr_node(call_node, TAINT_SINK_FUNCTION_REPLACEMENT) + self.ast_modified = call_modified = True + + if call_modified: + _set_metric_iast_instrumented_propagation() + + return call_node + + def visit_BinOp(self, call_node): # type: (ast.BinOp) -> Any + """ + Replace a binary operator + """ + self.generic_visit(call_node) + operator = call_node.op + + aspect = self._aspect_operators.get(operator.__class__) + if aspect: + self.ast_modified = True + _set_metric_iast_instrumented_propagation() + + return ast.Call(self._attr_node(call_node, aspect), [call_node.left, call_node.right], []) + + return call_node + + def visit_FormattedValue(self, fmt_value_node): # type: (ast.FormattedValue) -> Any + """ + Visit a FormattedValue node which are the constituent atoms for the + JoinedStr which are used to implement f-strings. + """ + + self.generic_visit(fmt_value_node) + + if hasattr(fmt_value_node, "value") and self._is_node_constant_or_binop(fmt_value_node.value): + return fmt_value_node + + func_name_node = self._attr_node(fmt_value_node, self._aspect_format_value) + + options_int = self._node( + ast.Constant, + fmt_value_node, + value=fmt_value_node.conversion, + kind=None, + ) + + format_spec = fmt_value_node.format_spec if fmt_value_node.format_spec else self._none_constant(fmt_value_node) + call_node = self._call_node( + fmt_value_node, + func=func_name_node, + args=[fmt_value_node.value, options_int, format_spec], + ) + + self.ast_modified = True + _set_metric_iast_instrumented_propagation() + return call_node + + def visit_JoinedStr(self, joinedstr_node): # type: (ast.JoinedStr) -> Any + """ + Replaced the JoinedStr AST node with a Call to the replacement function. Most of + the work inside fstring is done by visit_FormattedValue above. + """ + self.generic_visit(joinedstr_node) + + if all( + map( + lambda x: isinstance(x, ast.FormattedValue) or self._is_node_constant_or_binop(x), + joinedstr_node.values, + ) + ): + return joinedstr_node + + func_name_node = self._attr_node( + joinedstr_node, + self._aspect_build_string, + ctx=ast.Load(), + ) + call_node = self._call_node( + joinedstr_node, + func=func_name_node, + args=joinedstr_node.values, + ) + + self.ast_modified = True + _set_metric_iast_instrumented_propagation() + return call_node + + def visit_AugAssign(self, augassign_node): # type: (ast.AugAssign) -> Any + """Replace an inplace add or multiply.""" + if isinstance(augassign_node.target, ast.Subscript): + # Can't augassign to function call, ignore this node + augassign_node.target.avoid_convert = True # type: ignore[attr-defined] + self.generic_visit(augassign_node) + return augassign_node + + # TODO: Replace an inplace add or multiply (+= / *=) + return augassign_node + + def visit_Assign(self, assign_node): # type: (ast.Assign) -> Any + """ + Add the ignore marks for left-side subscripts or list/tuples to avoid problems + later with the visit_Subscript node. + """ + if isinstance(assign_node.value, ast.Subscript): + if hasattr(assign_node.value, "value") and hasattr(assign_node.value.value, "id"): + # Best effort to avoid converting type definitions + if assign_node.value.value.id in ( + "Callable", + "Dict", + "Generator", + "List", + "Optional", + "Sequence", + "Tuple", + "Type", + "TypeVar", + "Union", + ): + _mark_avoid_convert_recursively(assign_node.value) + + for target in assign_node.targets: + if isinstance(target, ast.Subscript): + # We can't assign to a function call, which is anyway going to rewrite + # the index destination so we just ignore that target + target.avoid_convert = True # type: ignore[attr-defined] + elif isinstance(target, (List, ast.Tuple)): + # Same for lists/tuples on the left side of the assignment + for element in target.elts: + if isinstance(element, ast.Subscript): + element.avoid_convert = True # type: ignore[attr-defined] + + # Create a normal assignment. This way we decompose multiple assignments + self.generic_visit(assign_node) + return assign_node + + def visit_Delete(self, assign_node): # type: (ast.Delete) -> Any + # del replaced_index(foo, bar) would fail so avoid converting the right hand side + # since it's going to be deleted anyway + + for target in assign_node.targets: + if isinstance(target, ast.Subscript): + target.avoid_convert = True # type: ignore[attr-defined] + + self.generic_visit(assign_node) + return assign_node + + def visit_AnnAssign(self, node): # type: (ast.AnnAssign) -> Any + # AnnAssign is a type annotation, we don't need to convert it + # and we avoid converting any subscript inside it. + _mark_avoid_convert_recursively(node) + self.generic_visit(node) + return node + + def visit_ClassDef(self, node): # type: (ast.ClassDef) -> Any + for i in node.bases: + _mark_avoid_convert_recursively(i) + + self.generic_visit(node) + return node + + def visit_Subscript(self, subscr_node): # type: (ast.Subscript) -> Any + """ + Turn an indexes[1] and slices[0:1:2] into the replacement function call + Optimization: dont convert if the indexes are strings + """ + self.generic_visit(subscr_node) + + # We mark nodes to avoid_convert (check visit_Delete, visit_AugAssign, visit_Assign) due to complex + # expressions that raise errors when try to replace with index aspects + if hasattr(subscr_node, "avoid_convert"): + return subscr_node + + # Optimization: String literal slices and indexes are not patched + if self._is_string_node(subscr_node.value): + return subscr_node + + attr_node = self._attr_node(subscr_node, "") + + call_node = self._call_node( + subscr_node, + func=attr_node, + args=[], + ) + if isinstance(subscr_node.slice, ast.Slice): + # Slice[0:1:2]. The other cases in this if are Indexes[0] + aspect_split = self._aspect_slice.split(".") + call_node.func.attr = aspect_split[1] + call_node.func.value.id = aspect_split[0] + none_node = self._none_constant(subscr_node) + lower = none_node if subscr_node.slice.lower is None else subscr_node.slice.lower + upper = none_node if subscr_node.slice.upper is None else subscr_node.slice.upper + step = none_node if subscr_node.slice.step is None else subscr_node.slice.step + call_node.args.extend([subscr_node.value, lower, upper, step]) + self.ast_modified = True + elif PY39_PLUS: + if self._is_string_node(subscr_node.slice): + return subscr_node + # In Py39+ the if subscr_node.slice member is not a Slice, is directly an unwrapped value + # for the index (e.g. Constant for a number, Name for a var, etc) + aspect_split = self._aspect_index.split(".") + call_node.func.attr = aspect_split[1] + call_node.func.value.id = aspect_split[0] + call_node.args.extend([subscr_node.value, subscr_node.slice]) + # TODO: python 3.8 isn't working correctly with index_aspect, tests raise: + # corrupted size vs. prev_size in fastbins + # Test failed with exit code -6 + # https://app.circleci.com/pipelines/github/DataDog/dd-trace-py/46665/workflows/3cf1257c-feaf-4653-bb9c-fb840baa1776/jobs/3031799 + # elif isinstance(subscr_node.slice, ast.Index): + # if self._is_string_node(subscr_node.slice.value): # type: ignore[attr-defined] + # return subscr_node + # aspect_split = self._aspect_index.split(".") + # call_node.func.attr = aspect_split[1] + # call_node.func.value.id = aspect_split[0] + # call_node.args.extend([subscr_node.value, subscr_node.slice.value]) # type: ignore[attr-defined] + else: + return subscr_node + + self.ast_modified = True + return call_node diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_input_info.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_input_info.py new file mode 100644 index 0000000..64a11c8 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_input_info.py @@ -0,0 +1,13 @@ +class Input_info(object): + __slots__ = ["name", "value", "origin"] + + def __init__(self, name, value, origin): + self.name = name + self.value = value + self.origin = origin + + def __eq__(self, other): + return self.name == other.name and self.value == other.value and self.origin == other.origin + + def __repr__(self): + return "input_info(%s, %s, %s)" % (str(self.name), str(self.value), str(self.origin)) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_loader.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_loader.py new file mode 100644 index 0000000..24da1ee --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_loader.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 + +from ddtrace.internal.logger import get_logger + +from ._ast.ast_patching import astpatch_module +from ._utils import _is_iast_enabled + + +log = get_logger(__name__) + + +IS_IAST_ENABLED = _is_iast_enabled() + + +def _exec_iast_patched_module(module_watchdog, module): + patched_source = None + if IS_IAST_ENABLED: + try: + module_path, patched_source = astpatch_module(module) + except Exception: + log.debug("Unexpected exception while AST patching", exc_info=True) + patched_source = None + + if patched_source: + # Patched source is executed instead of original module + compiled_code = compile(patched_source, module_path, "exec") + exec(compiled_code, module.__dict__) # nosec B102 + elif module_watchdog.loader is not None: + module_watchdog.loader.exec_module(module) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_metrics.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_metrics.py new file mode 100644 index 0000000..b1fad73 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_metrics.py @@ -0,0 +1,163 @@ +import os +import sys +import traceback +from typing import Dict + +from ddtrace.appsec._constants import IAST +from ddtrace.appsec._constants import IAST_SPAN_TAGS +from ddtrace.appsec._deduplications import deduplication +from ddtrace.internal import telemetry +from ddtrace.internal.logger import get_logger +from ddtrace.internal.telemetry.constants import TELEMETRY_NAMESPACE_TAG_IAST + + +log = get_logger(__name__) + +TELEMETRY_OFF_NAME = "OFF" +TELEMETRY_DEBUG_NAME = "DEBUG" +TELEMETRY_MANDATORY_NAME = "MANDATORY" +TELEMETRY_INFORMATION_NAME = "INFORMATION" + +TELEMETRY_DEBUG_VERBOSITY = 10 +TELEMETRY_INFORMATION_VERBOSITY = 20 +TELEMETRY_MANDATORY_VERBOSITY = 30 +TELEMETRY_OFF_VERBOSITY = 40 + +METRICS_REPORT_LVLS = ( + (TELEMETRY_DEBUG_VERBOSITY, TELEMETRY_DEBUG_NAME), + (TELEMETRY_INFORMATION_VERBOSITY, TELEMETRY_INFORMATION_NAME), + (TELEMETRY_MANDATORY_VERBOSITY, TELEMETRY_MANDATORY_NAME), + (TELEMETRY_OFF_VERBOSITY, TELEMETRY_OFF_NAME), +) + +_IAST_SPAN_METRICS: Dict[str, int] = {} + + +def get_iast_metrics_report_lvl(*args, **kwargs): + report_lvl_name = os.environ.get(IAST.TELEMETRY_REPORT_LVL, TELEMETRY_INFORMATION_NAME).upper() + report_lvl = 3 + for lvl, lvl_name in METRICS_REPORT_LVLS: + if report_lvl_name == lvl_name: + return lvl + return report_lvl + + +def metric_verbosity(lvl): + def wrapper(f): + if lvl >= get_iast_metrics_report_lvl(): + try: + return f + except Exception: + log.warning("Error reporting IAST metrics", exc_info=True) + return lambda: None # noqa: E731 + + return wrapper + + +@metric_verbosity(TELEMETRY_MANDATORY_VERBOSITY) +@deduplication +def _set_iast_error_metric(msg): + # type: (str) -> None + # Due to format_exc and format_exception returns the error and the last frame + try: + exception_type, exception_instance, _traceback_list = sys.exc_info() + res = [] + # first 3 frames are this function, the exception in aspects and the error line + res.extend(traceback.format_stack(limit=10)[:-3]) + + # get the frame with the error and the error message + result = traceback.format_exception(exception_type, exception_instance, _traceback_list) + res.extend(result[1:]) + + stack_trace = "".join(res) + tags = { + "lib_language": "python", + } + telemetry.telemetry_writer.add_log("ERROR", msg, stack_trace=stack_trace, tags=tags) + except Exception: + log.warning("Error reporting ASM WAF logs metrics", exc_info=True) + + +@metric_verbosity(TELEMETRY_MANDATORY_VERBOSITY) +def _set_metric_iast_instrumented_source(source_type): + from ._taint_tracking._native.taint_tracking import origin_to_str # noqa: F401 + + telemetry.telemetry_writer.add_count_metric( + TELEMETRY_NAMESPACE_TAG_IAST, "instrumented.source", 1, (("source_type", origin_to_str(source_type)),) + ) + + +@metric_verbosity(TELEMETRY_MANDATORY_VERBOSITY) +def _set_metric_iast_instrumented_propagation(): + telemetry.telemetry_writer.add_count_metric(TELEMETRY_NAMESPACE_TAG_IAST, "instrumented.propagation", 1) + + +@metric_verbosity(TELEMETRY_MANDATORY_VERBOSITY) +def _set_metric_iast_instrumented_sink(vulnerability_type, counter=1): + telemetry.telemetry_writer.add_count_metric( + TELEMETRY_NAMESPACE_TAG_IAST, "instrumented.sink", counter, (("vulnerability_type", vulnerability_type),) + ) + + +@metric_verbosity(TELEMETRY_INFORMATION_VERBOSITY) +def _set_metric_iast_executed_source(source_type): + from ._taint_tracking._native.taint_tracking import origin_to_str # noqa: F401 + + telemetry.telemetry_writer.add_count_metric( + TELEMETRY_NAMESPACE_TAG_IAST, "executed.source", 1, (("source_type", origin_to_str(source_type)),) + ) + + +@metric_verbosity(TELEMETRY_INFORMATION_VERBOSITY) +def _set_metric_iast_executed_sink(vulnerability_type): + telemetry.telemetry_writer.add_count_metric( + TELEMETRY_NAMESPACE_TAG_IAST, "executed.sink", 1, (("vulnerability_type", vulnerability_type),) + ) + + +def _request_tainted(): + from ._taint_tracking import num_objects_tainted + + return num_objects_tainted() + + +@metric_verbosity(TELEMETRY_INFORMATION_VERBOSITY) +def _set_metric_iast_request_tainted(): + total_objects_tainted = _request_tainted() + if total_objects_tainted > 0: + telemetry.telemetry_writer.add_count_metric( + TELEMETRY_NAMESPACE_TAG_IAST, "request.tainted", total_objects_tainted + ) + + +def _set_span_tag_iast_request_tainted(span): + total_objects_tainted = _request_tainted() + + if total_objects_tainted > 0: + span.set_tag(IAST_SPAN_TAGS.TELEMETRY_REQUEST_TAINTED, total_objects_tainted) + + +def _set_span_tag_iast_executed_sink(span): + data = get_iast_span_metrics() + + if data is not None: + for key, value in data.items(): + if key.startswith(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK): + span.set_tag(key, value) + + reset_iast_span_metrics() + + +def increment_iast_span_metric(prefix: str, metric_key: str, counter: int = 1) -> None: + data = get_iast_span_metrics() + full_key = prefix + "." + metric_key.lower() + result = data.get(full_key, 0) + data[full_key] = result + counter + + +def get_iast_span_metrics() -> Dict: + return _IAST_SPAN_METRICS + + +def reset_iast_span_metrics() -> None: + _IAST_SPAN_METRICS.clear() diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_overhead_control_engine.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_overhead_control_engine.py new file mode 100644 index 0000000..7862375 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_overhead_control_engine.py @@ -0,0 +1,133 @@ +""" +The Overhead control engine (OCE) is an element that by design ensures that the overhead does not go over a maximum +limit. It will measure operations being executed in a request and it will deactivate detection +(and therefore reduce the overhead to nearly 0) if a certain threshold is reached. +""" +import os +import threading +from typing import TYPE_CHECKING # noqa:F401 + +from ddtrace.internal.logger import get_logger +from ddtrace.sampler import RateSampler + + +if TYPE_CHECKING: # pragma: no cover + from typing import Set # noqa:F401 + from typing import Tuple # noqa:F401 + from typing import Type # noqa:F401 + + from ddtrace.span import Span # noqa:F401 + +log = get_logger(__name__) + + +def get_request_sampling_value(): # type: () -> float + # Percentage of requests analyzed by IAST (default: 30%) + return float(os.environ.get("DD_IAST_REQUEST_SAMPLING", 30.0)) + + +MAX_REQUESTS = int(os.environ.get("DD_IAST_MAX_CONCURRENT_REQUESTS", 2)) +MAX_VULNERABILITIES_PER_REQUEST = int(os.environ.get("DD_IAST_VULNERABILITIES_PER_REQUEST", 2)) + + +class Operation(object): + """Common operation related to Overhead Control Engine (OCE). Every vulnerabilities/taint_sinks should inherit + from this class. OCE instance calls these methods to control the overhead produced in each request. + """ + + _lock = threading.Lock() + _vulnerability_quota = MAX_VULNERABILITIES_PER_REQUEST + _reported_vulnerabilities = set() # type: Set[Tuple[str, int]] + + @classmethod + def reset(cls): + cls._vulnerability_quota = MAX_VULNERABILITIES_PER_REQUEST + cls._reported_vulnerabilities = set() + + @classmethod + def acquire_quota(cls): + # type: () -> bool + cls._lock.acquire() + result = False + if cls._vulnerability_quota > 0: + cls._vulnerability_quota -= 1 + result = True + cls._lock.release() + return result + + @classmethod + def increment_quota(cls): + # type: () -> bool + cls._lock.acquire() + result = False + if cls._vulnerability_quota < MAX_VULNERABILITIES_PER_REQUEST: + cls._vulnerability_quota += 1 + result = True + cls._lock.release() + return result + + @classmethod + def has_quota(cls): + # type: () -> bool + cls._lock.acquire() + result = cls._vulnerability_quota > 0 + cls._lock.release() + return result + + @classmethod + def is_not_reported(cls, filename, lineno): + # type: (str, int) -> bool + vulnerability_id = (filename, lineno) + if vulnerability_id in cls._reported_vulnerabilities: + return False + + cls._reported_vulnerabilities.add(vulnerability_id) + return True + + +class OverheadControl(object): + """This class is meant to control the overhead introduced by IAST analysis. + The goal is to do sampling at different levels of the IAST analysis (per process, per request, etc) + """ + + _lock = threading.Lock() + _request_quota = MAX_REQUESTS + _vulnerabilities = set() # type: Set[Type[Operation]] + _sampler = RateSampler(sample_rate=get_request_sampling_value() / 100.0) + + def reconfigure(self): + self._sampler = RateSampler(sample_rate=get_request_sampling_value() / 100.0) + + def acquire_request(self, span): + # type: (Span) -> bool + """Decide whether if IAST analysis will be done for this request. + - Block a request's quota at start of the request to limit simultaneous requests analyzed. + - Use sample rating to analyze only a percentage of the total requests (30% by default). + """ + if self._request_quota <= 0 or not self._sampler.sample(span): + return False + + with self._lock: + if self._request_quota <= 0: + return False + + self._request_quota -= 1 + + return True + + def release_request(self): + """increment request's quota at end of the request.""" + with self._lock: + self._request_quota += 1 + self.vulnerabilities_reset_quota() + + def register(self, klass): + # type: (Type[Operation]) -> Type[Operation] + """Register vulnerabilities/taint_sinks. This set of elements will restart for each request.""" + self._vulnerabilities.add(klass) + return klass + + def vulnerabilities_reset_quota(self): + # type: () -> None + for k in self._vulnerabilities: + k.reset() diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_patch.py new file mode 100644 index 0000000..d6d8fef --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_patch.py @@ -0,0 +1,177 @@ +import ctypes +import gc +import sys +from typing import TYPE_CHECKING # noqa:F401 + +from ddtrace.internal.logger import get_logger +from ddtrace.vendor.wrapt import FunctionWrapper +from ddtrace.vendor.wrapt import resolve_path + +from ._utils import _is_iast_enabled + + +if TYPE_CHECKING: # pragma: no cover + from typing import Any # noqa:F401 + from typing import Callable # noqa:F401 + from typing import Dict # noqa:F401 + from typing import Optional # noqa:F401 + + +_DD_ORIGINAL_ATTRIBUTES = {} # type: Dict[Any, Any] + +log = get_logger(__name__) + + +def set_and_check_module_is_patched(module_str, default_attr="_datadog_patch"): + # type: (str, str) -> Optional[bool] + try: + __import__(module_str) + module = sys.modules[module_str] + if getattr(module, default_attr, False): + return False + setattr(module, default_attr, True) + except ImportError: + pass + return True + + +def set_module_unpatched(module_str, default_attr="_datadog_patch"): + # type: (str, str) -> None + try: + __import__(module_str) + module = sys.modules[module_str] + setattr(module, default_attr, False) + except ImportError: + pass + + +def try_wrap_function_wrapper(module, name, wrapper): + # type: (str, str, Callable) -> None + try: + wrap_object(module, name, FunctionWrapper, (wrapper,)) + except (ImportError, AttributeError): + log.debug("IAST patching. Module %s.%s not exists", module, name) + + +def try_unwrap(module, name): + try: + (parent, attribute, _) = resolve_path(module, name) + if (parent, attribute) in _DD_ORIGINAL_ATTRIBUTES: + original = _DD_ORIGINAL_ATTRIBUTES[(parent, attribute)] + apply_patch(parent, attribute, original) + del _DD_ORIGINAL_ATTRIBUTES[(parent, attribute)] + except ModuleNotFoundError: + pass + + +def apply_patch(parent, attribute, replacement): + try: + current_attribute = getattr(parent, attribute) + # Avoid overwriting the original function if we call this twice + if not isinstance(current_attribute, FunctionWrapper): + _DD_ORIGINAL_ATTRIBUTES[(parent, attribute)] = current_attribute + setattr(parent, attribute, replacement) + except (TypeError, AttributeError): + patch_builtins(parent, attribute, replacement) + + +def wrap_object(module, name, factory, args=(), kwargs=None): + if kwargs is None: + kwargs = {} + (parent, attribute, original) = resolve_path(module, name) + wrapper = factory(original, *args, **kwargs) + apply_patch(parent, attribute, wrapper) + return wrapper + + +def patchable_builtin(klass): + refs = gc.get_referents(klass.__dict__) + return refs[0] + + +def patch_builtins(klass, attr, value): + """Based on forbiddenfruit package: + https://github.com/clarete/forbiddenfruit/blob/master/forbiddenfruit/__init__.py#L421 + --- + Patch a built-in `klass` with `attr` set to `value` + + This function monkey-patches the built-in python object `attr` adding a new + attribute to it. You can add any kind of argument to the `class`. + + It's possible to attach methods as class methods, just do the following: + + >>> def myclassmethod(cls): + ... return cls(1.5) + >>> curse(float, "myclassmethod", classmethod(myclassmethod)) + >>> float.myclassmethod() + 1.5 + + Methods will be automatically bound, so don't forget to add a self + parameter to them, like this: + + >>> def hello(self): + ... return self * 2 + >>> curse(str, "hello", hello) + >>> "yo".hello() + "yoyo" + """ + dikt = patchable_builtin(klass) + + old_value = dikt.get(attr, None) + old_name = "_c_%s" % attr # do not use .format here, it breaks py2.{5,6} + + # Patch the thing + dikt[attr] = value + + if old_value: + dikt[old_name] = old_value + + try: + dikt[attr].__name__ = old_value.__name__ + except (AttributeError, TypeError): # py2.5 will raise `TypeError` + pass + try: + dikt[attr].__qualname__ = old_value.__qualname__ + except AttributeError: + pass + + ctypes.pythonapi.PyType_Modified(ctypes.py_object(klass)) + + +def if_iast_taint_returned_object_for(origin, wrapped, instance, args, kwargs): + value = wrapped(*args, **kwargs) + + if _is_iast_enabled(): + try: + from ._taint_tracking import is_pyobject_tainted + from ._taint_tracking import taint_pyobject + from .processor import AppSecIastSpanProcessor + + if not AppSecIastSpanProcessor.is_span_analyzed(): + return value + + if not is_pyobject_tainted(value): + name = str(args[0]) if len(args) else "http.request.body" + return taint_pyobject(pyobject=value, source_name=name, source_value=value, source_origin=origin) + except Exception: + log.debug("Unexpected exception while tainting pyobject", exc_info=True) + return value + + +def if_iast_taint_yield_tuple_for(origins, wrapped, instance, args, kwargs): + if _is_iast_enabled(): + from ._taint_tracking import taint_pyobject + from .processor import AppSecIastSpanProcessor + + if not AppSecIastSpanProcessor.is_span_analyzed(): + for key, value in wrapped(*args, **kwargs): + yield key, value + + for key, value in wrapped(*args, **kwargs): + new_key = taint_pyobject(pyobject=key, source_name=key, source_value=key, source_origin=origins[0]) + new_value = taint_pyobject(pyobject=value, source_name=key, source_value=value, source_origin=origins[1]) + yield new_key, new_value + + else: + for key, value in wrapped(*args, **kwargs): + yield key, value diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_patch_modules.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_patch_modules.py new file mode 100644 index 0000000..05a6900 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_patch_modules.py @@ -0,0 +1,27 @@ +from ddtrace.vendor.wrapt.importer import when_imported + + +IAST_PATCH = { + "command_injection": True, + "path_traversal": True, + "weak_cipher": True, + "weak_hash": True, +} + + +def patch_iast(patch_modules=IAST_PATCH): + """Load IAST vulnerabilities sink points. + + IAST_PATCH: list of implemented vulnerabilities + """ + # TODO: Devise the correct patching strategy for IAST + from ddtrace._monkey import _on_import_factory + + for module in (m for m, e in patch_modules.items() if e): + when_imported("hashlib")( + _on_import_factory(module, prefix="ddtrace.appsec._iast.taint_sinks", raise_errors=False) + ) + + when_imported("json")( + _on_import_factory("json_tainting", prefix="ddtrace.appsec._iast._patches", raise_errors=False) + ) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_patches/json_tainting.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_patches/json_tainting.py new file mode 100644 index 0000000..0984b7a --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_patches/json_tainting.py @@ -0,0 +1,82 @@ +from ddtrace.internal.logger import get_logger +from ddtrace.settings.asm import config as asm_config + +from .._patch import set_and_check_module_is_patched +from .._patch import set_module_unpatched +from .._patch import try_unwrap +from .._patch import try_wrap_function_wrapper + + +log = get_logger(__name__) + + +_DEFAULT_ATTR = "_datadog_json_tainting_patch" + + +def get_version(): + # type: () -> str + return "" + + +def unpatch_iast(): + # type: () -> None + set_module_unpatched("json", default_attr=_DEFAULT_ATTR) + try_unwrap("json", "loads") + if asm_config._iast_lazy_taint: + try_unwrap("json.encoder", "JSONEncoder.default") + try_unwrap("simplejson.encoder", "JSONEncoder.default") + + +def patch(): + # type: () -> None + """Wrap functions which interact with file system.""" + if not set_and_check_module_is_patched("json", default_attr=_DEFAULT_ATTR): + return + try_wrap_function_wrapper("json", "loads", wrapped_loads) + if asm_config._iast_lazy_taint: + try_wrap_function_wrapper("json.encoder", "JSONEncoder.default", patched_json_encoder_default) + try_wrap_function_wrapper("simplejson.encoder", "JSONEncoder.default", patched_json_encoder_default) + + +def wrapped_loads(wrapped, instance, args, kwargs): + from .._taint_utils import taint_structure + + obj = wrapped(*args, **kwargs) + if asm_config._iast_enabled: + try: + from .._taint_tracking import get_tainted_ranges + from .._taint_tracking import is_pyobject_tainted + from .._taint_tracking import taint_pyobject + from ..processor import AppSecIastSpanProcessor + + if not AppSecIastSpanProcessor.is_span_analyzed(): + return obj + + if is_pyobject_tainted(args[0]) and obj: + # tainting object + ranges = get_tainted_ranges(args[0]) + if not ranges: + return obj + # take the first source as main source + source = ranges[0].source + if isinstance(obj, dict): + obj = taint_structure(obj, source.origin, source.origin) + elif isinstance(obj, list): + obj = taint_structure(obj, source.origin, source.origin) + elif isinstance(obj, (str, bytes, bytearray)): + obj = taint_pyobject(obj, source.name, source.value, source.origin) + pass + except Exception: + log.debug("Unexpected exception while reporting vulnerability", exc_info=True) + raise + return obj + + +def patched_json_encoder_default(original_func, instance, args, kwargs): + from .._taint_utils import LazyTaintDict + from .._taint_utils import LazyTaintList + + if isinstance(args[0], (LazyTaintList, LazyTaintDict)): + return args[0]._obj + + return original_func(*args, **kwargs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_stacktrace.cpython-311-x86_64-linux-gnu.so b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_stacktrace.cpython-311-x86_64-linux-gnu.so new file mode 100755 index 0000000000000000000000000000000000000000..28a86bf3842515014a06d9f5e774e53496f7b869 GIT binary patch literal 16976 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 literal 0 HcmV?d00001 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_dict.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_dict.py new file mode 100644 index 0000000..97df240 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_dict.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# +from typing import TYPE_CHECKING # noqa:F401 + + +if TYPE_CHECKING: + from typing import Dict # noqa:F401 + from typing import Tuple # noqa:F401 + + from ._taint_tracking import Source # noqa:F401 + +_IAST_TAINT_DICT = {} # type: Dict[int, Tuple[Tuple[Source, int, int],...]] + + +def get_taint_dict(): # type: () -> Dict[int, Tuple[Tuple[Source, int, int],...]] + return _IAST_TAINT_DICT + + +def clear_taint_mapping(): # type: () -> None + global _IAST_TAINT_DICT + _IAST_TAINT_DICT = {} diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/CMakeLists.txt b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/CMakeLists.txt new file mode 100644 index 0000000..9caa9fc --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/CMakeLists.txt @@ -0,0 +1,76 @@ +cmake_minimum_required(VERSION 3.19) +include(FetchContent) + +set(APP_NAME _native) +option(BUILD_MACOS "Build for MacOS" OFF) + +project(${APP_NAME}) + +set(CMAKE_CXX_STANDARD 17) + +# -U_FORTIFY_SOURCE to fix a bug in alpine and pybind11 +# https://github.com/pybind/pybind11/issues/1650 +# https://gitlab.alpinelinux.org/alpine/aports/-/issues/8626 +add_compile_options(-fPIC -fexceptions -fvisibility=hidden -fpermissive -pthread -Wall -Wno-unknown-pragmas -U_FORTIFY_SOURCE) + +if(BUILD_MACOS) + # https://pybind11.readthedocs.io/en/stable/compiling.html#building-manually + message(STATUS "Compile options for MacOS") + add_link_options(-ldl -undefined dynamic_lookup) +else() + message(STATUS "Compile options for Linux/Win") +endif(BUILD_MACOS) +unset(BUILD_MACOS CACHE) + +if(CMAKE_BUILD_TYPE STREQUAL "Release") + message("Release mode: using abseil") + FetchContent_Declare( + absl + URL "https://github.com/abseil/abseil-cpp/archive/refs/tags/20230802.1.zip" + ) + FetchContent_MakeAvailable(absl) +else() + message("Debug mode: not using abseil") +endif() + +include_directories(".") + +file(GLOB SOURCE_FILES "*.cpp" + "Aspects/*.cpp" + "Initializer/*.cpp" + "TaintedOps/*.cpp" + "TaintTracking/*.cpp" + "Utils/*.cpp") +file(GLOB HEADER_FILES "*.h" + "Aspects/*.h" + "Initializer/*.h" + "TaintedOps/*.h" + "TaintTracking/*.h" + "Utils/*.h" + ) + +# Debug messages +message(STATUS "PYTHON_LIBRARIES = ${Python_LIBRARIES}") +message(STATUS "PYTHON_EXECUTABLE = ${Python_EXECUTABLE}") +message(STATUS "PYTHON_INCLUDE_DIRS = ${Python_INCLUDE_DIRS}") +message(STATUS "Python_EXECUTABLE = ${Python_EXECUTABLE}") + +add_subdirectory(_vendor/pybind11) + +pybind11_add_module(_native SHARED ${SOURCE_FILES} ${HEADER_FILES}) +get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} DIRECTORY) +set_target_properties( + _native + PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}" +) + +if(CMAKE_BUILD_TYPE STREQUAL "Release") + target_link_libraries(${APP_NAME} PRIVATE absl::node_hash_map) +endif() + +install(TARGETS _native DESTINATION + LIBRARY DESTINATION ${LIB_INSTALL_DIR} + ARCHIVE DESTINATION ${LIB_INSTALL_DIR} + RUNTIME DESTINATION ${LIB_INSTALL_DIR} +) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/README.txt b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/README.txt new file mode 100644 index 0000000..f7f67f2 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/README.txt @@ -0,0 +1,37 @@ +# Compile extension with Cmake + +```bash +sh clean.sh +cmake -DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3.11 . && \ + make -j _native && \ + mv lib_native.so _native.so +``` + +## Verify compilation was correctly + +```bash +python3.11 +``` +```python +from _native import Source, TaintRange +source = Source(name="aaa", value="bbbb", origin="ccc") +source = Source("aaa", "bbbb", "ccc") +``` + +## Clean Cmake folders + +```bash +./clean.sh +``` + + +## Debug with Valgrind + +wget http://svn.python.org/projects/python/trunk/Misc/valgrind-python.supp + +valgrind --tool=memcheck --suppressions=ddtrace/appsec/_iast/_taint_tracking/valgrind-python.supp \ +python ddtrace/appsec/_iast/_taint_tracking/bench_overload.py --log-file="valgrind_bench_overload.out" + +# Debug with gdb + +gdb --args python -m pytest tests/appsec/iast/test_command_injection.py diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/__init__.py new file mode 100644 index 0000000..9506d7a --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/__init__.py @@ -0,0 +1,143 @@ +# #!/usr/bin/env python3 +# flake8: noqa +from typing import TYPE_CHECKING + +from .._metrics import _set_metric_iast_executed_source +from .._utils import _is_python_version_supported + + +if _is_python_version_supported(): + from .. import oce + from ._native import ops + from ._native.aspect_helpers import _convert_escaped_text_to_tainted_text + from ._native.aspect_helpers import as_formatted_evidence + from ._native.aspect_helpers import common_replace + from ._native.aspect_format import _format_aspect + from ._native.aspect_helpers import parse_params + from ._native.initializer import active_map_addreses_size + from ._native.initializer import create_context + from ._native.initializer import debug_taint_map + from ._native.initializer import destroy_context + from ._native.initializer import initializer_size + from ._native.initializer import num_objects_tainted + from ._native.initializer import reset_context + from ._native.taint_tracking import OriginType + from ._native.taint_tracking import Source + from ._native.taint_tracking import TagMappingMode + from ._native.taint_tracking import are_all_text_all_ranges + from ._native.taint_tracking import get_range_by_hash + from ._native.taint_tracking import get_ranges + from ._native.taint_tracking import is_notinterned_notfasttainted_unicode + from ._native.taint_tracking import is_tainted + from ._native.taint_tracking import origin_to_str + from ._native.taint_tracking import set_fast_tainted_if_notinterned_unicode + from ._native.taint_tracking import set_ranges + from ._native.taint_tracking import copy_ranges_from_strings + from ._native.taint_tracking import copy_and_shift_ranges_from_strings + from ._native.taint_tracking import shift_taint_range + from ._native.taint_tracking import shift_taint_ranges + from ._native.taint_tracking import str_to_origin + from ._native.taint_tracking import taint_range as TaintRange + + new_pyobject_id = ops.new_pyobject_id + set_ranges_from_values = ops.set_ranges_from_values + is_pyobject_tainted = is_tainted + +if TYPE_CHECKING: + from typing import Any + from typing import Dict + from typing import List + from typing import Tuple + from typing import Union + + +__all__ = [ + "_convert_escaped_text_to_tainted_text", + "new_pyobject_id", + "setup", + "Source", + "OriginType", + "TagMappingMode", + "TaintRange", + "get_ranges", + "set_ranges", + "copy_ranges_from_strings", + "copy_and_shift_ranges_from_strings", + "are_all_text_all_ranges", + "shift_taint_range", + "shift_taint_ranges", + "get_range_by_hash", + "is_notinterned_notfasttainted_unicode", + "set_fast_tainted_if_notinterned_unicode", + "aspect_helpers", + "reset_context", + "destroy_context", + "initializer_size", + "active_map_addreses_size", + "create_context", + "str_to_origin", + "origin_to_str", + "common_replace", + "_format_aspect", + "as_formatted_evidence", + "parse_params", + "num_objects_tainted", + "debug_taint_map", +] + + +def taint_pyobject(pyobject, source_name, source_value, source_origin=None): + # type: (Any, Any, Any, OriginType) -> Any + + # Pyobject must be Text with len > 1 + if not pyobject or not isinstance(pyobject, (str, bytes, bytearray)): + return pyobject + + if isinstance(source_name, (bytes, bytearray)): + source_name = str(source_name, encoding="utf8", errors="ignore") + if isinstance(source_name, OriginType): + source_name = origin_to_str(source_name) + + if isinstance(source_value, (bytes, bytearray)): + source_value = str(source_value, encoding="utf8", errors="ignore") + if source_origin is None: + source_origin = OriginType.PARAMETER + + pyobject_newid = set_ranges_from_values(pyobject, len(pyobject), source_name, source_value, source_origin) + _set_metric_iast_executed_source(source_origin) + return pyobject_newid + + +def taint_pyobject_with_ranges(pyobject, ranges): # type: (Any, tuple) -> None + set_ranges(pyobject, tuple(ranges)) + + +def get_tainted_ranges(pyobject): # type: (Any) -> tuple + return get_ranges(pyobject) + + +def taint_ranges_as_evidence_info(pyobject): + # type: (Any) -> Tuple[List[Dict[str, Union[Any, int]]], list[Source]] + value_parts = [] + sources = [] + current_pos = 0 + tainted_ranges = get_tainted_ranges(pyobject) + if not len(tainted_ranges): + return ([{"value": pyobject}], []) + + for _range in tainted_ranges: + if _range.start > current_pos: + value_parts.append({"value": pyobject[current_pos : _range.start]}) + + if _range.source not in sources: + sources.append(_range.source) + + value_parts.append( + {"value": pyobject[_range.start : _range.start + _range.length], "source": sources.index(_range.source)} + ) + current_pos = _range.start + _range.length + + if current_pos < len(pyobject): + value_parts.append({"value": pyobject[current_pos:]}) + + return value_parts, sources diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_native.cpython-311-x86_64-linux-gnu.so b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_native.cpython-311-x86_64-linux-gnu.so new file mode 100755 index 0000000000000000000000000000000000000000..ee286c98c361a952d5d1a05a8d816a4e294bf28e GIT binary patch literal 633224 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 jG`J?ig^++C@ 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 WFy!i^sk?s0KWY6QlTj00LHch~Do7Fl literal 0 HcmV?d00001 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/CMakeLists.txt b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/CMakeLists.txt new file mode 100644 index 0000000..87ec103 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/CMakeLists.txt @@ -0,0 +1,322 @@ +# CMakeLists.txt -- Build system for the pybind11 modules +# +# Copyright (c) 2015 Wenzel Jakob +# +# All rights reserved. Use of this source code is governed by a +# BSD-style license that can be found in the LICENSE file. + +cmake_minimum_required(VERSION 3.5) + +# The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with +# some versions of VS that have a patched CMake 3.11. This forces us to emulate +# the behavior using the following workaround: +if(${CMAKE_VERSION} VERSION_LESS 3.26) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +else() + cmake_policy(VERSION 3.26) +endif() + +# Avoid infinite recursion if tests include this as a subdirectory +if(DEFINED PYBIND11_MASTER_PROJECT) + return() +endif() + +# Extract project version from source +file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/include/pybind11/detail/common.h" + pybind11_version_defines REGEX "#define PYBIND11_VERSION_(MAJOR|MINOR|PATCH) ") + +foreach(ver ${pybind11_version_defines}) + if(ver MATCHES [[#define PYBIND11_VERSION_(MAJOR|MINOR|PATCH) +([^ ]+)$]]) + set(PYBIND11_VERSION_${CMAKE_MATCH_1} "${CMAKE_MATCH_2}") + endif() +endforeach() + +if(PYBIND11_VERSION_PATCH MATCHES [[\.([a-zA-Z0-9]+)$]]) + set(pybind11_VERSION_TYPE "${CMAKE_MATCH_1}") +endif() +string(REGEX MATCH "^[0-9]+" PYBIND11_VERSION_PATCH "${PYBIND11_VERSION_PATCH}") + +project( + pybind11 + LANGUAGES CXX + VERSION "${PYBIND11_VERSION_MAJOR}.${PYBIND11_VERSION_MINOR}.${PYBIND11_VERSION_PATCH}") + +# Standard includes +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) +include(CMakeDependentOption) + +if(NOT pybind11_FIND_QUIETLY) + message(STATUS "pybind11 v${pybind11_VERSION} ${pybind11_VERSION_TYPE}") +endif() + +# Check if pybind11 is being used directly or via add_subdirectory +if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) + ### Warn if not an out-of-source builds + if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) + set(lines + "You are building in-place. If that is not what you intended to " + "do, you can clean the source directory with:\n" + "rm -r CMakeCache.txt CMakeFiles/ cmake_uninstall.cmake pybind11Config.cmake " + "pybind11ConfigVersion.cmake tests/CMakeFiles/\n") + message(AUTHOR_WARNING ${lines}) + endif() + + set(PYBIND11_MASTER_PROJECT ON) + + if(OSX AND CMAKE_VERSION VERSION_LESS 3.7) + # Bug in macOS CMake < 3.7 is unable to download catch + message(WARNING "CMAKE 3.7+ needed on macOS to download catch, and newer HIGHLY recommended") + elseif(WINDOWS AND CMAKE_VERSION VERSION_LESS 3.8) + # Only tested with 3.8+ in CI. + message(WARNING "CMAKE 3.8+ tested on Windows, previous versions untested") + endif() + + message(STATUS "CMake ${CMAKE_VERSION}") + + if(CMAKE_CXX_STANDARD) + set(CMAKE_CXX_EXTENSIONS OFF) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + endif() + + set(pybind11_system "") + + set_property(GLOBAL PROPERTY USE_FOLDERS ON) +else() + set(PYBIND11_MASTER_PROJECT OFF) + set(pybind11_system SYSTEM) +endif() + +# Options +option(PYBIND11_INSTALL "Install pybind11 header files?" ${PYBIND11_MASTER_PROJECT}) +option(PYBIND11_TEST "Build pybind11 test suite?" ${PYBIND11_MASTER_PROJECT}) +option(PYBIND11_NOPYTHON "Disable search for Python" OFF) +option(PYBIND11_SIMPLE_GIL_MANAGEMENT + "Use simpler GIL management logic that does not support disassociation" OFF) +set(PYBIND11_INTERNALS_VERSION + "" + CACHE STRING "Override the ABI version, may be used to enable the unstable ABI.") + +if(PYBIND11_SIMPLE_GIL_MANAGEMENT) + add_compile_definitions(PYBIND11_SIMPLE_GIL_MANAGEMENT) +endif() + +cmake_dependent_option( + USE_PYTHON_INCLUDE_DIR + "Install pybind11 headers in Python include directory instead of default installation prefix" + OFF "PYBIND11_INSTALL" OFF) + +cmake_dependent_option(PYBIND11_FINDPYTHON "Force new FindPython" OFF + "NOT CMAKE_VERSION VERSION_LESS 3.12" OFF) + +# NB: when adding a header don't forget to also add it to setup.py +set(PYBIND11_HEADERS + include/pybind11/detail/class.h + include/pybind11/detail/common.h + include/pybind11/detail/descr.h + include/pybind11/detail/init.h + include/pybind11/detail/internals.h + include/pybind11/detail/type_caster_base.h + include/pybind11/detail/typeid.h + include/pybind11/attr.h + include/pybind11/buffer_info.h + include/pybind11/cast.h + include/pybind11/chrono.h + include/pybind11/common.h + include/pybind11/complex.h + include/pybind11/options.h + include/pybind11/eigen.h + include/pybind11/eigen/common.h + include/pybind11/eigen/matrix.h + include/pybind11/eigen/tensor.h + include/pybind11/embed.h + include/pybind11/eval.h + include/pybind11/gil.h + include/pybind11/iostream.h + include/pybind11/functional.h + include/pybind11/numpy.h + include/pybind11/operators.h + include/pybind11/pybind11.h + include/pybind11/pytypes.h + include/pybind11/stl.h + include/pybind11/stl_bind.h + include/pybind11/stl/filesystem.h + include/pybind11/type_caster_pyobject_ptr.h) + +# Compare with grep and warn if mismatched +if(PYBIND11_MASTER_PROJECT AND NOT CMAKE_VERSION VERSION_LESS 3.12) + file( + GLOB_RECURSE _pybind11_header_check + LIST_DIRECTORIES false + RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" + CONFIGURE_DEPENDS "include/pybind11/*.h") + set(_pybind11_here_only ${PYBIND11_HEADERS}) + set(_pybind11_disk_only ${_pybind11_header_check}) + list(REMOVE_ITEM _pybind11_here_only ${_pybind11_header_check}) + list(REMOVE_ITEM _pybind11_disk_only ${PYBIND11_HEADERS}) + if(_pybind11_here_only) + message(AUTHOR_WARNING "PYBIND11_HEADERS has extra files:" ${_pybind11_here_only}) + endif() + if(_pybind11_disk_only) + message(AUTHOR_WARNING "PYBIND11_HEADERS is missing files:" ${_pybind11_disk_only}) + endif() +endif() + +# CMake 3.12 added list(TRANSFORM PREPEND +# But we can't use it yet +string(REPLACE "include/" "${CMAKE_CURRENT_SOURCE_DIR}/include/" PYBIND11_HEADERS + "${PYBIND11_HEADERS}") + +# Cache variable so this can be used in parent projects +set(pybind11_INCLUDE_DIR + "${CMAKE_CURRENT_LIST_DIR}/include" + CACHE INTERNAL "Directory where pybind11 headers are located") + +# Backward compatible variable for add_subdirectory mode +if(NOT PYBIND11_MASTER_PROJECT) + set(PYBIND11_INCLUDE_DIR + "${pybind11_INCLUDE_DIR}" + CACHE INTERNAL "") +endif() + +# Note: when creating targets, you cannot use if statements at configure time - +# you need generator expressions, because those will be placed in the target file. +# You can also place ifs *in* the Config.in, but not here. + +# This section builds targets, but does *not* touch Python +# Non-IMPORT targets cannot be defined twice +if(NOT TARGET pybind11_headers) + # Build the headers-only target (no Python included): + # (long name used here to keep this from clashing in subdirectory mode) + add_library(pybind11_headers INTERFACE) + add_library(pybind11::pybind11_headers ALIAS pybind11_headers) # to match exported target + add_library(pybind11::headers ALIAS pybind11_headers) # easier to use/remember + + target_include_directories( + pybind11_headers ${pybind11_system} INTERFACE $ + $) + + target_compile_features(pybind11_headers INTERFACE cxx_inheriting_constructors cxx_user_literals + cxx_right_angle_brackets) + if(NOT "${PYBIND11_INTERNALS_VERSION}" STREQUAL "") + target_compile_definitions( + pybind11_headers INTERFACE "PYBIND11_INTERNALS_VERSION=${PYBIND11_INTERNALS_VERSION}") + endif() +else() + # It is invalid to install a target twice, too. + set(PYBIND11_INSTALL OFF) +endif() + +include("${CMAKE_CURRENT_SOURCE_DIR}/tools/pybind11Common.cmake") +# https://github.com/jtojnar/cmake-snips/#concatenating-paths-when-building-pkg-config-files +# TODO: cmake 3.20 adds the cmake_path() function, which obsoletes this snippet +include("${CMAKE_CURRENT_SOURCE_DIR}/tools/JoinPaths.cmake") + +# Relative directory setting +if(USE_PYTHON_INCLUDE_DIR AND DEFINED Python_INCLUDE_DIRS) + file(RELATIVE_PATH CMAKE_INSTALL_INCLUDEDIR ${CMAKE_INSTALL_PREFIX} ${Python_INCLUDE_DIRS}) +elseif(USE_PYTHON_INCLUDE_DIR AND DEFINED PYTHON_INCLUDE_DIR) + file(RELATIVE_PATH CMAKE_INSTALL_INCLUDEDIR ${CMAKE_INSTALL_PREFIX} ${PYTHON_INCLUDE_DIRS}) +endif() + +if(PYBIND11_INSTALL) + install(DIRECTORY ${pybind11_INCLUDE_DIR}/pybind11 DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + set(PYBIND11_CMAKECONFIG_INSTALL_DIR + "${CMAKE_INSTALL_DATAROOTDIR}/cmake/${PROJECT_NAME}" + CACHE STRING "install path for pybind11Config.cmake") + + if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}") + set(pybind11_INCLUDEDIR "${CMAKE_INSTALL_FULL_INCLUDEDIR}") + else() + set(pybind11_INCLUDEDIR "\$\{PACKAGE_PREFIX_DIR\}/${CMAKE_INSTALL_INCLUDEDIR}") + endif() + + configure_package_config_file( + tools/${PROJECT_NAME}Config.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" + INSTALL_DESTINATION ${PYBIND11_CMAKECONFIG_INSTALL_DIR}) + + if(CMAKE_VERSION VERSION_LESS 3.14) + # Remove CMAKE_SIZEOF_VOID_P from ConfigVersion.cmake since the library does + # not depend on architecture specific settings or libraries. + set(_PYBIND11_CMAKE_SIZEOF_VOID_P ${CMAKE_SIZEOF_VOID_P}) + unset(CMAKE_SIZEOF_VOID_P) + + write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY AnyNewerVersion) + + set(CMAKE_SIZEOF_VOID_P ${_PYBIND11_CMAKE_SIZEOF_VOID_P}) + else() + # CMake 3.14+ natively supports header-only libraries + write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY AnyNewerVersion ARCH_INDEPENDENT) + endif() + + install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake + tools/FindPythonLibsNew.cmake + tools/pybind11Common.cmake + tools/pybind11Tools.cmake + tools/pybind11NewTools.cmake + DESTINATION ${PYBIND11_CMAKECONFIG_INSTALL_DIR}) + + if(NOT PYBIND11_EXPORT_NAME) + set(PYBIND11_EXPORT_NAME "${PROJECT_NAME}Targets") + endif() + + install(TARGETS pybind11_headers EXPORT "${PYBIND11_EXPORT_NAME}") + + install( + EXPORT "${PYBIND11_EXPORT_NAME}" + NAMESPACE "pybind11::" + DESTINATION ${PYBIND11_CMAKECONFIG_INSTALL_DIR}) + + # pkg-config support + if(NOT prefix_for_pc_file) + set(prefix_for_pc_file "${CMAKE_INSTALL_PREFIX}") + endif() + join_paths(includedir_for_pc_file "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}") + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/tools/pybind11.pc.in" + "${CMAKE_CURRENT_BINARY_DIR}/pybind11.pc" @ONLY) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/pybind11.pc" + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig/") + + # Uninstall target + if(PYBIND11_MASTER_PROJECT) + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/tools/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY) + + add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P + ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) + endif() +endif() + +# BUILD_TESTING takes priority, but only if this is the master project +if(PYBIND11_MASTER_PROJECT AND DEFINED BUILD_TESTING) + if(BUILD_TESTING) + if(_pybind11_nopython) + message(FATAL_ERROR "Cannot activate tests in NOPYTHON mode") + else() + add_subdirectory(tests) + endif() + endif() +else() + if(PYBIND11_TEST) + if(_pybind11_nopython) + message(FATAL_ERROR "Cannot activate tests in NOPYTHON mode") + else() + add_subdirectory(tests) + endif() + endif() +endif() + +# Better symmetry with find_package(pybind11 CONFIG) mode. +if(NOT PYBIND11_MASTER_PROJECT) + set(pybind11_FOUND + TRUE + CACHE INTERNAL "True if pybind11 and all required components found on the system") +endif() diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/__init__.py new file mode 100644 index 0000000..7c10b30 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/__init__.py @@ -0,0 +1,17 @@ +import sys + +if sys.version_info < (3, 6): # noqa: UP036 + msg = "pybind11 does not support Python < 3.6. 2.9 was the last release supporting Python 2.7 and 3.5." + raise ImportError(msg) + + +from ._version import __version__, version_info +from .commands import get_cmake_dir, get_include, get_pkgconfig_dir + +__all__ = ( + "version_info", + "__version__", + "get_include", + "get_cmake_dir", + "get_pkgconfig_dir", +) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/__main__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/__main__.py new file mode 100644 index 0000000..180665c --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/__main__.py @@ -0,0 +1,62 @@ +# pylint: disable=missing-function-docstring + +import argparse +import sys +import sysconfig + +from ._version import __version__ +from .commands import get_cmake_dir, get_include, get_pkgconfig_dir + + +def print_includes() -> None: + dirs = [ + sysconfig.get_path("include"), + sysconfig.get_path("platinclude"), + get_include(), + ] + + # Make unique but preserve order + unique_dirs = [] + for d in dirs: + if d and d not in unique_dirs: + unique_dirs.append(d) + + print(" ".join("-I" + d for d in unique_dirs)) + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument( + "--version", + action="version", + version=__version__, + help="Print the version and exit.", + ) + parser.add_argument( + "--includes", + action="store_true", + help="Include flags for both pybind11 and Python headers.", + ) + parser.add_argument( + "--cmakedir", + action="store_true", + help="Print the CMake module directory, ideal for setting -Dpybind11_ROOT in CMake.", + ) + parser.add_argument( + "--pkgconfigdir", + action="store_true", + help="Print the pkgconfig directory, ideal for setting $PKG_CONFIG_PATH.", + ) + args = parser.parse_args() + if not sys.argv[1:]: + parser.print_help() + if args.includes: + print_includes() + if args.cmakedir: + print(get_cmake_dir()) + if args.pkgconfigdir: + print(get_pkgconfig_dir()) + + +if __name__ == "__main__": + main() diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/_version.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/_version.py new file mode 100644 index 0000000..9280fa0 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/_version.py @@ -0,0 +1,12 @@ +from typing import Union + + +def _to_int(s: str) -> Union[int, str]: + try: + return int(s) + except ValueError: + return s + + +__version__ = "2.11.1" +version_info = tuple(_to_int(s) for s in __version__.split(".")) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/commands.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/commands.py new file mode 100644 index 0000000..b11690f --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/commands.py @@ -0,0 +1,37 @@ +import os + +DIR = os.path.abspath(os.path.dirname(__file__)) + + +def get_include(user: bool = False) -> str: # noqa: ARG001 + """ + Return the path to the pybind11 include directory. The historical "user" + argument is unused, and may be removed. + """ + installed_path = os.path.join(DIR, "include") + source_path = os.path.join(os.path.dirname(DIR), "include") + return installed_path if os.path.exists(installed_path) else source_path + + +def get_cmake_dir() -> str: + """ + Return the path to the pybind11 CMake module directory. + """ + cmake_installed_path = os.path.join(DIR, "share", "cmake", "pybind11") + if os.path.exists(cmake_installed_path): + return cmake_installed_path + + msg = "pybind11 not installed, installation required to access the CMake files" + raise ImportError(msg) + + +def get_pkgconfig_dir() -> str: + """ + Return the path to the pybind11 pkgconfig directory. + """ + pkgconfig_installed_path = os.path.join(DIR, "share", "pkgconfig") + if os.path.exists(pkgconfig_installed_path): + return pkgconfig_installed_path + + msg = "pybind11 not installed, installation required to access the pkgconfig files" + raise ImportError(msg) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/py.typed b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/setup_helpers.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/setup_helpers.py new file mode 100644 index 0000000..aeeee9d --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/pybind11/setup_helpers.py @@ -0,0 +1,498 @@ +""" +This module provides helpers for C++11+ projects using pybind11. + +LICENSE: + +Copyright (c) 2016 Wenzel Jakob , All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +# IMPORTANT: If you change this file in the pybind11 repo, also review +# setup_helpers.pyi for matching changes. +# +# If you copy this file in, you don't +# need the .pyi file; it's just an interface file for static type checkers. + +import contextlib +import os +import platform +import shlex +import shutil +import sys +import sysconfig +import tempfile +import threading +import warnings +from functools import lru_cache +from pathlib import Path +from typing import ( + Any, + Callable, + Dict, + Iterable, + Iterator, + List, + Optional, + Tuple, + TypeVar, + Union, +) + +try: + from setuptools import Extension as _Extension + from setuptools.command.build_ext import build_ext as _build_ext +except ImportError: + from distutils.command.build_ext import build_ext as _build_ext # type: ignore[assignment] + from distutils.extension import Extension as _Extension # type: ignore[assignment] + +import distutils.ccompiler +import distutils.errors + +WIN = sys.platform.startswith("win32") and "mingw" not in sysconfig.get_platform() +MACOS = sys.platform.startswith("darwin") +STD_TMPL = "/std:c++{}" if WIN else "-std=c++{}" + + +# It is recommended to use PEP 518 builds if using this module. However, this +# file explicitly supports being copied into a user's project directory +# standalone, and pulling pybind11 with the deprecated setup_requires feature. +# If you copy the file, remember to add it to your MANIFEST.in, and add the current +# directory into your path if it sits beside your setup.py. + + +class Pybind11Extension(_Extension): + """ + Build a C++11+ Extension module with pybind11. This automatically adds the + recommended flags when you init the extension and assumes C++ sources - you + can further modify the options yourself. + + The customizations are: + + * ``/EHsc`` and ``/bigobj`` on Windows + * ``stdlib=libc++`` on macOS + * ``visibility=hidden`` and ``-g0`` on Unix + + Finally, you can set ``cxx_std`` via constructor or afterwards to enable + flags for C++ std, and a few extra helper flags related to the C++ standard + level. It is _highly_ recommended you either set this, or use the provided + ``build_ext``, which will search for the highest supported extension for + you if the ``cxx_std`` property is not set. Do not set the ``cxx_std`` + property more than once, as flags are added when you set it. Set the + property to None to disable the addition of C++ standard flags. + + If you want to add pybind11 headers manually, for example for an exact + git checkout, then set ``include_pybind11=False``. + """ + + # flags are prepended, so that they can be further overridden, e.g. by + # ``extra_compile_args=["-g"]``. + + def _add_cflags(self, flags: List[str]) -> None: + self.extra_compile_args[:0] = flags + + def _add_ldflags(self, flags: List[str]) -> None: + self.extra_link_args[:0] = flags + + def __init__(self, *args: Any, **kwargs: Any) -> None: + self._cxx_level = 0 + cxx_std = kwargs.pop("cxx_std", 0) + + if "language" not in kwargs: + kwargs["language"] = "c++" + + include_pybind11 = kwargs.pop("include_pybind11", True) + + super().__init__(*args, **kwargs) + + # Include the installed package pybind11 headers + if include_pybind11: + # If using setup_requires, this fails the first time - that's okay + try: + import pybind11 + + pyinc = pybind11.get_include() + + if pyinc not in self.include_dirs: + self.include_dirs.append(pyinc) + except ModuleNotFoundError: + pass + + self.cxx_std = cxx_std + + cflags = [] + if WIN: + cflags += ["/EHsc", "/bigobj"] + else: + cflags += ["-fvisibility=hidden"] + env_cflags = os.environ.get("CFLAGS", "") + env_cppflags = os.environ.get("CPPFLAGS", "") + c_cpp_flags = shlex.split(env_cflags) + shlex.split(env_cppflags) + if not any(opt.startswith("-g") for opt in c_cpp_flags): + cflags += ["-g0"] + self._add_cflags(cflags) + + @property + def cxx_std(self) -> int: + """ + The CXX standard level. If set, will add the required flags. If left at + 0, it will trigger an automatic search when pybind11's build_ext is + used. If None, will have no effect. Besides just the flags, this may + add a macos-min 10.9 or 10.14 flag if MACOSX_DEPLOYMENT_TARGET is + unset. + """ + return self._cxx_level + + @cxx_std.setter + def cxx_std(self, level: int) -> None: + if self._cxx_level: + warnings.warn( + "You cannot safely change the cxx_level after setting it!", stacklevel=2 + ) + + # MSVC 2015 Update 3 and later only have 14 (and later 17) modes, so + # force a valid flag here. + if WIN and level == 11: + level = 14 + + self._cxx_level = level + + if not level: + return + + cflags = [STD_TMPL.format(level)] + ldflags = [] + + if MACOS and "MACOSX_DEPLOYMENT_TARGET" not in os.environ: + # C++17 requires a higher min version of macOS. An earlier version + # (10.12 or 10.13) can be set manually via environment variable if + # you are careful in your feature usage, but 10.14 is the safest + # setting for general use. However, never set higher than the + # current macOS version! + current_macos = tuple(int(x) for x in platform.mac_ver()[0].split(".")[:2]) + desired_macos = (10, 9) if level < 17 else (10, 14) + macos_string = ".".join(str(x) for x in min(current_macos, desired_macos)) + macosx_min = f"-mmacosx-version-min={macos_string}" + cflags += [macosx_min] + ldflags += [macosx_min] + + self._add_cflags(cflags) + self._add_ldflags(ldflags) + + +# Just in case someone clever tries to multithread +tmp_chdir_lock = threading.Lock() + + +@contextlib.contextmanager +def tmp_chdir() -> Iterator[str]: + "Prepare and enter a temporary directory, cleanup when done" + + # Threadsafe + with tmp_chdir_lock: + olddir = os.getcwd() + try: + tmpdir = tempfile.mkdtemp() + os.chdir(tmpdir) + yield tmpdir + finally: + os.chdir(olddir) + shutil.rmtree(tmpdir) + + +# cf http://bugs.python.org/issue26689 +def has_flag(compiler: Any, flag: str) -> bool: + """ + Return the flag if a flag name is supported on the + specified compiler, otherwise None (can be used as a boolean). + If multiple flags are passed, return the first that matches. + """ + + with tmp_chdir(): + fname = Path("flagcheck.cpp") + # Don't trigger -Wunused-parameter. + fname.write_text("int main (int, char **) { return 0; }", encoding="utf-8") + + try: + compiler.compile([str(fname)], extra_postargs=[flag]) + except distutils.errors.CompileError: + return False + return True + + +# Every call will cache the result +cpp_flag_cache = None + + +@lru_cache() +def auto_cpp_level(compiler: Any) -> Union[str, int]: + """ + Return the max supported C++ std level (17, 14, or 11). Returns latest on Windows. + """ + + if WIN: + return "latest" + + levels = [17, 14, 11] + + for level in levels: + if has_flag(compiler, STD_TMPL.format(level)): + return level + + msg = "Unsupported compiler -- at least C++11 support is needed!" + raise RuntimeError(msg) + + +class build_ext(_build_ext): # noqa: N801 + """ + Customized build_ext that allows an auto-search for the highest supported + C++ level for Pybind11Extension. This is only needed for the auto-search + for now, and is completely optional otherwise. + """ + + def build_extensions(self) -> None: + """ + Build extensions, injecting C++ std for Pybind11Extension if needed. + """ + + for ext in self.extensions: + if hasattr(ext, "_cxx_level") and ext._cxx_level == 0: + ext.cxx_std = auto_cpp_level(self.compiler) + + super().build_extensions() + + +def intree_extensions( + paths: Iterable[str], package_dir: Optional[Dict[str, str]] = None +) -> List[Pybind11Extension]: + """ + Generate Pybind11Extensions from source files directly located in a Python + source tree. + + ``package_dir`` behaves as in ``setuptools.setup``. If unset, the Python + package root parent is determined as the first parent directory that does + not contain an ``__init__.py`` file. + """ + exts = [] + + if package_dir is None: + for path in paths: + parent, _ = os.path.split(path) + while os.path.exists(os.path.join(parent, "__init__.py")): + parent, _ = os.path.split(parent) + relname, _ = os.path.splitext(os.path.relpath(path, parent)) + qualified_name = relname.replace(os.path.sep, ".") + exts.append(Pybind11Extension(qualified_name, [path])) + return exts + + for path in paths: + for prefix, parent in package_dir.items(): + if path.startswith(parent): + relname, _ = os.path.splitext(os.path.relpath(path, parent)) + qualified_name = relname.replace(os.path.sep, ".") + if prefix: + qualified_name = prefix + "." + qualified_name + exts.append(Pybind11Extension(qualified_name, [path])) + break + else: + msg = ( + f"path {path} is not a child of any of the directories listed " + f"in 'package_dir' ({package_dir})" + ) + raise ValueError(msg) + + return exts + + +def naive_recompile(obj: str, src: str) -> bool: + """ + This will recompile only if the source file changes. It does not check + header files, so a more advanced function or Ccache is better if you have + editable header files in your package. + """ + return os.stat(obj).st_mtime < os.stat(src).st_mtime + + +def no_recompile(obg: str, src: str) -> bool: # noqa: ARG001 + """ + This is the safest but slowest choice (and is the default) - will always + recompile sources. + """ + return True + + +S = TypeVar("S", bound="ParallelCompile") + +CCompilerMethod = Callable[ + [ + distutils.ccompiler.CCompiler, + List[str], + Optional[str], + Optional[Union[Tuple[str], Tuple[str, Optional[str]]]], + Optional[List[str]], + bool, + Optional[List[str]], + Optional[List[str]], + Optional[List[str]], + ], + List[str], +] + + +# Optional parallel compile utility +# inspired by: http://stackoverflow.com/questions/11013851/speeding-up-build-process-with-distutils +# and: https://github.com/tbenthompson/cppimport/blob/stable/cppimport/build_module.py +# and NumPy's parallel distutils module: +# https://github.com/numpy/numpy/blob/master/numpy/distutils/ccompiler.py +class ParallelCompile: + """ + Make a parallel compile function. Inspired by + numpy.distutils.ccompiler.CCompiler.compile and cppimport. + + This takes several arguments that allow you to customize the compile + function created: + + envvar: + Set an environment variable to control the compilation threads, like + NPY_NUM_BUILD_JOBS + default: + 0 will automatically multithread, or 1 will only multithread if the + envvar is set. + max: + The limit for automatic multithreading if non-zero + needs_recompile: + A function of (obj, src) that returns True when recompile is needed. No + effect in isolated mode; use ccache instead, see + https://github.com/matplotlib/matplotlib/issues/1507/ + + To use:: + + ParallelCompile("NPY_NUM_BUILD_JOBS").install() + + or:: + + with ParallelCompile("NPY_NUM_BUILD_JOBS"): + setup(...) + + By default, this assumes all files need to be recompiled. A smarter + function can be provided via needs_recompile. If the output has not yet + been generated, the compile will always run, and this function is not + called. + """ + + __slots__ = ("envvar", "default", "max", "_old", "needs_recompile") + + def __init__( + self, + envvar: Optional[str] = None, + default: int = 0, + max: int = 0, # pylint: disable=redefined-builtin + needs_recompile: Callable[[str, str], bool] = no_recompile, + ) -> None: + self.envvar = envvar + self.default = default + self.max = max + self.needs_recompile = needs_recompile + self._old: List[CCompilerMethod] = [] + + def function(self) -> CCompilerMethod: + """ + Builds a function object usable as distutils.ccompiler.CCompiler.compile. + """ + + def compile_function( + compiler: distutils.ccompiler.CCompiler, + sources: List[str], + output_dir: Optional[str] = None, + macros: Optional[Union[Tuple[str], Tuple[str, Optional[str]]]] = None, + include_dirs: Optional[List[str]] = None, + debug: bool = False, + extra_preargs: Optional[List[str]] = None, + extra_postargs: Optional[List[str]] = None, + depends: Optional[List[str]] = None, + ) -> Any: + # These lines are directly from distutils.ccompiler.CCompiler + macros, objects, extra_postargs, pp_opts, build = compiler._setup_compile( # type: ignore[attr-defined] + output_dir, macros, include_dirs, sources, depends, extra_postargs + ) + cc_args = compiler._get_cc_args(pp_opts, debug, extra_preargs) # type: ignore[attr-defined] + + # The number of threads; start with default. + threads = self.default + + # Determine the number of compilation threads, unless set by an environment variable. + if self.envvar is not None: + threads = int(os.environ.get(self.envvar, self.default)) + + def _single_compile(obj: Any) -> None: + try: + src, ext = build[obj] + except KeyError: + return + + if not os.path.exists(obj) or self.needs_recompile(obj, src): + compiler._compile(obj, src, ext, cc_args, extra_postargs, pp_opts) # type: ignore[attr-defined] + + try: + # Importing .synchronize checks for platforms that have some multiprocessing + # capabilities but lack semaphores, such as AWS Lambda and Android Termux. + import multiprocessing.synchronize + from multiprocessing.pool import ThreadPool + except ImportError: + threads = 1 + + if threads == 0: + try: + threads = multiprocessing.cpu_count() + threads = self.max if self.max and self.max < threads else threads + except NotImplementedError: + threads = 1 + + if threads > 1: + with ThreadPool(threads) as pool: + for _ in pool.imap_unordered(_single_compile, objects): + pass + else: + for ob in objects: + _single_compile(ob) + + return objects + + return compile_function + + def install(self: S) -> S: + """ + Installs the compile function into distutils.ccompiler.CCompiler.compile. + """ + distutils.ccompiler.CCompiler.compile = self.function() # type: ignore[assignment] + return self + + def __enter__(self: S) -> S: + self._old.append(distutils.ccompiler.CCompiler.compile) + return self.install() + + def __exit__(self, *args: Any) -> None: + distutils.ccompiler.CCompiler.compile = self._old.pop() # type: ignore[assignment] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/FindCatch.cmake b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/FindCatch.cmake new file mode 100644 index 0000000..5d3fcbf --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/FindCatch.cmake @@ -0,0 +1,76 @@ +# - Find the Catch test framework or download it (single header) +# +# This is a quick module for internal use. It assumes that Catch is +# REQUIRED and that a minimum version is provided (not EXACT). If +# a suitable version isn't found locally, the single header file +# will be downloaded and placed in the build dir: PROJECT_BINARY_DIR. +# +# This code sets the following variables: +# CATCH_INCLUDE_DIR - path to catch.hpp +# CATCH_VERSION - version number + +option(DOWNLOAD_CATCH "Download catch2 if not found") + +if(NOT Catch_FIND_VERSION) + message(FATAL_ERROR "A version number must be specified.") +elseif(Catch_FIND_REQUIRED) + message(FATAL_ERROR "This module assumes Catch is not required.") +elseif(Catch_FIND_VERSION_EXACT) + message(FATAL_ERROR "Exact version numbers are not supported, only minimum.") +endif() + +# Extract the version number from catch.hpp +function(_get_catch_version) + file( + STRINGS "${CATCH_INCLUDE_DIR}/catch.hpp" version_line + REGEX "Catch v.*" + LIMIT_COUNT 1) + if(version_line MATCHES "Catch v([0-9]+)\\.([0-9]+)\\.([0-9]+)") + set(CATCH_VERSION + "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}" + PARENT_SCOPE) + endif() +endfunction() + +# Download the single-header version of Catch +function(_download_catch version destination_dir) + message(STATUS "Downloading catch v${version}...") + set(url https://github.com/philsquared/Catch/releases/download/v${version}/catch.hpp) + file( + DOWNLOAD ${url} "${destination_dir}/catch.hpp" + STATUS status + LOG log) + list(GET status 0 error) + if(error) + string(REPLACE "\n" "\n " log " ${log}") + message(FATAL_ERROR "Could not download URL:\n" " ${url}\n" "Log:\n" "${log}") + endif() + set(CATCH_INCLUDE_DIR + "${destination_dir}" + CACHE INTERNAL "") +endfunction() + +# Look for catch locally +find_path( + CATCH_INCLUDE_DIR + NAMES catch.hpp + PATH_SUFFIXES catch2) +if(CATCH_INCLUDE_DIR) + _get_catch_version() +endif() + +# Download the header if it wasn't found or if it's outdated +if(NOT CATCH_VERSION OR CATCH_VERSION VERSION_LESS ${Catch_FIND_VERSION}) + if(DOWNLOAD_CATCH) + _download_catch(${Catch_FIND_VERSION} "${PROJECT_BINARY_DIR}/catch/") + _get_catch_version() + else() + set(CATCH_FOUND FALSE) + return() + endif() +endif() + +add_library(Catch2::Catch2 IMPORTED INTERFACE) +set_property(TARGET Catch2::Catch2 PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${CATCH_INCLUDE_DIR}") + +set(CATCH_FOUND TRUE) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/FindEigen3.cmake b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/FindEigen3.cmake new file mode 100644 index 0000000..83625d9 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/FindEigen3.cmake @@ -0,0 +1,86 @@ +# - Try to find Eigen3 lib +# +# This module supports requiring a minimum version, e.g. you can do +# find_package(Eigen3 3.1.2) +# to require version 3.1.2 or newer of Eigen3. +# +# Once done this will define +# +# EIGEN3_FOUND - system has eigen lib with correct version +# EIGEN3_INCLUDE_DIR - the eigen include directory +# EIGEN3_VERSION - eigen version + +# Copyright (c) 2006, 2007 Montel Laurent, +# Copyright (c) 2008, 2009 Gael Guennebaud, +# Copyright (c) 2009 Benoit Jacob +# Redistribution and use is allowed according to the terms of the 2-clause BSD license. + +if(NOT Eigen3_FIND_VERSION) + if(NOT Eigen3_FIND_VERSION_MAJOR) + set(Eigen3_FIND_VERSION_MAJOR 2) + endif(NOT Eigen3_FIND_VERSION_MAJOR) + if(NOT Eigen3_FIND_VERSION_MINOR) + set(Eigen3_FIND_VERSION_MINOR 91) + endif(NOT Eigen3_FIND_VERSION_MINOR) + if(NOT Eigen3_FIND_VERSION_PATCH) + set(Eigen3_FIND_VERSION_PATCH 0) + endif(NOT Eigen3_FIND_VERSION_PATCH) + + set(Eigen3_FIND_VERSION + "${Eigen3_FIND_VERSION_MAJOR}.${Eigen3_FIND_VERSION_MINOR}.${Eigen3_FIND_VERSION_PATCH}") +endif(NOT Eigen3_FIND_VERSION) + +macro(_eigen3_check_version) + file(READ "${EIGEN3_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen3_version_header) + + string(REGEX MATCH "define[ \t]+EIGEN_WORLD_VERSION[ \t]+([0-9]+)" _eigen3_world_version_match + "${_eigen3_version_header}") + set(EIGEN3_WORLD_VERSION "${CMAKE_MATCH_1}") + string(REGEX MATCH "define[ \t]+EIGEN_MAJOR_VERSION[ \t]+([0-9]+)" _eigen3_major_version_match + "${_eigen3_version_header}") + set(EIGEN3_MAJOR_VERSION "${CMAKE_MATCH_1}") + string(REGEX MATCH "define[ \t]+EIGEN_MINOR_VERSION[ \t]+([0-9]+)" _eigen3_minor_version_match + "${_eigen3_version_header}") + set(EIGEN3_MINOR_VERSION "${CMAKE_MATCH_1}") + + set(EIGEN3_VERSION ${EIGEN3_WORLD_VERSION}.${EIGEN3_MAJOR_VERSION}.${EIGEN3_MINOR_VERSION}) + if(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) + set(EIGEN3_VERSION_OK FALSE) + else(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) + set(EIGEN3_VERSION_OK TRUE) + endif(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) + + if(NOT EIGEN3_VERSION_OK) + + message(STATUS "Eigen3 version ${EIGEN3_VERSION} found in ${EIGEN3_INCLUDE_DIR}, " + "but at least version ${Eigen3_FIND_VERSION} is required") + endif(NOT EIGEN3_VERSION_OK) +endmacro(_eigen3_check_version) + +if(EIGEN3_INCLUDE_DIR) + + # in cache already + _eigen3_check_version() + set(EIGEN3_FOUND ${EIGEN3_VERSION_OK}) + +else(EIGEN3_INCLUDE_DIR) + if(NOT DEFINED KDE4_INCLUDE_DIR) + set(KDE4_INCLUDE_DIR "") + endif() + + find_path( + EIGEN3_INCLUDE_DIR + NAMES signature_of_eigen3_matrix_library + PATHS ${CMAKE_INSTALL_PREFIX}/include ${KDE4_INCLUDE_DIR} + PATH_SUFFIXES eigen3 eigen) + + if(EIGEN3_INCLUDE_DIR) + _eigen3_check_version() + endif(EIGEN3_INCLUDE_DIR) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Eigen3 DEFAULT_MSG EIGEN3_INCLUDE_DIR EIGEN3_VERSION_OK) + + mark_as_advanced(EIGEN3_INCLUDE_DIR) + +endif(EIGEN3_INCLUDE_DIR) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/FindPythonLibsNew.cmake b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/FindPythonLibsNew.cmake new file mode 100644 index 0000000..ce558d4 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/FindPythonLibsNew.cmake @@ -0,0 +1,287 @@ +# - Find python libraries +# This module finds the libraries corresponding to the Python interpreter +# FindPythonInterp provides. +# This code sets the following variables: +# +# PYTHONLIBS_FOUND - have the Python libs been found +# PYTHON_PREFIX - path to the Python installation +# PYTHON_LIBRARIES - path to the python library +# PYTHON_INCLUDE_DIRS - path to where Python.h is found +# PYTHON_MODULE_EXTENSION - lib extension, e.g. '.so' or '.pyd' +# PYTHON_MODULE_PREFIX - lib name prefix: usually an empty string +# PYTHON_SITE_PACKAGES - path to installation site-packages +# PYTHON_IS_DEBUG - whether the Python interpreter is a debug build +# +# Thanks to talljimbo for the patch adding the 'LDVERSION' config +# variable usage. + +#============================================================================= +# Copyright 2001-2009 Kitware, Inc. +# Copyright 2012 Continuum Analytics, Inc. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the names of Kitware, Inc., the Insight Software Consortium, +# nor the names of their contributors may be used to endorse or promote +# products derived from this software without specific prior written +# permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#============================================================================= + +# Checking for the extension makes sure that `LibsNew` was found and not just `Libs`. +if(PYTHONLIBS_FOUND AND PYTHON_MODULE_EXTENSION) + return() +endif() + +if(PythonLibsNew_FIND_QUIETLY) + set(_pythonlibs_quiet QUIET) +else() + set(_pythonlibs_quiet "") +endif() + +if(PythonLibsNew_FIND_REQUIRED) + set(_pythonlibs_required REQUIRED) +endif() + +# Check to see if the `python` command is present and from a virtual +# environment, conda, or GHA activation - if it is, try to use that. + +if(NOT DEFINED PYTHON_EXECUTABLE) + if(DEFINED ENV{VIRTUAL_ENV}) + find_program( + PYTHON_EXECUTABLE python + PATHS "$ENV{VIRTUAL_ENV}" "$ENV{VIRTUAL_ENV}/bin" + NO_DEFAULT_PATH) + elseif(DEFINED ENV{CONDA_PREFIX}) + find_program( + PYTHON_EXECUTABLE python + PATHS "$ENV{CONDA_PREFIX}" "$ENV{CONDA_PREFIX}/bin" + NO_DEFAULT_PATH) + elseif(DEFINED ENV{pythonLocation}) + find_program( + PYTHON_EXECUTABLE python + PATHS "$ENV{pythonLocation}" "$ENV{pythonLocation}/bin" + NO_DEFAULT_PATH) + endif() + if(NOT PYTHON_EXECUTABLE) + unset(PYTHON_EXECUTABLE) + endif() +endif() + +# Use the Python interpreter to find the libs. +if(NOT PythonLibsNew_FIND_VERSION) + set(PythonLibsNew_FIND_VERSION "3.6") +endif() + +find_package(PythonInterp ${PythonLibsNew_FIND_VERSION} ${_pythonlibs_required} + ${_pythonlibs_quiet}) + +if(NOT PYTHONINTERP_FOUND) + set(PYTHONLIBS_FOUND FALSE) + set(PythonLibsNew_FOUND FALSE) + return() +endif() + +# According to https://stackoverflow.com/questions/646518/python-how-to-detect-debug-interpreter +# testing whether sys has the gettotalrefcount function is a reliable, cross-platform +# way to detect a CPython debug interpreter. +# +# The library suffix is from the config var LDVERSION sometimes, otherwise +# VERSION. VERSION will typically be like "2.7" on unix, and "27" on windows. +execute_process( + COMMAND + "${PYTHON_EXECUTABLE}" "-c" " +import sys;import struct; +import sysconfig as s +USE_SYSCONFIG = sys.version_info >= (3, 10) +if not USE_SYSCONFIG: + from distutils import sysconfig as ds +print('.'.join(str(v) for v in sys.version_info)); +print(sys.prefix); +if USE_SYSCONFIG: + scheme = s.get_default_scheme() + if scheme == 'posix_local': + # Debian's default scheme installs to /usr/local/ but we want to find headers in /usr/ + scheme = 'posix_prefix' + print(s.get_path('platinclude', scheme)) + print(s.get_path('platlib')) + print(s.get_config_var('EXT_SUFFIX') or s.get_config_var('SO')) +else: + print(ds.get_python_inc(plat_specific=True)); + print(ds.get_python_lib(plat_specific=True)); + print(ds.get_config_var('EXT_SUFFIX') or ds.get_config_var('SO')); +print(hasattr(sys, 'gettotalrefcount')+0); +print(struct.calcsize('@P')); +print(s.get_config_var('LDVERSION') or s.get_config_var('VERSION')); +print(s.get_config_var('LIBDIR') or ''); +print(s.get_config_var('MULTIARCH') or ''); +" + RESULT_VARIABLE _PYTHON_SUCCESS + OUTPUT_VARIABLE _PYTHON_VALUES + ERROR_VARIABLE _PYTHON_ERROR_VALUE) + +if(NOT _PYTHON_SUCCESS MATCHES 0) + if(PythonLibsNew_FIND_REQUIRED) + message(FATAL_ERROR "Python config failure:\n${_PYTHON_ERROR_VALUE}") + endif() + set(PYTHONLIBS_FOUND FALSE) + set(PythonLibsNew_FOUND FALSE) + return() +endif() + +option( + PYBIND11_PYTHONLIBS_OVERWRITE + "Overwrite cached values read from Python library (classic search). Turn off if cross-compiling and manually setting these values." + ON) +# Can manually set values when cross-compiling +macro(_PYBIND11_GET_IF_UNDEF lst index name) + if(PYBIND11_PYTHONLIBS_OVERWRITE OR NOT DEFINED "${name}") + list(GET "${lst}" "${index}" "${name}") + endif() +endmacro() + +# Convert the process output into a list +if(WIN32) + string(REGEX REPLACE "\\\\" "/" _PYTHON_VALUES ${_PYTHON_VALUES}) +endif() +string(REGEX REPLACE ";" "\\\\;" _PYTHON_VALUES ${_PYTHON_VALUES}) +string(REGEX REPLACE "\n" ";" _PYTHON_VALUES ${_PYTHON_VALUES}) +_pybind11_get_if_undef(_PYTHON_VALUES 0 _PYTHON_VERSION_LIST) +_pybind11_get_if_undef(_PYTHON_VALUES 1 PYTHON_PREFIX) +_pybind11_get_if_undef(_PYTHON_VALUES 2 PYTHON_INCLUDE_DIR) +_pybind11_get_if_undef(_PYTHON_VALUES 3 PYTHON_SITE_PACKAGES) +_pybind11_get_if_undef(_PYTHON_VALUES 4 PYTHON_MODULE_EXTENSION) +_pybind11_get_if_undef(_PYTHON_VALUES 5 PYTHON_IS_DEBUG) +_pybind11_get_if_undef(_PYTHON_VALUES 6 PYTHON_SIZEOF_VOID_P) +_pybind11_get_if_undef(_PYTHON_VALUES 7 PYTHON_LIBRARY_SUFFIX) +_pybind11_get_if_undef(_PYTHON_VALUES 8 PYTHON_LIBDIR) +_pybind11_get_if_undef(_PYTHON_VALUES 9 PYTHON_MULTIARCH) + +# Make sure the Python has the same pointer-size as the chosen compiler +# Skip if CMAKE_SIZEOF_VOID_P is not defined +# This should be skipped for (non-Apple) cross-compiles (like EMSCRIPTEN) +if(NOT CMAKE_CROSSCOMPILING + AND CMAKE_SIZEOF_VOID_P + AND (NOT "${PYTHON_SIZEOF_VOID_P}" STREQUAL "${CMAKE_SIZEOF_VOID_P}")) + if(PythonLibsNew_FIND_REQUIRED) + math(EXPR _PYTHON_BITS "${PYTHON_SIZEOF_VOID_P} * 8") + math(EXPR _CMAKE_BITS "${CMAKE_SIZEOF_VOID_P} * 8") + message(FATAL_ERROR "Python config failure: Python is ${_PYTHON_BITS}-bit, " + "chosen compiler is ${_CMAKE_BITS}-bit") + endif() + set(PYTHONLIBS_FOUND FALSE) + set(PythonLibsNew_FOUND FALSE) + return() +endif() + +# The built-in FindPython didn't always give the version numbers +string(REGEX REPLACE "\\." ";" _PYTHON_VERSION_LIST ${_PYTHON_VERSION_LIST}) +list(GET _PYTHON_VERSION_LIST 0 PYTHON_VERSION_MAJOR) +list(GET _PYTHON_VERSION_LIST 1 PYTHON_VERSION_MINOR) +list(GET _PYTHON_VERSION_LIST 2 PYTHON_VERSION_PATCH) +set(PYTHON_VERSION "${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}.${PYTHON_VERSION_PATCH}") + +# Make sure all directory separators are '/' +string(REGEX REPLACE "\\\\" "/" PYTHON_PREFIX "${PYTHON_PREFIX}") +string(REGEX REPLACE "\\\\" "/" PYTHON_INCLUDE_DIR "${PYTHON_INCLUDE_DIR}") +string(REGEX REPLACE "\\\\" "/" PYTHON_SITE_PACKAGES "${PYTHON_SITE_PACKAGES}") + +if(DEFINED PYTHON_LIBRARY) + # Don't write to PYTHON_LIBRARY if it's already set +elseif(CMAKE_HOST_WIN32) + set(PYTHON_LIBRARY "${PYTHON_PREFIX}/libs/python${PYTHON_LIBRARY_SUFFIX}.lib") + + # when run in a venv, PYTHON_PREFIX points to it. But the libraries remain in the + # original python installation. They may be found relative to PYTHON_INCLUDE_DIR. + if(NOT EXISTS "${PYTHON_LIBRARY}") + get_filename_component(_PYTHON_ROOT ${PYTHON_INCLUDE_DIR} DIRECTORY) + set(PYTHON_LIBRARY "${_PYTHON_ROOT}/libs/python${PYTHON_LIBRARY_SUFFIX}.lib") + endif() + + # if we are in MSYS & MINGW, and we didn't find windows python lib, look for system python lib + if(DEFINED ENV{MSYSTEM} + AND MINGW + AND NOT EXISTS "${PYTHON_LIBRARY}") + if(PYTHON_MULTIARCH) + set(_PYTHON_LIBS_SEARCH "${PYTHON_LIBDIR}/${PYTHON_MULTIARCH}" "${PYTHON_LIBDIR}") + else() + set(_PYTHON_LIBS_SEARCH "${PYTHON_LIBDIR}") + endif() + unset(PYTHON_LIBRARY) + find_library( + PYTHON_LIBRARY + NAMES "python${PYTHON_LIBRARY_SUFFIX}" + PATHS ${_PYTHON_LIBS_SEARCH} + NO_DEFAULT_PATH) + endif() + + # raise an error if the python libs are still not found. + if(NOT EXISTS "${PYTHON_LIBRARY}") + message(FATAL_ERROR "Python libraries not found") + endif() + +else() + if(PYTHON_MULTIARCH) + set(_PYTHON_LIBS_SEARCH "${PYTHON_LIBDIR}/${PYTHON_MULTIARCH}" "${PYTHON_LIBDIR}") + else() + set(_PYTHON_LIBS_SEARCH "${PYTHON_LIBDIR}") + endif() + #message(STATUS "Searching for Python libs in ${_PYTHON_LIBS_SEARCH}") + # Probably this needs to be more involved. It would be nice if the config + # information the python interpreter itself gave us were more complete. + find_library( + PYTHON_LIBRARY + NAMES "python${PYTHON_LIBRARY_SUFFIX}" + PATHS ${_PYTHON_LIBS_SEARCH} + NO_DEFAULT_PATH) + + # If all else fails, just set the name/version and let the linker figure out the path. + if(NOT PYTHON_LIBRARY) + set(PYTHON_LIBRARY python${PYTHON_LIBRARY_SUFFIX}) + endif() +endif() + +mark_as_advanced(PYTHON_LIBRARY PYTHON_INCLUDE_DIR) + +# We use PYTHON_INCLUDE_DIR, PYTHON_LIBRARY and PYTHON_DEBUG_LIBRARY for the +# cache entries because they are meant to specify the location of a single +# library. We now set the variables listed by the documentation for this +# module. +set(PYTHON_INCLUDE_DIRS "${PYTHON_INCLUDE_DIR}") +set(PYTHON_LIBRARIES "${PYTHON_LIBRARY}") +if(NOT PYTHON_DEBUG_LIBRARY) + set(PYTHON_DEBUG_LIBRARY "") +endif() +set(PYTHON_DEBUG_LIBRARIES "${PYTHON_DEBUG_LIBRARY}") + +find_package_message(PYTHON "Found PythonLibs: ${PYTHON_LIBRARIES}" + "${PYTHON_EXECUTABLE}${PYTHON_VERSION_STRING}") + +set(PYTHONLIBS_FOUND TRUE) +set(PythonLibsNew_FOUND TRUE) + +if(NOT PYTHON_MODULE_PREFIX) + set(PYTHON_MODULE_PREFIX "") +endif() diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/JoinPaths.cmake b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/JoinPaths.cmake new file mode 100644 index 0000000..c68d91b --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/JoinPaths.cmake @@ -0,0 +1,23 @@ +# This module provides function for joining paths +# known from most languages +# +# SPDX-License-Identifier: (MIT OR CC0-1.0) +# Copyright 2020 Jan Tojnar +# https://github.com/jtojnar/cmake-snips +# +# Modelled after Python’s os.path.join +# https://docs.python.org/3.7/library/os.path.html#os.path.join +# Windows not supported +function(join_paths joined_path first_path_segment) + set(temp_path "${first_path_segment}") + foreach(current_segment IN LISTS ARGN) + if(NOT ("${current_segment}" STREQUAL "")) + if(IS_ABSOLUTE "${current_segment}") + set(temp_path "${current_segment}") + else() + set(temp_path "${temp_path}/${current_segment}") + endif() + endif() + endforeach() + set(${joined_path} "${temp_path}" PARENT_SCOPE) +endfunction() diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/check-style.sh b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/check-style.sh new file mode 100644 index 0000000..6d83252 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/check-style.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# +# Script to check include/test code for common pybind11 code style errors. +# +# This script currently checks for +# +# 1. missing space between keyword and parenthesis, e.g.: for(, if(, while( +# 2. Missing space between right parenthesis and brace, e.g. 'for (...){' +# 3. opening brace on its own line. It should always be on the same line as the +# if/while/for/do statement. +# +# Invoke as: tools/check-style.sh +# + +check_style_errors=0 +IFS=$'\n' + + +found="$(grep '\<\(if\|for\|while\|catch\)(\|){' "$@" -rn --color=always)" +if [ -n "$found" ]; then + echo -e '\033[31;01mError: found the following coding style problems:\033[0m' + check_style_errors=1 + echo "${found//^/ /}" +fi + +found="$(awk ' +function prefix(filename, lineno) { + return " \033[35m" filename "\033[36m:\033[32m" lineno "\033[36m:\033[0m" +} +function mark(pattern, string) { sub(pattern, "\033[01;31m&\033[0m", string); return string } +last && /^\s*{/ { + print prefix(FILENAME, FNR-1) mark("\\)\\s*$", last) + print prefix(FILENAME, FNR) mark("^\\s*{", $0) + last="" +} +{ last = /(if|for|while|catch|switch)\s*\(.*\)\s*$/ ? $0 : "" } +' "$(find include -type f)" "$@")" +if [ -n "$found" ]; then + check_style_errors=1 + echo -e '\033[31;01mError: braces should occur on the same line as the if/while/.. statement. Found issues in the following files:\033[0m' + echo "$found" +fi + +exit $check_style_errors diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/cmake_uninstall.cmake.in b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/cmake_uninstall.cmake.in new file mode 100644 index 0000000..1e5d2bb --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/cmake_uninstall.cmake.in @@ -0,0 +1,23 @@ +# Source: https://gitlab.kitware.com/cmake/community/-/wikis/FAQ#can-i-do-make-uninstall-with-cmake + +if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") + message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt") +endif() + +file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files) +string(REGEX REPLACE "\n" ";" files "${files}") +foreach(file ${files}) + message(STATUS "Uninstalling $ENV{DESTDIR}${file}") + if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + exec_program( + "@CMAKE_COMMAND@" ARGS + "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval) + if(NOT "${rm_retval}" STREQUAL 0) + message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") + endif() + else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + message(STATUS "File $ENV{DESTDIR}${file} does not exist.") + endif() +endforeach() diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/codespell_ignore_lines_from_errors.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/codespell_ignore_lines_from_errors.py new file mode 100644 index 0000000..4ec9add --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/codespell_ignore_lines_from_errors.py @@ -0,0 +1,39 @@ +"""Simple script for rebuilding .codespell-ignore-lines + +Usage: + +cat < /dev/null > .codespell-ignore-lines +pre-commit run --all-files codespell >& /tmp/codespell_errors.txt +python3 tools/codespell_ignore_lines_from_errors.py /tmp/codespell_errors.txt > .codespell-ignore-lines + +git diff to review changes, then commit, push. +""" + +import sys +from typing import List + + +def run(args: List[str]) -> None: + assert len(args) == 1, "codespell_errors.txt" + cache = {} + done = set() + with open(args[0]) as f: + lines = f.read().splitlines() + + for line in sorted(lines): + i = line.find(" ==> ") + if i > 0: + flds = line[:i].split(":") + if len(flds) >= 2: + filename, line_num = flds[:2] + if filename not in cache: + with open(filename) as f: + cache[filename] = f.read().splitlines() + supp = cache[filename][int(line_num) - 1] + if supp not in done: + print(supp) + done.add(supp) + + +if __name__ == "__main__": + run(args=sys.argv[1:]) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/libsize.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/libsize.py new file mode 100644 index 0000000..1ac9afb --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/libsize.py @@ -0,0 +1,36 @@ +import os +import sys + +# Internal build script for generating debugging test .so size. +# Usage: +# python libsize.py file.so save.txt -- displays the size of file.so and, if save.txt exists, compares it to the +# size in it, then overwrites save.txt with the new size for future runs. + +if len(sys.argv) != 3: + sys.exit("Invalid arguments: usage: python libsize.py file.so save.txt") + +lib = sys.argv[1] +save = sys.argv[2] + +if not os.path.exists(lib): + sys.exit(f"Error: requested file ({lib}) does not exist") + +libsize = os.path.getsize(lib) + +print("------", os.path.basename(lib), "file size:", libsize, end="") + +if os.path.exists(save): + with open(save) as sf: + oldsize = int(sf.readline()) + + if oldsize > 0: + change = libsize - oldsize + if change == 0: + print(" (no change)") + else: + print(f" (change of {change:+} bytes = {change / oldsize:+.2%})") +else: + print() + +with open(save, "w") as sf: + sf.write(str(libsize)) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/make_changelog.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/make_changelog.py new file mode 100644 index 0000000..b5bd832 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/make_changelog.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +import re + +import ghapi.all +from rich import print +from rich.syntax import Syntax + +ENTRY = re.compile( + r""" + Suggested \s changelog \s entry: + .* + ```rst + \s* + (.*?) + \s* + ``` +""", + re.DOTALL | re.VERBOSE, +) + +print() + + +api = ghapi.all.GhApi(owner="pybind", repo="pybind11") + +issues_pages = ghapi.page.paged( + api.issues.list_for_repo, labels="needs changelog", state="closed" +) +issues = (issue for page in issues_pages for issue in page) +missing = [] + +for issue in issues: + changelog = ENTRY.findall(issue.body or "") + if not changelog or not changelog[0]: + missing.append(issue) + else: + (msg,) = changelog + if not msg.startswith("* "): + msg = "* " + msg + if not msg.endswith("."): + msg += "." + + msg += f"\n `#{issue.number} <{issue.html_url}>`_" + + print(Syntax(msg, "rst", theme="ansi_light", word_wrap=True)) + print() + +if missing: + print() + print("[blue]" + "-" * 30) + print() + + for issue in missing: + print(f"[red bold]Missing:[/red bold][red] {issue.title}") + print(f"[red] {issue.html_url}\n") + + print("[bold]Template:\n") + msg = "## Suggested changelog entry:\n\n```rst\n\n```" + print(Syntax(msg, "md", theme="ansi_light")) + +print() diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pybind11.pc.in b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pybind11.pc.in new file mode 100644 index 0000000..402f0b3 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pybind11.pc.in @@ -0,0 +1,7 @@ +prefix=@prefix_for_pc_file@ +includedir=@includedir_for_pc_file@ + +Name: @PROJECT_NAME@ +Description: Seamless operability between C++11 and Python +Version: @PROJECT_VERSION@ +Cflags: -I${includedir} diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pybind11Common.cmake b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pybind11Common.cmake new file mode 100644 index 0000000..308d1b7 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pybind11Common.cmake @@ -0,0 +1,405 @@ +#[======================================================[.rst + +Adds the following targets:: + + pybind11::pybind11 - link to headers and pybind11 + pybind11::module - Adds module links + pybind11::embed - Adds embed links + pybind11::lto - Link time optimizations (only if CMAKE_INTERPROCEDURAL_OPTIMIZATION is not set) + pybind11::thin_lto - Link time optimizations (only if CMAKE_INTERPROCEDURAL_OPTIMIZATION is not set) + pybind11::python_link_helper - Adds link to Python libraries + pybind11::windows_extras - MSVC bigobj and mp for building multithreaded + pybind11::opt_size - avoid optimizations that increase code size + +Adds the following functions:: + + pybind11_strip(target) - strip target after building on linux/macOS + pybind11_find_import(module) - See if a module is installed. + +#]======================================================] + +# CMake 3.10 has an include_guard command, but we can't use that yet +# include_guard(global) (pre-CMake 3.10) +if(TARGET pybind11::pybind11) + return() +endif() + +# If we are in subdirectory mode, all IMPORTED targets must be GLOBAL. If we +# are in CONFIG mode, they should be "normal" targets instead. +# In CMake 3.11+ you can promote a target to global after you create it, +# which might be simpler than this check. +get_property( + is_config + TARGET pybind11::headers + PROPERTY IMPORTED) +if(NOT is_config) + set(optional_global GLOBAL) +endif() + +# If not run in Python mode, we still would like this to at least +# include pybind11's include directory: +set(pybind11_INCLUDE_DIRS + "${pybind11_INCLUDE_DIR}" + CACHE INTERNAL "Include directory for pybind11 (Python not requested)") + +# --------------------- Shared targets ---------------------------- + +# Build an interface library target: +add_library(pybind11::pybind11 IMPORTED INTERFACE ${optional_global}) +set_property( + TARGET pybind11::pybind11 + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES pybind11::headers) + +# Build a module target: +add_library(pybind11::module IMPORTED INTERFACE ${optional_global}) +set_property( + TARGET pybind11::module + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES pybind11::pybind11) + +# Build an embed library target: +add_library(pybind11::embed IMPORTED INTERFACE ${optional_global}) +set_property( + TARGET pybind11::embed + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES pybind11::pybind11) + +# --------------------------- link helper --------------------------- + +add_library(pybind11::python_link_helper IMPORTED INTERFACE ${optional_global}) + +if(CMAKE_VERSION VERSION_LESS 3.13) + # In CMake 3.11+, you can set INTERFACE properties via the normal methods, and + # this would be simpler. + set_property( + TARGET pybind11::python_link_helper + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES "$<$:-undefined dynamic_lookup>") +else() + # link_options was added in 3.13+ + # This is safer, because you are ensured the deduplication pass in CMake will not consider + # these separate and remove one but not the other. + set_property( + TARGET pybind11::python_link_helper + APPEND + PROPERTY INTERFACE_LINK_OPTIONS "$<$:LINKER:-undefined,dynamic_lookup>") +endif() + +# ------------------------ Windows extras ------------------------- + +add_library(pybind11::windows_extras IMPORTED INTERFACE ${optional_global}) + +if(MSVC) # That's also clang-cl + # /bigobj is needed for bigger binding projects due to the limit to 64k + # addressable sections + set_property( + TARGET pybind11::windows_extras + APPEND + PROPERTY INTERFACE_COMPILE_OPTIONS $<$:/bigobj>) + + # /MP enables multithreaded builds (relevant when there are many files) for MSVC + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") # no Clang no Intel + if(CMAKE_VERSION VERSION_LESS 3.11) + set_property( + TARGET pybind11::windows_extras + APPEND + PROPERTY INTERFACE_COMPILE_OPTIONS $<$>:/MP>) + else() + # Only set these options for C++ files. This is important so that, for + # instance, projects that include other types of source files like CUDA + # .cu files don't get these options propagated to nvcc since that would + # cause the build to fail. + set_property( + TARGET pybind11::windows_extras + APPEND + PROPERTY INTERFACE_COMPILE_OPTIONS + $<$>:$<$:/MP>>) + endif() + endif() +endif() + +# ----------------------- Optimize binary size -------------------------- + +add_library(pybind11::opt_size IMPORTED INTERFACE ${optional_global}) + +if(MSVC) + set(PYBIND11_OPT_SIZE /Os) +else() + set(PYBIND11_OPT_SIZE -Os) +endif() + +set_property( + TARGET pybind11::opt_size + APPEND + PROPERTY INTERFACE_COMPILE_OPTIONS $<$:${PYBIND11_OPT_SIZE}> + $<$:${PYBIND11_OPT_SIZE}> + $<$:${PYBIND11_OPT_SIZE}>) + +# ----------------------- Legacy option -------------------------- + +# Warn or error if old variable name used +if(PYBIND11_CPP_STANDARD) + string(REGEX MATCH [[..$]] VAL "${PYBIND11_CPP_STANDARD}") + if(CMAKE_CXX_STANDARD) + if(NOT CMAKE_CXX_STANDARD STREQUAL VAL) + message(WARNING "CMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} does not match " + "PYBIND11_CPP_STANDARD=${PYBIND11_CPP_STANDARD}, " + "please remove PYBIND11_CPP_STANDARD from your cache") + endif() + else() + set(supported_standards 11 14 17 20) + if("${VAL}" IN_LIST supported_standards) + message(WARNING "USE -DCMAKE_CXX_STANDARD=${VAL} instead of PYBIND11_CPP_STANDARD") + set(CMAKE_CXX_STANDARD + ${VAL} + CACHE STRING "From PYBIND11_CPP_STANDARD") + else() + message(FATAL_ERROR "PYBIND11_CPP_STANDARD should be replaced with CMAKE_CXX_STANDARD " + "(last two chars: ${VAL} not understood as a valid CXX std)") + endif() + endif() +endif() + +# --------------------- Python specifics ------------------------- + +# CMake 3.27 removes the classic FindPythonInterp if CMP0148 is NEW +if(CMAKE_VERSION VERSION_LESS "3.27") + set(_pybind11_missing_old_python "OLD") +else() + cmake_policy(GET CMP0148 _pybind11_missing_old_python) +endif() + +# Check to see which Python mode we are in, new, old, or no python +if(PYBIND11_NOPYTHON) + set(_pybind11_nopython ON) +elseif( + _pybind11_missing_old_python STREQUAL "NEW" + OR PYBIND11_FINDPYTHON + OR Python_FOUND + OR Python2_FOUND + OR Python3_FOUND) + # New mode + include("${CMAKE_CURRENT_LIST_DIR}/pybind11NewTools.cmake") + +else() + + # Classic mode + include("${CMAKE_CURRENT_LIST_DIR}/pybind11Tools.cmake") + +endif() + +# --------------------- pybind11_find_import ------------------------------- + +if(NOT _pybind11_nopython) + # Check to see if modules are importable. Use REQUIRED to force an error if + # one of the modules is not found. _FOUND will be set if the + # package was found (underscores replace dashes if present). QUIET will hide + # the found message, and VERSION will require a minimum version. A successful + # find will cache the result. + function(pybind11_find_import PYPI_NAME) + # CMake variables need underscores (PyPI doesn't care) + string(REPLACE "-" "_" NORM_PYPI_NAME "${PYPI_NAME}") + + # Return if found previously + if(${NORM_PYPI_NAME}_FOUND) + return() + endif() + + set(options "REQUIRED;QUIET") + set(oneValueArgs "VERSION") + cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "" ${ARGN}) + + if(ARG_REQUIRED) + set(status_level FATAL_ERROR) + else() + set(status_level WARNING) + endif() + + execute_process( + COMMAND + ${${_Python}_EXECUTABLE} -c + "from pkg_resources import get_distribution; print(get_distribution('${PYPI_NAME}').version)" + RESULT_VARIABLE RESULT_PRESENT + OUTPUT_VARIABLE PKG_VERSION + ERROR_QUIET) + + string(STRIP "${PKG_VERSION}" PKG_VERSION) + + # If a result is present, this failed + if(RESULT_PRESENT) + set(${NORM_PYPI_NAME}_FOUND + ${NORM_PYPI_NAME}-NOTFOUND + CACHE INTERNAL "") + # Always warn or error + message( + ${status_level} + "Missing: ${PYPI_NAME} ${ARG_VERSION}\nTry: ${${_Python}_EXECUTABLE} -m pip install ${PYPI_NAME}" + ) + else() + if(ARG_VERSION AND PKG_VERSION VERSION_LESS ARG_VERSION) + message( + ${status_level} + "Version incorrect: ${PYPI_NAME} ${PKG_VERSION} found, ${ARG_VERSION} required - try upgrading" + ) + else() + set(${NORM_PYPI_NAME}_FOUND + YES + CACHE INTERNAL "") + set(${NORM_PYPI_NAME}_VERSION + ${PKG_VERSION} + CACHE INTERNAL "") + endif() + if(NOT ARG_QUIET) + message(STATUS "Found ${PYPI_NAME} ${PKG_VERSION}") + endif() + endif() + if(NOT ARG_VERSION OR (NOT PKG_VERSION VERSION_LESS ARG_VERSION)) + # We have successfully found a good version, cache to avoid calling again. + endif() + endfunction() +endif() + +# --------------------- LTO ------------------------------- + +include(CheckCXXCompilerFlag) + +# Checks whether the given CXX/linker flags can compile and link a cxx file. +# cxxflags and linkerflags are lists of flags to use. The result variable is a +# unique variable name for each set of flags: the compilation result will be +# cached base on the result variable. If the flags work, sets them in +# cxxflags_out/linkerflags_out internal cache variables (in addition to +# ${result}). +function(_pybind11_return_if_cxx_and_linker_flags_work result cxxflags linkerflags cxxflags_out + linkerflags_out) + set(CMAKE_REQUIRED_LIBRARIES ${linkerflags}) + check_cxx_compiler_flag("${cxxflags}" ${result}) + if(${result}) + set(${cxxflags_out} + "${cxxflags}" + PARENT_SCOPE) + set(${linkerflags_out} + "${linkerflags}" + PARENT_SCOPE) + endif() +endfunction() + +function(_pybind11_generate_lto target prefer_thin_lto) + if(MINGW) + message(STATUS "${target} disabled (problems with undefined symbols for MinGW for now)") + return() + endif() + + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + set(cxx_append "") + set(linker_append "") + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT APPLE) + # Clang Gold plugin does not support -Os; append -O3 to MinSizeRel builds to override it + set(linker_append ";$<$:-O3>") + elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND NOT MINGW) + set(cxx_append ";-fno-fat-lto-objects") + endif() + + if(CMAKE_SYSTEM_PROCESSOR MATCHES "ppc64le" OR CMAKE_SYSTEM_PROCESSOR MATCHES "mips64") + set(NO_FLTO_ARCH TRUE) + else() + set(NO_FLTO_ARCH FALSE) + endif() + + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" + AND prefer_thin_lto + AND NOT NO_FLTO_ARCH) + _pybind11_return_if_cxx_and_linker_flags_work( + HAS_FLTO_THIN "-flto=thin${cxx_append}" "-flto=thin${linker_append}" + PYBIND11_LTO_CXX_FLAGS PYBIND11_LTO_LINKER_FLAGS) + endif() + + if(NOT HAS_FLTO_THIN AND NOT NO_FLTO_ARCH) + _pybind11_return_if_cxx_and_linker_flags_work( + HAS_FLTO "-flto${cxx_append}" "-flto${linker_append}" PYBIND11_LTO_CXX_FLAGS + PYBIND11_LTO_LINKER_FLAGS) + endif() + elseif(CMAKE_CXX_COMPILER_ID MATCHES "IntelLLVM") + # IntelLLVM equivalent to LTO is called IPO; also IntelLLVM is WIN32/UNIX + # WARNING/HELP WANTED: This block of code is currently not covered by pybind11 GitHub Actions! + if(WIN32) + _pybind11_return_if_cxx_and_linker_flags_work( + HAS_INTEL_IPO "-Qipo" "-Qipo" PYBIND11_LTO_CXX_FLAGS PYBIND11_LTO_LINKER_FLAGS) + else() + _pybind11_return_if_cxx_and_linker_flags_work( + HAS_INTEL_IPO "-ipo" "-ipo" PYBIND11_LTO_CXX_FLAGS PYBIND11_LTO_LINKER_FLAGS) + endif() + elseif(CMAKE_CXX_COMPILER_ID MATCHES "Intel") + # Intel equivalent to LTO is called IPO + _pybind11_return_if_cxx_and_linker_flags_work(HAS_INTEL_IPO "-ipo" "-ipo" + PYBIND11_LTO_CXX_FLAGS PYBIND11_LTO_LINKER_FLAGS) + elseif(MSVC) + # cmake only interprets libraries as linker flags when they start with a - (otherwise it + # converts /LTCG to \LTCG as if it was a Windows path). Luckily MSVC supports passing flags + # with - instead of /, even if it is a bit non-standard: + _pybind11_return_if_cxx_and_linker_flags_work(HAS_MSVC_GL_LTCG "/GL" "-LTCG" + PYBIND11_LTO_CXX_FLAGS PYBIND11_LTO_LINKER_FLAGS) + endif() + + # Enable LTO flags if found, except for Debug builds + if(PYBIND11_LTO_CXX_FLAGS) + # CONFIG takes multiple values in CMake 3.19+, until then we have to use OR + set(is_debug "$,$>") + set(not_debug "$") + set(cxx_lang "$") + if(MSVC AND CMAKE_VERSION VERSION_LESS 3.11) + set(genex "${not_debug}") + else() + set(genex "$") + endif() + set_property( + TARGET ${target} + APPEND + PROPERTY INTERFACE_COMPILE_OPTIONS "$<${genex}:${PYBIND11_LTO_CXX_FLAGS}>") + if(CMAKE_PROJECT_NAME STREQUAL "pybind11") + message(STATUS "${target} enabled") + endif() + else() + if(CMAKE_PROJECT_NAME STREQUAL "pybind11") + message(STATUS "${target} disabled (not supported by the compiler and/or linker)") + endif() + endif() + + if(PYBIND11_LTO_LINKER_FLAGS) + if(CMAKE_VERSION VERSION_LESS 3.11) + set_property( + TARGET ${target} + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES "$<${not_debug}:${PYBIND11_LTO_LINKER_FLAGS}>") + else() + set_property( + TARGET ${target} + APPEND + PROPERTY INTERFACE_LINK_OPTIONS "$<${not_debug}:${PYBIND11_LTO_LINKER_FLAGS}>") + endif() + endif() +endfunction() + +if(NOT DEFINED CMAKE_INTERPROCEDURAL_OPTIMIZATION) + add_library(pybind11::lto IMPORTED INTERFACE ${optional_global}) + _pybind11_generate_lto(pybind11::lto FALSE) + + add_library(pybind11::thin_lto IMPORTED INTERFACE ${optional_global}) + _pybind11_generate_lto(pybind11::thin_lto TRUE) +endif() + +# ---------------------- pybind11_strip ----------------------------- + +function(pybind11_strip target_name) + # Strip unnecessary sections of the binary on Linux/macOS + if(CMAKE_STRIP) + if(APPLE) + set(x_opt -x) + endif() + + add_custom_command( + TARGET ${target_name} + POST_BUILD + COMMAND ${CMAKE_STRIP} ${x_opt} $) + endif() +endfunction() diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pybind11Config.cmake.in b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pybind11Config.cmake.in new file mode 100644 index 0000000..5734f43 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pybind11Config.cmake.in @@ -0,0 +1,233 @@ +#[=============================================================================[.rst: + +pybind11Config.cmake +#################### + +Exported variables +================== + +This module sets the following variables in your project: + +``pybind11_FOUND`` + true if pybind11 and all required components found on the system +``pybind11_VERSION`` + pybind11 version in format Major.Minor.Release +``pybind11_VERSION_TYPE`` + pybind11 version type (``dev*`` or empty for a release) +``pybind11_INCLUDE_DIRS`` + Directories where pybind11 and python headers are located. +``pybind11_INCLUDE_DIR`` + Directory where pybind11 headers are located. +``pybind11_DEFINITIONS`` + Definitions necessary to use pybind11, namely USING_pybind11. +``pybind11_LIBRARIES`` + Compile flags and python libraries (as needed) to link against. +``pybind11_LIBRARY`` + Empty. + +Available components: None + + +Exported targets +================ + +If pybind11 is found, this module defines the following ``IMPORTED`` +interface library targets: + +``pybind11::module`` + for extension modules. +``pybind11::embed`` + for embedding the Python interpreter. + +Python headers, libraries (as needed by platform), and the C++ standard +are attached to the target. + +Advanced targets are also supplied - these are primary for users building +complex applications, and they are available in all modes: + +``pybind11::headers`` + Just the pybind11 headers and minimum compile requirements. +``pybind11::pybind11`` + Python headers too. +``pybind11::python_link_helper`` + Just the "linking" part of ``pybind11:module``, for CMake < 3.15. +``pybind11::thin_lto`` + An alternative to ``INTERPROCEDURAL_OPTIMIZATION``. +``pybind11::lto`` + An alternative to ``INTERPROCEDURAL_OPTIMIZATION`` (also avoids thin LTO on clang). +``pybind11::windows_extras`` + Adds bigobj and mp for MSVC. + +Modes +===== + +There are two modes provided; classic, which is built on the old Python +discovery packages in CMake, or the new FindPython mode, which uses FindPython +from 3.12+ forward (3.15+ _highly_ recommended). If you set the minimum or +maximum version of CMake to 3.27+, then FindPython is the default (since +FindPythonInterp/FindPythonLibs has been removed via policy `CMP0148`). + +New FindPython mode +^^^^^^^^^^^^^^^^^^^ + +To activate this mode, either call ``find_package(Python COMPONENTS Interpreter Development)`` +before finding this package, or set the ``PYBIND11_FINDPYTHON`` variable to ON. In this mode, +you can either use the basic targets, or use the FindPython tools: + +.. code-block:: cmake + + find_package(Python COMPONENTS Interpreter Development) + find_package(pybind11 CONFIG) + + # pybind11 method: + pybind11_add_module(MyModule1 src1.cpp) + + # Python method: + Python_add_library(MyModule2 src2.cpp) + target_link_libraries(MyModule2 pybind11::headers) + set_target_properties(MyModule2 PROPERTIES + INTERPROCEDURAL_OPTIMIZATION ON + CXX_VISIBILITY_PRESET ON + VISIBILITY_INLINES_HIDDEN ON) + +If you build targets yourself, you may be interested in stripping the output +for reduced size; this is the one other feature that the helper function gives you. + +Classic mode +^^^^^^^^^^^^ + +Set PythonLibsNew variables to influence python detection and +CMAKE_CXX_STANDARD to influence standard setting. + +.. code-block:: cmake + + find_package(pybind11 CONFIG REQUIRED) + + # Create an extension module + add_library(mylib MODULE main.cpp) + target_link_libraries(mylib PUBLIC pybind11::module) + + # Or embed the Python interpreter into an executable + add_executable(myexe main.cpp) + target_link_libraries(myexe PUBLIC pybind11::embed) + + +Hints +===== + +The following variables can be set to guide the search for this package: + +``pybind11_DIR`` + CMake variable, set to directory containing this Config file. +``CMAKE_PREFIX_PATH`` + CMake variable, set to root directory of this package. +``PATH`` + Environment variable, set to bin directory of this package. +``CMAKE_DISABLE_FIND_PACKAGE_pybind11`` + CMake variable, disables ``find_package(pybind11)`` when not ``REQUIRED``, + perhaps to force internal build. + +Commands +======== + +pybind11_add_module +^^^^^^^^^^^^^^^^^^^ + +This module defines the following commands to assist with creating Python modules: + +.. code-block:: cmake + + pybind11_add_module( + [STATIC|SHARED|MODULE] + [THIN_LTO] [OPT_SIZE] [NO_EXTRAS] [WITHOUT_SOABI] + ... + ) + +Add a module and setup all helpers. You can select the type of the library; the +default is ``MODULE``. There are several options: + +``OPT_SIZE`` + Optimize for size, even if the ``CMAKE_BUILD_TYPE`` is not ``MinSizeRel``. +``THIN_LTO`` + Use thin TLO instead of regular if there's a choice (pybind11's selection + is disabled if ``CMAKE_INTERPROCEDURAL_OPTIMIZATIONS`` is set). +``WITHOUT_SOABI`` + Disable the SOABI component (``PYBIND11_NEWPYTHON`` mode only). +``NO_EXTRAS`` + Disable all extras, exit immediately after making the module. + +pybind11_strip +^^^^^^^^^^^^^^ + +.. code-block:: cmake + + pybind11_strip() + +Strip a target after building it (linux/macOS), called by ``pybind11_add_module``. + +pybind11_extension +^^^^^^^^^^^^^^^^^^ + +.. code-block:: cmake + + pybind11_extension() + +Sets the Python extension name correctly for Python on your platform, called by +``pybind11_add_module``. + +pybind11_find_import(module) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: cmake + + pybind11_find_import( [VERSION ] [REQUIRED] [QUIET]) + +See if a module is installed. Use the registered name (the one on PyPI). You +can specify a ``VERSION``, and you can specify ``REQUIRED`` or ``QUIET``. Only available if +``NOPYTHON`` mode is not active. Sets ``module_VERSION`` and ``module_FOUND``. Caches the +result once a valid install is found. + +Suggested usage +=============== + +Using ``find_package`` with version info is not recommended except for release versions. + +.. code-block:: cmake + + find_package(pybind11 CONFIG) + find_package(pybind11 2.9 EXACT CONFIG REQUIRED) + +#]=============================================================================] +@PACKAGE_INIT@ + +# Location of pybind11/pybind11.h +# This will be relative unless explicitly set as absolute +set(pybind11_INCLUDE_DIR "@pybind11_INCLUDEDIR@") + +set(pybind11_LIBRARY "") +set(pybind11_DEFINITIONS USING_pybind11) +set(pybind11_VERSION_TYPE "@pybind11_VERSION_TYPE@") + +check_required_components(pybind11) + +if(TARGET pybind11::python_link_helper) + # This has already been setup elsewhere, such as with a previous call or + # add_subdirectory + return() +endif() + +include("${CMAKE_CURRENT_LIST_DIR}/pybind11Targets.cmake") + +# Easier to use / remember +add_library(pybind11::headers IMPORTED INTERFACE) +set_target_properties(pybind11::headers PROPERTIES INTERFACE_LINK_LIBRARIES + pybind11::pybind11_headers) + +include("${CMAKE_CURRENT_LIST_DIR}/pybind11Common.cmake") + +if(NOT pybind11_FIND_QUIETLY) + message( + STATUS + "Found pybind11: ${pybind11_INCLUDE_DIR} (found version \"${pybind11_VERSION}${pybind11_VERSION_TYPE}\")" + ) +endif() diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pybind11NewTools.cmake b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pybind11NewTools.cmake new file mode 100644 index 0000000..7d7424a --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pybind11NewTools.cmake @@ -0,0 +1,256 @@ +# tools/pybind11NewTools.cmake -- Build system for the pybind11 modules +# +# Copyright (c) 2020 Wenzel Jakob and Henry Schreiner +# +# All rights reserved. Use of this source code is governed by a +# BSD-style license that can be found in the LICENSE file. + +if(CMAKE_VERSION VERSION_LESS 3.12) + message(FATAL_ERROR "You cannot use the new FindPython module with CMake < 3.12") +endif() + +include_guard(DIRECTORY) + +get_property( + is_config + TARGET pybind11::headers + PROPERTY IMPORTED) + +if(pybind11_FIND_QUIETLY) + set(_pybind11_quiet QUIET) +else() + set(_pybind11_quiet "") +endif() + +if(NOT Python_FOUND AND NOT Python3_FOUND) + if(NOT DEFINED Python_FIND_IMPLEMENTATIONS) + set(Python_FIND_IMPLEMENTATIONS CPython PyPy) + endif() + + # GitHub Actions like activation + if(NOT DEFINED Python_ROOT_DIR AND DEFINED ENV{pythonLocation}) + set(Python_ROOT_DIR "$ENV{pythonLocation}") + endif() + + find_package(Python 3.6 REQUIRED COMPONENTS Interpreter Development ${_pybind11_quiet}) + + # If we are in submodule mode, export the Python targets to global targets. + # If this behavior is not desired, FindPython _before_ pybind11. + if(NOT is_config) + set_property(TARGET Python::Python PROPERTY IMPORTED_GLOBAL TRUE) + set_property(TARGET Python::Interpreter PROPERTY IMPORTED_GLOBAL TRUE) + if(TARGET Python::Module) + set_property(TARGET Python::Module PROPERTY IMPORTED_GLOBAL TRUE) + endif() + endif() +endif() + +if(Python_FOUND) + set(_Python + Python + CACHE INTERNAL "" FORCE) +elseif(Python3_FOUND) + set(_Python + Python3 + CACHE INTERNAL "" FORCE) +endif() + +if(PYBIND11_MASTER_PROJECT) + if(${_Python}_INTERPRETER_ID MATCHES "PyPy") + message(STATUS "PyPy ${${_Python}_PyPy_VERSION} (Py ${${_Python}_VERSION})") + else() + message(STATUS "${_Python} ${${_Python}_VERSION}") + endif() +endif() + +# If a user finds Python, they may forget to include the Interpreter component +# and the following two steps require it. It is highly recommended by CMake +# when finding development libraries anyway, so we will require it. +if(NOT DEFINED ${_Python}_EXECUTABLE) + message( + FATAL_ERROR + "${_Python} was found without the Interpreter component. Pybind11 requires this component.") + +endif() + +if(NOT ${_Python}_EXECUTABLE STREQUAL PYBIND11_PYTHON_EXECUTABLE_LAST) + # Detect changes to the Python version/binary in subsequent CMake runs, and refresh config if needed + unset(PYTHON_IS_DEBUG CACHE) + unset(PYTHON_MODULE_EXTENSION CACHE) + set(PYBIND11_PYTHON_EXECUTABLE_LAST + "${${_Python}_EXECUTABLE}" + CACHE INTERNAL "Python executable during the last CMake run") +endif() + +if(NOT DEFINED PYTHON_IS_DEBUG) + # Debug check - see https://stackoverflow.com/questions/646518/python-how-to-detect-debug-Interpreter + execute_process( + COMMAND "${${_Python}_EXECUTABLE}" "-c" + "import sys; sys.exit(hasattr(sys, 'gettotalrefcount'))" + RESULT_VARIABLE _PYTHON_IS_DEBUG) + set(PYTHON_IS_DEBUG + "${_PYTHON_IS_DEBUG}" + CACHE INTERNAL "Python debug status") +endif() + +# Get the suffix - SO is deprecated, should use EXT_SUFFIX, but this is +# required for PyPy3 (as of 7.3.1) +if(NOT DEFINED PYTHON_MODULE_EXTENSION) + execute_process( + COMMAND + "${${_Python}_EXECUTABLE}" "-c" + "import sys, importlib; s = importlib.import_module('distutils.sysconfig' if sys.version_info < (3, 10) else 'sysconfig'); print(s.get_config_var('EXT_SUFFIX') or s.get_config_var('SO'))" + OUTPUT_VARIABLE _PYTHON_MODULE_EXTENSION + ERROR_VARIABLE _PYTHON_MODULE_EXTENSION_ERR + OUTPUT_STRIP_TRAILING_WHITESPACE) + + if(_PYTHON_MODULE_EXTENSION STREQUAL "") + message( + FATAL_ERROR "pybind11 could not query the module file extension, likely the 'distutils'" + "package is not installed. Full error message:\n${_PYTHON_MODULE_EXTENSION_ERR}") + endif() + + # This needs to be available for the pybind11_extension function + set(PYTHON_MODULE_EXTENSION + "${_PYTHON_MODULE_EXTENSION}" + CACHE INTERNAL "") +endif() + +# Python debug libraries expose slightly different objects before 3.8 +# https://docs.python.org/3.6/c-api/intro.html#debugging-builds +# https://stackoverflow.com/questions/39161202/how-to-work-around-missing-pymodule-create2-in-amd64-win-python35-d-lib +if(PYTHON_IS_DEBUG) + set_property( + TARGET pybind11::pybind11 + APPEND + PROPERTY INTERFACE_COMPILE_DEFINITIONS Py_DEBUG) +endif() + +# Check on every access - since Python can change - do nothing in that case. + +if(DEFINED ${_Python}_INCLUDE_DIRS) + # Only add Python for build - must be added during the import for config + # since it has to be re-discovered. + # + # This needs to be a target to be included after the local pybind11 + # directory, just in case there there is an installed pybind11 sitting + # next to Python's includes. It also ensures Python is a SYSTEM library. + add_library(pybind11::python_headers INTERFACE IMPORTED) + set_property( + TARGET pybind11::python_headers PROPERTY INTERFACE_INCLUDE_DIRECTORIES + "$") + set_property( + TARGET pybind11::pybind11 + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES pybind11::python_headers) + set(pybind11_INCLUDE_DIRS + "${pybind11_INCLUDE_DIR}" "${${_Python}_INCLUDE_DIRS}" + CACHE INTERNAL "Directories where pybind11 and possibly Python headers are located") +endif() + +# In CMake 3.18+, you can find these separately, so include an if +if(TARGET ${_Python}::Python) + set_property( + TARGET pybind11::embed + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES ${_Python}::Python) +endif() + +# CMake 3.15+ has this +if(TARGET ${_Python}::Module) + set_property( + TARGET pybind11::module + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES ${_Python}::Module) +else() + set_property( + TARGET pybind11::module + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES pybind11::python_link_helper) +endif() + +# WITHOUT_SOABI and WITH_SOABI will disable the custom extension handling used by pybind11. +# WITH_SOABI is passed on to python_add_library. +function(pybind11_add_module target_name) + cmake_parse_arguments(PARSE_ARGV 1 ARG + "STATIC;SHARED;MODULE;THIN_LTO;OPT_SIZE;NO_EXTRAS;WITHOUT_SOABI" "" "") + + if(ARG_STATIC) + set(lib_type STATIC) + elseif(ARG_SHARED) + set(lib_type SHARED) + else() + set(lib_type MODULE) + endif() + + if("${_Python}" STREQUAL "Python") + python_add_library(${target_name} ${lib_type} ${ARG_UNPARSED_ARGUMENTS}) + elseif("${_Python}" STREQUAL "Python3") + python3_add_library(${target_name} ${lib_type} ${ARG_UNPARSED_ARGUMENTS}) + else() + message(FATAL_ERROR "Cannot detect FindPython version: ${_Python}") + endif() + + target_link_libraries(${target_name} PRIVATE pybind11::headers) + + if(lib_type STREQUAL "MODULE") + target_link_libraries(${target_name} PRIVATE pybind11::module) + else() + target_link_libraries(${target_name} PRIVATE pybind11::embed) + endif() + + if(MSVC) + target_link_libraries(${target_name} PRIVATE pybind11::windows_extras) + endif() + + # -fvisibility=hidden is required to allow multiple modules compiled against + # different pybind versions to work properly, and for some features (e.g. + # py::module_local). We force it on everything inside the `pybind11` + # namespace; also turning it on for a pybind module compilation here avoids + # potential warnings or issues from having mixed hidden/non-hidden types. + if(NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET) + set_target_properties(${target_name} PROPERTIES CXX_VISIBILITY_PRESET "hidden") + endif() + + if(NOT DEFINED CMAKE_CUDA_VISIBILITY_PRESET) + set_target_properties(${target_name} PROPERTIES CUDA_VISIBILITY_PRESET "hidden") + endif() + + # If we don't pass a WITH_SOABI or WITHOUT_SOABI, use our own default handling of extensions + if(NOT ARG_WITHOUT_SOABI AND NOT "WITH_SOABI" IN_LIST ARG_UNPARSED_ARGUMENTS) + pybind11_extension(${target_name}) + endif() + + if(ARG_NO_EXTRAS) + return() + endif() + + if(NOT DEFINED CMAKE_INTERPROCEDURAL_OPTIMIZATION) + if(ARG_THIN_LTO) + target_link_libraries(${target_name} PRIVATE pybind11::thin_lto) + else() + target_link_libraries(${target_name} PRIVATE pybind11::lto) + endif() + endif() + + # Use case-insensitive comparison to match the result of $ + string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE) + if(NOT MSVC AND NOT "${uppercase_CMAKE_BUILD_TYPE}" MATCHES DEBUG|RELWITHDEBINFO) + # Strip unnecessary sections of the binary on Linux/macOS + pybind11_strip(${target_name}) + endif() + + if(MSVC) + target_link_libraries(${target_name} PRIVATE pybind11::windows_extras) + endif() + + if(ARG_OPT_SIZE) + target_link_libraries(${target_name} PRIVATE pybind11::opt_size) + endif() +endfunction() + +function(pybind11_extension name) + # The extension is precomputed + set_target_properties(${name} PROPERTIES PREFIX "" SUFFIX "${PYTHON_MODULE_EXTENSION}") + +endfunction() diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pybind11Tools.cmake b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pybind11Tools.cmake new file mode 100644 index 0000000..66ad00a --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pybind11Tools.cmake @@ -0,0 +1,233 @@ +# tools/pybind11Tools.cmake -- Build system for the pybind11 modules +# +# Copyright (c) 2020 Wenzel Jakob +# +# All rights reserved. Use of this source code is governed by a +# BSD-style license that can be found in the LICENSE file. + +# include_guard(global) (pre-CMake 3.10) +if(TARGET pybind11::python_headers) + return() +endif() + +# Built-in in CMake 3.5+ +include(CMakeParseArguments) + +if(pybind11_FIND_QUIETLY) + set(_pybind11_quiet QUIET) +else() + set(_pybind11_quiet "") +endif() + +# If this is the first run, PYTHON_VERSION can stand in for PYBIND11_PYTHON_VERSION +if(NOT DEFINED PYBIND11_PYTHON_VERSION AND DEFINED PYTHON_VERSION) + message(WARNING "Set PYBIND11_PYTHON_VERSION to search for a specific version, not " + "PYTHON_VERSION (which is an output). Assuming that is what you " + "meant to do and continuing anyway.") + set(PYBIND11_PYTHON_VERSION + "${PYTHON_VERSION}" + CACHE STRING "Python version to use for compiling modules") + unset(PYTHON_VERSION) + unset(PYTHON_VERSION CACHE) +elseif(DEFINED PYBIND11_PYTHON_VERSION) + # If this is set as a normal variable, promote it + set(PYBIND11_PYTHON_VERSION + "${PYBIND11_PYTHON_VERSION}" + CACHE STRING "Python version to use for compiling modules") +else() + # Make an empty cache variable. + set(PYBIND11_PYTHON_VERSION + "" + CACHE STRING "Python version to use for compiling modules") +endif() + +# A user can set versions manually too +set(Python_ADDITIONAL_VERSIONS + "3.11;3.10;3.9;3.8;3.7;3.6" + CACHE INTERNAL "") + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") +find_package(PythonLibsNew ${PYBIND11_PYTHON_VERSION} MODULE REQUIRED ${_pybind11_quiet}) +list(REMOVE_AT CMAKE_MODULE_PATH -1) + +# Makes a normal variable a cached variable +macro(_PYBIND11_PROMOTE_TO_CACHE NAME) + set(_tmp_ptc "${${NAME}}") + # CMake 3.21 complains if a cached variable is shadowed by a normal one + unset(${NAME}) + set(${NAME} + "${_tmp_ptc}" + CACHE INTERNAL "") +endmacro() + +# Cache variables so pybind11_add_module can be used in parent projects +_pybind11_promote_to_cache(PYTHON_INCLUDE_DIRS) +_pybind11_promote_to_cache(PYTHON_LIBRARIES) +_pybind11_promote_to_cache(PYTHON_MODULE_PREFIX) +_pybind11_promote_to_cache(PYTHON_MODULE_EXTENSION) +_pybind11_promote_to_cache(PYTHON_VERSION_MAJOR) +_pybind11_promote_to_cache(PYTHON_VERSION_MINOR) +_pybind11_promote_to_cache(PYTHON_VERSION) +_pybind11_promote_to_cache(PYTHON_IS_DEBUG) + +if(PYBIND11_MASTER_PROJECT) + if(PYTHON_MODULE_EXTENSION MATCHES "pypy") + if(NOT DEFINED PYPY_VERSION) + execute_process( + COMMAND ${PYTHON_EXECUTABLE} -c + [=[import sys; sys.stdout.write(".".join(map(str, sys.pypy_version_info[:3])))]=] + OUTPUT_VARIABLE pypy_version) + set(PYPY_VERSION + ${pypy_version} + CACHE INTERNAL "") + endif() + message(STATUS "PYPY ${PYPY_VERSION} (Py ${PYTHON_VERSION})") + else() + message(STATUS "PYTHON ${PYTHON_VERSION}") + endif() +endif() + +# Only add Python for build - must be added during the import for config since +# it has to be re-discovered. +# +# This needs to be an target to it is included after the local pybind11 +# directory, just in case there are multiple versions of pybind11, we want the +# one we expect. +add_library(pybind11::python_headers INTERFACE IMPORTED) +set_property(TARGET pybind11::python_headers PROPERTY INTERFACE_INCLUDE_DIRECTORIES + "$") +set_property( + TARGET pybind11::pybind11 + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES pybind11::python_headers) + +set(pybind11_INCLUDE_DIRS + "${pybind11_INCLUDE_DIR}" "${PYTHON_INCLUDE_DIRS}" + CACHE INTERNAL "Directories where pybind11 and possibly Python headers are located") + +# Python debug libraries expose slightly different objects before 3.8 +# https://docs.python.org/3.6/c-api/intro.html#debugging-builds +# https://stackoverflow.com/questions/39161202/how-to-work-around-missing-pymodule-create2-in-amd64-win-python35-d-lib +if(PYTHON_IS_DEBUG) + set_property( + TARGET pybind11::pybind11 + APPEND + PROPERTY INTERFACE_COMPILE_DEFINITIONS Py_DEBUG) +endif() + +# The <3.11 code here does not support release/debug builds at the same time, like on vcpkg +if(CMAKE_VERSION VERSION_LESS 3.11) + set_property( + TARGET pybind11::module + APPEND + PROPERTY + INTERFACE_LINK_LIBRARIES + pybind11::python_link_helper + "$<$,$>:$>" + ) + + set_property( + TARGET pybind11::embed + APPEND + PROPERTY INTERFACE_LINK_LIBRARIES pybind11::pybind11 $) +else() + # The IMPORTED INTERFACE library here is to ensure that "debug" and "release" get processed outside + # of a generator expression - https://gitlab.kitware.com/cmake/cmake/-/issues/18424, as they are + # target_link_library keywords rather than real libraries. + add_library(pybind11::_ClassicPythonLibraries IMPORTED INTERFACE) + target_link_libraries(pybind11::_ClassicPythonLibraries INTERFACE ${PYTHON_LIBRARIES}) + target_link_libraries( + pybind11::module + INTERFACE + pybind11::python_link_helper + "$<$,$>:pybind11::_ClassicPythonLibraries>") + + target_link_libraries(pybind11::embed INTERFACE pybind11::pybind11 + pybind11::_ClassicPythonLibraries) +endif() + +function(pybind11_extension name) + # The prefix and extension are provided by FindPythonLibsNew.cmake + set_target_properties(${name} PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" + SUFFIX "${PYTHON_MODULE_EXTENSION}") +endfunction() + +# Build a Python extension module: +# pybind11_add_module( [MODULE | SHARED] [EXCLUDE_FROM_ALL] +# [NO_EXTRAS] [THIN_LTO] [OPT_SIZE] source1 [source2 ...]) +# +function(pybind11_add_module target_name) + set(options "MODULE;SHARED;EXCLUDE_FROM_ALL;NO_EXTRAS;SYSTEM;THIN_LTO;OPT_SIZE") + cmake_parse_arguments(ARG "${options}" "" "" ${ARGN}) + + if(ARG_MODULE AND ARG_SHARED) + message(FATAL_ERROR "Can't be both MODULE and SHARED") + elseif(ARG_SHARED) + set(lib_type SHARED) + else() + set(lib_type MODULE) + endif() + + if(ARG_EXCLUDE_FROM_ALL) + set(exclude_from_all EXCLUDE_FROM_ALL) + else() + set(exclude_from_all "") + endif() + + add_library(${target_name} ${lib_type} ${exclude_from_all} ${ARG_UNPARSED_ARGUMENTS}) + + target_link_libraries(${target_name} PRIVATE pybind11::module) + + if(ARG_SYSTEM) + message( + STATUS + "Warning: this does not have an effect - use NO_SYSTEM_FROM_IMPORTED if using imported targets" + ) + endif() + + pybind11_extension(${target_name}) + + # -fvisibility=hidden is required to allow multiple modules compiled against + # different pybind versions to work properly, and for some features (e.g. + # py::module_local). We force it on everything inside the `pybind11` + # namespace; also turning it on for a pybind module compilation here avoids + # potential warnings or issues from having mixed hidden/non-hidden types. + if(NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET) + set_target_properties(${target_name} PROPERTIES CXX_VISIBILITY_PRESET "hidden") + endif() + + if(NOT DEFINED CMAKE_CUDA_VISIBILITY_PRESET) + set_target_properties(${target_name} PROPERTIES CUDA_VISIBILITY_PRESET "hidden") + endif() + + if(ARG_NO_EXTRAS) + return() + endif() + + if(NOT DEFINED CMAKE_INTERPROCEDURAL_OPTIMIZATION) + if(ARG_THIN_LTO) + target_link_libraries(${target_name} PRIVATE pybind11::thin_lto) + else() + target_link_libraries(${target_name} PRIVATE pybind11::lto) + endif() + endif() + + # Use case-insensitive comparison to match the result of $ + string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE) + if(NOT MSVC AND NOT "${uppercase_CMAKE_BUILD_TYPE}" MATCHES DEBUG|RELWITHDEBINFO) + pybind11_strip(${target_name}) + endif() + + if(MSVC) + target_link_libraries(${target_name} PRIVATE pybind11::windows_extras) + endif() + + if(ARG_OPT_SIZE) + target_link_libraries(${target_name} PRIVATE pybind11::opt_size) + endif() +endfunction() + +# Provide general way to call common Python commands in "common" file. +set(_Python + PYTHON + CACHE INTERNAL "" FORCE) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pyproject.toml b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pyproject.toml new file mode 100644 index 0000000..8fe2f47 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools>=42", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/setup_global.py.in b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/setup_global.py.in new file mode 100644 index 0000000..885ac5c --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/setup_global.py.in @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 + +# Setup script for pybind11-global (in the sdist or in tools/setup_global.py in the repository) +# This package is targeted for easy use from CMake. + +import glob +import os +import re + +# Setuptools has to be before distutils +from setuptools import setup + +from distutils.command.install_headers import install_headers + +class InstallHeadersNested(install_headers): + def run(self): + headers = self.distribution.headers or [] + for header in headers: + # Remove pybind11/include/ + short_header = header.split("/", 2)[-1] + + dst = os.path.join(self.install_dir, os.path.dirname(short_header)) + self.mkpath(dst) + (out, _) = self.copy_file(header, dst) + self.outfiles.append(out) + + +main_headers = glob.glob("pybind11/include/pybind11/*.h") +detail_headers = glob.glob("pybind11/include/pybind11/detail/*.h") +eigen_headers = glob.glob("pybind11/include/pybind11/eigen/*.h") +stl_headers = glob.glob("pybind11/include/pybind11/stl/*.h") +cmake_files = glob.glob("pybind11/share/cmake/pybind11/*.cmake") +pkgconfig_files = glob.glob("pybind11/share/pkgconfig/*.pc") +headers = main_headers + detail_headers + stl_headers + eigen_headers + +cmdclass = {"install_headers": InstallHeadersNested} +$extra_cmd + +# This will _not_ affect installing from wheels, +# only building wheels or installing from SDist. +# Primarily intended on Windows, where this is sometimes +# customized (for example, conda-forge uses Library/) +base = os.environ.get("PYBIND11_GLOBAL_PREFIX", "") + +# Must have a separator +if base and not base.endswith("/"): + base += "/" + +setup( + name="pybind11_global", + version="$version", + packages=[], + headers=headers, + data_files=[ + (base + "share/cmake/pybind11", cmake_files), + (base + "share/pkgconfig", pkgconfig_files), + (base + "include/pybind11", main_headers), + (base + "include/pybind11/detail", detail_headers), + (base + "include/pybind11/eigen", eigen_headers), + (base + "include/pybind11/stl", stl_headers), + ], + cmdclass=cmdclass, +) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/setup_main.py.in b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/setup_main.py.in new file mode 100644 index 0000000..6358cc7 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/_vendor/pybind11/tools/setup_main.py.in @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 + +# Setup script (in the sdist or in tools/setup_main.py in the repository) + +from setuptools import setup + +cmdclass = {} +$extra_cmd + +setup( + name="pybind11", + version="$version", + download_url='https://github.com/pybind/pybind11/tarball/v$version', + packages=[ + "pybind11", + "pybind11.include.pybind11", + "pybind11.include.pybind11.detail", + "pybind11.include.pybind11.eigen", + "pybind11.include.pybind11.stl", + "pybind11.share.cmake.pybind11", + "pybind11.share.pkgconfig", + ], + package_data={ + "pybind11": ["py.typed"], + "pybind11.include.pybind11": ["*.h"], + "pybind11.include.pybind11.detail": ["*.h"], + "pybind11.include.pybind11.eigen": ["*.h"], + "pybind11.include.pybind11.stl": ["*.h"], + "pybind11.share.cmake.pybind11": ["*.cmake"], + "pybind11.share.pkgconfig": ["*.pc"], + }, + extras_require={ + "global": ["pybind11_global==$version"] + }, + entry_points={ + "console_scripts": [ + "pybind11-config = pybind11.__main__:main", + ], + "pipx.run": [ + "pybind11 = pybind11.__main__:main", + ] + }, + cmdclass=cmdclass +) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/aspects.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/aspects.py new file mode 100644 index 0000000..f52651d --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/aspects.py @@ -0,0 +1,942 @@ +from builtins import bytearray as builtin_bytearray +from builtins import bytes as builtin_bytes +from builtins import str as builtin_str +import codecs +from types import BuiltinFunctionType +from typing import TYPE_CHECKING +from typing import Any + +from .._metrics import _set_iast_error_metric +from .._taint_tracking import TagMappingMode +from .._taint_tracking import TaintRange +from .._taint_tracking import _convert_escaped_text_to_tainted_text +from .._taint_tracking import _format_aspect +from .._taint_tracking import are_all_text_all_ranges +from .._taint_tracking import as_formatted_evidence +from .._taint_tracking import common_replace +from .._taint_tracking import copy_and_shift_ranges_from_strings +from .._taint_tracking import copy_ranges_from_strings +from .._taint_tracking import get_ranges +from .._taint_tracking import get_tainted_ranges +from .._taint_tracking import is_pyobject_tainted +from .._taint_tracking import new_pyobject_id +from .._taint_tracking import parse_params +from .._taint_tracking import set_ranges +from .._taint_tracking import shift_taint_range +from .._taint_tracking import taint_pyobject_with_ranges +from .._taint_tracking._native import aspects # noqa: F401 + + +if TYPE_CHECKING: + from typing import Callable # noqa:F401 + from typing import Dict # noqa:F401 + from typing import List # noqa:F401 + from typing import Optional # noqa:F401 + from typing import Sequence # noqa:F401 + from typing import Tuple # noqa:F401 + from typing import Union # noqa:F401 + + TEXT_TYPE = Union[str, bytes, bytearray] + +TEXT_TYPES = (str, bytes, bytearray) + + +_add_aspect = aspects.add_aspect +_extend_aspect = aspects.extend_aspect +_index_aspect = aspects.index_aspect +_join_aspect = aspects.join_aspect +_slice_aspect = aspects.slice_aspect + +__all__ = ["add_aspect", "str_aspect", "bytearray_extend_aspect", "decode_aspect", "encode_aspect"] + + +def add_aspect(op1, op2): + if not isinstance(op1, TEXT_TYPES) or not isinstance(op2, TEXT_TYPES) or type(op1) != type(op2): + return op1 + op2 + return _add_aspect(op1, op2) + + +def str_aspect(orig_function, flag_added_args, *args, **kwargs): + # type: (Optional[Callable], int, Any, Any) -> str + if orig_function: + if orig_function != builtin_str: + if flag_added_args > 0: + args = args[flag_added_args:] + return orig_function(*args, **kwargs) + result = builtin_str(*args, **kwargs) + else: + result = args[0].str(*args[1:], **kwargs) + + if args and isinstance(args[0], TEXT_TYPES) and is_pyobject_tainted(args[0]): + try: + if isinstance(args[0], (bytes, bytearray)): + encoding = parse_params(1, "encoding", "utf-8", *args, **kwargs) + errors = parse_params(2, "errors", "strict", *args, **kwargs) + check_offset = args[0].decode(encoding, errors) + else: + check_offset = args[0] + offset = result.index(check_offset) + copy_and_shift_ranges_from_strings(args[0], result, offset) + except Exception as e: + _set_iast_error_metric("IAST propagation error. str_aspect. {}".format(e)) + return result + + +def bytes_aspect(orig_function, flag_added_args, *args, **kwargs): + # type: (Optional[Callable], int, Any, Any) -> bytes + if orig_function: + if orig_function != builtin_bytes: + if flag_added_args > 0: + args = args[flag_added_args:] + return orig_function(*args, **kwargs) + result = builtin_bytes(*args, **kwargs) + else: + result = args[0].bytes(*args[1:], **kwargs) + + if args and isinstance(args[0], TEXT_TYPES) and is_pyobject_tainted(args[0]): + try: + copy_ranges_from_strings(args[0], result) + except Exception as e: + _set_iast_error_metric("IAST propagation error. bytes_aspect. {}".format(e)) + return result + + +def bytearray_aspect(orig_function, flag_added_args, *args, **kwargs): + # type: (Optional[Callable], int, Any, Any) -> bytearray + if orig_function: + if orig_function != builtin_bytearray: + if flag_added_args > 0: + args = args[flag_added_args:] + return orig_function(*args, **kwargs) + result = builtin_bytearray(*args, **kwargs) + else: + result = args[0].bytearray(*args[1:], **kwargs) + + if args and isinstance(args[0], TEXT_TYPES) and is_pyobject_tainted(args[0]): + try: + copy_ranges_from_strings(args[0], result) + except Exception as e: + _set_iast_error_metric("IAST propagation error. bytearray_aspect. {}".format(e)) + return result + + +def join_aspect(orig_function, flag_added_args, *args, **kwargs): + # type: (Optional[Callable], int, Any, Any) -> Any + if not orig_function: + orig_function = args[0].join + if not isinstance(orig_function, BuiltinFunctionType): + if flag_added_args > 0: + args = args[flag_added_args:] + return orig_function(*args, **kwargs) + + if not args: + return orig_function(*args, **kwargs) + + joiner = args[0] + args = args[flag_added_args:] + if not isinstance(joiner, TEXT_TYPES): + return joiner.join(*args, **kwargs) + try: + return _join_aspect(joiner, *args, **kwargs) + except Exception as e: + _set_iast_error_metric("IAST propagation error. join_aspect. {}".format(e)) + return joiner.join(*args, **kwargs) + + +def index_aspect(candidate_text, index) -> Any: + result = candidate_text[index] + + if not isinstance(candidate_text, TEXT_TYPES) or not isinstance(index, int): + return result + + try: + return _index_aspect(candidate_text, index) + except Exception as e: + _set_iast_error_metric("IAST propagation error. index_aspect. {}".format(e)) + return result + + +def slice_aspect(candidate_text, start, stop, step) -> Any: + if ( + not isinstance(candidate_text, TEXT_TYPES) + or (start is not None and not isinstance(start, int)) + or (stop is not None and not isinstance(stop, int)) + or (step is not None and not isinstance(step, int)) + ): + return candidate_text[start:stop:step] + result = candidate_text[start:stop:step] + try: + new_result = _slice_aspect(candidate_text, start, stop, step) + if new_result != result: + raise Exception("Propagation result %r is different to candidate_text[slice] %r" % (new_result, result)) + return new_result + except Exception as e: + _set_iast_error_metric("IAST propagation error. slice_aspect. {}".format(e)) + return result + + +def bytearray_extend_aspect(orig_function, flag_added_args, *args, **kwargs): + # type: (Optional[Callable], int, Any, Any) -> Any + if orig_function and not isinstance(orig_function, BuiltinFunctionType): + if flag_added_args > 0: + args = args[flag_added_args:] + return orig_function(*args, **kwargs) + + if len(args) < 2: + # If we're not receiving at least 2 arguments, means the call was + # ``x.extend()`` and not ``x.extend(y)`` + # so either not the extend we're looking for, or no changes in taint ranges. + return args[0].extend(*args[1:], **kwargs) + + op1 = args[0] + op2 = args[1] + if not isinstance(op1, bytearray) or not isinstance(op2, (bytearray, bytes)): + return op1.extend(*args[1:], **kwargs) + try: + return _extend_aspect(op1, op2) + except Exception as e: + _set_iast_error_metric("IAST propagation error. extend_aspect. {}".format(e)) + return op1.extend(op2) + + +def modulo_aspect(candidate_text, candidate_tuple): + # type: (Any, Any) -> Any + if not isinstance(candidate_text, TEXT_TYPES): + return candidate_text % candidate_tuple + + try: + if isinstance(candidate_tuple, tuple): + parameter_list = candidate_tuple + else: + parameter_list = (candidate_tuple,) + + ranges_orig, candidate_text_ranges = are_all_text_all_ranges(candidate_text, parameter_list) + if not ranges_orig: + return candidate_text % candidate_tuple + + return _convert_escaped_text_to_tainted_text( + as_formatted_evidence( + candidate_text, + candidate_text_ranges, + tag_mapping_function=TagMappingMode.Mapper, + ) + % tuple( + as_formatted_evidence( + parameter, + tag_mapping_function=TagMappingMode.Mapper, + ) + if isinstance(parameter, TEXT_TYPES) + else parameter + for parameter in parameter_list + ), + ranges_orig=ranges_orig, + ) + except Exception as e: + _set_iast_error_metric("IAST propagation error. modulo_aspect. {}".format(e)) + return candidate_text % candidate_tuple + + +def build_string_aspect(*args): # type: (List[Any]) -> str + return join_aspect("".join, 1, "", args) + + +def ljust_aspect(orig_function, flag_added_args, *args, **kwargs): + # type: (Optional[Callable], int, Any, Any) -> Union[str, bytes, bytearray] + if not orig_function: + orig_function = args[0].ljust + if not isinstance(orig_function, BuiltinFunctionType): + if flag_added_args > 0: + args = args[flag_added_args:] + return orig_function(*args, **kwargs) + + candidate_text = args[0] + args = args[flag_added_args:] + + result = candidate_text.ljust(*args, **kwargs) + + if not isinstance(candidate_text, TEXT_TYPES): + return result + + try: + ranges_new = get_ranges(candidate_text) + fillchar = parse_params(1, "fillchar", " ", *args, **kwargs) + fillchar_ranges = get_ranges(fillchar) + if ranges_new is None or (not ranges_new and not fillchar_ranges): + return result + + if fillchar_ranges: + # Can only be one char, so we create one range to cover from the start to the end + ranges_new = ranges_new + [shift_taint_range(fillchar_ranges[0], len(candidate_text))] + + new_result = candidate_text.ljust(parse_params(0, "width", None, *args, **kwargs), fillchar) + taint_pyobject_with_ranges(new_result, ranges_new) + return new_result + except Exception as e: + _set_iast_error_metric("IAST propagation error. ljust_aspect. {}".format(e)) + + return result + + +def zfill_aspect(orig_function, flag_added_args, *args, **kwargs): + # type: (Optional[Callable], int, Any, Any) -> Any + if orig_function and not isinstance(orig_function, BuiltinFunctionType): + if flag_added_args > 0: + args = args[flag_added_args:] + return orig_function(*args, **kwargs) + + candidate_text = args[0] + args = args[flag_added_args:] + + result = candidate_text.zfill(*args, **kwargs) + + if not isinstance(candidate_text, TEXT_TYPES): + return result + + try: + ranges_orig = get_ranges(candidate_text) + if not ranges_orig: + return result + prefix = candidate_text[0] in ("-", "+") + + difflen = len(result) - len(candidate_text) + ranges_new = [] # type: List[TaintRange] + ranges_new_append = ranges_new.append + ranges_new_extend = ranges_new.extend + + for r in ranges_orig: + if not prefix or r.start > 0: + ranges_new_append(TaintRange(start=r.start + difflen, length=r.length, source=r.source)) + else: + ranges_new_extend( + [ + TaintRange(start=0, length=1, source=r.source), + TaintRange(start=r.start + difflen + 1, length=r.length - 1, source=r.source), + ] + ) + taint_pyobject_with_ranges(result, tuple(ranges_new)) + except Exception as e: + _set_iast_error_metric("IAST propagation error. format_aspect. {}".format(e)) + + return result + + +def format_aspect( + orig_function, # type: Optional[Callable] + flag_added_args, # type: int + *args, # type: Any + **kwargs, # type: Dict[str, Any] +): # type: (...) -> str + if not orig_function: + orig_function = args[0].format + + if not isinstance(orig_function, BuiltinFunctionType): + if flag_added_args > 0: + args = args[flag_added_args:] + return orig_function(*args, **kwargs) + + if not args: + return orig_function(*args, **kwargs) + + candidate_text = args[0] # type: str + args = args[flag_added_args:] + + result = candidate_text.format(*args, **kwargs) + + if not isinstance(candidate_text, TEXT_TYPES): + return result + + try: + params = tuple(args) + tuple(kwargs.values()) + new_result = _format_aspect(candidate_text, params, *args, **kwargs) + if new_result != result: + raise Exception("Propagation result %r is different to candidate_text.format %r" % (new_result, result)) + return new_result + except Exception as e: + _set_iast_error_metric("IAST propagation error. format_aspect. {}".format(e)) + + return result + + +def format_map_aspect( + orig_function, flag_added_args, *args, **kwargs +): # type: (Optional[Callable], int, Any, Any) -> str + if orig_function and not isinstance(orig_function, BuiltinFunctionType): + if flag_added_args > 0: + args = args[flag_added_args:] + return orig_function(*args, **kwargs) + + if orig_function and not args: + return orig_function(*args, **kwargs) + + candidate_text = args[0] # type: str + args = args[flag_added_args:] + if not isinstance(candidate_text, TEXT_TYPES): + return candidate_text.format_map(*args, **kwargs) + + try: + mapping = parse_params(0, "mapping", None, *args, **kwargs) + mapping_tuple = tuple(mapping if not isinstance(mapping, dict) else mapping.values()) + ranges_orig, candidate_text_ranges = are_all_text_all_ranges( + candidate_text, + args + mapping_tuple, + ) + if not ranges_orig: + return candidate_text.format_map(*args, **kwargs) + + return _convert_escaped_text_to_tainted_text( + as_formatted_evidence( + candidate_text, candidate_text_ranges, tag_mapping_function=TagMappingMode.Mapper + ).format_map( + { + key: as_formatted_evidence(value, tag_mapping_function=TagMappingMode.Mapper) + if isinstance(value, TEXT_TYPES) + else value + for key, value in mapping.items() + } + ), + ranges_orig=ranges_orig, + ) + except Exception as e: + _set_iast_error_metric("IAST propagation error. format_map_aspect. {}".format(e)) + return candidate_text.format_map(*args, **kwargs) + + +def repr_aspect(orig_function, flag_added_args, *args, **kwargs): + # type: (Optional[Callable], Any, Any, Any) -> Any + + # DEV: We call this function directly passing None as orig_function + if orig_function is not None and not ( + orig_function is repr or getattr(orig_function, "__name__", None) == "__repr__" + ): + if flag_added_args > 0: + args = args[flag_added_args:] + return orig_function(*args, **kwargs) + + result = repr(*args, **kwargs) + + if args and isinstance(args[0], TEXT_TYPES) and is_pyobject_tainted(args[0]): + try: + if isinstance(args[0], bytes): + check_offset = ascii(args[0])[2:-1] + elif isinstance(args[0], bytearray): + check_offset = ascii(args[0])[12:-2] + else: + check_offset = args[0] + try: + offset = result.index(check_offset) + except ValueError: + offset = 0 + + copy_and_shift_ranges_from_strings(args[0], result, offset, len(check_offset)) + except Exception as e: + _set_iast_error_metric("IAST propagation error. repr_aspect. {}".format(e)) + return result + + +def format_value_aspect( + element, # type: Any + options=0, # type: int + format_spec=None, # type: Optional[str] +): # type: (...) -> str + if options == 115: + new_text = str_aspect(str, 0, element) + elif options == 114: + # TODO: use our repr once we have implemented it + new_text = repr_aspect(repr, 0, element) + elif options == 97: + new_text = ascii(element) + else: + new_text = element + if not isinstance(new_text, TEXT_TYPES): + return format(new_text) + + try: + if format_spec: + # Apply formatting + text_ranges = get_tainted_ranges(new_text) + if text_ranges: + new_new_text = ("{:%s}" % format_spec).format(new_text) + try: + new_ranges = list() + for text_range in text_ranges: + new_ranges.append(shift_taint_range(text_range, new_new_text.index(new_text))) + if new_ranges: + taint_pyobject_with_ranges(new_new_text, tuple(new_ranges)) + return new_new_text + except ValueError: + return ("{:%s}" % format_spec).format(new_text) + else: + return ("{:%s}" % format_spec).format(new_text) + else: + return str_aspect(str, 0, new_text) + except Exception as e: + _set_iast_error_metric("IAST propagation error. format_value_aspect. {}".format(e)) + return new_text + + +def incremental_translation(self, incr_coder, funcode, empty): + tainted_ranges = iter(get_tainted_ranges(self)) + result_list, new_ranges = [], [] + result_length, i = 0, 0 + tainted_range = next(tainted_ranges, None) + tainted_new_length = 0 + in_tainted = False + tainted_start = 0 + bytes_iterated = 0 + try: + for i in range(len(self)): + if tainted_range is None: + # no more tainted ranges, finish decoding all at once + new_prod = funcode(self[i:]) + result_list.append(new_prod) + break + if i == tainted_range.start: + # start new tainted range + tainted_start = bytes_iterated + tainted_new_length = 0 + in_tainted = True + + new_prod = funcode(self[i : i + 1]) + result_list.append(new_prod) + result_length += len(new_prod) + + if in_tainted: + tainted_new_length += len(new_prod) + else: + bytes_iterated += len(new_prod) + + if i + 1 == tainted_range.start + tainted_range.length and tainted_new_length > 0: + # end range. Do no taint partial multi-bytes character that comes next. + new_ranges.append( + TaintRange( + start=tainted_start, + length=tainted_new_length, + source=tainted_range.source, + ) + ) + + tainted_range = next(tainted_ranges, None) + result_list.append(funcode(self[:0], True)) + except UnicodeDecodeError as e: + offset = -len(incr_coder.getstate()[0]) + raise UnicodeDecodeError(e.args[0], self, i + e.args[2] + offset, i + e.args[3] + offset, *e.args[4:]) + except UnicodeEncodeError: + funcode(self) + result = empty.join(result_list) + taint_pyobject_with_ranges(result, new_ranges) + return result + + +def decode_aspect(orig_function, flag_added_args, *args, **kwargs): + if orig_function and (not flag_added_args or not args): + # This patch is unexpected, so we fallback + # to executing the original function + return orig_function(*args, **kwargs) + + self = args[0] + args = args[(flag_added_args or 1) :] + # Assume we call decode method of the first argument + result = self.decode(*args, **kwargs) + + if not is_pyobject_tainted(self) or not isinstance(self, bytes): + return result + + try: + codec = args[0] if args else "utf-8" + inc_dec = codecs.getincrementaldecoder(codec)(**kwargs) + return incremental_translation(self, inc_dec, inc_dec.decode, "") + except Exception as e: + _set_iast_error_metric("IAST propagation error. decode_aspect. {}".format(e)) + return result + + +def encode_aspect(orig_function, flag_added_args, *args, **kwargs): + if orig_function and (not flag_added_args or not args): + # This patch is unexpected, so we fallback + # to executing the original function + return orig_function(*args, **kwargs) + + self = args[0] + args = args[(flag_added_args or 1) :] + # Assume we call encode method of the first argument + result = self.encode(*args, **kwargs) + + if not is_pyobject_tainted(self) or not isinstance(self, str): + return result + + try: + codec = args[0] if args else "utf-8" + inc_enc = codecs.getincrementalencoder(codec)(**kwargs) + return incremental_translation(self, inc_enc, inc_enc.encode, b"") + except Exception as e: + _set_iast_error_metric("IAST propagation error. encode_aspect. {}".format(e)) + return result + + +def upper_aspect( + orig_function, flag_added_args, *args, **kwargs +): # type: (Optional[Callable], int, Any, Any) -> TEXT_TYPE + if orig_function and (not isinstance(orig_function, BuiltinFunctionType) or not args): + if flag_added_args > 0: + args = args[flag_added_args:] + return orig_function(*args, **kwargs) + + candidate_text = args[0] + args = args[flag_added_args:] + if not isinstance(candidate_text, TEXT_TYPES): + return candidate_text.upper(*args, **kwargs) + + try: + return common_replace("upper", candidate_text, *args, **kwargs) + except Exception as e: + _set_iast_error_metric("IAST propagation error. upper_aspect. {}".format(e)) + return candidate_text.upper(*args, **kwargs) + + +def lower_aspect( + orig_function, flag_added_args, *args, **kwargs +): # type: (Optional[Callable], int, Any, Any) -> TEXT_TYPE + if orig_function and (not isinstance(orig_function, BuiltinFunctionType) or not args): + if flag_added_args > 0: + args = args[flag_added_args:] + return orig_function(*args, **kwargs) + + candidate_text = args[0] + args = args[flag_added_args:] + if not isinstance(candidate_text, TEXT_TYPES): + return candidate_text.lower(*args, **kwargs) + + try: + return common_replace("lower", candidate_text, *args, **kwargs) + except Exception as e: + _set_iast_error_metric("IAST propagation error. lower_aspect. {}".format(e)) + return candidate_text.lower(*args, **kwargs) + + +def _distribute_ranges_and_escape( + split_elements, # type: List[Optional[TEXT_TYPE]] + len_separator, # type: int + ranges, # type: Tuple[TaintRange, ...] +): # type: (...) -> List[Optional[TEXT_TYPE]] + # FIXME: converts to set, and then to list again, probably to remove + # duplicates. This should be removed once the ranges values on the + # taint dictionary are stored in a set. + range_set = set(ranges) + range_set_remove = range_set.remove + formatted_elements = [] # type: List[Optional[TEXT_TYPE]] + formatted_elements_append = formatted_elements.append + element_start = 0 + extra = 0 + + for element in split_elements: + if element is None: + extra += len_separator + continue + # DEV: If this if is True, it means that the element is part of bytes/bytearray + if isinstance(element, int): + len_element = 1 + else: + len_element = len(element) + element_end = element_start + len_element + new_ranges = {} # type: Dict[TaintRange, TaintRange] + + for taint_range in ranges: + if (taint_range.start + taint_range.length) <= (element_start + extra): + try: + range_set_remove(taint_range) + except KeyError: + # If the range appears twice in ranges, it will be + # iterated twice, but it's only once in range_set, + # raising KeyError at remove, so it can be safely ignored + pass + continue + + if taint_range.start > element_end: + continue + + start = max(taint_range.start, element_start) + end = min((taint_range.start + taint_range.length), element_end) + if end <= start: + continue + + if end - element_start < 1: + continue + + new_range = TaintRange( + start=start - element_start, + length=end - element_start, + source=taint_range.source, + ) + new_ranges[new_range] = taint_range + + element_ranges = tuple(new_ranges.keys()) + # DEV: If this if is True, it means that the element is part of bytes/bytearray + if isinstance(element, int): + element_new_id = new_pyobject_id(bytes([element])) + else: + element_new_id = new_pyobject_id(element) + set_ranges(element_new_id, element_ranges) + + formatted_elements_append( + as_formatted_evidence( + element_new_id, + element_ranges, + TagMappingMode.Mapper_Replace, + new_ranges, + ) + ) + + element_start = element_end + len_separator + return formatted_elements + + +def aspect_replace_api( + candidate_text, old_value, new_value, count, orig_result +): # type: (Any, Any, Any, int, Any) -> str + ranges_orig, candidate_text_ranges = are_all_text_all_ranges(candidate_text, (old_value, new_value)) + if not ranges_orig: # Ranges in args/kwargs are checked + return orig_result + + empty = b"" if isinstance(candidate_text, (bytes, bytearray)) else "" # type: TEXT_TYPE + + if old_value: + elements = candidate_text.split(old_value, count) # type: Sequence[TEXT_TYPE] + else: + if count == -1: + elements = ( + [ + empty, + ] + + ( + list(candidate_text) if isinstance(candidate_text, str) else [bytes([x]) for x in candidate_text] # type: ignore + ) + + [ + empty, + ] + ) + else: + if isinstance(candidate_text, str): + elements = ( + [ + empty, + ] + + list(candidate_text[: count - 1]) + + [candidate_text[count - 1 :]] + ) + if len(elements) == count and elements[-1] != "": + elements.append(empty) + else: + elements = ( + [ + empty, + ] + + [bytes([x]) for x in candidate_text[: count - 1]] + + [bytes([x for x in candidate_text[count - 1 :]])] + ) + if len(elements) == count and elements[-1] != b"": + elements.append(empty) + i = 0 + new_elements = [] # type: List[Optional[TEXT_TYPE]] + new_elements_append = new_elements.append + + # if new value is blank, _distribute_ranges_and_escape function doesn't + # understand what is the replacement to move the ranges. + # In the other hand, Split function splits a string and the occurrence is + # in the first or last position, split adds ''. IE: + # 'XabcX'.split('X') -> ['', 'abc', ''] + # We add "None" in the old position and _distribute_ranges_and_escape + # knows that this is the position of a old value and move len(old_value) + # positions of the range + if new_value in ("", b""): + len_elements = len(elements) + for element in elements: + if i == 0 and element in ("", b""): + new_elements_append(None) + i += 1 + continue + if i + 1 == len_elements and element in ("", b""): + new_elements_append(None) + continue + + new_elements_append(element) + + if count < 0 and i + 1 < len(elements): + new_elements_append(None) + elif i >= count and i + 1 < len(elements): + new_elements_append(old_value) + i += 1 + else: + new_elements = elements # type: ignore + + if candidate_text_ranges: + new_elements = _distribute_ranges_and_escape( + new_elements, + len(old_value), + candidate_text_ranges, + ) + + result_formatted = as_formatted_evidence(new_value, tag_mapping_function=TagMappingMode.Mapper).join(new_elements) + + result = _convert_escaped_text_to_tainted_text( + result_formatted, + ranges_orig=ranges_orig, + ) + + return result + + +def replace_aspect( + orig_function, flag_added_args, *args, **kwargs +): # type: (Optional[Callable], int, Any, Any) -> TEXT_TYPE + if orig_function and (not isinstance(orig_function, BuiltinFunctionType) or not args): + if flag_added_args > 0: + args = args[flag_added_args:] + return orig_function(*args, **kwargs) + + candidate_text = args[0] + args = args[flag_added_args:] + orig_result = candidate_text.replace(*args, **kwargs) + if not isinstance(candidate_text, TEXT_TYPES): + return orig_result + + ### + # Optimization: if we're not going to replace, just return the original string + count = parse_params(2, "count", -1, *args, **kwargs) + if count == 0: + return candidate_text + ### + try: + old_value = parse_params(0, "old_value", None, *args, **kwargs) + new_value = parse_params(1, "new_value", None, *args, **kwargs) + + if old_value is None or new_value is None: + return orig_result + + if old_value not in candidate_text or old_value == new_value: + return candidate_text + + if orig_result in ("", b"", bytearray(b"")): + return orig_result + + if count < -1: + count = -1 + + aspect_result = aspect_replace_api(candidate_text, old_value, new_value, count, orig_result) + + if aspect_result != orig_result: + return orig_result + + return aspect_result + except Exception as e: + _set_iast_error_metric("IAST propagation error. replace_aspect. {}".format(e)) + return orig_result + + +def swapcase_aspect( + orig_function, flag_added_args, *args, **kwargs +): # type: (Optional[Callable], int, Any, Any) -> TEXT_TYPE + if orig_function and (not isinstance(orig_function, BuiltinFunctionType) or not args): + if flag_added_args > 0: + args = args[flag_added_args:] + return orig_function(*args, **kwargs) + + candidate_text = args[0] + args = args[flag_added_args:] + if not isinstance(candidate_text, TEXT_TYPES): + return candidate_text.swapcase(*args, **kwargs) + try: + return common_replace("swapcase", candidate_text, *args, **kwargs) + except Exception as e: + _set_iast_error_metric("IAST propagation error. swapcase_aspect. {}".format(e)) + return candidate_text.swapcase(*args, **kwargs) + + +def title_aspect( + orig_function, flag_added_args, *args, **kwargs +): # type: (Optional[Callable], int, Any, Any) -> TEXT_TYPE + if orig_function and (not isinstance(orig_function, BuiltinFunctionType) or not args): + if flag_added_args > 0: + args = args[flag_added_args:] + return orig_function(*args, **kwargs) + + candidate_text = args[0] + args = args[flag_added_args:] + if not isinstance(candidate_text, TEXT_TYPES): + return candidate_text.title(*args, **kwargs) + try: + return common_replace("title", candidate_text, *args, **kwargs) + except Exception as e: + _set_iast_error_metric("IAST propagation error. title_aspect. {}".format(e)) + return candidate_text.title(*args, **kwargs) + + +def capitalize_aspect( + orig_function, flag_added_args, *args, **kwargs +): # type: (Optional[Callable], int, Any, Any) -> TEXT_TYPE + if orig_function and (not isinstance(orig_function, BuiltinFunctionType) or not args): + if flag_added_args > 0: + args = args[flag_added_args:] + return orig_function(*args, **kwargs) + + candidate_text = args[0] + args = args[flag_added_args:] + if not isinstance(candidate_text, TEXT_TYPES): + return candidate_text.capitalize(*args, **kwargs) + + try: + return common_replace("capitalize", candidate_text, *args, **kwargs) + except Exception as e: + _set_iast_error_metric("IAST propagation error. capitalize_aspect. {}".format(e)) + return candidate_text.capitalize(*args, **kwargs) + + +def casefold_aspect( + orig_function, flag_added_args, *args, **kwargs +): # type: (Optional[Callable], int, Any, Any) -> TEXT_TYPE + if orig_function: + if not isinstance(orig_function, BuiltinFunctionType) or not args: + if flag_added_args > 0: + args = args[flag_added_args:] + return orig_function(*args, **kwargs) + else: + orig_function = getattr(args[0], "casefold", None) + + if orig_function and orig_function.__qualname__ not in ("str.casefold", "bytes.casefold", "bytearray.casefold"): + if flag_added_args > 0: + args = args[flag_added_args:] + return orig_function(*args, **kwargs) + + candidate_text = args[0] + args = args[flag_added_args:] + if not isinstance(candidate_text, TEXT_TYPES): + if flag_added_args > 0: + args = args[flag_added_args:] + return candidate_text.casefold(*args, **kwargs) + try: + return common_replace("casefold", candidate_text, *args, **kwargs) + except Exception as e: + _set_iast_error_metric("IAST propagation error. casefold_aspect. {}".format(e)) + return candidate_text.casefold(*args, **kwargs) # type: ignore[union-attr] + + +def translate_aspect( + orig_function, flag_added_args, *args, **kwargs +): # type: (Optional[Callable], int, Any, Any) -> TEXT_TYPE + if orig_function and (not isinstance(orig_function, BuiltinFunctionType) or not args): + if flag_added_args > 0: + args = args[flag_added_args:] + return orig_function(*args, **kwargs) + + candidate_text = args[0] + args = args[flag_added_args:] + if not isinstance(candidate_text, TEXT_TYPES): + return candidate_text.translate(*args, **kwargs) + try: + return common_replace("translate", candidate_text, *args, **kwargs) + except Exception as e: + _set_iast_error_metric("IAST propagation error. translate_aspect. {}".format(e)) + return candidate_text.translate(*args, **kwargs) + + +def empty_func(*args, **kwargs): + pass diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/clean.sh b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/clean.sh new file mode 100644 index 0000000..c86b825 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_tracking/clean.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -exu +cd -- "$(dirname -- "${BASH_SOURCE[0]}")" || exit + +rm -rf CMakeFiles/ CMakeCache.txt Makefile cmake_install.cmake __pycache__/ .cmake *.cbp Testing +rm -rf cmake-build-debug cmake-build-default cmake-build-tests \ No newline at end of file diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_utils.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_utils.py new file mode 100644 index 0000000..5beb9c4 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_taint_utils.py @@ -0,0 +1,548 @@ +#!/usr/bin/env python3 +from collections import abc +from typing import Any +from typing import List +from typing import Optional +from typing import Union + +from ddtrace.internal.logger import get_logger +from ddtrace.settings.asm import config as asm_config + + +DBAPI_INTEGRATIONS = ("sqlite", "psycopg", "mysql", "mariadb") +DBAPI_PREFIXES = ("django-",) + +log = get_logger(__name__) + + +# Non Lazy Tainting + + +# don't use dataclass that can create circular import problems here +# @dataclasses.dataclass +class _DeepTaintCommand: + def __init__( + self, + pre: bool, + source_key: str, + obj: Any, + store_struct: Union[list, dict], + key: Optional[List[str]] = None, + struct: Optional[Union[list, dict]] = None, + is_key: bool = False, + ): + self.pre = pre + self.source_key = source_key + self.obj = obj + self.store_struct = store_struct + self.key = key + self.struct = struct + self.is_key = is_key + + def store(self, value): + if isinstance(self.store_struct, list): + self.store_struct.append(value) + elif isinstance(self.store_struct, dict): + key = self.key[0] if self.key else None + self.store_struct[key] = value + else: + raise ValueError(f"store_struct of type {type(self.store_struct)}") + + def post(self, struct): + return self.__class__(False, self.source_key, self.obj, self.store_struct, self.key, struct) + + +def build_new_tainted_object_from_generic_object(initial_object, wanted_object): + if initial_object.__class__ is wanted_object.__class__: + return wanted_object + #### custom tailor actions + wanted_type = initial_object.__class__.__module__, initial_object.__class__.__name__ + if wanted_type == ("builtins", "tuple"): + return tuple(wanted_object) + # Django + if wanted_type == ("django.http.request", "HttpHeaders"): + res = initial_object.__class__({}) + res._store = {k.lower(): (k, v) for k, v in wanted_object.items()} + return res + if wanted_type == ("django.http.request", "QueryDict"): + res = initial_object.__class__() + for k, v in wanted_object.items(): + dict.__setitem__(res, k, v) + return res + # Flask 2+ + if wanted_type == ("werkzeug.datastructures.structures", "ImmutableMultiDict"): + return initial_object.__class__(wanted_object) + # Flask 1 + if wanted_type == ("werkzeug.datastructures", "ImmutableMultiDict"): + return initial_object.__class__(wanted_object) + + # if the class is unknown, return the initial object + # this may prevent interned string to be tainted but ensure + # that normal behavior of the code is not changed. + return initial_object + + +def taint_structure(main_obj, source_key, source_value, override_pyobject_tainted=False): + """taint any structured object + use a queue like mechanism to avoid recursion + Best effort: mutate mutable structures and rebuild immutable ones if possible + """ + from ._taint_tracking import is_pyobject_tainted + from ._taint_tracking import taint_pyobject + + if not main_obj: + return main_obj + + main_res = [] + try: + # fifo contains tuple (pre/post:bool, source key, object to taint, + # key to use, struct to store result, struct to ) + stack = [_DeepTaintCommand(True, source_key, main_obj, main_res)] + while stack: + command = stack.pop() + if command.pre: # first processing of the object + if not command.obj: + command.store(command.obj) + elif isinstance(command.obj, (str, bytes, bytearray)): + if override_pyobject_tainted or not is_pyobject_tainted(command.obj): + new_obj = taint_pyobject( + pyobject=command.obj, + source_name=command.source_key, + source_value=command.obj, + source_origin=source_key if command.is_key else source_value, + ) + command.store(new_obj) + else: + command.store(command.obj) + elif isinstance(command.obj, abc.Mapping): + res = {} + stack.append(command.post(res)) + # use dict fondamental enumeration if possible to bypass any override of custom classes + iterable = dict.items(command.obj) if isinstance(command.obj, dict) else command.obj.items() + todo = [] + for k, v in list(iterable): + key_store = [] + todo.append(_DeepTaintCommand(True, k, k, key_store, is_key=True)) + todo.append(_DeepTaintCommand(True, k, v, res, key_store)) + stack.extend(reversed(todo)) + elif isinstance(command.obj, abc.Sequence): + res = [] + stack.append(command.post(res)) + todo = [_DeepTaintCommand(True, command.source_key, v, res) for v in command.obj] + stack.extend(reversed(todo)) + else: + command.store(command.obj) + else: + command.store(build_new_tainted_object_from_generic_object(command.obj, command.struct)) + except BaseException: + log.debug("taint_structure error", exc_info=True) + pass + finally: + return main_res[0] if main_res else main_obj + + +# Lazy Tainting + + +def _is_tainted_struct(obj): + return hasattr(obj, "_origins") + + +class LazyTaintList: + """ + Encapsulate a list to lazily taint all content on any depth + It will appear and act as the original list except for some additional private fields + """ + + def __init__(self, original_list, origins=(0, 0), override_pyobject_tainted=False, source_name="[]"): + self._obj = original_list._obj if _is_tainted_struct(original_list) else original_list + self._origins = origins + self._origin_value = origins[1] + self._override_pyobject_tainted = override_pyobject_tainted + self._source_name = source_name + + def _taint(self, value): + if value: + if isinstance(value, (str, bytes, bytearray)): + from ._taint_tracking import is_pyobject_tainted + from ._taint_tracking import taint_pyobject + + if not is_pyobject_tainted(value) or self._override_pyobject_tainted: + try: + # TODO: migrate this part to shift ranges instead of creating a new one + value = taint_pyobject( + pyobject=value, + source_name=self._source_name, + source_value=value, + source_origin=self._origin_value, + ) + except SystemError: + # TODO: Find the root cause for + # SystemError: NULL object passed to Py_BuildValue + log.debug("IAST SystemError while tainting value: %s", value, exc_info=True) + except Exception: + log.debug("IAST Unexpected exception while tainting value", exc_info=True) + elif isinstance(value, abc.Mapping) and not _is_tainted_struct(value): + value = LazyTaintDict( + value, origins=self._origins, override_pyobject_tainted=self._override_pyobject_tainted + ) + elif isinstance(value, abc.Sequence) and not _is_tainted_struct(value): + value = LazyTaintList( + value, + origins=self._origins, + override_pyobject_tainted=self._override_pyobject_tainted, + source_name=self._source_name, + ) + return value + + def __add__(self, other): + if _is_tainted_struct(other): + other = other._obj + return LazyTaintList( + self._obj + other, + origins=self._origins, + override_pyobject_tainted=self._override_pyobject_tainted, + source_name=self._source_name, + ) + + @property # type: ignore + def __class__(self): + return list + + def __contains__(self, item): + return item in self._obj + + def __delitem__(self, key): + del self._obj[key] + + def __eq__(self, other): + if _is_tainted_struct(other): + other = other._obj + return self._obj == other + + def __ge__(self, other): + if _is_tainted_struct(other): + other = other._obj + return self._obj >= other + + def __getitem__(self, key): + return self._taint(self._obj[key]) + + def __gt__(self, other): + if _is_tainted_struct(other): + other = other._obj + return self._obj > other + + def __iadd__(self, other): + if _is_tainted_struct(other): + other = other._obj + self._obj += other + + def __imul__(self, other): + self._obj *= other + + def __iter__(self): + return (self[i] for i in range(len(self._obj))) + + def __le__(self, other): + if _is_tainted_struct(other): + other = other._obj + return self._obj <= other + + def __len__(self): + return len(self._obj) + + def __lt__(self, other): + if _is_tainted_struct(other): + other = other._obj + return self._obj < other + + def __mul__(self, other): + return LazyTaintList( + self._obj * other, + origins=self._origins, + override_pyobject_tainted=self._override_pyobject_tainted, + source_name=self._source_name, + ) + + def __ne__(self, other): + if _is_tainted_struct(other): + other = other._obj + return self._obj != other + + def __repr__(self): + return repr(self._obj) + + def __reversed__(self): + return (self[i] for i in reversed(range(len(self._obj)))) + + def __setitem__(self, key, value): + self._obj[key] = value + + def __str__(self): + return str(self._obj) + + def append(self, item): + self._obj.append(item) + + def clear(self): + # TODO: stop tainting in this case + self._obj.clear() + + def copy(self): + return LazyTaintList( + self._obj.copy(), + origins=self._origins, + override_pyobject_tainted=self._override_pyobject_tainted, + source_name=self._source_name, + ) + + def count(self, *args): + return self._obj.count(*args) + + def extend(self, *args): + return self._obj.extend(*args) + + def index(self, *args): + return self._obj.index(*args) + + def insert(self, *args): + return self._obj.insert(*args) + + def pop(self, *args): + return self._taint(self._obj.pop(*args)) + + def remove(self, *args): + return self._obj.remove(*args) + + def reverse(self, *args): + return self._obj.reverse(*args) + + def sort(self, *args): + return self._obj.sort(*args) + + # psycopg2 support + def __conform__(self, proto): + return self + + def getquoted(self) -> bytes: + import psycopg2.extensions as ext + + value = ext.adapt(self._obj).getquoted() + value = self._taint(value) + return value + + +class LazyTaintDict: + def __init__(self, original_dict, origins=(0, 0), override_pyobject_tainted=False): + self._obj = original_dict + self._origins = origins + self._origin_key = origins[0] + self._origin_value = origins[1] + self._override_pyobject_tainted = override_pyobject_tainted + + def _taint(self, value, key, origin=None): + if origin is None: + origin = self._origin_value + if value: + if isinstance(value, (str, bytes, bytearray)): + from ._taint_tracking import is_pyobject_tainted + from ._taint_tracking import taint_pyobject + + if not is_pyobject_tainted(value) or self._override_pyobject_tainted: + try: + # TODO: migrate this part to shift ranges instead of creating a new one + value = taint_pyobject( + pyobject=value, + source_name=key, + source_value=value, + source_origin=origin, + ) + except SystemError: + # TODO: Find the root cause for + # SystemError: NULL object passed to Py_BuildValue + log.debug("IAST SystemError while tainting value: %s", value, exc_info=True) + except Exception: + log.debug("IAST Unexpected exception while tainting value", exc_info=True) + elif isinstance(value, abc.Mapping) and not _is_tainted_struct(value): + value = LazyTaintDict( + value, origins=self._origins, override_pyobject_tainted=self._override_pyobject_tainted + ) + elif isinstance(value, abc.Sequence) and not _is_tainted_struct(value): + value = LazyTaintList( + value, + origins=self._origins, + override_pyobject_tainted=self._override_pyobject_tainted, + source_name=key, + ) + return value + + @property # type: ignore + def __class__(self): + return dict + + def __contains__(self, item): + return item in self._obj + + def __delitem__(self, key): + del self._obj[key] + + def __eq__(self, other): + if _is_tainted_struct(other): + other = other._obj + return self._obj == other + + def __ge__(self, other): + if _is_tainted_struct(other): + other = other._obj + return self._obj >= other + + def __getitem__(self, key): + return self._taint(self._obj[key], key) + + def __gt__(self, other): + if _is_tainted_struct(other): + other = other._obj + return self._obj > other + + def __ior__(self, other): + if _is_tainted_struct(other): + other = other._obj + self._obj |= other + + def __iter__(self): + return iter(self.keys()) + + def __le__(self, other): + if _is_tainted_struct(other): + other = other._obj + return self._obj <= other + + def __len__(self): + return len(self._obj) + + def __lt__(self, other): + if _is_tainted_struct(other): + other = other._obj + return self._obj < other + + def __ne__(self, other): + if _is_tainted_struct(other): + other = other._obj + return self._obj != other + + def __or__(self, other): + if _is_tainted_struct(other): + other = other._obj + return LazyTaintDict( + self._obj | other, + origins=self._origins, + override_pyobject_tainted=self._override_pyobject_tainted, + ) + + def __repr__(self): + return repr(self._obj) + + def __reversed__(self): + return reversed(self.keys()) + + def __setitem__(self, key, value): + self._obj[key] = value + + def __str__(self): + return str(self._obj) + + def clear(self): + # TODO: stop tainting in this case + self._obj.clear() + + def copy(self): + return LazyTaintDict( + self._obj.copy(), + origins=self._origins, + override_pyobject_tainted=self._override_pyobject_tainted, + ) + + @classmethod + def fromkeys(cls, *args): + return dict.fromkeys(*args) + + def get(self, key, default=None): + observer = object() + res = self._obj.get(key, observer) + if res is observer: + return default + return self._taint(res, key) + + def items(self): + for k in self.keys(): + yield (k, self[k]) + + def keys(self): + for k in self._obj.keys(): + yield self._taint(k, k, self._origin_key) + + def pop(self, *args): + return self._taint(self._obj.pop(*args), "pop") + + def popitem(self): + k, v = self._obj.popitem() + return self._taint(k, k), self._taint(v, k) + + def remove(self, *args): + return self._obj.remove(*args) + + def setdefault(self, *args): + return self._taint(self._obj.setdefault(*args), args[0]) + + def update(self, *args, **kargs): + self._obj.update(*args, **kargs) + + def values(self): + for _, v in self.items(): + yield v + + # Django Query Dict support + def getlist(self, key, default=None): + return self._taint(self._obj.getlist(key, default=default), key) + + def setlist(self, key, list_): + self._obj.setlist(key, list_) + + def appendlist(self, key, item): + self._obj.appendlist(key, item) + + def setlistdefault(self, key, default_list=None): + return self._taint(self._obj.setlistdefault(key, default_list=default_list), key) + + def lists(self): + return self._taint(self._obj.lists(), self._origin_value) + + def dict(self): + return self + + def urlencode(self, safe=None): + return self._taint(self._obj.urlencode(safe=safe), self._origin_value) + + +def supported_dbapi_integration(integration_name): + return integration_name in DBAPI_INTEGRATIONS or integration_name.startswith(DBAPI_PREFIXES) + + +def check_tainted_args(args, kwargs, tracer, integration_name, method): + if supported_dbapi_integration(integration_name) and method.__name__ == "execute": + from ._taint_tracking import is_pyobject_tainted + + return len(args) and args[0] and is_pyobject_tainted(args[0]) + + return False + + +if asm_config._iast_lazy_taint: + # redefining taint_structure to use lazy object if required + + def taint_structure(main_obj, source_key, source_value, override_pyobject_tainted=False): # noqa: F811 + if isinstance(main_obj, abc.Mapping): + return LazyTaintDict(main_obj, source_key, source_value, override_pyobject_tainted) + elif isinstance(main_obj, abc.Sequence): + return LazyTaintList(main_obj, source_key, source_value, override_pyobject_tainted) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_utils.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_utils.py new file mode 100644 index 0000000..3f994b8 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/_utils.py @@ -0,0 +1,124 @@ +import json +import re +import string +import sys +from typing import TYPE_CHECKING # noqa:F401 + +import attr + +from ddtrace.internal.logger import get_logger +from ddtrace.settings.asm import config as asm_config + + +if TYPE_CHECKING: + from typing import Any # noqa:F401 + from typing import List # noqa:F401 + from typing import Set # noqa:F401 + from typing import Tuple # noqa:F401 + + +def _is_python_version_supported(): # type: () -> bool + # IAST supports Python versions 3.6 to 3.12 + return (3, 6, 0) <= sys.version_info < (3, 13, 0) + + +def _is_iast_enabled(): + if not asm_config._iast_enabled: + return False + + if not _is_python_version_supported(): + log = get_logger(__name__) + log.info("IAST is not compatible with the current Python version") + return False + + return True + + +# Used to cache the compiled regular expression +_SOURCE_NAME_SCRUB = None +_SOURCE_VALUE_SCRUB = None + + +def _has_to_scrub(s): # type: (str) -> bool + global _SOURCE_NAME_SCRUB + global _SOURCE_VALUE_SCRUB + + if _SOURCE_NAME_SCRUB is None: + _SOURCE_NAME_SCRUB = re.compile(asm_config._iast_redaction_name_pattern) + _SOURCE_VALUE_SCRUB = re.compile(asm_config._iast_redaction_value_pattern) + + return _SOURCE_NAME_SCRUB.match(s) is not None or _SOURCE_VALUE_SCRUB.match(s) is not None + + +_REPLACEMENTS = string.ascii_letters +_LEN_REPLACEMENTS = len(_REPLACEMENTS) + + +def _scrub(s, has_range=False): # type: (str, bool) -> str + if has_range: + return "".join([_REPLACEMENTS[i % _LEN_REPLACEMENTS] for i in range(len(s))]) + return "*" * len(s) + + +def _is_evidence_value_parts(value): # type: (Any) -> bool + return isinstance(value, (set, list)) + + +def _scrub_get_tokens_positions(text, tokens): + # type: (str, Set[str]) -> List[Tuple[int, int]] + token_positions = [] + + for token in tokens: + position = text.find(token) + if position != -1: + token_positions.append((position, position + len(token))) + + token_positions.sort() + return token_positions + + +def _iast_report_to_str(data): + from ._taint_tracking import OriginType + from ._taint_tracking import origin_to_str + + class OriginTypeEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, OriginType): + # if the obj is uuid, we simply return the value of uuid + return origin_to_str(obj) + return json.JSONEncoder.default(self, obj) + + return json.dumps(attr.asdict(data, filter=lambda attr, x: x is not None), cls=OriginTypeEncoder) + + +def _get_patched_code(module_path, module_name): # type: (str, str) -> str + """ + Print the patched code to stdout, for debugging purposes. + """ + import astunparse + + from ddtrace.appsec._iast._ast.ast_patching import get_encoding + from ddtrace.appsec._iast._ast.ast_patching import visit_ast + + with open(module_path, "r", encoding=get_encoding(module_path)) as source_file: + source_text = source_file.read() + + new_source = visit_ast( + source_text, + module_path, + module_name=module_name, + ) + + # If no modifications are done, + # visit_ast returns None + if not new_source: + return "" + + new_code = astunparse.unparse(new_source) + return new_code + + +if __name__ == "__main__": + MODULE_PATH = sys.argv[1] + MODULE_NAME = sys.argv[2] + print(_get_patched_code(MODULE_PATH, MODULE_NAME)) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/constants.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/constants.py new file mode 100644 index 0000000..bd9e739 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/constants.py @@ -0,0 +1,89 @@ +from typing import Any +from typing import Dict + + +VULN_INSECURE_HASHING_TYPE = "WEAK_HASH" +VULN_WEAK_CIPHER_TYPE = "WEAK_CIPHER" +VULN_SQL_INJECTION = "SQL_INJECTION" +VULN_PATH_TRAVERSAL = "PATH_TRAVERSAL" +VULN_WEAK_RANDOMNESS = "WEAK_RANDOMNESS" +VULN_INSECURE_COOKIE = "INSECURE_COOKIE" +VULN_NO_HTTPONLY_COOKIE = "NO_HTTPONLY_COOKIE" +VULN_NO_SAMESITE_COOKIE = "NO_SAMESITE_COOKIE" +VULN_CMDI = "COMMAND_INJECTION" +VULN_SSRF = "SSRF" + +VULNERABILITY_TOKEN_TYPE = Dict[int, Dict[str, Any]] + +EVIDENCE_ALGORITHM_TYPE = "ALGORITHM" +EVIDENCE_SQL_INJECTION = "SQL_INJECTION" +EVIDENCE_PATH_TRAVERSAL = "PATH_TRAVERSAL" +EVIDENCE_WEAK_RANDOMNESS = "WEAK_RANDOMNESS" +EVIDENCE_COOKIE = "COOKIE" +EVIDENCE_CMDI = "COMMAND" +EVIDENCE_SSRF = "SSRF" + +MD5_DEF = "md5" +SHA1_DEF = "sha1" + +DES_DEF = "des" +BLOWFISH_DEF = "blowfish" +RC2_DEF = "rc2" +RC4_DEF = "rc4" +IDEA_DEF = "idea" + +DD_IAST_TELEMETRY_VERBOSITY = "DD_IAST_TELEMETRY_VERBOSITY" + +DEFAULT_WEAK_HASH_ALGORITHMS = {MD5_DEF, SHA1_DEF} + +DEFAULT_WEAK_CIPHER_ALGORITHMS = {DES_DEF, BLOWFISH_DEF, RC2_DEF, RC4_DEF, IDEA_DEF} + +DEFAULT_WEAK_RANDOMNESS_FUNCTIONS = { + "random", + "randint", + "randrange", + "choice", + "shuffle", + "betavariate", + "gammavariate", + "expovariate", + "choices", + "gauss", + "uniform", + "lognormvariate", + "normalvariate", + "paretovariate", + "sample", + "triangular", + "vonmisesvariate", + "weibullvariate", + "randbytes", +} + +DEFAULT_PATH_TRAVERSAL_FUNCTIONS = { + "glob": {"glob"}, + "os": { + "mkdir", + "remove", + "rename", + "rmdir", + "listdir", + }, + "pickle": {"load"}, + "_pickle": {"load"}, + "posix": { + "mkdir", + "remove", + "rename", + "rmdir", + "listdir", + }, + "shutil": { + "copy", + "copytree", + "move", + "rmtree", + }, + "tarfile": {"open"}, + "zipfile": {"ZipFile"}, +} diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/processor.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/processor.py new file mode 100644 index 0000000..4109b8e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/processor.py @@ -0,0 +1,95 @@ +from typing import TYPE_CHECKING + +import attr + +from ddtrace.appsec._constants import APPSEC +from ddtrace.appsec._constants import IAST +from ddtrace.constants import ORIGIN_KEY +from ddtrace.ext import SpanTypes +from ddtrace.internal import core +from ddtrace.internal.logger import get_logger +from ddtrace.internal.processor import SpanProcessor + +from .._trace_utils import _asm_manual_keep +from . import oce +from ._metrics import _set_metric_iast_request_tainted +from ._metrics import _set_span_tag_iast_executed_sink +from ._metrics import _set_span_tag_iast_request_tainted +from ._utils import _iast_report_to_str +from ._utils import _is_iast_enabled + + +if TYPE_CHECKING: # pragma: no cover + from typing import Optional # noqa:F401 + + from ddtrace.span import Span # noqa:F401 + +log = get_logger(__name__) + + +@attr.s(eq=False) +class AppSecIastSpanProcessor(SpanProcessor): + @staticmethod + def is_span_analyzed(span=None): + # type: (Optional[Span]) -> bool + if span is None: + from ddtrace import tracer + + span = tracer.current_root_span() + + if span and span.span_type == SpanTypes.WEB and core.get_item(IAST.REQUEST_IAST_ENABLED, span=span): + return True + return False + + def on_span_start(self, span): + # type: (Span) -> None + if span.span_type != SpanTypes.WEB: + return + + if not _is_iast_enabled(): + return + + request_iast_enabled = False + if oce.acquire_request(span): + from ._taint_tracking import create_context + + request_iast_enabled = True + create_context() + + core.set_item(IAST.REQUEST_IAST_ENABLED, request_iast_enabled, span=span) + + def on_span_finish(self, span): + # type: (Span) -> None + """Report reported vulnerabilities. + + Span Tags: + - `_dd.iast.json`: Only when one or more vulnerabilities have been detected will we include the custom tag. + - `_dd.iast.enabled`: Set to 1 when IAST is enabled in a request. If a request is disabled + (e.g. by sampling), then it is not set. + """ + if span.span_type != SpanTypes.WEB: + return + + if not core.get_item(IAST.REQUEST_IAST_ENABLED, span=span): + span.set_metric(IAST.ENABLED, 0.0) + return + + from ._taint_tracking import reset_context # noqa: F401 + + span.set_metric(IAST.ENABLED, 1.0) + + data = core.get_item(IAST.CONTEXT_KEY, span=span) + + if data: + span.set_tag_str(IAST.JSON, _iast_report_to_str(data)) + _asm_manual_keep(span) + + _set_metric_iast_request_tainted() + _set_span_tag_iast_request_tainted(span) + _set_span_tag_iast_executed_sink(span) + reset_context() + + if span.get_tag(ORIGIN_KEY) is None: + span.set_tag_str(ORIGIN_KEY, APPSEC.ORIGIN_VALUE) + + oce.release_request() diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/reporter.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/reporter.py new file mode 100644 index 0000000..5a95aa1 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/reporter.py @@ -0,0 +1,87 @@ +from functools import reduce +import json +import operator +import os +from typing import TYPE_CHECKING +from typing import List +from typing import Set +import zlib + +import attr + + +if TYPE_CHECKING: + import Any # noqa:F401 + import Dict # noqa:F401 + import Optional # noqa:F401 + + +def _only_if_true(value): + return value if value else None + + +@attr.s(eq=False, hash=False) +class Evidence(object): + value = attr.ib(type=str, default=None) # type: Optional[str] + pattern = attr.ib(type=str, default=None) # type: Optional[str] + valueParts = attr.ib(type=list, default=None) # type: Optional[List[Dict[str, Any]]] + redacted = attr.ib(type=bool, default=False, converter=_only_if_true) # type: bool + + def _valueParts_hash(self): + if not self.valueParts: + return + + _hash = 0 + for part in self.valueParts: + json_str = json.dumps(part, sort_keys=True) + part_hash = zlib.crc32(json_str.encode()) + _hash ^= part_hash + + return _hash + + def __hash__(self): + return hash((self.value, self.pattern, self._valueParts_hash(), self.redacted)) + + def __eq__(self, other): + return ( + self.value == other.value + and self.pattern == other.pattern + and self._valueParts_hash() == other._valueParts_hash() + and self.redacted == other.redacted + ) + + +@attr.s(eq=True, hash=True) +class Location(object): + spanId = attr.ib(type=int, eq=False, hash=False, repr=False) # type: int + path = attr.ib(type=str, default=None) # type: Optional[str] + line = attr.ib(type=int, default=None) # type: Optional[int] + + +@attr.s(eq=True, hash=True) +class Vulnerability(object): + type = attr.ib(type=str) # type: str + evidence = attr.ib(type=Evidence, repr=False) # type: Evidence + location = attr.ib(type=Location, hash="PYTEST_CURRENT_TEST" in os.environ) # type: Location + hash = attr.ib(init=False, eq=False, hash=False, repr=False) # type: int + + def __attrs_post_init__(self): + self.hash = zlib.crc32(repr(self).encode()) + + +@attr.s(eq=True, hash=True) +class Source(object): + origin = attr.ib(type=str) # type: str + name = attr.ib(type=str) # type: str + redacted = attr.ib(type=bool, default=False, converter=_only_if_true) # type: bool + value = attr.ib(type=str, default=None) # type: Optional[str] + pattern = attr.ib(type=str, default=None) # type: Optional[str] + + +@attr.s(eq=False, hash=False) +class IastSpanReporter(object): + sources = attr.ib(type=List[Source], factory=list) # type: List[Source] + vulnerabilities = attr.ib(type=Set[Vulnerability], factory=set) # type: Set[Vulnerability] + + def __hash__(self): + return reduce(operator.xor, (hash(obj) for obj in set(self.sources) | self.vulnerabilities)) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/__init__.py new file mode 100644 index 0000000..e7c8787 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/__init__.py @@ -0,0 +1,8 @@ +from .ast_taint import ast_function +from .path_traversal import open_path_traversal + + +__all__ = [ + "open_path_traversal", + "ast_function", +] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/_base.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/_base.py new file mode 100644 index 0000000..8327bd8 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/_base.py @@ -0,0 +1,314 @@ +import os +import time +from typing import TYPE_CHECKING # noqa:F401 +from typing import cast # noqa:F401 + +from ddtrace import tracer +from ddtrace.appsec._constants import IAST +from ddtrace.internal import core +from ddtrace.internal.logger import get_logger +from ddtrace.internal.utils.cache import LFUCache +from ddtrace.settings.asm import config as asm_config + +from ..._deduplications import deduplication +from .._overhead_control_engine import Operation +from .._stacktrace import get_info_frame +from .._utils import _has_to_scrub +from .._utils import _is_evidence_value_parts +from .._utils import _scrub +from ..processor import AppSecIastSpanProcessor +from ..reporter import Evidence +from ..reporter import IastSpanReporter +from ..reporter import Location +from ..reporter import Source +from ..reporter import Vulnerability + + +if TYPE_CHECKING: # pragma: no cover + from typing import Any # noqa:F401 + from typing import Callable # noqa:F401 + from typing import Dict # noqa:F401 + from typing import List # noqa:F401 + from typing import Optional # noqa:F401 + from typing import Set # noqa:F401 + from typing import Text # noqa:F401 + from typing import Union # noqa:F401 + +log = get_logger(__name__) + +CWD = os.path.abspath(os.getcwd()) + + +class taint_sink_deduplication(deduplication): + def __call__(self, *args, **kwargs): + # we skip 0, 1 and last position because its the cls, span and sources respectively + result = None + if self.is_deduplication_enabled() is False: + result = self.func(*args, **kwargs) + else: + raw_log_hash = hash("".join([str(arg) for arg in args[2:-1]])) + last_reported_timestamp = self.get_last_time_reported(raw_log_hash) + if time.time() > last_reported_timestamp: + result = self.func(*args, **kwargs) + self.reported_logs[raw_log_hash] = time.time() + self._time_lapse + return result + + +def _check_positions_contained(needle, container): + needle_start, needle_end = needle + container_start, container_end = container + + return ( + (container_start <= needle_start < container_end) + or (container_start < needle_end <= container_end) + or (needle_start <= container_start < needle_end) + or (needle_start < container_end <= needle_end) + ) + + +class VulnerabilityBase(Operation): + vulnerability_type = "" + evidence_type = "" + _redacted_report_cache = LFUCache() + + @classmethod + def _reset_cache(cls): + cls._redacted_report_cache.clear() + + @classmethod + def wrap(cls, func): + # type: (Callable) -> Callable + def wrapper(wrapped, instance, args, kwargs): + # type: (Callable, Any, Any, Any) -> Any + """Get the current root Span and attach it to the wrapped function. We need the span to report the + vulnerability and update the context with the report information. + """ + if AppSecIastSpanProcessor.is_span_analyzed() and cls.has_quota(): + return func(wrapped, instance, args, kwargs) + else: + log.debug("IAST: no vulnerability quota to analyze more sink points") + return wrapped(*args, **kwargs) + + return wrapper + + @classmethod + @taint_sink_deduplication + def _prepare_report(cls, span, vulnerability_type, evidence, file_name, line_number, sources): + report = core.get_item(IAST.CONTEXT_KEY, span=span) + if report: + report.vulnerabilities.add( + Vulnerability( + type=vulnerability_type, + evidence=evidence, + location=Location(path=file_name, line=line_number, spanId=span.span_id), + ) + ) + + else: + report = IastSpanReporter( + vulnerabilities={ + Vulnerability( + type=vulnerability_type, + evidence=evidence, + location=Location(path=file_name, line=line_number, spanId=span.span_id), + ) + } + ) + if sources: + + def cast_value(value): + if isinstance(value, (bytes, bytearray)): + value_decoded = value.decode("utf-8") + else: + value_decoded = value + return value_decoded + + report.sources = [Source(origin=x.origin, name=x.name, value=cast_value(x.value)) for x in sources] + + redacted_report = cls._redacted_report_cache.get( + hash(report), lambda x: cls._redact_report(cast(IastSpanReporter, report)) + ) + core.set_item(IAST.CONTEXT_KEY, redacted_report, span=span) + + return True + + @classmethod + def report(cls, evidence_value="", sources=None): + # type: (Union[Text|List[Dict[str, Any]]], Optional[List[Source]]) -> None + """Build a IastSpanReporter instance to report it in the `AppSecIastSpanProcessor` as a string JSON""" + + if cls.acquire_quota(): + if not tracer or not hasattr(tracer, "current_root_span"): + log.debug( + "[IAST] VulnerabilityReporter is trying to report an evidence, " + "but not tracer or tracer has no root span" + ) + return None + + span = tracer.current_root_span() + if not span: + log.debug( + "[IAST] VulnerabilityReporter. No root span in the current execution. Skipping IAST taint sink." + ) + return None + + file_name = None + line_number = None + + skip_location = getattr(cls, "skip_location", False) + if not skip_location: + frame_info = get_info_frame(CWD) + if not frame_info: + return None + + file_name, line_number = frame_info + + # Remove CWD prefix + if file_name.startswith(CWD): + file_name = os.path.relpath(file_name, start=CWD) + + if not cls.is_not_reported(file_name, line_number): + return + + if _is_evidence_value_parts(evidence_value): + evidence = Evidence(valueParts=evidence_value) + # Evidence is a string in weak cipher, weak hash and weak randomness + elif isinstance(evidence_value, (str, bytes, bytearray)): + evidence = Evidence(value=evidence_value) + else: + log.debug("Unexpected evidence_value type: %s", type(evidence_value)) + evidence = Evidence(value="") + + result = cls._prepare_report(span, cls.vulnerability_type, evidence, file_name, line_number, sources) + # If result is None that's mean deduplication raises and no vulnerability wasn't reported, with that, + # we need to restore the quota + if not result: + cls.increment_quota() + + @classmethod + def _extract_sensitive_tokens(cls, report): + # type: (Dict[Vulnerability, str]) -> Dict[int, Dict[str, Any]] + log.debug("Base class VulnerabilityBase._extract_sensitive_tokens called") + return {} + + @classmethod + def _get_vulnerability_text(cls, vulnerability): + if vulnerability and vulnerability.evidence.value is not None: + return vulnerability.evidence.value + + if vulnerability.evidence.valueParts is not None: + return "".join( + [ + (part.get("value", "") if type(part) is not str else part) + for part in vulnerability.evidence.valueParts + ] + ) + + return "" + + @classmethod + def replace_tokens( + cls, + vuln, + vulns_to_tokens, + has_range=False, + ): + ret = vuln.evidence.value + replaced = False + + for token in vulns_to_tokens[hash(vuln)]["tokens"]: + ret = ret.replace(token, _scrub(token, has_range)) + replaced = True + + return ret, replaced + + @classmethod + def _redact_report(cls, report): # type: (IastSpanReporter) -> IastSpanReporter + if not asm_config._iast_redaction_enabled: + return report + + # See if there is a match on either any of the sources or value parts of the report + found = False + + for source in report.sources: + # Join them so we only run the regexps once for each source + joined_fields = "%s%s" % (source.name, source.value) + if _has_to_scrub(joined_fields): + found = True + break + + vulns_to_text = {} + + if not found: + # Check the evidence's value/s + for vuln in report.vulnerabilities: + vulnerability_text = cls._get_vulnerability_text(vuln) + if _has_to_scrub(vulnerability_text): + vulns_to_text[vuln] = vulnerability_text + found = True + break + + if not found: + return report + + if not vulns_to_text: + vulns_to_text = {vuln: cls._get_vulnerability_text(vuln) for vuln in report.vulnerabilities} + + # If we're here, some potentially sensitive information was found, we delegate on + # the specific subclass the task of extracting the variable tokens (e.g. literals inside + # quotes for SQL Injection). Note that by just having one potentially sensitive match + # we need to then scrub all the tokens, thus why we do it in two steps instead of one + vulns_to_tokens = cls._extract_sensitive_tokens(vulns_to_text) + + if not vulns_to_tokens: + return report + + all_tokens = set() # type: Set[str] + for _, value_dict in vulns_to_tokens.items(): + all_tokens.update(value_dict["tokens"]) + + # Iterate over all the sources, if one of the tokens match it, redact it + for source in report.sources: + if source.name in all_tokens or source.value in all_tokens: + source.pattern = _scrub(source.value, has_range=True) + source.redacted = True + source.value = None + + # Same for all the evidence values + for vuln in report.vulnerabilities: + # Use the initial hash directly as iteration key since the vuln itself will change + vuln_hash = hash(vuln) + if vuln.evidence.value is not None: + pattern, replaced = cls.replace_tokens(vuln, vulns_to_tokens, hasattr(vuln.evidence.value, "source")) + if replaced: + vuln.evidence.pattern = pattern + vuln.evidence.redacted = True + vuln.evidence.value = None + elif vuln.evidence.valueParts is not None: + idx = 0 + for part in vuln.evidence.valueParts: + value = part["value"] + part_len = len(value) + part_start = idx + part_end = idx + part_len + pattern_list = [] + + for positions in vulns_to_tokens[vuln_hash]["token_positions"]: + if _check_positions_contained(positions, (part_start, part_end)): + part_scrub_start = max(positions[0] - idx, 0) + part_scrub_end = positions[1] - idx + to_scrub = value[part_scrub_start:part_scrub_end] + scrubbed = _scrub(to_scrub, "source" in part) + pattern_list.append(value[:part_scrub_start] + scrubbed + value[part_scrub_end:]) + part["redacted"] = True + else: + pattern_list.append(value[part_start:part_end]) + continue + + if "redacted" in part: + part["pattern"] = "".join(pattern_list) + del part["value"] + + idx += part_len + + return report diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/ast_taint.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/ast_taint.py new file mode 100644 index 0000000..af8f59b --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/ast_taint.py @@ -0,0 +1,47 @@ +from typing import TYPE_CHECKING # noqa:F401 + +from ..._constants import IAST_SPAN_TAGS +from .._metrics import _set_metric_iast_executed_sink +from .._metrics import increment_iast_span_metric +from ..constants import DEFAULT_PATH_TRAVERSAL_FUNCTIONS +from ..constants import DEFAULT_WEAK_RANDOMNESS_FUNCTIONS +from .path_traversal import check_and_report_path_traversal +from .weak_randomness import WeakRandomness + + +if TYPE_CHECKING: + from typing import Any # noqa:F401 + from typing import Callable # noqa:F401 + + +def ast_function( + func, # type: Callable + flag_added_args, # type: Any + *args, # type: Any + **kwargs, # type: Any +): # type: (...) -> Any + instance = getattr(func, "__self__", None) + func_name = getattr(func, "__name__", None) + cls_name = "" + if instance is not None and func_name: + try: + cls_name = instance.__class__.__name__ + except AttributeError: + pass + + if flag_added_args > 0: + args = args[flag_added_args:] + + if ( + instance.__class__.__module__ == "random" + and cls_name == "Random" + and func_name in DEFAULT_WEAK_RANDOMNESS_FUNCTIONS + ): + # Weak, run the analyzer + increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, WeakRandomness.vulnerability_type) + _set_metric_iast_executed_sink(WeakRandomness.vulnerability_type) + WeakRandomness.report(evidence_value=cls_name + "." + func_name) + elif hasattr(func, "__module__") and DEFAULT_PATH_TRAVERSAL_FUNCTIONS.get(func.__module__): + if func_name in DEFAULT_PATH_TRAVERSAL_FUNCTIONS[func.__module__]: + check_and_report_path_traversal(*args, **kwargs) + return func(*args, **kwargs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/command_injection.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/command_injection.py new file mode 100644 index 0000000..b792adc --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/command_injection.py @@ -0,0 +1,254 @@ +import os +import re +import subprocess # nosec +from typing import TYPE_CHECKING # noqa:F401 +from typing import List # noqa:F401 +from typing import Set # noqa:F401 +from typing import Union # noqa:F401 + +from ddtrace.contrib import trace_utils +from ddtrace.internal.logger import get_logger +from ddtrace.settings.asm import config as asm_config + +from ..._constants import IAST_SPAN_TAGS +from .. import oce +from .._metrics import increment_iast_span_metric +from .._utils import _has_to_scrub +from .._utils import _scrub +from .._utils import _scrub_get_tokens_positions +from ..constants import EVIDENCE_CMDI +from ..constants import VULN_CMDI +from ._base import VulnerabilityBase +from ._base import _check_positions_contained + + +if TYPE_CHECKING: + from typing import Any # noqa:F401 + from typing import Dict # noqa:F401 + + from ..reporter import IastSpanReporter # noqa:F401 + from ..reporter import Vulnerability # noqa:F401 + + +log = get_logger(__name__) + +_INSIDE_QUOTES_REGEXP = re.compile(r"^(?:\s*(?:sudo|doas)\s+)?\b\S+\b\s*(.*)") + + +def get_version(): + # type: () -> str + return "" + + +def patch(): + if not asm_config._iast_enabled: + return + + if not getattr(os, "_datadog_cmdi_patch", False): + trace_utils.wrap(os, "system", _iast_cmdi_ossystem) + + # all os.spawn* variants eventually use this one: + trace_utils.wrap(os, "_spawnvef", _iast_cmdi_osspawn) + + if not getattr(subprocess, "_datadog_cmdi_patch", False): + trace_utils.wrap(subprocess, "Popen.__init__", _iast_cmdi_subprocess_init) + + os._datadog_cmdi_patch = True + subprocess._datadog_cmdi_patch = True + + +def unpatch(): + # type: () -> None + trace_utils.unwrap(os, "system") + trace_utils.unwrap(os, "_spawnvef") + trace_utils.unwrap(subprocess.Popen, "__init__") + + os._datadog_cmdi_patch = False # type: ignore[attr-defined] + subprocess._datadog_cmdi_patch = False # type: ignore[attr-defined] + + +def _iast_cmdi_ossystem(wrapped, instance, args, kwargs): + _iast_report_cmdi(args[0]) + return wrapped(*args, **kwargs) + + +def _iast_cmdi_osspawn(wrapped, instance, args, kwargs): + mode, file, func_args, _, _ = args + _iast_report_cmdi(func_args) + + return wrapped(*args, **kwargs) + + +def _iast_cmdi_subprocess_init(wrapped, instance, args, kwargs): + cmd_args = args[0] if len(args) else kwargs["args"] + _iast_report_cmdi(cmd_args) + + return wrapped(*args, **kwargs) + + +@oce.register +class CommandInjection(VulnerabilityBase): + vulnerability_type = VULN_CMDI + evidence_type = EVIDENCE_CMDI + + @classmethod + def report(cls, evidence_value=None, sources=None): + if isinstance(evidence_value, (str, bytes, bytearray)): + from .._taint_tracking import taint_ranges_as_evidence_info + + evidence_value, sources = taint_ranges_as_evidence_info(evidence_value) + super(CommandInjection, cls).report(evidence_value=evidence_value, sources=sources) + + @classmethod + def _extract_sensitive_tokens(cls, vulns_to_text): + # type: (Dict[Vulnerability, str]) -> Dict[int, Dict[str, Any]] + ret = {} # type: Dict[int, Dict[str, Any]] + for vuln, text in vulns_to_text.items(): + vuln_hash = hash(vuln) + ret[vuln_hash] = { + "tokens": set(_INSIDE_QUOTES_REGEXP.findall(text)), + } + ret[vuln_hash]["token_positions"] = _scrub_get_tokens_positions(text, ret[vuln_hash]["tokens"]) + + return ret + + @classmethod + def replace_tokens( + cls, + vuln, + vulns_to_tokens, + has_range=False, + ): + ret = vuln.evidence.value + replaced = False + + for token in vulns_to_tokens[hash(vuln)]["tokens"]: + ret = ret.replace(token, "") + replaced = True + + return ret, replaced + + @classmethod + def _redact_report(cls, report): # type: (IastSpanReporter) -> IastSpanReporter + if not asm_config._iast_redaction_enabled: + return report + + # See if there is a match on either any of the sources or value parts of the report + found = False + + for source in report.sources: + # Join them so we only run the regexps once for each source + joined_fields = "%s%s" % (source.name, source.value) + if _has_to_scrub(joined_fields): + found = True + break + + vulns_to_text = {} + + if not found: + # Check the evidence's value/s + for vuln in report.vulnerabilities: + vulnerability_text = cls._get_vulnerability_text(vuln) + if _has_to_scrub(vulnerability_text) or _INSIDE_QUOTES_REGEXP.match(vulnerability_text): + vulns_to_text[vuln] = vulnerability_text + found = True + break + + if not found: + return report + + if not vulns_to_text: + vulns_to_text = {vuln: cls._get_vulnerability_text(vuln) for vuln in report.vulnerabilities} + + # If we're here, some potentially sensitive information was found, we delegate on + # the specific subclass the task of extracting the variable tokens (e.g. literals inside + # quotes for SQL Injection). Note that by just having one potentially sensitive match + # we need to then scrub all the tokens, thus why we do it in two steps instead of one + vulns_to_tokens = cls._extract_sensitive_tokens(vulns_to_text) + + if not vulns_to_tokens: + return report + + all_tokens = set() # type: Set[str] + for _, value_dict in vulns_to_tokens.items(): + all_tokens.update(value_dict["tokens"]) + + # Iterate over all the sources, if one of the tokens match it, redact it + for source in report.sources: + if source.name in "".join(all_tokens) or source.value in "".join(all_tokens): + source.pattern = _scrub(source.value, has_range=True) + source.redacted = True + source.value = None + + # Same for all the evidence values + try: + for vuln in report.vulnerabilities: + # Use the initial hash directly as iteration key since the vuln itself will change + vuln_hash = hash(vuln) + if vuln.evidence.value is not None: + pattern, replaced = cls.replace_tokens( + vuln, vulns_to_tokens, hasattr(vuln.evidence.value, "source") + ) + if replaced: + vuln.evidence.pattern = pattern + vuln.evidence.redacted = True + vuln.evidence.value = None + elif vuln.evidence.valueParts is not None: + idx = 0 + new_value_parts = [] + for part in vuln.evidence.valueParts: + value = part["value"] + part_len = len(value) + part_start = idx + part_end = idx + part_len + pattern_list = [] + + for positions in vulns_to_tokens[vuln_hash]["token_positions"]: + if _check_positions_contained(positions, (part_start, part_end)): + part_scrub_start = max(positions[0] - idx, 0) + part_scrub_end = positions[1] - idx + pattern_list.append(value[:part_scrub_start] + "" + value[part_scrub_end:]) + if part.get("source", False) is not False: + source = report.sources[part["source"]] + if source.redacted: + part["redacted"] = source.redacted + part["pattern"] = source.pattern + del part["value"] + new_value_parts.append(part) + break + else: + part["value"] = "".join(pattern_list) + new_value_parts.append(part) + new_value_parts.append({"redacted": True}) + break + else: + new_value_parts.append(part) + pattern_list.append(value[part_start:part_end]) + break + + idx += part_len + vuln.evidence.valueParts = new_value_parts + except (ValueError, KeyError): + log.debug("an error occurred while redacting cmdi", exc_info=True) + return report + + +def _iast_report_cmdi(shell_args): + # type: (Union[str, List[str]]) -> None + report_cmdi = "" + from .._metrics import _set_metric_iast_executed_sink + from .._taint_tracking import is_pyobject_tainted + from .._taint_tracking.aspects import join_aspect + + if isinstance(shell_args, (list, tuple)): + for arg in shell_args: + if is_pyobject_tainted(arg): + report_cmdi = join_aspect(" ".join, 1, " ", shell_args) + break + elif is_pyobject_tainted(shell_args): + report_cmdi = shell_args + + if report_cmdi: + increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, CommandInjection.vulnerability_type) + _set_metric_iast_executed_sink(CommandInjection.vulnerability_type) + CommandInjection.report(evidence_value=report_cmdi) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py new file mode 100644 index 0000000..bb81477 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py @@ -0,0 +1,72 @@ +from typing import TYPE_CHECKING # noqa:F401 + +from ..._constants import IAST_SPAN_TAGS +from .. import oce +from .._metrics import _set_metric_iast_executed_sink +from .._metrics import increment_iast_span_metric +from ..constants import EVIDENCE_COOKIE +from ..constants import VULN_INSECURE_COOKIE +from ..constants import VULN_NO_HTTPONLY_COOKIE +from ..constants import VULN_NO_SAMESITE_COOKIE +from ..taint_sinks._base import VulnerabilityBase + + +if TYPE_CHECKING: + from typing import Dict # noqa:F401 + from typing import Optional # noqa:F401 + + +@oce.register +class InsecureCookie(VulnerabilityBase): + vulnerability_type = VULN_INSECURE_COOKIE + evidence_type = EVIDENCE_COOKIE + scrub_evidence = False + skip_location = True + + +@oce.register +class NoHttpOnlyCookie(VulnerabilityBase): + vulnerability_type = VULN_NO_HTTPONLY_COOKIE + evidence_type = EVIDENCE_COOKIE + skip_location = True + + +@oce.register +class NoSameSite(VulnerabilityBase): + vulnerability_type = VULN_NO_SAMESITE_COOKIE + evidence_type = EVIDENCE_COOKIE + skip_location = True + + +def asm_check_cookies(cookies): # type: (Optional[Dict[str, str]]) -> None + if not cookies: + return + + for cookie_key, cookie_value in cookies.items(): + lvalue = cookie_value.lower().replace(" ", "") + + if ";secure" not in lvalue: + increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, InsecureCookie.vulnerability_type) + _set_metric_iast_executed_sink(InsecureCookie.vulnerability_type) + InsecureCookie.report(evidence_value=cookie_key) + + if ";httponly" not in lvalue: + increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, NoHttpOnlyCookie.vulnerability_type) + _set_metric_iast_executed_sink(NoHttpOnlyCookie.vulnerability_type) + NoHttpOnlyCookie.report(evidence_value=cookie_key) + + if ";samesite=" in lvalue: + ss_tokens = lvalue.split(";samesite=") + if len(ss_tokens) == 0: + report_samesite = True + elif ss_tokens[1].startswith("strict") or ss_tokens[1].startswith("lax"): + report_samesite = False + else: + report_samesite = True + else: + report_samesite = True + + if report_samesite: + increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, NoSameSite.vulnerability_type) + _set_metric_iast_executed_sink(NoSameSite.vulnerability_type) + NoSameSite.report(evidence_value=cookie_key) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/path_traversal.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/path_traversal.py new file mode 100644 index 0000000..c761800 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/path_traversal.py @@ -0,0 +1,70 @@ +from typing import Any + +from ddtrace.internal.logger import get_logger + +from ..._constants import IAST_SPAN_TAGS +from .. import oce +from .._metrics import _set_metric_iast_instrumented_sink +from .._metrics import increment_iast_span_metric +from .._patch import set_and_check_module_is_patched +from .._patch import set_module_unpatched +from ..constants import EVIDENCE_PATH_TRAVERSAL +from ..constants import VULN_PATH_TRAVERSAL +from ..processor import AppSecIastSpanProcessor +from ._base import VulnerabilityBase + + +log = get_logger(__name__) + + +@oce.register +class PathTraversal(VulnerabilityBase): + vulnerability_type = VULN_PATH_TRAVERSAL + evidence_type = EVIDENCE_PATH_TRAVERSAL + + @classmethod + def report(cls, evidence_value=None, sources=None): + if isinstance(evidence_value, (str, bytes, bytearray)): + from .._taint_tracking import taint_ranges_as_evidence_info + + evidence_value, sources = taint_ranges_as_evidence_info(evidence_value) + super(PathTraversal, cls).report(evidence_value=evidence_value, sources=sources) + + +def get_version(): + # type: () -> str + return "" + + +def unpatch_iast(): + # type: () -> None + set_module_unpatched("builtins", default_attr="_datadog_path_traversal_patch") + + +def patch(): + # type: () -> None + """Wrap functions which interact with file system.""" + if not set_and_check_module_is_patched("builtins", default_attr="_datadog_path_traversal_patch"): + return + _set_metric_iast_instrumented_sink(VULN_PATH_TRAVERSAL) + + +def check_and_report_path_traversal(*args: Any, **kwargs: Any) -> None: + if AppSecIastSpanProcessor.is_span_analyzed() and PathTraversal.has_quota(): + try: + from .._metrics import _set_metric_iast_executed_sink + from .._taint_tracking import is_pyobject_tainted + + increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, PathTraversal.vulnerability_type) + _set_metric_iast_executed_sink(PathTraversal.vulnerability_type) + if is_pyobject_tainted(args[0]): + PathTraversal.report(evidence_value=args[0]) + except Exception: + log.debug("Unexpected exception while reporting vulnerability", exc_info=True) + else: + log.debug("IAST: no vulnerability quota to analyze more sink points") + + +def open_path_traversal(*args, **kwargs): + check_and_report_path_traversal(*args, **kwargs) + return open(*args, **kwargs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/sql_injection.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/sql_injection.py new file mode 100644 index 0000000..314bcd6 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/sql_injection.py @@ -0,0 +1,44 @@ +import re +from typing import TYPE_CHECKING # noqa:F401 + +from .. import oce +from .._taint_tracking import taint_ranges_as_evidence_info +from .._utils import _scrub_get_tokens_positions +from ..constants import EVIDENCE_SQL_INJECTION +from ..constants import VULN_SQL_INJECTION +from ._base import VulnerabilityBase + + +if TYPE_CHECKING: + from typing import Any # noqa:F401 + from typing import Dict # noqa:F401 + + from .reporter import Vulnerability # noqa:F401 + + +_INSIDE_QUOTES_REGEXP = re.compile(r'["\']([^"\']*?)["\']') + + +@oce.register +class SqlInjection(VulnerabilityBase): + vulnerability_type = VULN_SQL_INJECTION + evidence_type = EVIDENCE_SQL_INJECTION + + @classmethod + def report(cls, evidence_value=None, sources=None): + if isinstance(evidence_value, (str, bytes, bytearray)): + evidence_value, sources = taint_ranges_as_evidence_info(evidence_value) + super(SqlInjection, cls).report(evidence_value=evidence_value, sources=sources) + + @classmethod + def _extract_sensitive_tokens(cls, vulns_to_text): + # type: (Dict[Vulnerability, str]) -> Dict[int, Dict[str, Any]] + ret = {} # type: Dict[int, Dict[str, Any]] + for vuln, text in vulns_to_text.items(): + vuln_hash = hash(vuln) + ret[vuln_hash] = { + "tokens": set(_INSIDE_QUOTES_REGEXP.findall(text)), + } + ret[vuln_hash]["token_positions"] = _scrub_get_tokens_positions(text, ret[vuln_hash]["tokens"]) + + return ret diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/ssrf.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/ssrf.py new file mode 100644 index 0000000..5e8a502 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/ssrf.py @@ -0,0 +1,175 @@ +import re +from typing import Callable # noqa:F401 +from typing import Dict # noqa:F401 +from typing import Set # noqa:F401 + +from ddtrace.internal.logger import get_logger +from ddtrace.settings.asm import config as asm_config + +from ..._constants import IAST_SPAN_TAGS +from .. import oce +from .._metrics import increment_iast_span_metric +from .._taint_tracking import taint_ranges_as_evidence_info +from .._utils import _has_to_scrub +from .._utils import _scrub +from .._utils import _scrub_get_tokens_positions +from ..constants import EVIDENCE_SSRF +from ..constants import VULN_SSRF +from ..constants import VULNERABILITY_TOKEN_TYPE +from ..processor import AppSecIastSpanProcessor +from ..reporter import IastSpanReporter # noqa:F401 +from ..reporter import Vulnerability +from ._base import VulnerabilityBase +from ._base import _check_positions_contained + + +log = get_logger(__name__) + + +_AUTHORITY_REGEXP = re.compile(r"(?:\/\/([^:@\/]+)(?::([^@\/]+))?@).*") +_QUERY_FRAGMENT_REGEXP = re.compile(r"[?#&]([^=&;]+)=(?P[^?#&]+)") + + +@oce.register +class SSRF(VulnerabilityBase): + vulnerability_type = VULN_SSRF + evidence_type = EVIDENCE_SSRF + + @classmethod + def report(cls, evidence_value=None, sources=None): + if isinstance(evidence_value, (str, bytes, bytearray)): + evidence_value, sources = taint_ranges_as_evidence_info(evidence_value) + super(SSRF, cls).report(evidence_value=evidence_value, sources=sources) + + @classmethod + def _extract_sensitive_tokens(cls, vulns_to_text: Dict[Vulnerability, str]) -> VULNERABILITY_TOKEN_TYPE: + ret = {} # type: VULNERABILITY_TOKEN_TYPE + for vuln, text in vulns_to_text.items(): + vuln_hash = hash(vuln) + authority = [] + authority_found = _AUTHORITY_REGEXP.findall(text) + if authority_found: + authority = list(authority_found[0]) + query = [value for param, value in _QUERY_FRAGMENT_REGEXP.findall(text)] + ret[vuln_hash] = { + "tokens": set(authority + query), + } + ret[vuln_hash]["token_positions"] = _scrub_get_tokens_positions(text, ret[vuln_hash]["tokens"]) + + return ret + + @classmethod + def _redact_report(cls, report): # type: (IastSpanReporter) -> IastSpanReporter + if not asm_config._iast_redaction_enabled: + return report + + # See if there is a match on either any of the sources or value parts of the report + found = False + + for source in report.sources: + # Join them so we only run the regexps once for each source + joined_fields = "%s%s" % (source.name, source.value) + if _has_to_scrub(joined_fields): + found = True + break + + vulns_to_text = {} + + if not found: + # Check the evidence's value/s + for vuln in report.vulnerabilities: + vulnerability_text = cls._get_vulnerability_text(vuln) + if _has_to_scrub(vulnerability_text) or _AUTHORITY_REGEXP.match(vulnerability_text): + vulns_to_text[vuln] = vulnerability_text + found = True + break + + if not found: + return report + + if not vulns_to_text: + vulns_to_text = {vuln: cls._get_vulnerability_text(vuln) for vuln in report.vulnerabilities} + + # If we're here, some potentially sensitive information was found, we delegate on + # the specific subclass the task of extracting the variable tokens (e.g. literals inside + # quotes for SQL Injection). Note that by just having one potentially sensitive match + # we need to then scrub all the tokens, thus why we do it in two steps instead of one + vulns_to_tokens = cls._extract_sensitive_tokens(vulns_to_text) + + if not vulns_to_tokens: + return report + + all_tokens = set() # type: Set[str] + for _, value_dict in vulns_to_tokens.items(): + all_tokens.update(value_dict["tokens"]) + + # Iterate over all the sources, if one of the tokens match it, redact it + for source in report.sources: + if source.name in "".join(all_tokens) or source.value in "".join(all_tokens): + source.pattern = _scrub(source.value, has_range=True) + source.redacted = True + source.value = None + + # Same for all the evidence values + for vuln in report.vulnerabilities: + # Use the initial hash directly as iteration key since the vuln itself will change + vuln_hash = hash(vuln) + if vuln.evidence.value is not None: + pattern, replaced = cls.replace_tokens(vuln, vulns_to_tokens, hasattr(vuln.evidence.value, "source")) + if replaced: + vuln.evidence.pattern = pattern + vuln.evidence.redacted = True + vuln.evidence.value = None + elif vuln.evidence.valueParts is not None: + idx = 0 + new_value_parts = [] + for part in vuln.evidence.valueParts: + value = part["value"] + part_len = len(value) + part_start = idx + part_end = idx + part_len + pattern_list = [] + + for positions in vulns_to_tokens[vuln_hash]["token_positions"]: + if _check_positions_contained(positions, (part_start, part_end)): + part_scrub_start = max(positions[0] - idx, 0) + part_scrub_end = positions[1] - idx + pattern_list.append(value[:part_scrub_start] + "" + value[part_scrub_end:]) + if part.get("source", False) is not False: + source = report.sources[part["source"]] + if source.redacted: + part["redacted"] = source.redacted + part["pattern"] = source.pattern + del part["value"] + new_value_parts.append(part) + break + else: + part["value"] = "".join(pattern_list) + new_value_parts.append(part) + new_value_parts.append({"redacted": True}) + break + else: + new_value_parts.append(part) + pattern_list.append(value[part_start:part_end]) + break + + idx += part_len + vuln.evidence.valueParts = new_value_parts + return report + + +def _iast_report_ssrf(func: Callable, *args, **kwargs): + from .._metrics import _set_metric_iast_executed_sink + + report_ssrf = kwargs.get("url", False) + increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, SSRF.vulnerability_type) + _set_metric_iast_executed_sink(SSRF.vulnerability_type) + if report_ssrf: + if AppSecIastSpanProcessor.is_span_analyzed() and SSRF.has_quota(): + try: + from .._taint_tracking import is_pyobject_tainted + + if is_pyobject_tainted(report_ssrf): + SSRF.report(evidence_value=report_ssrf) + except Exception: + log.debug("Unexpected exception while reporting vulnerability", exc_info=True) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/weak_cipher.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/weak_cipher.py new file mode 100644 index 0000000..3199528 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/weak_cipher.py @@ -0,0 +1,166 @@ +import os +from typing import TYPE_CHECKING # noqa:F401 + +from ddtrace.internal.logger import get_logger + +from ..._constants import IAST_SPAN_TAGS +from .. import oce +from .._metrics import _set_metric_iast_executed_sink +from .._metrics import _set_metric_iast_instrumented_sink +from .._metrics import increment_iast_span_metric +from .._patch import set_and_check_module_is_patched +from .._patch import set_module_unpatched +from .._patch import try_unwrap +from .._patch import try_wrap_function_wrapper +from ..constants import BLOWFISH_DEF +from ..constants import DEFAULT_WEAK_CIPHER_ALGORITHMS +from ..constants import DES_DEF +from ..constants import EVIDENCE_ALGORITHM_TYPE +from ..constants import RC2_DEF +from ..constants import RC4_DEF +from ..constants import VULN_WEAK_CIPHER_TYPE +from ._base import VulnerabilityBase + + +if TYPE_CHECKING: # pragma: no cover + from typing import Any # noqa:F401 + from typing import Callable # noqa:F401 + from typing import Set # noqa:F401 + +log = get_logger(__name__) + + +def get_weak_cipher_algorithms(): + # type: () -> Set + CONFIGURED_WEAK_CIPHER_ALGORITHMS = None + DD_IAST_WEAK_CIPHER_ALGORITHMS = os.getenv("DD_IAST_WEAK_CIPHER_ALGORITHMS") + if DD_IAST_WEAK_CIPHER_ALGORITHMS: + CONFIGURED_WEAK_CIPHER_ALGORITHMS = set( + algo.strip() for algo in DD_IAST_WEAK_CIPHER_ALGORITHMS.lower().split(",") + ) + return CONFIGURED_WEAK_CIPHER_ALGORITHMS or DEFAULT_WEAK_CIPHER_ALGORITHMS + + +@oce.register +class WeakCipher(VulnerabilityBase): + vulnerability_type = VULN_WEAK_CIPHER_TYPE + evidence_type = EVIDENCE_ALGORITHM_TYPE + + +def unpatch_iast(): + # type: () -> None + set_module_unpatched("Crypto", default_attr="_datadog_weak_cipher_patch") + set_module_unpatched("cryptography", default_attr="_datadog_weak_cipher_patch") + + try_unwrap("Crypto.Cipher.DES", "new") + try_unwrap("Crypto.Cipher.Blowfish", "new") + try_unwrap("Crypto.Cipher.ARC2", "new") + try_unwrap("Crypto.Cipher.ARC4", "ARC4Cipher.encrypt") + try_unwrap("Crypto.Cipher._mode_cbc", "CbcMode.encrypt") + try_unwrap("Crypto.Cipher._mode_cfb", "CfbMode.encrypt") + try_unwrap("Crypto.Cipher._mode_ofb", "OfbMode.encrypt") + try_unwrap("cryptography.hazmat.primitives.ciphers", "Cipher.encryptor") + + +def get_version(): + # type: () -> str + return "" + + +def patch(): + # type: () -> None + """Wrap hashing functions. + Weak hashing algorithms are those that have been proven to be of high risk, or even completely broken, + and thus are not fit for use. + """ + if not set_and_check_module_is_patched("Crypto", default_attr="_datadog_weak_cipher_patch"): + return + if not set_and_check_module_is_patched("cryptography", default_attr="_datadog_weak_cipher_patch"): + return + + weak_cipher_algorithms = get_weak_cipher_algorithms() + num_instrumented_sinks = 0 + # pycryptodome methods + if DES_DEF in weak_cipher_algorithms: + try_wrap_function_wrapper("Crypto.Cipher.DES", "new", wrapped_aux_des_function) + num_instrumented_sinks += 1 + if BLOWFISH_DEF in weak_cipher_algorithms: + try_wrap_function_wrapper("Crypto.Cipher.Blowfish", "new", wrapped_aux_blowfish_function) + num_instrumented_sinks += 1 + if RC2_DEF in weak_cipher_algorithms: + try_wrap_function_wrapper("Crypto.Cipher.ARC2", "new", wrapped_aux_rc2_function) + num_instrumented_sinks += 1 + if RC4_DEF in weak_cipher_algorithms: + try_wrap_function_wrapper("Crypto.Cipher.ARC4", "ARC4Cipher.encrypt", wrapped_rc4_function) + num_instrumented_sinks += 1 + + if weak_cipher_algorithms: + try_wrap_function_wrapper("Crypto.Cipher._mode_cbc", "CbcMode.encrypt", wrapped_function) + try_wrap_function_wrapper("Crypto.Cipher._mode_cfb", "CfbMode.encrypt", wrapped_function) + try_wrap_function_wrapper("Crypto.Cipher._mode_ecb", "EcbMode.encrypt", wrapped_function) + try_wrap_function_wrapper("Crypto.Cipher._mode_ofb", "OfbMode.encrypt", wrapped_function) + num_instrumented_sinks += 4 + + # cryptography methods + try_wrap_function_wrapper( + "cryptography.hazmat.primitives.ciphers", "Cipher.encryptor", wrapped_cryptography_function + ) + num_instrumented_sinks += 1 + + _set_metric_iast_instrumented_sink(VULN_WEAK_CIPHER_TYPE, num_instrumented_sinks) + + +def wrapped_aux_rc2_function(wrapped, instance, args, kwargs): + result = wrapped(*args, **kwargs) + result._dd_weakcipher_algorithm = "RC2" + return result + + +def wrapped_aux_des_function(wrapped, instance, args, kwargs): + result = wrapped(*args, **kwargs) + result._dd_weakcipher_algorithm = "DES" + return result + + +def wrapped_aux_blowfish_function(wrapped, instance, args, kwargs): + result = wrapped(*args, **kwargs) + result._dd_weakcipher_algorithm = "Blowfish" + return result + + +@WeakCipher.wrap +def wrapped_rc4_function(wrapped, instance, args, kwargs): + # type: (Callable, Any, Any, Any) -> Any + increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, WeakCipher.vulnerability_type) + _set_metric_iast_executed_sink(WeakCipher.vulnerability_type) + WeakCipher.report( + evidence_value="RC4", + ) + return wrapped(*args, **kwargs) + + +@WeakCipher.wrap +def wrapped_function(wrapped, instance, args, kwargs): + # type: (Callable, Any, Any, Any) -> Any + if hasattr(instance, "_dd_weakcipher_algorithm"): + evidence = instance._dd_weakcipher_algorithm + "_" + str(instance.__class__.__name__) + increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, WeakCipher.vulnerability_type) + _set_metric_iast_executed_sink(WeakCipher.vulnerability_type) + WeakCipher.report( + evidence_value=evidence, + ) + + return wrapped(*args, **kwargs) + + +@WeakCipher.wrap +def wrapped_cryptography_function(wrapped, instance, args, kwargs): + # type: (Callable, Any, Any, Any) -> Any + algorithm_name = instance.algorithm.name.lower() + if algorithm_name in get_weak_cipher_algorithms(): + increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, WeakCipher.vulnerability_type) + _set_metric_iast_executed_sink(WeakCipher.vulnerability_type) + WeakCipher.report( + evidence_value=algorithm_name, + ) + return wrapped(*args, **kwargs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/weak_hash.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/weak_hash.py new file mode 100644 index 0000000..9bebaf8 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/weak_hash.py @@ -0,0 +1,171 @@ +import os +import sys +from typing import TYPE_CHECKING # noqa:F401 + +from ddtrace.internal.logger import get_logger + +from ..._constants import IAST_SPAN_TAGS +from .. import oce +from .._metrics import _set_metric_iast_executed_sink +from .._metrics import _set_metric_iast_instrumented_sink +from .._metrics import increment_iast_span_metric +from .._patch import set_and_check_module_is_patched +from .._patch import set_module_unpatched +from .._patch import try_unwrap +from .._patch import try_wrap_function_wrapper +from ..constants import DEFAULT_WEAK_HASH_ALGORITHMS +from ..constants import EVIDENCE_ALGORITHM_TYPE +from ..constants import MD5_DEF +from ..constants import SHA1_DEF +from ..constants import VULN_INSECURE_HASHING_TYPE +from ._base import VulnerabilityBase + + +if TYPE_CHECKING: # pragma: no cover + from typing import Any # noqa:F401 + from typing import Callable # noqa:F401 + from typing import Set # noqa:F401 + +log = get_logger(__name__) + + +def get_weak_hash_algorithms(): + # type: () -> Set + CONFIGURED_WEAK_HASH_ALGORITHMS = None + DD_IAST_WEAK_HASH_ALGORITHMS = os.getenv("DD_IAST_WEAK_HASH_ALGORITHMS") + if DD_IAST_WEAK_HASH_ALGORITHMS: + CONFIGURED_WEAK_HASH_ALGORITHMS = set(algo.strip() for algo in DD_IAST_WEAK_HASH_ALGORITHMS.lower().split(",")) + + return CONFIGURED_WEAK_HASH_ALGORITHMS or DEFAULT_WEAK_HASH_ALGORITHMS + + +@oce.register +class WeakHash(VulnerabilityBase): + vulnerability_type = VULN_INSECURE_HASHING_TYPE + evidence_type = EVIDENCE_ALGORITHM_TYPE + + +def unpatch_iast(): + # type: () -> None + set_module_unpatched("hashlib", default_attr="_datadog_weak_hash_patch") + set_module_unpatched("Crypto", default_attr="_datadog_weak_hash_patch") + + if sys.version_info >= (3, 0, 0): + try_unwrap("_hashlib", "HASH.digest") + try_unwrap("_hashlib", "HASH.hexdigest") + try_unwrap(("_%s" % MD5_DEF), "MD5Type.digest") + try_unwrap(("_%s" % MD5_DEF), "MD5Type.hexdigest") + try_unwrap(("_%s" % SHA1_DEF), "SHA1Type.digest") + try_unwrap(("_%s" % SHA1_DEF), "SHA1Type.hexdigest") + else: + try_unwrap("hashlib", MD5_DEF) + try_unwrap("hashlib", SHA1_DEF) + try_unwrap("hashlib", "new") + + # pycryptodome methods + try_unwrap("Crypto.Hash.MD5", "MD5Hash.digest") + try_unwrap("Crypto.Hash.MD5", "MD5Hash.hexdigest") + try_unwrap("Crypto.Hash.SHA1", "SHA1Hash.digest") + try_unwrap("Crypto.Hash.SHA1", "SHA1Hash.hexdigest") + + +def get_version(): + # type: () -> str + return "" + + +def patch(): + # type: () -> None + """Wrap hashing functions. + Weak hashing algorithms are those that have been proven to be of high risk, or even completely broken, + and thus are not fit for use. + """ + + if not set_and_check_module_is_patched("hashlib", default_attr="_datadog_weak_hash_patch"): + return + + if not set_and_check_module_is_patched("Crypto", default_attr="_datadog_weak_hash_patch"): + return + + weak_hash_algorithms = get_weak_hash_algorithms() + num_instrumented_sinks = 0 + if sys.version_info >= (3, 0, 0): + try_wrap_function_wrapper("_hashlib", "HASH.digest", wrapped_digest_function) + try_wrap_function_wrapper("_hashlib", "HASH.hexdigest", wrapped_digest_function) + num_instrumented_sinks += 2 + if MD5_DEF in weak_hash_algorithms: + try_wrap_function_wrapper(("_%s" % MD5_DEF), "MD5Type.digest", wrapped_md5_function) + try_wrap_function_wrapper(("_%s" % MD5_DEF), "MD5Type.hexdigest", wrapped_md5_function) + num_instrumented_sinks += 2 + if SHA1_DEF in weak_hash_algorithms: + try_wrap_function_wrapper(("_%s" % SHA1_DEF), "SHA1Type.digest", wrapped_sha1_function) + try_wrap_function_wrapper(("_%s" % SHA1_DEF), "SHA1Type.hexdigest", wrapped_sha1_function) + num_instrumented_sinks += 2 + else: + if MD5_DEF in weak_hash_algorithms: + try_wrap_function_wrapper("hashlib", MD5_DEF, wrapped_md5_function) + num_instrumented_sinks += 1 + if SHA1_DEF in weak_hash_algorithms: + try_wrap_function_wrapper("hashlib", SHA1_DEF, wrapped_sha1_function) + num_instrumented_sinks += 1 + try_wrap_function_wrapper("hashlib", "new", wrapped_new_function) + num_instrumented_sinks += 1 + + # pycryptodome methods + if MD5_DEF in weak_hash_algorithms: + try_wrap_function_wrapper("Crypto.Hash.MD5", "MD5Hash.digest", wrapped_md5_function) + try_wrap_function_wrapper("Crypto.Hash.MD5", "MD5Hash.hexdigest", wrapped_md5_function) + num_instrumented_sinks += 2 + if SHA1_DEF in weak_hash_algorithms: + try_wrap_function_wrapper("Crypto.Hash.SHA1", "SHA1Hash.digest", wrapped_sha1_function) + try_wrap_function_wrapper("Crypto.Hash.SHA1", "SHA1Hash.hexdigest", wrapped_sha1_function) + num_instrumented_sinks += 2 + + if num_instrumented_sinks > 0: + _set_metric_iast_instrumented_sink(VULN_INSECURE_HASHING_TYPE, num_instrumented_sinks) + + +@WeakHash.wrap +def wrapped_digest_function(wrapped, instance, args, kwargs): + # type: (Callable, Any, Any, Any) -> Any + if instance.name.lower() in get_weak_hash_algorithms(): + increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, WeakHash.vulnerability_type) + _set_metric_iast_executed_sink(WeakHash.vulnerability_type) + WeakHash.report( + evidence_value=instance.name, + ) + return wrapped(*args, **kwargs) + + +@WeakHash.wrap +def wrapped_md5_function(wrapped, instance, args, kwargs): + # type: (Callable, Any, Any, Any) -> Any + return wrapped_function(wrapped, MD5_DEF, instance, args, kwargs) + + +@WeakHash.wrap +def wrapped_sha1_function(wrapped, instance, args, kwargs): + # type: (Callable, Any, Any, Any) -> Any + return wrapped_function(wrapped, SHA1_DEF, instance, args, kwargs) + + +@WeakHash.wrap +def wrapped_new_function(wrapped, instance, args, kwargs): + # type: (Callable, Any, Any, Any) -> Any + if args[0].lower() in get_weak_hash_algorithms(): + increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, WeakHash.vulnerability_type) + _set_metric_iast_executed_sink(WeakHash.vulnerability_type) + WeakHash.report( + evidence_value=args[0].lower(), + ) + return wrapped(*args, **kwargs) + + +def wrapped_function(wrapped, evidence, instance, args, kwargs): + # type: (Callable, str, Any, Any, Any) -> Any + increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, WeakHash.vulnerability_type) + _set_metric_iast_executed_sink(WeakHash.vulnerability_type) + WeakHash.report( + evidence_value=evidence, + ) + return wrapped(*args, **kwargs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/weak_randomness.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/weak_randomness.py new file mode 100644 index 0000000..bd7fc6e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_iast/taint_sinks/weak_randomness.py @@ -0,0 +1,14 @@ +from .. import oce +from ..constants import EVIDENCE_WEAK_RANDOMNESS +from ..constants import VULN_WEAK_RANDOMNESS +from ._base import VulnerabilityBase + + +@oce.register +class WeakRandomness(VulnerabilityBase): + vulnerability_type = VULN_WEAK_RANDOMNESS + evidence_type = EVIDENCE_WEAK_RANDOMNESS + + @classmethod + def report(cls, evidence_value=None, sources=None): + super(WeakRandomness, cls).report(evidence_value=evidence_value) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_metrics.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_metrics.py new file mode 100644 index 0000000..e4a1a20 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_metrics.py @@ -0,0 +1,124 @@ +from ddtrace.appsec import _asm_request_context +from ddtrace.appsec._ddwaf import DDWaf_info +from ddtrace.appsec._ddwaf import version +from ddtrace.appsec._deduplications import deduplication +from ddtrace.internal.logger import get_logger + + +log = get_logger(__name__) + + +@deduplication +def _set_waf_error_metric(msg: str, stack_trace: str, info: DDWaf_info) -> None: + # perf - avoid importing telemetry until needed + from ddtrace.internal import telemetry + + try: + tags = { + "waf_version": version(), + "lib_language": "python", + } + if info and info.version: + tags["event_rules_version"] = info.version + telemetry.telemetry_writer.add_log("ERROR", msg, stack_trace=stack_trace, tags=tags) + except Exception: + log.warning("Error reporting ASM WAF logs metrics", exc_info=True) + + +def _set_waf_updates_metric(info): + # perf - avoid importing telemetry until needed + from ddtrace.internal import telemetry + from ddtrace.internal.telemetry.constants import TELEMETRY_NAMESPACE_TAG_APPSEC + + try: + if info and info.version: + tags = ( + ("event_rules_version", info.version), + ("waf_version", version()), + ) + else: + tags = (("waf_version", version()),) + + telemetry.telemetry_writer.add_count_metric( + TELEMETRY_NAMESPACE_TAG_APPSEC, + "waf.updates", + 1.0, + tags=tags, + ) + except Exception: + log.warning("Error reporting ASM WAF updates metrics", exc_info=True) + + +def _set_waf_init_metric(info): + # perf - avoid importing telemetry until needed + from ddtrace.internal import telemetry + from ddtrace.internal.telemetry.constants import TELEMETRY_NAMESPACE_TAG_APPSEC + + try: + if info and info.version: + tags = ( + ("event_rules_version", info.version), + ("waf_version", version()), + ) + else: + tags = ( + ( + "waf_version", + version(), + ), + ) + + telemetry.telemetry_writer.add_count_metric( + TELEMETRY_NAMESPACE_TAG_APPSEC, + "waf.init", + 1.0, + tags=tags, + ) + except Exception: + log.warning("Error reporting ASM WAF init metrics", exc_info=True) + + +def _set_waf_request_metrics(*args): + # perf - avoid importing telemetry until needed + from ddtrace.internal import telemetry + from ddtrace.internal.telemetry.constants import TELEMETRY_NAMESPACE_TAG_APPSEC + + try: + list_results, list_result_info, list_is_blocked = _asm_request_context.get_waf_results() or ([], [], []) + if any((list_results, list_result_info, list_is_blocked)): + is_blocked = any(list_is_blocked) + is_triggered = any((result.data for result in list_results)) + is_timeout = any((result.timeout for result in list_results)) + # TODO: enable it when Telemetry intake accepts this tag + # is_truncation = any((result.truncation for result in list_results)) + has_info = any(list_result_info) + + if has_info and list_result_info[0].version: + tags_request = ( + ( + "event_rules_version", + list_result_info[0].version, + ), + ("waf_version", version()), + ("rule_triggered", str(is_triggered).lower()), + ("request_blocked", str(is_blocked).lower()), + ("waf_timeout", str(is_timeout).lower()), + ) + else: + tags_request = ( + ("waf_version", version()), + ("rule_triggered", str(is_triggered).lower()), + ("request_blocked", str(is_blocked).lower()), + ("waf_timeout", str(is_timeout).lower()), + ) + + telemetry.telemetry_writer.add_count_metric( + TELEMETRY_NAMESPACE_TAG_APPSEC, + "waf.requests", + 1.0, + tags=tags_request, + ) + except Exception: + log.warning("Error reporting ASM WAF requests metrics", exc_info=True) + finally: + _asm_request_context.reset_waf_results() diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_processor.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_processor.py new file mode 100644 index 0000000..c691486 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_processor.py @@ -0,0 +1,409 @@ +import dataclasses +import errno +import json +from json.decoder import JSONDecodeError +import os +import os.path +import traceback +from typing import Any +from typing import Dict +from typing import List +from typing import Optional +from typing import Set +from typing import Tuple +from typing import Union + +from ddtrace.appsec import _asm_request_context +from ddtrace.appsec._capabilities import _appsec_rc_file_is_not_static +from ddtrace.appsec._constants import APPSEC +from ddtrace.appsec._constants import DEFAULT +from ddtrace.appsec._constants import SPAN_DATA_NAMES +from ddtrace.appsec._constants import WAF_ACTIONS +from ddtrace.appsec._constants import WAF_CONTEXT_NAMES +from ddtrace.appsec._constants import WAF_DATA_NAMES +from ddtrace.appsec._ddwaf.ddwaf_types import ddwaf_context_capsule +from ddtrace.appsec._metrics import _set_waf_error_metric +from ddtrace.appsec._metrics import _set_waf_init_metric +from ddtrace.appsec._metrics import _set_waf_request_metrics +from ddtrace.appsec._metrics import _set_waf_updates_metric +from ddtrace.appsec._trace_utils import _asm_manual_keep +from ddtrace.constants import ORIGIN_KEY +from ddtrace.constants import RUNTIME_FAMILY +from ddtrace.ext import SpanTypes +from ddtrace.internal import core +from ddtrace.internal.logger import get_logger +from ddtrace.internal.processor import SpanProcessor +from ddtrace.internal.rate_limiter import RateLimiter +from ddtrace.settings.asm import config as asm_config +from ddtrace.span import Span + + +log = get_logger(__name__) + + +def _transform_headers(data: Union[Dict[str, str], List[Tuple[str, str]]]) -> Dict[str, Union[str, List[str]]]: + normalized: Dict[str, Union[str, List[str]]] = {} + headers = data if isinstance(data, list) else data.items() + for header, value in headers: + header = header.lower() + if header in ("cookie", "set-cookie"): + continue + if header in normalized: # if a header with the same lowercase name already exists, let's make it an array + existing = normalized[header] + if isinstance(existing, list): + existing.append(value) + else: + normalized[header] = [existing, value] + else: + normalized[header] = value + return normalized + + +def get_rules() -> str: + return os.getenv("DD_APPSEC_RULES", default=DEFAULT.RULES) + + +def get_appsec_obfuscation_parameter_key_regexp() -> bytes: + return os.getenvb(b"DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP", DEFAULT.APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP) + + +def get_appsec_obfuscation_parameter_value_regexp() -> bytes: + return os.getenvb( + b"DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP", DEFAULT.APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP + ) + + +_COLLECTED_REQUEST_HEADERS = { + "accept", + "accept-encoding", + "accept-language", + "cf-connecting-ip", + "cf-connecting-ipv6", + "content-encoding", + "content-language", + "content-length", + "content-type", + "fastly-client-ip", + "forwarded", + "forwarded-for", + "host", + "true-client-ip", + "user-agent", + "via", + "x-client-ip", + "x-cluster-client-ip", + "x-forwarded", + "x-forwarded-for", + "x-real-ip", +} + + +def _set_headers(span: Span, headers: Any, kind: str) -> None: + from ddtrace.contrib.trace_utils import _normalize_tag_name + + for k in headers: + if isinstance(k, tuple): + key, value = k + else: + key, value = k, headers[k] + if key.lower() in _COLLECTED_REQUEST_HEADERS: + # since the header value can be a list, use `set_tag()` to ensure it is converted to a string + span.set_tag(_normalize_tag_name(kind, key), value) + + +def _get_rate_limiter() -> RateLimiter: + return RateLimiter(int(os.getenv("DD_APPSEC_TRACE_RATE_LIMIT", DEFAULT.TRACE_RATE_LIMIT))) + + +@dataclasses.dataclass(eq=False) +class AppSecSpanProcessor(SpanProcessor): + rules: str = dataclasses.field(default_factory=get_rules) + obfuscation_parameter_key_regexp: bytes = dataclasses.field( + default_factory=get_appsec_obfuscation_parameter_key_regexp + ) + obfuscation_parameter_value_regexp: bytes = dataclasses.field( + default_factory=get_appsec_obfuscation_parameter_value_regexp + ) + _addresses_to_keep: Set[str] = dataclasses.field(default_factory=set) + _rate_limiter: RateLimiter = dataclasses.field(default_factory=_get_rate_limiter) + + @property + def enabled(self): + return self._ddwaf is not None + + def __post_init__(self) -> None: + from ddtrace.appsec._ddwaf import DDWaf + + try: + with open(self.rules, "r") as f: + rules = json.load(f) + self._update_actions(rules) + + except EnvironmentError as err: + if err.errno == errno.ENOENT: + log.error("[DDAS-0001-03] ASM could not read the rule file %s. Reason: file does not exist", self.rules) + else: + # TODO: try to log reasons + log.error("[DDAS-0001-03] ASM could not read the rule file %s.", self.rules) + raise + except JSONDecodeError: + log.error("[DDAS-0001-03] ASM could not read the rule file %s. Reason: invalid JSON file", self.rules) + raise + except Exception: + # TODO: try to log reasons + log.error("[DDAS-0001-03] ASM could not read the rule file %s.", self.rules) + raise + try: + self._ddwaf = DDWaf(rules, self.obfuscation_parameter_key_regexp, self.obfuscation_parameter_value_regexp) + if not self._ddwaf._handle or self._ddwaf.info.failed: + stack_trace = "DDWAF.__init__: invalid rules\n ruleset: %s\nloaded:%s\nerrors:%s\n" % ( + rules, + self._ddwaf.info.loaded, + self._ddwaf.info.errors, + ) + _set_waf_error_metric("WAF init error. Invalid rules", stack_trace, self._ddwaf.info) + + _set_waf_init_metric(self._ddwaf.info) + except ValueError: + # Partial of DDAS-0005-00 + log.warning("[DDAS-0005-00] WAF initialization failed") + raise + self._update_required() + + def _update_required(self): + self._addresses_to_keep.clear() + for address in self._ddwaf.required_data: + self._addresses_to_keep.add(address) + # we always need the request headers + self._addresses_to_keep.add(WAF_DATA_NAMES.REQUEST_HEADERS_NO_COOKIES) + # we always need the response headers + self._addresses_to_keep.add(WAF_DATA_NAMES.RESPONSE_HEADERS_NO_COOKIES) + + def _update_actions(self, rules: Dict[str, Any]) -> None: + new_actions = rules.get("actions", []) + self._actions: Dict[str, Dict[str, Any]] = WAF_ACTIONS.DEFAULT_ACTIONS + for a in new_actions: + self._actions[a.get(WAF_ACTIONS.ID, None)] = a + if "actions" in rules: + del rules["actions"] + + def _update_rules(self, new_rules: Dict[str, Any]) -> bool: + result = False + if not _appsec_rc_file_is_not_static(): + return result + try: + self._update_actions(new_rules) + result = self._ddwaf.update_rules(new_rules) + _set_waf_updates_metric(self._ddwaf.info) + except TypeError: + error_msg = "Error updating ASM rules. TypeError exception " + log.debug(error_msg, exc_info=True) + _set_waf_error_metric(error_msg, traceback.format_exc(), self._ddwaf.info) + if not result: + error_msg = "Error updating ASM rules. Invalid rules" + log.debug(error_msg) + _set_waf_error_metric(error_msg, "", self._ddwaf.info) + self._update_required() + return result + + def on_span_start(self, span: Span) -> None: + from ddtrace.contrib import trace_utils + + if span.span_type != SpanTypes.WEB: + return + + if _asm_request_context.free_context_available(): + _asm_request_context.register(span) + else: + new_asm_context = _asm_request_context.asm_request_context_manager() + new_asm_context.__enter__() + _asm_request_context.register(span, new_asm_context) + + ctx = self._ddwaf._at_request_start() + + peer_ip = _asm_request_context.get_ip() + headers = _asm_request_context.get_headers() + headers_case_sensitive = _asm_request_context.get_headers_case_sensitive() + + span.set_metric(APPSEC.ENABLED, 1.0) + span.set_tag_str(RUNTIME_FAMILY, "python") + + def waf_callable(custom_data=None): + return self._waf_action(span._local_root or span, ctx, custom_data) + + _asm_request_context.set_waf_callback(waf_callable) + _asm_request_context.add_context_callback(_set_waf_request_metrics) + if headers is not None: + _asm_request_context.set_waf_address(SPAN_DATA_NAMES.REQUEST_HEADERS_NO_COOKIES, headers, span) + _asm_request_context.set_waf_address( + SPAN_DATA_NAMES.REQUEST_HEADERS_NO_COOKIES_CASE, headers_case_sensitive, span + ) + if not peer_ip: + return + + ip = trace_utils._get_request_header_client_ip(headers, peer_ip, headers_case_sensitive) + # Save the IP and headers in the context so the retrieval can be skipped later + _asm_request_context.set_waf_address(SPAN_DATA_NAMES.REQUEST_HTTP_IP, ip, span) + if ip and self._is_needed(WAF_DATA_NAMES.REQUEST_HTTP_IP): + log.debug("[DDAS-001-00] Executing ASM WAF for checking IP block") + # _asm_request_context.call_callback() + _asm_request_context.call_waf_callback({"REQUEST_HTTP_IP": None}) + + def _waf_action( + self, span: Span, ctx: ddwaf_context_capsule, custom_data: Optional[Dict[str, Any]] = None + ) -> Optional[Dict[str, Any]]: + """ + Call the `WAF` with the given parameters. If `custom_data_names` is specified as + a list of `(WAF_NAME, WAF_STR)` tuples specifying what values of the `WAF_DATA_NAMES` + constant class will be checked. Else, it will check all the possible values + from `WAF_DATA_NAMES`. + + If `custom_data_values` is specified, it must be a dictionary where the key is the + `WAF_DATA_NAMES` key and the value the custom value. If not used, the values will + be retrieved from the `core`. This can be used when you don't want to store + the value in the `core` before checking the `WAF`. + """ + if span.span_type != SpanTypes.WEB: + return None + + if core.get_item(WAF_CONTEXT_NAMES.BLOCKED, span=span) or core.get_item(WAF_CONTEXT_NAMES.BLOCKED): + # We still must run the waf if we need to extract schemas for API SECURITY + if not custom_data or not custom_data.get("PROCESSOR_SETTINGS", {}).get("extract-schema", False): + return None + + data = {} + iter_data = [(key, WAF_DATA_NAMES[key]) for key in custom_data] if custom_data is not None else WAF_DATA_NAMES + data_already_sent = _asm_request_context.get_data_sent() + if data_already_sent is None: + data_already_sent = set() + + # type ignore because mypy seems to not detect that both results of the if + # above can iter if not None + force_keys = custom_data.get("PROCESSOR_SETTINGS", {}).get("extract-schema", False) if custom_data else False + for key, waf_name in iter_data: # type: ignore[attr-defined] + if key in data_already_sent: + continue + if self._is_needed(waf_name) or force_keys: + value = None + if custom_data is not None and custom_data.get(key) is not None: + value = custom_data.get(key) + elif key in SPAN_DATA_NAMES: + value = _asm_request_context.get_value("waf_addresses", SPAN_DATA_NAMES[key]) + if value is not None: + data[waf_name] = _transform_headers(value) if key.endswith("HEADERS_NO_COOKIES") else value + data_already_sent.add(key) + log.debug("[action] WAF got value %s", SPAN_DATA_NAMES.get(key, key)) + + waf_results = self._ddwaf.run(ctx, data, asm_config._waf_timeout) + if waf_results and waf_results.data: + log.debug("[DDAS-011-00] ASM In-App WAF returned: %s. Timeout %s", waf_results.data, waf_results.timeout) + + for action in waf_results.actions: + action_type = self._actions.get(action, {}).get(WAF_ACTIONS.TYPE, None) + if action_type == WAF_ACTIONS.BLOCK_ACTION: + blocked = self._actions[action][WAF_ACTIONS.PARAMETERS] + break + elif action_type == WAF_ACTIONS.REDIRECT_ACTION: + blocked = self._actions[action][WAF_ACTIONS.PARAMETERS] + location = blocked.get("location", "") + if not location: + blocked = WAF_ACTIONS.DEFAULT_PARAMETERS + break + status_code = str(blocked.get("status_code", "")) + if not (status_code[:3].isdigit() and status_code.startswith("3")): + blocked["status_code"] = "303" + blocked[WAF_ACTIONS.TYPE] = "none" + break + else: + blocked = {} + _asm_request_context.set_waf_results(waf_results, self._ddwaf.info, bool(blocked)) + if blocked: + core.set_item(WAF_CONTEXT_NAMES.BLOCKED, blocked, span=span) + core.set_item(WAF_CONTEXT_NAMES.BLOCKED, blocked) + + try: + info = self._ddwaf.info + if info.errors: + errors = json.dumps(info.errors) + span.set_tag_str(APPSEC.EVENT_RULE_ERRORS, errors) + _set_waf_error_metric("WAF run. Error", errors, info) + if waf_results.timeout: + _set_waf_error_metric("WAF run. Timeout errors", "", info) + span.set_tag_str(APPSEC.EVENT_RULE_VERSION, info.version) + from ddtrace.appsec._ddwaf import version + + span.set_tag_str(APPSEC.WAF_VERSION, version()) + + def update_metric(name, value): + old_value = span.get_metric(name) + if old_value is None: + old_value = 0.0 + span.set_metric(name, value + old_value) + + span.set_metric(APPSEC.EVENT_RULE_LOADED, info.loaded) + span.set_metric(APPSEC.EVENT_RULE_ERROR_COUNT, info.failed) + if waf_results: + update_metric(APPSEC.WAF_DURATION, waf_results.runtime) + update_metric(APPSEC.WAF_DURATION_EXT, waf_results.total_runtime) + except (JSONDecodeError, ValueError): + log.warning("Error parsing data ASM In-App WAF metrics report %s", info.errors) + except Exception: + log.warning("Error executing ASM In-App WAF metrics report: %s", exc_info=True) + + if (waf_results and waf_results.data) or blocked: + # We run the rate limiter only if there is an attack, its goal is to limit the number of collected asm + # events + allowed = self._rate_limiter.is_allowed(span.start_ns) + if not allowed: + # TODO: add metric collection to keep an eye (when it's name is clarified) + return waf_results.derivatives + + for id_tag, kind in [ + (SPAN_DATA_NAMES.REQUEST_HEADERS_NO_COOKIES, "request"), + (SPAN_DATA_NAMES.RESPONSE_HEADERS_NO_COOKIES, "response"), + ]: + headers_req = _asm_request_context.get_waf_address(id_tag) + if headers_req: + _set_headers(span, headers_req, kind=kind) + + _asm_request_context.store_waf_results_data(waf_results.data) + if blocked: + span.set_tag(APPSEC.BLOCKED, "true") + _set_waf_request_metrics() + + # Partial DDAS-011-00 + span.set_tag_str(APPSEC.EVENT, "true") + + remote_ip = _asm_request_context.get_waf_address(SPAN_DATA_NAMES.REQUEST_HTTP_IP) + if remote_ip: + # Note that if the ip collection is disabled by the env var + # DD_TRACE_CLIENT_IP_HEADER_DISABLED actor.ip won't be sent + span.set_tag_str("actor.ip", remote_ip) + + # Right now, we overwrite any value that could be already there. We need to reconsider when ASM/AppSec's + # specs are updated. + _asm_manual_keep(span) + if span.get_tag(ORIGIN_KEY) is None: + span.set_tag_str(ORIGIN_KEY, APPSEC.ORIGIN_VALUE) + return waf_results.derivatives + + def _is_needed(self, address: str) -> bool: + return address in self._addresses_to_keep + + def on_span_finish(self, span: Span) -> None: + try: + if span.span_type == SpanTypes.WEB: + # Force to set respond headers at the end + headers_req = core.get_item(SPAN_DATA_NAMES.RESPONSE_HEADERS_NO_COOKIES, span=span) + if headers_req: + _set_headers(span, headers_req, kind="response") + + # this call is only necessary for tests or frameworks that are not using blocking + if span.get_tag(APPSEC.JSON) is None and _asm_request_context.in_context(): + log.debug("metrics waf call") + _asm_request_context.call_waf_callback() + + self._ddwaf._at_request_end() + finally: + # release asm context if it was created by the span + _asm_request_context.unregister(span) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/__init__.py new file mode 100644 index 0000000..84d0af7 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/__init__.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 + +from sys import version_info + + +if version_info < (3, 7, 0): + from .module_names_py36 import STDLIB_MODULE_NAMES +elif version_info < (3, 8, 0): + from .module_names_py37 import STDLIB_MODULE_NAMES +elif version_info < (3, 9, 0): + from .module_names_py38 import STDLIB_MODULE_NAMES +elif version_info < (3, 10, 0): + from .module_names_py39 import STDLIB_MODULE_NAMES +elif version_info < (3, 11, 0): + from .module_names_py310 import STDLIB_MODULE_NAMES +else: + from .module_names_py311 import STDLIB_MODULE_NAMES + + +def _stdlib_for_python_version(): # type: () -> set + return STDLIB_MODULE_NAMES diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/module_names_py310.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/module_names_py310.py new file mode 100644 index 0000000..338f807 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/module_names_py310.py @@ -0,0 +1,218 @@ +STDLIB_MODULE_NAMES = { + "__future__", + "_ast", + "_compression", + "_thread", + "abc", + "aifc", + "argparse", + "array", + "ast", + "asynchat", + "asyncio", + "asyncore", + "atexit", + "audioop", + "base64", + "bdb", + "binascii", + "binhex", + "bisect", + "builtins", + "bz2", + "cProfile", + "calendar", + "cgi", + "cgitb", + "chunk", + "cmath", + "cmd", + "code", + "codecs", + "codeop", + "collections", + "colorsys", + "compileall", + "concurrent", + "configparser", + "contextlib", + "contextvars", + "copy", + "copyreg", + "crypt", + "csv", + "ctypes", + "curses", + "dataclasses", + "datetime", + "dbm", + "decimal", + "difflib", + "dis", + "distutils", + "doctest", + "email", + "encodings", + "ensurepip", + "enum", + "errno", + "faulthandler", + "fcntl", + "filecmp", + "fileinput", + "fnmatch", + "fractions", + "ftplib", + "functools", + "gc", + "getopt", + "getpass", + "gettext", + "glob", + "graphlib", + "grp", + "gzip", + "hashlib", + "heapq", + "hmac", + "html", + "http", + "idlelib", + "imaplib", + "imghdr", + "imp", + "importlib", + "inspect", + "io", + "ipaddress", + "itertools", + "json", + "keyword", + "lib2to3", + "linecache", + "locale", + "logging", + "lzma", + "mailbox", + "mailcap", + "marshal", + "math", + "mimetypes", + "mmap", + "modulefinder", + "msilib", + "msvcrt", + "multiprocessing", + "netrc", + "nis", + "nntplib", + "ntpath", + "numbers", + "opcode", + "operator", + "optparse", + "os", + "ossaudiodev", + "pathlib", + "pdb", + "pickle", + "pickletools", + "pipes", + "pkgutil", + "platform", + "plistlib", + "poplib", + "posix", + "posixpath", + "pprint", + "profile", + "pstats", + "pty", + "pwd", + "py_compile", + "pyclbr", + "pydoc", + "queue", + "quopri", + "random", + "re", + "readline", + "reprlib", + "resource", + "rlcompleter", + "runpy", + "sched", + "secrets", + "select", + "selectors", + "shelve", + "shlex", + "shutil", + "signal", + "site", + "smtpd", + "smtplib", + "sndhdr", + "socket", + "socketserver", + "spwd", + "sqlite3", + "sre", + "sre_compile", + "sre_constants", + "sre_parse", + "ssl", + "stat", + "statistics", + "string", + "stringprep", + "struct", + "subprocess", + "sunau", + "symtable", + "sys", + "sysconfig", + "syslog", + "tabnanny", + "tarfile", + "telnetlib", + "tempfile", + "termios", + "test", + "textwrap", + "threading", + "time", + "timeit", + "tkinter", + "token", + "tokenize", + "trace", + "traceback", + "tracemalloc", + "tty", + "turtle", + "turtledemo", + "types", + "typing", + "unicodedata", + "unittest", + "urllib", + "uu", + "uuid", + "venv", + "warnings", + "wave", + "weakref", + "webbrowser", + "winreg", + "winsound", + "wsgiref", + "xdrlib", + "xml", + "xmlrpc", + "zipapp", + "zipfile", + "zipimport", + "zlib", + "zoneinfo", +} diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/module_names_py311.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/module_names_py311.py new file mode 100644 index 0000000..47030a1 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/module_names_py311.py @@ -0,0 +1,218 @@ +STDLIB_MODULE_NAMES = { + "__future__", + "_ast", + "_compression", + "_thread", + "abc", + "aifc", + "argparse", + "array", + "ast", + "asynchat", + "asyncio", + "asyncore", + "atexit", + "audioop", + "base64", + "bdb", + "binascii", + "bisect", + "builtins", + "bz2", + "cProfile", + "calendar", + "cgi", + "cgitb", + "chunk", + "cmath", + "cmd", + "code", + "codecs", + "codeop", + "collections", + "colorsys", + "compileall", + "concurrent", + "configparser", + "contextlib", + "contextvars", + "copy", + "copyreg", + "crypt", + "csv", + "ctypes", + "curses", + "dataclasses", + "datetime", + "dbm", + "decimal", + "difflib", + "dis", + "distutils", + "doctest", + "email", + "encodings", + "ensurepip", + "enum", + "errno", + "faulthandler", + "fcntl", + "filecmp", + "fileinput", + "fnmatch", + "fractions", + "ftplib", + "functools", + "gc", + "getopt", + "getpass", + "gettext", + "glob", + "graphlib", + "grp", + "gzip", + "hashlib", + "heapq", + "hmac", + "html", + "http", + "idlelib", + "imaplib", + "imghdr", + "imp", + "importlib", + "inspect", + "io", + "ipaddress", + "itertools", + "json", + "keyword", + "lib2to3", + "linecache", + "locale", + "logging", + "lzma", + "mailbox", + "mailcap", + "marshal", + "math", + "mimetypes", + "mmap", + "modulefinder", + "msilib", + "msvcrt", + "multiprocessing", + "netrc", + "nis", + "nntplib", + "ntpath", + "numbers", + "opcode", + "operator", + "optparse", + "os", + "ossaudiodev", + "pathlib", + "pdb", + "pickle", + "pickletools", + "pipes", + "pkgutil", + "platform", + "plistlib", + "poplib", + "posix", + "posixpath", + "pprint", + "profile", + "pstats", + "pty", + "pwd", + "py_compile", + "pyclbr", + "pydoc", + "queue", + "quopri", + "random", + "re", + "readline", + "reprlib", + "resource", + "rlcompleter", + "runpy", + "sched", + "secrets", + "select", + "selectors", + "shelve", + "shlex", + "shutil", + "signal", + "site", + "smtpd", + "smtplib", + "sndhdr", + "socket", + "socketserver", + "spwd", + "sqlite3", + "sre", + "sre_compile", + "sre_constants", + "sre_parse", + "ssl", + "stat", + "statistics", + "string", + "stringprep", + "struct", + "subprocess", + "sunau", + "symtable", + "sys", + "sysconfig", + "syslog", + "tabnanny", + "tarfile", + "telnetlib", + "tempfile", + "termios", + "test", + "textwrap", + "threading", + "time", + "timeit", + "tkinter", + "token", + "tokenize", + "tomllib", + "trace", + "traceback", + "tracemalloc", + "tty", + "turtle", + "turtledemo", + "types", + "typing", + "unicodedata", + "unittest", + "urllib", + "uu", + "uuid", + "venv", + "warnings", + "wave", + "weakref", + "webbrowser", + "winreg", + "winsound", + "wsgiref", + "xdrlib", + "xml", + "xmlrpc", + "zipapp", + "zipfile", + "zipimport", + "zlib", + "zoneinfo", +} diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/module_names_py36.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/module_names_py36.py new file mode 100644 index 0000000..c4eb7c6 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/module_names_py36.py @@ -0,0 +1,220 @@ +STDLIB_MODULE_NAMES = { + "__future__", + "_ast", + "_compression", + "_dummy_thread", + "_thread", + "abc", + "aifc", + "argparse", + "array", + "ast", + "asynchat", + "asyncio", + "asyncore", + "atexit", + "audioop", + "base64", + "bdb", + "binascii", + "binhex", + "bisect", + "builtins", + "bz2", + "cProfile", + "calendar", + "cgi", + "cgitb", + "chunk", + "cmath", + "cmd", + "code", + "codecs", + "codeop", + "collections", + "colorsys", + "compileall", + "concurrent", + "configparser", + "contextlib", + "copy", + "copyreg", + "crypt", + "csv", + "ctypes", + "curses", + "datetime", + "dbm", + "decimal", + "difflib", + "dis", + "distutils", + "doctest", + "dummy_threading", + "email", + "encodings", + "ensurepip", + "enum", + "errno", + "faulthandler", + "fcntl", + "filecmp", + "fileinput", + "fnmatch", + "formatter", + "fpectl", + "fractions", + "ftplib", + "functools", + "gc", + "getopt", + "getpass", + "gettext", + "glob", + "grp", + "gzip", + "hashlib", + "heapq", + "hmac", + "html", + "http", + "imaplib", + "imghdr", + "imp", + "importlib", + "inspect", + "io", + "ipaddress", + "itertools", + "json", + "keyword", + "lib2to3", + "linecache", + "locale", + "logging", + "lzma", + "macpath", + "mailbox", + "mailcap", + "marshal", + "math", + "mimetypes", + "mmap", + "modulefinder", + "msilib", + "msvcrt", + "multiprocessing", + "netrc", + "nis", + "nntplib", + "ntpath", + "numbers", + "opcode", + "operator", + "optparse", + "os", + "ossaudiodev", + "parser", + "pathlib", + "pdb", + "pickle", + "pickletools", + "pipes", + "pkgutil", + "platform", + "plistlib", + "poplib", + "posix", + "posixpath", + "pprint", + "profile", + "pstats", + "pty", + "pwd", + "py_compile", + "pyclbr", + "pydoc", + "queue", + "quopri", + "random", + "re", + "readline", + "reprlib", + "resource", + "rlcompleter", + "runpy", + "sched", + "secrets", + "select", + "selectors", + "shelve", + "shlex", + "shutil", + "signal", + "site", + "smtpd", + "smtplib", + "sndhdr", + "socket", + "socketserver", + "spwd", + "sqlite3", + "sre", + "sre_compile", + "sre_constants", + "sre_parse", + "ssl", + "stat", + "statistics", + "string", + "stringprep", + "struct", + "subprocess", + "sunau", + "symbol", + "symtable", + "sys", + "sysconfig", + "syslog", + "tabnanny", + "tarfile", + "telnetlib", + "tempfile", + "termios", + "test", + "textwrap", + "threading", + "time", + "timeit", + "tkinter", + "token", + "tokenize", + "trace", + "traceback", + "tracemalloc", + "tty", + "turtle", + "turtledemo", + "types", + "typing", + "unicodedata", + "unittest", + "urllib", + "uu", + "uuid", + "venv", + "warnings", + "wave", + "weakref", + "webbrowser", + "winreg", + "winsound", + "wsgiref", + "xdrlib", + "xml", + "xmlrpc", + "zipapp", + "zipfile", + "zipimport", + "zlib", +} diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/module_names_py37.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/module_names_py37.py new file mode 100644 index 0000000..0f989b2 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/module_names_py37.py @@ -0,0 +1,221 @@ +STDLIB_MODULE_NAMES = { + "__future__", + "_ast", + "_compression", + "_dummy_thread", + "_thread", + "abc", + "aifc", + "argparse", + "array", + "ast", + "asynchat", + "asyncio", + "asyncore", + "atexit", + "audioop", + "base64", + "bdb", + "binascii", + "binhex", + "bisect", + "builtins", + "bz2", + "cProfile", + "calendar", + "cgi", + "cgitb", + "chunk", + "cmath", + "cmd", + "code", + "codecs", + "codeop", + "collections", + "colorsys", + "compileall", + "concurrent", + "configparser", + "contextlib", + "contextvars", + "copy", + "copyreg", + "crypt", + "csv", + "ctypes", + "curses", + "dataclasses", + "datetime", + "dbm", + "decimal", + "difflib", + "dis", + "distutils", + "doctest", + "dummy_threading", + "email", + "encodings", + "ensurepip", + "enum", + "errno", + "faulthandler", + "fcntl", + "filecmp", + "fileinput", + "fnmatch", + "formatter", + "fractions", + "ftplib", + "functools", + "gc", + "getopt", + "getpass", + "gettext", + "glob", + "grp", + "gzip", + "hashlib", + "heapq", + "hmac", + "html", + "http", + "imaplib", + "imghdr", + "imp", + "importlib", + "inspect", + "io", + "ipaddress", + "itertools", + "json", + "keyword", + "lib2to3", + "linecache", + "locale", + "logging", + "lzma", + "macpath", + "mailbox", + "mailcap", + "marshal", + "math", + "mimetypes", + "mmap", + "modulefinder", + "msilib", + "msvcrt", + "multiprocessing", + "netrc", + "nis", + "nntplib", + "ntpath", + "numbers", + "opcode", + "operator", + "optparse", + "os", + "ossaudiodev", + "parser", + "pathlib", + "pdb", + "pickle", + "pickletools", + "pipes", + "pkgutil", + "platform", + "plistlib", + "poplib", + "posix", + "posixpath", + "pprint", + "profile", + "pstats", + "pty", + "pwd", + "py_compile", + "pyclbr", + "pydoc", + "queue", + "quopri", + "random", + "re", + "readline", + "reprlib", + "resource", + "rlcompleter", + "runpy", + "sched", + "secrets", + "select", + "selectors", + "shelve", + "shlex", + "shutil", + "signal", + "site", + "smtpd", + "smtplib", + "sndhdr", + "socket", + "socketserver", + "spwd", + "sqlite3", + "sre", + "sre_compile", + "sre_constants", + "sre_parse", + "ssl", + "stat", + "statistics", + "string", + "stringprep", + "struct", + "subprocess", + "sunau", + "symbol", + "symtable", + "sys", + "sysconfig", + "syslog", + "tabnanny", + "tarfile", + "telnetlib", + "tempfile", + "termios", + "test", + "textwrap", + "threading", + "time", + "timeit", + "tkinter", + "token", + "tokenize", + "trace", + "traceback", + "tracemalloc", + "tty", + "turtle", + "turtledemo", + "types", + "typing", + "unicodedata", + "unittest", + "urllib", + "uu", + "uuid", + "venv", + "warnings", + "wave", + "weakref", + "webbrowser", + "winreg", + "winsound", + "wsgiref", + "xdrlib", + "xml", + "xmlrpc", + "zipapp", + "zipfile", + "zipimport", + "zlib", +} diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/module_names_py38.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/module_names_py38.py new file mode 100644 index 0000000..1e5be0b --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/module_names_py38.py @@ -0,0 +1,220 @@ +STDLIB_MODULE_NAMES = { + "__future__", + "_ast", + "_compression", + "_dummy_thread", + "_thread", + "abc", + "aifc", + "argparse", + "array", + "ast", + "asynchat", + "asyncio", + "asyncore", + "atexit", + "audioop", + "base64", + "bdb", + "binascii", + "binhex", + "bisect", + "builtins", + "bz2", + "cProfile", + "calendar", + "cgi", + "cgitb", + "chunk", + "cmath", + "cmd", + "code", + "codecs", + "codeop", + "collections", + "colorsys", + "compileall", + "concurrent", + "configparser", + "contextlib", + "contextvars", + "copy", + "copyreg", + "crypt", + "csv", + "ctypes", + "curses", + "dataclasses", + "datetime", + "dbm", + "decimal", + "difflib", + "dis", + "distutils", + "doctest", + "dummy_threading", + "email", + "encodings", + "ensurepip", + "enum", + "errno", + "faulthandler", + "fcntl", + "filecmp", + "fileinput", + "fnmatch", + "formatter", + "fractions", + "ftplib", + "functools", + "gc", + "getopt", + "getpass", + "gettext", + "glob", + "grp", + "gzip", + "hashlib", + "heapq", + "hmac", + "html", + "http", + "imaplib", + "imghdr", + "imp", + "importlib", + "inspect", + "io", + "ipaddress", + "itertools", + "json", + "keyword", + "lib2to3", + "linecache", + "locale", + "logging", + "lzma", + "mailbox", + "mailcap", + "marshal", + "math", + "mimetypes", + "mmap", + "modulefinder", + "msilib", + "msvcrt", + "multiprocessing", + "netrc", + "nis", + "nntplib", + "ntpath", + "numbers", + "opcode", + "operator", + "optparse", + "os", + "ossaudiodev", + "parser", + "pathlib", + "pdb", + "pickle", + "pickletools", + "pipes", + "pkgutil", + "platform", + "plistlib", + "poplib", + "posix", + "posixpath", + "pprint", + "profile", + "pstats", + "pty", + "pwd", + "py_compile", + "pyclbr", + "pydoc", + "queue", + "quopri", + "random", + "re", + "readline", + "reprlib", + "resource", + "rlcompleter", + "runpy", + "sched", + "secrets", + "select", + "selectors", + "shelve", + "shlex", + "shutil", + "signal", + "site", + "smtpd", + "smtplib", + "sndhdr", + "socket", + "socketserver", + "spwd", + "sqlite3", + "sre", + "sre_compile", + "sre_constants", + "sre_parse", + "ssl", + "stat", + "statistics", + "string", + "stringprep", + "struct", + "subprocess", + "sunau", + "symbol", + "symtable", + "sys", + "sysconfig", + "syslog", + "tabnanny", + "tarfile", + "telnetlib", + "tempfile", + "termios", + "test", + "textwrap", + "threading", + "time", + "timeit", + "tkinter", + "token", + "tokenize", + "trace", + "traceback", + "tracemalloc", + "tty", + "turtle", + "turtledemo", + "types", + "typing", + "unicodedata", + "unittest", + "urllib", + "uu", + "uuid", + "venv", + "warnings", + "wave", + "weakref", + "webbrowser", + "winreg", + "winsound", + "wsgiref", + "xdrlib", + "xml", + "xmlrpc", + "zipapp", + "zipfile", + "zipimport", + "zlib", +} diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/module_names_py39.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/module_names_py39.py new file mode 100644 index 0000000..6bdc900 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_python_info/stdlib/module_names_py39.py @@ -0,0 +1,220 @@ +STDLIB_MODULE_NAMES = { + "__future__", + "_ast", + "_compression", + "_thread", + "abc", + "aifc", + "argparse", + "array", + "ast", + "asynchat", + "asyncio", + "asyncore", + "atexit", + "audioop", + "base64", + "bdb", + "binascii", + "binhex", + "bisect", + "builtins", + "bz2", + "cProfile", + "calendar", + "cgi", + "cgitb", + "chunk", + "cmath", + "cmd", + "code", + "codecs", + "codeop", + "collections", + "colorsys", + "compileall", + "concurrent", + "configparser", + "contextlib", + "contextvars", + "copy", + "copyreg", + "crypt", + "csv", + "ctypes", + "curses", + "dataclasses", + "datetime", + "dbm", + "decimal", + "difflib", + "dis", + "distutils", + "doctest", + "email", + "encodings", + "ensurepip", + "enum", + "errno", + "faulthandler", + "fcntl", + "filecmp", + "fileinput", + "fnmatch", + "formatter", + "fractions", + "ftplib", + "functools", + "gc", + "getopt", + "getpass", + "gettext", + "glob", + "graphlib", + "grp", + "gzip", + "hashlib", + "heapq", + "hmac", + "html", + "http", + "imaplib", + "imghdr", + "imp", + "importlib", + "inspect", + "io", + "ipaddress", + "itertools", + "json", + "keyword", + "lib2to3", + "linecache", + "locale", + "logging", + "lzma", + "mailbox", + "mailcap", + "marshal", + "math", + "mimetypes", + "mmap", + "modulefinder", + "msilib", + "msvcrt", + "multiprocessing", + "netrc", + "nis", + "nntplib", + "ntpath", + "numbers", + "opcode", + "operator", + "optparse", + "os", + "ossaudiodev", + "parser", + "pathlib", + "pdb", + "pickle", + "pickletools", + "pipes", + "pkgutil", + "platform", + "plistlib", + "poplib", + "posix", + "posixpath", + "pprint", + "profile", + "pstats", + "pty", + "pwd", + "py_compile", + "pyclbr", + "pydoc", + "queue", + "quopri", + "random", + "re", + "readline", + "reprlib", + "resource", + "rlcompleter", + "runpy", + "sched", + "secrets", + "select", + "selectors", + "shelve", + "shlex", + "shutil", + "signal", + "site", + "smtpd", + "smtplib", + "sndhdr", + "socket", + "socketserver", + "spwd", + "sqlite3", + "sre", + "sre_compile", + "sre_constants", + "sre_parse", + "ssl", + "stat", + "statistics", + "string", + "stringprep", + "struct", + "subprocess", + "sunau", + "symbol", + "symtable", + "sys", + "sysconfig", + "syslog", + "tabnanny", + "tarfile", + "telnetlib", + "tempfile", + "termios", + "test", + "textwrap", + "threading", + "time", + "timeit", + "tkinter", + "token", + "tokenize", + "trace", + "traceback", + "tracemalloc", + "tty", + "turtle", + "turtledemo", + "types", + "typing", + "unicodedata", + "unittest", + "urllib", + "uu", + "uuid", + "venv", + "warnings", + "wave", + "weakref", + "webbrowser", + "winreg", + "winsound", + "wsgiref", + "xdrlib", + "xml", + "xmlrpc", + "zipapp", + "zipfile", + "zipimport", + "zlib", + "zoneinfo", +} diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_remoteconfiguration.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_remoteconfiguration.py new file mode 100644 index 0000000..a8a7162 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_remoteconfiguration.py @@ -0,0 +1,248 @@ +# -*- coding: utf-8 -*- +import json +import os +from typing import Any +from typing import Dict +from typing import Mapping +from typing import Optional + +from ddtrace import Tracer +from ddtrace import config +from ddtrace.appsec._capabilities import _appsec_rc_file_is_not_static +from ddtrace.appsec._capabilities import _asm_feature_is_required +from ddtrace.appsec._constants import PRODUCTS +from ddtrace.appsec._utils import _appsec_rc_features_is_enabled +from ddtrace.constants import APPSEC_ENV +from ddtrace.internal import forksafe +from ddtrace.internal.logger import get_logger +from ddtrace.internal.remoteconfig._connectors import PublisherSubscriberConnector +from ddtrace.internal.remoteconfig._publishers import RemoteConfigPublisherMergeDicts +from ddtrace.internal.remoteconfig._pubsub import PubSub +from ddtrace.internal.remoteconfig._subscribers import RemoteConfigSubscriber +from ddtrace.internal.remoteconfig.worker import remoteconfig_poller +from ddtrace.internal.utils.formats import asbool +from ddtrace.settings.asm import config as asm_config + + +log = get_logger(__name__) + +APPSEC_PRODUCTS = [PRODUCTS.ASM_FEATURES, PRODUCTS.ASM, PRODUCTS.ASM_DATA, PRODUCTS.ASM_DD] + + +class AppSecRC(PubSub): + __subscriber_class__ = RemoteConfigSubscriber + __publisher_class__ = RemoteConfigPublisherMergeDicts + __shared_data__ = PublisherSubscriberConnector() + + def __init__(self, _preprocess_results, callback): + self._publisher = self.__publisher_class__(self.__shared_data__, _preprocess_results) + self._subscriber = self.__subscriber_class__(self.__shared_data__, callback, "ASM") + + +def _forksafe_appsec_rc(): + remoteconfig_poller.start_subscribers_by_product(APPSEC_PRODUCTS) + + +def enable_appsec_rc(test_tracer: Optional[Tracer] = None) -> None: + """Remote config will be used by ASM libraries to receive four different updates from the backend. + Each update has it’s own product: + - ASM_FEATURES product - To allow users enable or disable ASM remotely + - ASM product - To allow clients to activate or deactivate rules + - ASM_DD product - To allow the library to receive rules updates + - ASM_DATA product - To allow the library to receive list of blocked IPs and users + + If environment variable `DD_APPSEC_ENABLED` is not set, registering ASM_FEATURE can enable ASM remotely. If + it's set to true, we will register the rest of the products. + + Parameters `test_tracer` and `start_subscribers` are needed for testing purposes + """ + # Import tracer here to avoid a circular import + if test_tracer is None: + from ddtrace import tracer + else: + tracer = test_tracer + + log.debug("[%s][P: %s] Register ASM Remote Config Callback", os.getpid(), os.getppid()) + asm_callback = ( + remoteconfig_poller.get_registered(PRODUCTS.ASM_FEATURES) + or remoteconfig_poller.get_registered(PRODUCTS.ASM) + or AppSecRC(_preprocess_results_appsec_1click_activation, _appsec_callback) + ) + + if _asm_feature_is_required(): + remoteconfig_poller.register(PRODUCTS.ASM_FEATURES, asm_callback) + + if tracer._asm_enabled and _appsec_rc_file_is_not_static(): + remoteconfig_poller.register(PRODUCTS.ASM_DATA, asm_callback) # IP Blocking + remoteconfig_poller.register(PRODUCTS.ASM, asm_callback) # Exclusion Filters & Custom Rules + remoteconfig_poller.register(PRODUCTS.ASM_DD, asm_callback) # DD Rules + + forksafe.register(_forksafe_appsec_rc) + + +def disable_appsec_rc(): + # only used to avoid data leaks between tests + for product_name in APPSEC_PRODUCTS: + remoteconfig_poller.unregister(product_name) + + +def _add_rules_to_list(features: Mapping[str, Any], feature: str, message: str, ruleset: Dict[str, Any]) -> None: + rules = features.get(feature, None) + if rules is not None: + try: + if ruleset.get(feature) is None: + ruleset[feature] = [] + ruleset[feature] += rules + log.debug("Reloading Appsec %s: %s", message, str(rules)[:20]) + except json.JSONDecodeError: + log.error("ERROR Appsec %s: invalid JSON content from remote configuration", message) + + +def _appsec_callback(features: Mapping[str, Any], test_tracer: Optional[Tracer] = None) -> None: + config = features.get("config", {}) + _appsec_1click_activation(config, test_tracer) + _appsec_api_security_settings(config, test_tracer) + _appsec_rules_data(config, test_tracer) + + +def _appsec_rules_data(features: Mapping[str, Any], test_tracer: Optional[Tracer]) -> bool: + # Tracer is a parameter for testing propose + # Import tracer here to avoid a circular import + if test_tracer is None: + from ddtrace import tracer + else: + tracer = test_tracer + + if features and tracer._appsec_processor: + ruleset = {} # type: dict[str, Optional[list[Any]]] + _add_rules_to_list(features, "rules_data", "rules data", ruleset) + _add_rules_to_list(features, "custom_rules", "custom rules", ruleset) + _add_rules_to_list(features, "rules", "Datadog rules", ruleset) + _add_rules_to_list(features, "exclusions", "exclusion filters", ruleset) + _add_rules_to_list(features, "rules_override", "rules override", ruleset) + _add_rules_to_list(features, "scanners", "scanners", ruleset) + _add_rules_to_list(features, "processors", "processors", ruleset) + if ruleset: + return tracer._appsec_processor._update_rules({k: v for k, v in ruleset.items() if v is not None}) + + return False + + +def _preprocess_results_appsec_1click_activation( + features: Dict[str, Any], pubsub_instance: Optional[PubSub] = None +) -> Dict[str, Any]: + """The main process has the responsibility to enable or disable the ASM products. The child processes don't + care about that, the children only need to know about payload content. + """ + if _appsec_rc_features_is_enabled(): + log.debug( + "[%s][P: %s] Receiving ASM Remote Configuration ASM_FEATURES: %s", + os.getpid(), + os.getppid(), + features.get("asm", {}), + ) + + rc_asm_enabled = None + if features is not None: + if APPSEC_ENV in os.environ: + rc_asm_enabled = asbool(os.environ.get(APPSEC_ENV)) + elif features == {}: + rc_asm_enabled = False + else: + asm_features = features.get("asm", {}) + if asm_features is not None: + rc_asm_enabled = asm_features.get("enabled") + log.debug( + "[%s][P: %s] ASM Remote Configuration ASM_FEATURES. Appsec enabled: %s", + os.getpid(), + os.getppid(), + rc_asm_enabled, + ) + if rc_asm_enabled is not None: + from ddtrace.appsec._constants import PRODUCTS + + if pubsub_instance is None: + pubsub_instance = ( + remoteconfig_poller.get_registered(PRODUCTS.ASM_FEATURES) + or remoteconfig_poller.get_registered(PRODUCTS.ASM) + or AppSecRC(_preprocess_results_appsec_1click_activation, _appsec_callback) + ) + + if rc_asm_enabled and _appsec_rc_file_is_not_static(): + remoteconfig_poller.register(PRODUCTS.ASM_DATA, pubsub_instance) # IP Blocking + remoteconfig_poller.register(PRODUCTS.ASM, pubsub_instance) # Exclusion Filters & Custom Rules + remoteconfig_poller.register(PRODUCTS.ASM_DD, pubsub_instance) # DD Rules + else: + remoteconfig_poller.unregister(PRODUCTS.ASM_DATA) + remoteconfig_poller.unregister(PRODUCTS.ASM) + remoteconfig_poller.unregister(PRODUCTS.ASM_DD) + + features["asm"] = {"enabled": rc_asm_enabled} + return features + + +def _appsec_1click_activation(features: Mapping[str, Any], test_tracer: Optional[Tracer] = None) -> None: + """This callback updates appsec enabled in tracer and config instances following this logic: + ``` + | DD_APPSEC_ENABLED | RC Enabled | Result | + |-------------------|------------|----------| + | | | Disabled | + | | false | Disabled | + | | true | Enabled | + | false | | Disabled | + | true | | Enabled | + | false | true | Disabled | + | true | true | Enabled | + ``` + """ + if _appsec_rc_features_is_enabled(): + # Tracer is a parameter for testing propose + # Import tracer here to avoid a circular import + if test_tracer is None: + from ddtrace import tracer + else: + tracer = test_tracer + + log.debug("[%s][P: %s] ASM_FEATURES: %s", os.getpid(), os.getppid(), str(features)[:100]) + if APPSEC_ENV in os.environ: + # no one click activation if var env is set + rc_asm_enabled = asbool(os.environ.get(APPSEC_ENV)) + elif features is False: + rc_asm_enabled = False + else: + rc_asm_enabled = features.get("asm", {}).get("enabled", False) + + log.debug("APPSEC_ENABLED: %s", rc_asm_enabled) + if rc_asm_enabled is not None: + log.debug( + "[%s][P: %s] Updating ASM Remote Configuration ASM_FEATURES: %s", + os.getpid(), + os.getppid(), + rc_asm_enabled, + ) + + if rc_asm_enabled: + if not tracer._asm_enabled: + tracer.configure(appsec_enabled=True) + else: + asm_config._asm_enabled = True + else: + if tracer._asm_enabled: + tracer.configure(appsec_enabled=False) + else: + asm_config._asm_enabled = False + + +def _appsec_api_security_settings(features: Mapping[str, Any], test_tracer: Optional[Tracer] = None) -> None: + """ + Update API Security settings from remote config + Actually: Update sample rate + """ + if config._remote_config_enabled and asm_config._api_security_enabled: + rc_api_security_sample_rate = features.get("api_security", {}).get("request_sample_rate", None) + if rc_api_security_sample_rate is not None: + try: + sample_rate = max(0.0, min(1.0, float(rc_api_security_sample_rate))) + asm_config._api_security_sample_rate = sample_rate + except BaseException: # nosec + pass diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_trace_utils.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_trace_utils.py new file mode 100644 index 0000000..a09b8c8 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_trace_utils.py @@ -0,0 +1,346 @@ +from typing import Optional + +from ddtrace import Span +from ddtrace import Tracer +from ddtrace import constants +from ddtrace.appsec import _asm_request_context +from ddtrace.appsec._constants import APPSEC +from ddtrace.appsec._constants import LOGIN_EVENTS_MODE +from ddtrace.appsec._constants import WAF_CONTEXT_NAMES +from ddtrace.appsec._utils import _safe_userid +from ddtrace.contrib.trace_utils import set_user +from ddtrace.ext import SpanTypes +from ddtrace.ext import user +from ddtrace.internal import core +from ddtrace.internal.logger import get_logger +from ddtrace.settings.asm import config as asm_config + + +log = get_logger(__name__) + + +def _asm_manual_keep(span: Span) -> None: + from ddtrace.internal.constants import SAMPLING_DECISION_TRACE_TAG_KEY + from ddtrace.internal.sampling import SamplingMechanism + + span.set_tag(constants.MANUAL_KEEP_KEY) + # set decision maker to ASM = -5 + span.set_tag_str(SAMPLING_DECISION_TRACE_TAG_KEY, "-%d" % SamplingMechanism.APPSEC) + + +def _track_user_login_common( + tracer: Tracer, + success: bool, + metadata: Optional[dict] = None, + login_events_mode: str = LOGIN_EVENTS_MODE.SDK, + login: Optional[str] = None, + name: Optional[str] = None, + email: Optional[str] = None, + span: Optional[Span] = None, +) -> Optional[Span]: + if span is None: + span = tracer.current_root_span() + if span: + success_str = "success" if success else "failure" + tag_prefix = "%s.%s" % (APPSEC.USER_LOGIN_EVENT_PREFIX, success_str) + + if success: + span.set_tag_str(APPSEC.USER_LOGIN_EVENT_SUCCESS_TRACK, "true") + else: + span.set_tag_str(APPSEC.USER_LOGIN_EVENT_FAILURE_TRACK, "true") + + # This is used to mark if the call was done from the SDK of the automatic login events + if login_events_mode == LOGIN_EVENTS_MODE.SDK: + span.set_tag_str("%s.sdk" % tag_prefix, "true") + + mode_tag = APPSEC.AUTO_LOGIN_EVENTS_SUCCESS_MODE if success else APPSEC.AUTO_LOGIN_EVENTS_FAILURE_MODE + auto_tag_mode = ( + login_events_mode if login_events_mode != LOGIN_EVENTS_MODE.SDK else asm_config._automatic_login_events_mode + ) + span.set_tag_str(mode_tag, auto_tag_mode) + + tag_metadata_prefix = "%s.%s" % (APPSEC.USER_LOGIN_EVENT_PREFIX_PUBLIC, success_str) + if metadata is not None: + for k, v in metadata.items(): + span.set_tag_str("%s.%s" % (tag_metadata_prefix, k), str(v)) + + if login: + span.set_tag_str("%s.login" % tag_prefix, login) + + if email: + span.set_tag_str("%s.email" % tag_prefix, email) + + if name: + span.set_tag_str("%s.username" % tag_prefix, name) + + _asm_manual_keep(span) + return span + else: + log.warning( + "No root span in the current execution. Skipping track_user_success_login tags. " + "See https://docs.datadoghq.com/security_platform/application_security/setup_and_configure/" + "?tab=set_user&code-lang=python for more information.", + ) + return None + + +def track_user_login_success_event( + tracer: Tracer, + user_id: str, + metadata: Optional[dict] = None, + login: Optional[str] = None, + name: Optional[str] = None, + email: Optional[str] = None, + scope: Optional[str] = None, + role: Optional[str] = None, + session_id: Optional[str] = None, + propagate: bool = False, + login_events_mode: str = LOGIN_EVENTS_MODE.SDK, + span: Optional[Span] = None, +) -> None: + """ + Add a new login success tracking event. The parameters after metadata (name, email, + scope, role, session_id, propagate) will be passed to the `set_user` function that will be called + by this one, see: + https://docs.datadoghq.com/logs/log_configuration/attributes_naming_convention/#user-related-attributes + https://docs.datadoghq.com/security_platform/application_security/setup_and_configure/?tab=set_tag&code-lang=python + + :param tracer: tracer instance to use + :param user_id: a string with the UserId + :param metadata: a dictionary with additional metadata information to be stored with the event + """ + + span = _track_user_login_common(tracer, True, metadata, login_events_mode, login, name, email, span) + if not span: + return + + if ( + user_id + and (login_events_mode not in (LOGIN_EVENTS_MODE.SDK, LOGIN_EVENTS_MODE.EXTENDED)) + and not asm_config._user_model_login_field + ): + user_id = _safe_userid(user_id) + + set_user(tracer, user_id, name, email, scope, role, session_id, propagate, span) + + +def track_user_login_failure_event( + tracer: Tracer, + user_id: Optional[str], + exists: bool, + metadata: Optional[dict] = None, + login_events_mode: str = LOGIN_EVENTS_MODE.SDK, +) -> None: + """ + Add a new login failure tracking event. + :param tracer: tracer instance to use + :param user_id: a string with the UserId if exists=True or the username if not + :param exists: a boolean indicating if the user exists in the system + :param metadata: a dictionary with additional metadata information to be stored with the event + """ + + if ( + user_id + and (login_events_mode not in (LOGIN_EVENTS_MODE.SDK, LOGIN_EVENTS_MODE.EXTENDED)) + and not asm_config._user_model_login_field + ): + user_id = _safe_userid(user_id) + + span = _track_user_login_common(tracer, False, metadata, login_events_mode) + if not span: + return + + if user_id: + span.set_tag_str("%s.failure.%s" % (APPSEC.USER_LOGIN_EVENT_PREFIX_PUBLIC, user.ID), str(user_id)) + exists_str = "true" if exists else "false" + span.set_tag_str("%s.failure.%s" % (APPSEC.USER_LOGIN_EVENT_PREFIX_PUBLIC, user.EXISTS), exists_str) + + +def track_user_signup_event( + tracer: Tracer, user_id: str, success: bool, login_events_mode: str = LOGIN_EVENTS_MODE.SDK +) -> None: + span = tracer.current_root_span() + if span: + success_str = "true" if success else "false" + span.set_tag_str(APPSEC.USER_SIGNUP_EVENT, success_str) + span.set_tag_str(user.ID, user_id) + _asm_manual_keep(span) + + # This is used to mark if the call was done from the SDK of the automatic login events + if login_events_mode == LOGIN_EVENTS_MODE.SDK: + span.set_tag_str("%s.sdk" % APPSEC.USER_SIGNUP_EVENT, "true") + else: + span.set_tag_str("%s.auto.mode" % APPSEC.USER_SIGNUP_EVENT, str(login_events_mode)) + + return + else: + log.warning( + "No root span in the current execution. Skipping track_user_signup tags. " + "See https://docs.datadoghq.com/security_platform/application_security/setup_and_configure/" + "?tab=set_user&code-lang=python for more information.", + ) + + +def track_custom_event(tracer: Tracer, event_name: str, metadata: dict) -> None: + """ + Add a new custom tracking event. + + :param tracer: tracer instance to use + :param event_name: the name of the custom event + :param metadata: a dictionary with additional metadata information to be stored with the event + """ + + if not event_name: + log.warning("Empty event name given to track_custom_event. Skipping setting tags.") + return + + if not metadata: + log.warning("Empty metadata given to track_custom_event. Skipping setting tags.") + return + + span = tracer.current_root_span() + if not span: + log.warning( + "No root span in the current execution. Skipping track_custom_event tags. " + "See https://docs.datadoghq.com/security_platform/application_security" + "/setup_and_configure/" + "?tab=set_user&code-lang=python for more information.", + ) + return + + span.set_tag_str("%s.%s.track" % (APPSEC.CUSTOM_EVENT_PREFIX, event_name), "true") + + for k, v in metadata.items(): + if isinstance(v, bool): + str_v = "true" if v else "false" + else: + str_v = str(v) + span.set_tag_str("%s.%s.%s" % (APPSEC.CUSTOM_EVENT_PREFIX, event_name, k), str_v) + _asm_manual_keep(span) + + +def should_block_user(tracer: Tracer, userid: str) -> bool: + """ + Return true if the specified User ID should be blocked. + + :param tracer: tracer instance to use + :param userid: the ID of the user as registered by `set_user` + """ + + if not asm_config._asm_enabled: + log.warning( + "One click blocking of user ids is disabled. To use this feature please enable " + "Application Security Monitoring" + ) + return False + + # Early check to avoid calling the WAF if the request is already blocked + span = tracer.current_root_span() + if not span: + log.warning( + "No root span in the current execution. should_block_user returning False" + "See https://docs.datadoghq.com/security_platform/application_security" + "/setup_and_configure/" + "?tab=set_user&code-lang=python for more information.", + ) + return False + + if core.get_item(WAF_CONTEXT_NAMES.BLOCKED, span=span): + return True + + _asm_request_context.call_waf_callback(custom_data={"REQUEST_USER_ID": str(userid)}) + return bool(core.get_item(WAF_CONTEXT_NAMES.BLOCKED, span=span)) + + +def block_request() -> None: + """ + Block the current request and return a 403 Unauthorized response. If the response + has already been started to be sent this could not work. The behaviour of this function + could be different among frameworks, but it usually involves raising some kind of internal Exception, + meaning that if you capture the exception the request blocking could not work. + """ + if not asm_config._asm_enabled: + log.warning("block_request() is disabled. To use this feature please enable" "Application Security Monitoring") + return + + _asm_request_context.block_request() + + +def block_request_if_user_blocked(tracer: Tracer, userid: str) -> None: + """ + Check if the specified User ID should be blocked and if positive + block the current request using `block_request`. + + :param tracer: tracer instance to use + :param userid: the ID of the user as registered by `set_user` + """ + if not asm_config._asm_enabled: + log.warning("should_block_user call requires ASM to be enabled") + return + + if should_block_user(tracer, userid): + span = tracer.current_root_span() + if span: + span.set_tag_str(user.ID, str(userid)) + _asm_request_context.block_request() + + +def _on_django_login( + pin, + request, + user, + mode, + info_retriever, +): + if not asm_config._asm_enabled: + return + + if user: + if str(user) != "AnonymousUser": + user_id, user_extra = info_retriever.get_user_info() + + with pin.tracer.trace("django.contrib.auth.login", span_type=SpanTypes.AUTH): + from ddtrace.contrib.django.compat import user_is_authenticated + + if user_is_authenticated(user): + session_key = getattr(request, "session_key", None) + track_user_login_success_event( + pin.tracer, + user_id=user_id, + session_id=session_key, + propagate=True, + login_events_mode=mode, + **user_extra, + ) + else: + # Login failed but the user exists + track_user_login_failure_event(pin.tracer, user_id=user_id, exists=True, login_events_mode=mode) + else: + # Login failed and the user is unknown + user_id = info_retriever.get_userid() + track_user_login_failure_event(pin.tracer, user_id=user_id, exists=False, login_events_mode=mode) + + +def _on_django_auth(result_user, mode, kwargs, pin, info_retriever): + if not asm_config._asm_enabled: + return True, result_user + + extended_userid_fields = info_retriever.possible_user_id_fields + info_retriever.possible_login_fields + userid_list = info_retriever.possible_user_id_fields if mode == "safe" else extended_userid_fields + + for possible_key in userid_list: + if possible_key in kwargs: + user_id = kwargs[possible_key] + break + else: + user_id = None + + if not result_user: + with pin.tracer.trace("django.contrib.auth.login", span_type=SpanTypes.AUTH): + track_user_login_failure_event(pin.tracer, user_id=user_id, exists=False, login_events_mode=mode) + + return False, None + + +core.on("django.login", _on_django_login) +core.on("django.auth", _on_django_auth, "user") diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_utils.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_utils.py new file mode 100644 index 0000000..8efd4cf --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/_utils.py @@ -0,0 +1,176 @@ +import os +import uuid + +from ddtrace.appsec._constants import API_SECURITY +from ddtrace.constants import APPSEC_ENV +from ddtrace.internal.compat import to_unicode +from ddtrace.internal.logger import get_logger +from ddtrace.internal.utils.http import _get_blocked_template # noqa:F401 +from ddtrace.internal.utils.http import parse_form_multipart # noqa:F401 +from ddtrace.internal.utils.http import parse_form_params # noqa:F401 +from ddtrace.settings import _config as config +from ddtrace.settings.asm import config as asm_config + + +log = get_logger(__name__) + + +def parse_response_body(raw_body): + import json + + import xmltodict + + from ddtrace.appsec import _asm_request_context + from ddtrace.appsec._constants import SPAN_DATA_NAMES + from ddtrace.contrib.trace_utils import _get_header_value_case_insensitive + + if not raw_body: + return + + if isinstance(raw_body, dict): + return raw_body + + headers = _asm_request_context.get_waf_address(SPAN_DATA_NAMES.RESPONSE_HEADERS_NO_COOKIES) + if not headers: + return + content_type = _get_header_value_case_insensitive( + {to_unicode(k): to_unicode(v) for k, v in dict(headers).items()}, + "content-type", + ) + if not content_type: + return + + def access_body(bd): + if isinstance(bd, list) and isinstance(bd[0], (str, bytes)): + bd = bd[0][:0].join(bd) + if getattr(bd, "decode", False): + bd = bd.decode("UTF-8", errors="ignore") + if len(bd) >= API_SECURITY.MAX_PAYLOAD_SIZE: + raise ValueError("response body larger than 16MB") + return bd + + req_body = None + try: + # TODO handle charset + if "json" in content_type: + req_body = json.loads(access_body(raw_body)) + elif "xml" in content_type: + req_body = xmltodict.parse(access_body(raw_body)) + else: + return + except BaseException: + log.debug("Failed to parse response body", exc_info=True) + else: + return req_body + + +def _appsec_rc_features_is_enabled() -> bool: + if config._remote_config_enabled: + return APPSEC_ENV not in os.environ + return False + + +def _appsec_apisec_features_is_active() -> bool: + return asm_config._asm_enabled and asm_config._api_security_enabled and asm_config._api_security_sample_rate > 0.0 + + +def _safe_userid(user_id): + try: + _ = int(user_id) + return user_id + except ValueError: + try: + _ = uuid.UUID(user_id) + return user_id + except ValueError: + pass + + return None + + +class _UserInfoRetriever: + def __init__(self, user): + self.user = user + self.possible_user_id_fields = ["pk", "id", "uid", "userid", "user_id", "PK", "ID", "UID", "USERID"] + self.possible_login_fields = ["username", "user", "login", "USERNAME", "USER", "LOGIN"] + self.possible_email_fields = ["email", "mail", "address", "EMAIL", "MAIL", "ADDRESS"] + self.possible_name_fields = [ + "name", + "fullname", + "full_name", + "first_name", + "NAME", + "FULLNAME", + "FULL_NAME", + "FIRST_NAME", + ] + + def find_in_user_model(self, possible_fields): + for field in possible_fields: + value = getattr(self.user, field, None) + if value: + return value + + return None # explicit to make clear it has a meaning + + def get_userid(self): + user_login = getattr(self.user, asm_config._user_model_login_field, None) + if user_login: + return user_login + + user_login = self.find_in_user_model(self.possible_user_id_fields) + if config._automatic_login_events_mode == "extended": + return user_login + + return _safe_userid(user_login) + + def get_username(self): + username = getattr(self.user, asm_config._user_model_name_field, None) + if username: + return username + + if hasattr(self.user, "get_username"): + try: + return self.user.get_username() + except Exception: + log.debug("User model get_username member produced an exception: ", exc_info=True) + + return self.find_in_user_model(self.possible_login_fields) + + def get_user_email(self): + email = getattr(self.user, asm_config._user_model_email_field, None) + if email: + return email + + return self.find_in_user_model(self.possible_email_fields) + + def get_name(self): + name = getattr(self.user, asm_config._user_model_name_field, None) + if name: + return name + + return self.find_in_user_model(self.possible_name_fields) + + def get_user_info(self): + """ + In safe mode, try to get the user id from the user object. + In extended mode, try to also get the username (which will be the returned user_id), + email and name. + """ + user_extra_info = {} + + user_id = self.get_userid() + if asm_config._automatic_login_events_mode == "extended": + if not user_id: + user_id = self.find_in_user_model(self.possible_user_id_fields) + + user_extra_info = { + "login": self.get_username(), + "email": self.get_user_email(), + "name": self.get_name(), + } + + if not user_id: + return None, {} + + return user_id, user_extra_info diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/iast/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/iast/__init__.py new file mode 100644 index 0000000..d109e02 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/iast/__init__.py @@ -0,0 +1 @@ +from ddtrace.appsec._iast import ddtrace_iast_flask_patch # noqa: F401 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/rules.json b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/rules.json new file mode 100644 index 0000000..d572c00 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/rules.json @@ -0,0 +1,9320 @@ +{ + "version": "2.2", + "metadata": { + "rules_version": "1.10.0" + }, + "rules": [ + { + "id": "blk-001-001", + "name": "Block IP Addresses", + "tags": { + "type": "block_ip", + "category": "security_response" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "http.client_ip" + } + ], + "data": "blocked_ips" + }, + "operator": "ip_match" + } + ], + "transformers": [], + "on_match": [ + "block" + ] + }, + { + "id": "blk-001-002", + "name": "Block User Addresses", + "tags": { + "type": "block_user", + "category": "security_response" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "usr.id" + } + ], + "data": "blocked_users" + }, + "operator": "exact_match" + } + ], + "transformers": [], + "on_match": [ + "block" + ] + }, + { + "id": "crs-913-110", + "name": "Acunetix", + "tags": { + "type": "commercial_scanner", + "crs_id": "913110", + "category": "attack_attempt", + "tool_name": "Acunetix", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies" + } + ], + "list": [ + "acunetix-product", + "(acunetix web vulnerability scanner", + "acunetix-scanning-agreement", + "acunetix-user-agreement", + "md5(acunetix_wvs_security_test)" + ] + }, + "operator": "phrase_match" + } + ], + "transformers": [ + "lowercase" + ] + }, + { + "id": "crs-913-120", + "name": "Known security scanner filename/argument", + "tags": { + "type": "security_scanner", + "crs_id": "913120", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "list": [ + "/.adsensepostnottherenonobook", + "/hello.html", + "/actsensepostnottherenonotive", + "/acunetix-wvs-test-for-some-inexistent-file", + "/antidisestablishmentarianism", + "/appscan_fingerprint/mac_address", + "/arachni-", + "/cybercop", + "/nessus_is_probing_you_", + "/nessustest", + "/netsparker-", + "/rfiinc.txt", + "/thereisnowaythat-you-canbethere", + "/w3af/remotefileinclude.html", + "appscan_fingerprint", + "w00tw00t.at.isc.sans.dfind", + "w00tw00t.at.blackhats.romanian.anti-sec" + ] + }, + "operator": "phrase_match" + } + ], + "transformers": [ + "lowercase" + ] + }, + { + "id": "crs-920-260", + "name": "Unicode Full/Half Width Abuse Attack Attempt", + "tags": { + "type": "http_protocol_violation", + "crs_id": "920260", + "category": "attack_attempt", + "cwe": "176", + "capec": "1000/255/153/267/71", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.uri.raw" + } + ], + "regex": "\\%u[fF]{2}[0-9a-fA-F]{2}", + "options": { + "case_sensitive": true, + "min_length": 6 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "crs-921-110", + "name": "HTTP Request Smuggling Attack", + "tags": { + "type": "http_protocol_violation", + "crs_id": "921110", + "category": "attack_attempt", + "cwe": "444", + "capec": "1000/210/272/220/33" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + } + ], + "regex": "(?:get|post|head|options|connect|put|delete|trace|track|patch|propfind|propatch|mkcol|copy|move|lock|unlock)\\s+[^\\s]+\\s+http/\\d", + "options": { + "case_sensitive": true, + "min_length": 12 + } + }, + "operator": "match_regex" + } + ], + "transformers": [ + "lowercase" + ] + }, + { + "id": "crs-921-160", + "name": "HTTP Header Injection Attack via payload (CR/LF and header-name detected)", + "tags": { + "type": "http_protocol_violation", + "crs_id": "921160", + "category": "attack_attempt", + "cwe": "113", + "capec": "1000/210/272/220/105" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.path_params" + } + ], + "regex": "[\\n\\r]+(?:refresh|(?:set-)?cookie|(?:x-)?(?:forwarded-(?:for|host|server)|via|remote-ip|remote-addr|originating-IP))\\s*:", + "options": { + "case_sensitive": true, + "min_length": 3 + } + }, + "operator": "match_regex" + } + ], + "transformers": [ + "lowercase" + ] + }, + { + "id": "crs-930-100", + "name": "Obfuscated Path Traversal Attack (/../)", + "tags": { + "type": "lfi", + "crs_id": "930100", + "category": "attack_attempt", + "cwe": "22", + "capec": "1000/255/153/126", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.uri.raw" + }, + { + "address": "server.request.headers.no_cookies" + } + ], + "regex": "(?:%(?:c(?:0%(?:[2aq]f|5c|9v)|1%(?:[19p]c|8s|af))|2(?:5(?:c(?:0%25af|1%259c)|2f|5c)|%46|f)|(?:(?:f(?:8%8)?0%8|e)0%80%a|bg%q)f|%3(?:2(?:%(?:%6|4)6|F)|5%%63)|u(?:221[56]|002f|EFC8|F025)|1u|5c)|0x(?:2f|5c)|\\/|\\x5c)(?:%(?:(?:f(?:(?:c%80|8)%8)?0%8|e)0%80%ae|2(?:(?:5(?:c0%25a|2))?e|%45)|u(?:(?:002|ff0)e|2024)|%32(?:%(?:%6|4)5|E)|c0(?:%[256aef]e|\\.))|\\.(?:%0[01])?|0x2e){2,3}(?:%(?:c(?:0%(?:[2aq]f|5c|9v)|1%(?:[19p]c|8s|af))|2(?:5(?:c(?:0%25af|1%259c)|2f|5c)|%46|f)|(?:(?:f(?:8%8)?0%8|e)0%80%a|bg%q)f|%3(?:2(?:%(?:%6|4)6|F)|5%%63)|u(?:221[56]|002f|EFC8|F025)|1u|5c)|0x(?:2f|5c)|\\/|\\x5c)", + "options": { + "min_length": 4 + } + }, + "operator": "match_regex" + } + ], + "transformers": [ + "normalizePath" + ] + }, + { + "id": "crs-930-110", + "name": "Simple Path Traversal Attack (/../)", + "tags": { + "type": "lfi", + "crs_id": "930110", + "category": "attack_attempt", + "cwe": "22", + "capec": "1000/255/153/126", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.uri.raw" + }, + { + "address": "server.request.headers.no_cookies" + } + ], + "regex": "(?:(?:^|[\\x5c/])\\.{2,3}[\\x5c/]|[\\x5c/]\\.{2,3}(?:[\\x5c/]|$))", + "options": { + "case_sensitive": true, + "min_length": 3 + } + }, + "operator": "match_regex" + } + ], + "transformers": [ + "removeNulls" + ] + }, + { + "id": "crs-930-120", + "name": "OS File Access Attempt", + "tags": { + "type": "lfi", + "crs_id": "930120", + "category": "attack_attempt", + "cwe": "22", + "capec": "1000/255/153/126", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "list": [ + "/.htaccess", + "/.htdigest", + "/.htpasswd", + "/.addressbook", + "/.aptitude/config", + ".aws/config", + ".aws/credentials", + "/.bash_config", + "/.bash_history", + "/.bash_logout", + "/.bash_profile", + "/.bashrc", + ".cache/notify-osd.log", + ".config/odesk/odesk team.conf", + "/.cshrc", + "/.dockerignore", + ".drush/", + "/.eslintignore", + "/.fbcindex", + "/.forward", + "/.git", + ".git/", + "/.gitattributes", + "/.gitconfig", + ".gnupg/", + ".hplip/hplip.conf", + "/.ksh_history", + "/.lesshst", + ".lftp/", + "/.lhistory", + "/.lldb-history", + ".local/share/mc/", + "/.lynx_cookies", + "/.my.cnf", + "/.mysql_history", + "/.nano_history", + "/.node_repl_history", + "/.pearrc", + "/.pgpass", + "/.php_history", + "/.pinerc", + ".pki/", + "/.proclog", + "/.procmailrc", + "/.psql_history", + "/.python_history", + "/.rediscli_history", + "/.rhistory", + "/.rhosts", + "/.sh_history", + "/.sqlite_history", + ".ssh/authorized_keys", + ".ssh/config", + ".ssh/id_dsa", + ".ssh/id_dsa.pub", + ".ssh/id_rsa", + ".ssh/id_rsa.pub", + ".ssh/identity", + ".ssh/identity.pub", + ".ssh/id_ecdsa", + ".ssh/id_ecdsa.pub", + ".ssh/known_hosts", + ".subversion/auth", + ".subversion/config", + ".subversion/servers", + ".tconn/tconn.conf", + "/.tcshrc", + ".vidalia/vidalia.conf", + "/.viminfo", + "/.vimrc", + "/.www_acl", + "/.wwwacl", + "/.xauthority", + "/.zhistory", + "/.zshrc", + "/.zsh_history", + "/.nsconfig", + "data/elasticsearch", + "data/kafka", + "etc/ansible", + "etc/bind", + "etc/centos-release", + "etc/centos-release-upstream", + "etc/clam.d", + "etc/elasticsearch", + "etc/freshclam.conf", + "etc/gshadow", + "etc/gshadow-", + "etc/httpd", + "etc/kafka", + "etc/kibana", + "etc/logstash", + "etc/lvm", + "etc/mongod.conf", + "etc/my.cnf", + "etc/nuxeo.conf", + "etc/pki", + "etc/postfix", + "etc/scw-release", + "etc/subgid", + "etc/subgid-", + "etc/sudoers.d", + "etc/sysconfig", + "etc/system-release-cpe", + "opt/nuxeo", + "opt/tomcat", + "tmp/kafka-logs", + "usr/lib/rpm/rpm.log", + "var/data/elasticsearch", + "var/lib/elasticsearch", + "etc/.java", + "etc/acpi", + "etc/alsa", + "etc/alternatives", + "etc/apache2", + "etc/apm", + "etc/apparmor", + "etc/apparmor.d", + "etc/apport", + "etc/apt", + "etc/asciidoc", + "etc/avahi", + "etc/bash_completion.d", + "etc/binfmt.d", + "etc/bluetooth", + "etc/bonobo-activation", + "etc/brltty", + "etc/ca-certificates", + "etc/calendar", + "etc/chatscripts", + "etc/chromium-browser", + "etc/clamav", + "etc/cni", + "etc/console-setup", + "etc/coraza-waf", + "etc/cracklib", + "etc/cron.d", + "etc/cron.daily", + "etc/cron.hourly", + "etc/cron.monthly", + "etc/cron.weekly", + "etc/cups", + "etc/cups.save", + "etc/cupshelpers", + "etc/dbus-1", + "etc/dconf", + "etc/default", + "etc/depmod.d", + "etc/dhcp", + "etc/dictionaries-common", + "etc/dkms", + "etc/dnsmasq.d", + "etc/dockeretc/dpkg", + "etc/emacs", + "etc/environment.d", + "etc/fail2ban", + "etc/firebird", + "etc/firefox", + "etc/fonts", + "etc/fwupd", + "etc/gconf", + "etc/gdb", + "etc/gdm3", + "etc/geoclue", + "etc/ghostscript", + "etc/gimp", + "etc/glvnd", + "etc/gnome", + "etc/gnome-vfs-2.0", + "etc/gnucash", + "etc/gnustep", + "etc/groff", + "etc/grub.d", + "etc/gss", + "etc/gtk-2.0", + "etc/gtk-3.0", + "etc/hp", + "etc/ifplugd", + "etc/imagemagick-6", + "etc/init", + "etc/init.d", + "etc/initramfs-tools", + "etc/insserv.conf.d", + "etc/iproute2", + "etc/iptables", + "etc/java", + "etc/java-11-openjdk", + "etc/java-17-oracle", + "etc/java-8-openjdk", + "etc/kernel", + "etc/ld.so.conf.d", + "etc/ldap", + "etc/libblockdev", + "etc/libibverbs.d", + "etc/libnl-3", + "etc/libpaper.d", + "etc/libreoffice", + "etc/lighttpd", + "etc/logcheck", + "etc/logrotate.d", + "etc/lynx", + "etc/mail", + "etc/mc", + "etc/menu", + "etc/menu-methods", + "etc/modprobe.d", + "etc/modsecurity", + "etc/modules-load.d", + "etc/monit", + "etc/mono", + "etc/mplayer", + "etc/mpv", + "etc/muttrc.d", + "etc/mysql", + "etc/netplan", + "etc/network", + "etc/networkd-dispatcher", + "etc/networkmanager", + "etc/newt", + "etc/nghttpx", + "etc/nikto", + "etc/odbcdatasources", + "etc/openal", + "etc/openmpi", + "etc/opt", + "etc/osync", + "etc/packagekit", + "etc/pam.d", + "etc/pcmcia", + "etc/perl", + "etc/php", + "etc/pki", + "etc/pm", + "etc/polkit-1", + "etc/postfix", + "etc/ppp", + "etc/profile.d", + "etc/proftpd", + "etc/pulse", + "etc/python", + "etc/rc0.d", + "etc/rc1.d", + "etc/rc2.d", + "etc/rc3.d", + "etc/rc4.d", + "etc/rc5.d", + "etc/rc6.d", + "etc/rcs.d", + "etc/resolvconf", + "etc/rsyslog.d", + "etc/samba", + "etc/sane.d", + "etc/security", + "etc/selinux", + "etc/sensors.d", + "etc/sgml", + "etc/signon-ui", + "etc/skel", + "etc/snmp", + "etc/sound", + "etc/spamassassin", + "etc/speech-dispatcher", + "etc/ssh", + "etc/ssl", + "etc/sudoers.d", + "etc/sysctl.d", + "etc/sysstat", + "etc/systemd", + "etc/terminfo", + "etc/texmf", + "etc/thermald", + "etc/thnuclnt", + "etc/thunderbird", + "etc/timidity", + "etc/tmpfiles.d", + "etc/ubuntu-advantage", + "etc/udev", + "etc/udisks2", + "etc/ufw", + "etc/update-manager", + "etc/update-motd.d", + "etc/update-notifier", + "etc/upower", + "etc/urlview", + "etc/usb_modeswitch.d", + "etc/vim", + "etc/vmware", + "etc/vmware-installer", + "etc/vmware-vix", + "etc/vulkan", + "etc/w3m", + "etc/wireshark", + "etc/wpa_supplicant", + "etc/x11", + "etc/xdg", + "etc/xml", + "etc/redis.conf", + "etc/redis-sentinel.conf", + "etc/php.ini", + "bin/php.ini", + "etc/httpd/php.ini", + "usr/lib/php.ini", + "usr/lib/php/php.ini", + "usr/local/etc/php.ini", + "usr/local/lib/php.ini", + "usr/local/php/lib/php.ini", + "usr/local/php4/lib/php.ini", + "usr/local/php5/lib/php.ini", + "usr/local/apache/conf/php.ini", + "etc/php4.4/fcgi/php.ini", + "etc/php4/apache/php.ini", + "etc/php4/apache2/php.ini", + "etc/php5/apache/php.ini", + "etc/php5/apache2/php.ini", + "etc/php/php.ini", + "etc/php/php4/php.ini", + "etc/php/apache/php.ini", + "etc/php/apache2/php.ini", + "web/conf/php.ini", + "usr/local/zend/etc/php.ini", + "opt/xampp/etc/php.ini", + "var/local/www/conf/php.ini", + "etc/php/cgi/php.ini", + "etc/php4/cgi/php.ini", + "etc/php5/cgi/php.ini", + "home2/bin/stable/apache/php.ini", + "home/bin/stable/apache/php.ini", + "etc/httpd/conf.d/php.conf", + "php5/php.ini", + "php4/php.ini", + "php/php.ini", + "windows/php.ini", + "winnt/php.ini", + "apache/php/php.ini", + "xampp/apache/bin/php.ini", + "netserver/bin/stable/apache/php.ini", + "volumes/macintosh_hd1/usr/local/php/lib/php.ini", + "etc/mono/1.0/machine.config", + "etc/mono/2.0/machine.config", + "etc/mono/2.0/web.config", + "etc/mono/config", + "usr/local/cpanel/logs/stats_log", + "usr/local/cpanel/logs/access_log", + "usr/local/cpanel/logs/error_log", + "usr/local/cpanel/logs/license_log", + "usr/local/cpanel/logs/login_log", + "var/cpanel/cpanel.config", + "usr/local/psa/admin/logs/httpsd_access_log", + "usr/local/psa/admin/logs/panel.log", + "usr/local/psa/admin/conf/php.ini", + "etc/sw-cp-server/applications.d/plesk.conf", + "usr/local/psa/admin/conf/site_isolation_settings.ini", + "usr/local/sb/config", + "etc/sw-cp-server/applications.d/00-sso-cpserver.conf", + "etc/sso/sso_config.ini", + "etc/mysql/conf.d/old_passwords.cnf", + "var/mysql.log", + "var/mysql-bin.index", + "var/data/mysql-bin.index", + "program files/mysql/mysql server 5.0/data/{host}.err", + "program files/mysql/mysql server 5.0/data/mysql.log", + "program files/mysql/mysql server 5.0/data/mysql.err", + "program files/mysql/mysql server 5.0/data/mysql-bin.log", + "program files/mysql/mysql server 5.0/data/mysql-bin.index", + "program files/mysql/data/{host}.err", + "program files/mysql/data/mysql.log", + "program files/mysql/data/mysql.err", + "program files/mysql/data/mysql-bin.log", + "program files/mysql/data/mysql-bin.index", + "mysql/data/{host}.err", + "mysql/data/mysql.log", + "mysql/data/mysql.err", + "mysql/data/mysql-bin.log", + "mysql/data/mysql-bin.index", + "usr/local/mysql/data/mysql.log", + "usr/local/mysql/data/mysql.err", + "usr/local/mysql/data/mysql-bin.log", + "usr/local/mysql/data/mysql-slow.log", + "usr/local/mysql/data/mysqlderror.log", + "usr/local/mysql/data/{host}.err", + "usr/local/mysql/data/mysql-bin.index", + "var/lib/mysql/my.cnf", + "etc/mysql/my.cnf", + "etc/my.cnf", + "program files/mysql/mysql server 5.0/my.ini", + "program files/mysql/mysql server 5.0/my.cnf", + "program files/mysql/my.ini", + "program files/mysql/my.cnf", + "mysql/my.ini", + "mysql/my.cnf", + "mysql/bin/my.ini", + "var/postgresql/log/postgresql.log", + "usr/internet/pgsql/data/postmaster.log", + "usr/local/pgsql/data/postgresql.log", + "usr/local/pgsql/data/pg_log", + "postgresql/log/pgadmin.log", + "var/lib/pgsql/data/postgresql.conf", + "var/postgresql/db/postgresql.conf", + "var/nm2/postgresql.conf", + "usr/local/pgsql/data/postgresql.conf", + "usr/local/pgsql/data/pg_hba.conf", + "usr/internet/pgsql/data/pg_hba.conf", + "usr/local/pgsql/data/passwd", + "usr/local/pgsql/bin/pg_passwd", + "etc/postgresql/postgresql.conf", + "etc/postgresql/pg_hba.conf", + "home/postgres/data/postgresql.conf", + "home/postgres/data/pg_version", + "home/postgres/data/pg_ident.conf", + "home/postgres/data/pg_hba.conf", + "program files/postgresql/8.3/data/pg_hba.conf", + "program files/postgresql/8.3/data/pg_ident.conf", + "program files/postgresql/8.3/data/postgresql.conf", + "program files/postgresql/8.4/data/pg_hba.conf", + "program files/postgresql/8.4/data/pg_ident.conf", + "program files/postgresql/8.4/data/postgresql.conf", + "program files/postgresql/9.0/data/pg_hba.conf", + "program files/postgresql/9.0/data/pg_ident.conf", + "program files/postgresql/9.0/data/postgresql.conf", + "program files/postgresql/9.1/data/pg_hba.conf", + "program files/postgresql/9.1/data/pg_ident.conf", + "program files/postgresql/9.1/data/postgresql.conf", + "wamp/logs/access.log", + "wamp/logs/apache_error.log", + "wamp/logs/genquery.log", + "wamp/logs/mysql.log", + "wamp/logs/slowquery.log", + "wamp/bin/apache/apache2.2.22/logs/access.log", + "wamp/bin/apache/apache2.2.22/logs/error.log", + "wamp/bin/apache/apache2.2.21/logs/access.log", + "wamp/bin/apache/apache2.2.21/logs/error.log", + "wamp/bin/mysql/mysql5.5.24/data/mysql-bin.index", + "wamp/bin/mysql/mysql5.5.16/data/mysql-bin.index", + "wamp/bin/apache/apache2.2.21/conf/httpd.conf", + "wamp/bin/apache/apache2.2.22/conf/httpd.conf", + "wamp/bin/apache/apache2.2.21/wampserver.conf", + "wamp/bin/apache/apache2.2.22/wampserver.conf", + "wamp/bin/apache/apache2.2.22/conf/wampserver.conf", + "wamp/bin/mysql/mysql5.5.24/my.ini", + "wamp/bin/mysql/mysql5.5.24/wampserver.conf", + "wamp/bin/mysql/mysql5.5.16/my.ini", + "wamp/bin/mysql/mysql5.5.16/wampserver.conf", + "wamp/bin/php/php5.3.8/php.ini", + "wamp/bin/php/php5.4.3/php.ini", + "xampp/apache/logs/access.log", + "xampp/apache/logs/error.log", + "xampp/mysql/data/mysql-bin.index", + "xampp/mysql/data/mysql.err", + "xampp/mysql/data/{host}.err", + "xampp/sendmail/sendmail.log", + "xampp/apache/conf/httpd.conf", + "xampp/filezillaftp/filezilla server.xml", + "xampp/mercurymail/mercury.ini", + "xampp/php/php.ini", + "xampp/phpmyadmin/config.inc.php", + "xampp/sendmail/sendmail.ini", + "xampp/webalizer/webalizer.conf", + "opt/lampp/etc/httpd.conf", + "xampp/htdocs/aca.txt", + "xampp/htdocs/admin.php", + "xampp/htdocs/leer.txt", + "usr/local/apache/logs/audit_log", + "usr/local/apache2/logs/audit_log", + "logs/security_debug_log", + "logs/security_log", + "usr/local/apache/conf/modsec.conf", + "usr/local/apache2/conf/modsec.conf", + "winnt/system32/logfiles/msftpsvc", + "winnt/system32/logfiles/msftpsvc1", + "winnt/system32/logfiles/msftpsvc2", + "windows/system32/logfiles/msftpsvc", + "windows/system32/logfiles/msftpsvc1", + "windows/system32/logfiles/msftpsvc2", + "etc/logrotate.d/proftpd", + "www/logs/proftpd.system.log", + "etc/pam.d/proftpd", + "etc/proftp.conf", + "etc/protpd/proftpd.conf", + "etc/vhcs2/proftpd/proftpd.conf", + "etc/proftpd/modules.conf", + "etc/vsftpd.chroot_list", + "etc/logrotate.d/vsftpd.log", + "etc/vsftpd/vsftpd.conf", + "etc/vsftpd.conf", + "etc/chrootusers", + "var/adm/log/xferlog", + "etc/wu-ftpd/ftpaccess", + "etc/wu-ftpd/ftphosts", + "etc/wu-ftpd/ftpusers", + "logs/pure-ftpd.log", + "usr/sbin/pure-config.pl", + "usr/etc/pure-ftpd.conf", + "etc/pure-ftpd/pure-ftpd.conf", + "usr/local/etc/pure-ftpd.conf", + "usr/local/etc/pureftpd.pdb", + "usr/local/pureftpd/etc/pureftpd.pdb", + "usr/local/pureftpd/sbin/pure-config.pl", + "usr/local/pureftpd/etc/pure-ftpd.conf", + "etc/pure-ftpd.conf", + "etc/pure-ftpd/pure-ftpd.pdb", + "etc/pureftpd.pdb", + "etc/pureftpd.passwd", + "etc/pure-ftpd/pureftpd.pdb", + "usr/ports/ftp/pure-ftpd/pure-ftpd.conf", + "usr/ports/ftp/pure-ftpd/pureftpd.pdb", + "usr/ports/ftp/pure-ftpd/pureftpd.passwd", + "usr/ports/net/pure-ftpd/pure-ftpd.conf", + "usr/ports/net/pure-ftpd/pureftpd.pdb", + "usr/ports/net/pure-ftpd/pureftpd.passwd", + "usr/pkgsrc/net/pureftpd/pure-ftpd.conf", + "usr/pkgsrc/net/pureftpd/pureftpd.pdb", + "usr/pkgsrc/net/pureftpd/pureftpd.passwd", + "usr/ports/contrib/pure-ftpd/pure-ftpd.conf", + "usr/ports/contrib/pure-ftpd/pureftpd.pdb", + "usr/ports/contrib/pure-ftpd/pureftpd.passwd", + "usr/sbin/mudlogd", + "etc/muddleftpd/mudlog", + "etc/muddleftpd.com", + "etc/muddleftpd/mudlogd.conf", + "etc/muddleftpd/muddleftpd.conf", + "usr/sbin/mudpasswd", + "etc/muddleftpd/muddleftpd.passwd", + "etc/muddleftpd/passwd", + "etc/logrotate.d/ftp", + "etc/ftpchroot", + "etc/ftphosts", + "etc/ftpusers", + "winnt/system32/logfiles/smtpsvc", + "winnt/system32/logfiles/smtpsvc1", + "winnt/system32/logfiles/smtpsvc2", + "winnt/system32/logfiles/smtpsvc3", + "winnt/system32/logfiles/smtpsvc4", + "winnt/system32/logfiles/smtpsvc5", + "windows/system32/logfiles/smtpsvc", + "windows/system32/logfiles/smtpsvc1", + "windows/system32/logfiles/smtpsvc2", + "windows/system32/logfiles/smtpsvc3", + "windows/system32/logfiles/smtpsvc4", + "windows/system32/logfiles/smtpsvc5", + "etc/osxhttpd/osxhttpd.conf", + "system/library/webobjects/adaptors/apache2.2/apache.conf", + "etc/apache2/sites-available/default", + "etc/apache2/sites-available/default-ssl", + "etc/apache2/sites-enabled/000-default", + "etc/apache2/sites-enabled/default", + "etc/apache2/apache2.conf", + "etc/apache2/ports.conf", + "usr/local/etc/apache/httpd.conf", + "usr/pkg/etc/httpd/httpd.conf", + "usr/pkg/etc/httpd/httpd-default.conf", + "usr/pkg/etc/httpd/httpd-vhosts.conf", + "etc/httpd/mod_php.conf", + "etc/httpd/extra/httpd-ssl.conf", + "etc/rc.d/rc.httpd", + "usr/local/apache/conf/httpd.conf.default", + "usr/local/apache/conf/access.conf", + "usr/local/apache22/conf/httpd.conf", + "usr/local/apache22/httpd.conf", + "usr/local/etc/apache22/conf/httpd.conf", + "usr/local/apps/apache22/conf/httpd.conf", + "etc/apache22/conf/httpd.conf", + "etc/apache22/httpd.conf", + "opt/apache22/conf/httpd.conf", + "usr/local/etc/apache2/vhosts.conf", + "usr/local/apache/conf/vhosts.conf", + "usr/local/apache2/conf/vhosts.conf", + "usr/local/apache/conf/vhosts-custom.conf", + "usr/local/apache2/conf/vhosts-custom.conf", + "etc/apache/default-server.conf", + "etc/apache2/default-server.conf", + "usr/local/apache2/conf/extra/httpd-ssl.conf", + "usr/local/apache2/conf/ssl.conf", + "etc/httpd/conf.d", + "usr/local/etc/apache22/httpd.conf", + "usr/local/etc/apache2/httpd.conf", + "etc/apache2/httpd2.conf", + "etc/apache2/ssl-global.conf", + "etc/apache2/vhosts.d/00_default_vhost.conf", + "apache/conf/httpd.conf", + "etc/apache/httpd.conf", + "etc/httpd/conf", + "http/httpd.conf", + "usr/local/apache1.3/conf/httpd.conf", + "usr/local/etc/httpd/conf", + "var/apache/conf/httpd.conf", + "var/www/conf", + "www/apache/conf/httpd.conf", + "www/conf/httpd.conf", + "etc/init.d", + "etc/apache/access.conf", + "etc/rc.conf", + "www/logs/freebsddiary-error.log", + "www/logs/freebsddiary-access_log", + "library/webserver/documents/index.html", + "library/webserver/documents/index.htm", + "library/webserver/documents/default.html", + "library/webserver/documents/default.htm", + "library/webserver/documents/index.php", + "library/webserver/documents/default.php", + "usr/local/etc/webmin/miniserv.conf", + "etc/webmin/miniserv.conf", + "usr/local/etc/webmin/miniserv.users", + "etc/webmin/miniserv.users", + "winnt/system32/logfiles/w3svc/inetsvn1.log", + "winnt/system32/logfiles/w3svc1/inetsvn1.log", + "winnt/system32/logfiles/w3svc2/inetsvn1.log", + "winnt/system32/logfiles/w3svc3/inetsvn1.log", + "windows/system32/logfiles/w3svc/inetsvn1.log", + "windows/system32/logfiles/w3svc1/inetsvn1.log", + "windows/system32/logfiles/w3svc2/inetsvn1.log", + "windows/system32/logfiles/w3svc3/inetsvn1.log", + "apache/logs/error.log", + "apache/logs/access.log", + "apache2/logs/error.log", + "apache2/logs/access.log", + "logs/error.log", + "logs/access.log", + "etc/httpd/logs/access_log", + "etc/httpd/logs/access.log", + "etc/httpd/logs/error_log", + "etc/httpd/logs/error.log", + "usr/local/apache/logs/access_log", + "usr/local/apache/logs/access.log", + "usr/local/apache/logs/error_log", + "usr/local/apache/logs/error.log", + "usr/local/apache2/logs/access_log", + "usr/local/apache2/logs/access.log", + "usr/local/apache2/logs/error_log", + "usr/local/apache2/logs/error.log", + "var/www/logs/access_log", + "var/www/logs/access.log", + "var/www/logs/error_log", + "var/www/logs/error.log", + "opt/lampp/logs/access_log", + "opt/lampp/logs/error_log", + "opt/xampp/logs/access_log", + "opt/xampp/logs/error_log", + "opt/lampp/logs/access.log", + "opt/lampp/logs/error.log", + "opt/xampp/logs/access.log", + "opt/xampp/logs/error.log", + "program files/apache group/apache/logs/access.log", + "program files/apache group/apache/logs/error.log", + "program files/apache software foundation/apache2.2/logs/error.log", + "program files/apache software foundation/apache2.2/logs/access.log", + "opt/apache/apache.conf", + "opt/apache/conf/apache.conf", + "opt/apache2/apache.conf", + "opt/apache2/conf/apache.conf", + "opt/httpd/apache.conf", + "opt/httpd/conf/apache.conf", + "etc/httpd/apache.conf", + "etc/apache2/apache.conf", + "etc/httpd/conf/apache.conf", + "usr/local/apache/apache.conf", + "usr/local/apache/conf/apache.conf", + "usr/local/apache2/apache.conf", + "usr/local/apache2/conf/apache.conf", + "usr/local/php/apache.conf.php", + "usr/local/php4/apache.conf.php", + "usr/local/php5/apache.conf.php", + "usr/local/php/apache.conf", + "usr/local/php4/apache.conf", + "usr/local/php5/apache.conf", + "private/etc/httpd/apache.conf", + "opt/apache/apache2.conf", + "opt/apache/conf/apache2.conf", + "opt/apache2/apache2.conf", + "opt/apache2/conf/apache2.conf", + "opt/httpd/apache2.conf", + "opt/httpd/conf/apache2.conf", + "etc/httpd/apache2.conf", + "etc/httpd/conf/apache2.conf", + "usr/local/apache/apache2.conf", + "usr/local/apache/conf/apache2.conf", + "usr/local/apache2/apache2.conf", + "usr/local/apache2/conf/apache2.conf", + "usr/local/php/apache2.conf.php", + "usr/local/php4/apache2.conf.php", + "usr/local/php5/apache2.conf.php", + "usr/local/php/apache2.conf", + "usr/local/php4/apache2.conf", + "usr/local/php5/apache2.conf", + "private/etc/httpd/apache2.conf", + "usr/local/apache/conf/httpd.conf", + "usr/local/apache2/conf/httpd.conf", + "etc/httpd/conf/httpd.conf", + "etc/apache/apache.conf", + "etc/apache/conf/httpd.conf", + "etc/apache2/httpd.conf", + "usr/apache2/conf/httpd.conf", + "usr/apache/conf/httpd.conf", + "usr/local/etc/apache/conf/httpd.conf", + "usr/local/apache/httpd.conf", + "usr/local/apache2/httpd.conf", + "usr/local/httpd/conf/httpd.conf", + "usr/local/etc/apache2/conf/httpd.conf", + "usr/local/etc/httpd/conf/httpd.conf", + "usr/local/apps/apache2/conf/httpd.conf", + "usr/local/apps/apache/conf/httpd.conf", + "usr/local/php/httpd.conf.php", + "usr/local/php4/httpd.conf.php", + "usr/local/php5/httpd.conf.php", + "usr/local/php/httpd.conf", + "usr/local/php4/httpd.conf", + "usr/local/php5/httpd.conf", + "etc/apache2/conf/httpd.conf", + "etc/http/conf/httpd.conf", + "etc/httpd/httpd.conf", + "etc/http/httpd.conf", + "etc/httpd.conf", + "opt/apache/conf/httpd.conf", + "opt/apache2/conf/httpd.conf", + "var/www/conf/httpd.conf", + "private/etc/httpd/httpd.conf", + "private/etc/httpd/httpd.conf.default", + "etc/apache2/vhosts.d/default_vhost.include", + "etc/apache2/conf.d/charset", + "etc/apache2/conf.d/security", + "etc/apache2/envvars", + "etc/apache2/mods-available/autoindex.conf", + "etc/apache2/mods-available/deflate.conf", + "etc/apache2/mods-available/dir.conf", + "etc/apache2/mods-available/mem_cache.conf", + "etc/apache2/mods-available/mime.conf", + "etc/apache2/mods-available/proxy.conf", + "etc/apache2/mods-available/setenvif.conf", + "etc/apache2/mods-available/ssl.conf", + "etc/apache2/mods-enabled/alias.conf", + "etc/apache2/mods-enabled/deflate.conf", + "etc/apache2/mods-enabled/dir.conf", + "etc/apache2/mods-enabled/mime.conf", + "etc/apache2/mods-enabled/negotiation.conf", + "etc/apache2/mods-enabled/php5.conf", + "etc/apache2/mods-enabled/status.conf", + "program files/apache group/apache/conf/httpd.conf", + "program files/apache group/apache2/conf/httpd.conf", + "program files/xampp/apache/conf/apache.conf", + "program files/xampp/apache/conf/apache2.conf", + "program files/xampp/apache/conf/httpd.conf", + "program files/apache group/apache/apache.conf", + "program files/apache group/apache/conf/apache.conf", + "program files/apache group/apache2/conf/apache.conf", + "program files/apache group/apache/apache2.conf", + "program files/apache group/apache/conf/apache2.conf", + "program files/apache group/apache2/conf/apache2.conf", + "program files/apache software foundation/apache2.2/conf/httpd.conf", + "volumes/macintosh_hd1/opt/httpd/conf/httpd.conf", + "volumes/macintosh_hd1/opt/apache/conf/httpd.conf", + "volumes/macintosh_hd1/opt/apache2/conf/httpd.conf", + "volumes/macintosh_hd1/usr/local/php/httpd.conf.php", + "volumes/macintosh_hd1/usr/local/php4/httpd.conf.php", + "volumes/macintosh_hd1/usr/local/php5/httpd.conf.php", + "volumes/webbackup/opt/apache2/conf/httpd.conf", + "volumes/webbackup/private/etc/httpd/httpd.conf", + "volumes/webbackup/private/etc/httpd/httpd.conf.default", + "usr/local/etc/apache/vhosts.conf", + "usr/local/jakarta/tomcat/conf/jakarta.conf", + "usr/local/jakarta/tomcat/conf/server.xml", + "usr/local/jakarta/tomcat/conf/context.xml", + "usr/local/jakarta/tomcat/conf/workers.properties", + "usr/local/jakarta/tomcat/conf/logging.properties", + "usr/local/jakarta/dist/tomcat/conf/jakarta.conf", + "usr/local/jakarta/dist/tomcat/conf/server.xml", + "usr/local/jakarta/dist/tomcat/conf/context.xml", + "usr/local/jakarta/dist/tomcat/conf/workers.properties", + "usr/local/jakarta/dist/tomcat/conf/logging.properties", + "usr/share/tomcat6/conf/server.xml", + "usr/share/tomcat6/conf/context.xml", + "usr/share/tomcat6/conf/workers.properties", + "usr/share/tomcat6/conf/logging.properties", + "var/cpanel/tomcat.options", + "usr/local/jakarta/tomcat/logs/catalina.out", + "usr/local/jakarta/tomcat/logs/catalina.err", + "opt/tomcat/logs/catalina.out", + "opt/tomcat/logs/catalina.err", + "usr/share/logs/catalina.out", + "usr/share/logs/catalina.err", + "usr/share/tomcat/logs/catalina.out", + "usr/share/tomcat/logs/catalina.err", + "usr/share/tomcat6/logs/catalina.out", + "usr/share/tomcat6/logs/catalina.err", + "usr/local/apache/logs/mod_jk.log", + "usr/local/jakarta/tomcat/logs/mod_jk.log", + "usr/local/jakarta/dist/tomcat/logs/mod_jk.log", + "opt/[jboss]/server/default/conf/jboss-minimal.xml", + "opt/[jboss]/server/default/conf/jboss-service.xml", + "opt/[jboss]/server/default/conf/jndi.properties", + "opt/[jboss]/server/default/conf/log4j.xml", + "opt/[jboss]/server/default/conf/login-config.xml", + "opt/[jboss]/server/default/conf/standardjaws.xml", + "opt/[jboss]/server/default/conf/standardjboss.xml", + "opt/[jboss]/server/default/conf/server.log.properties", + "opt/[jboss]/server/default/deploy/jboss-logging.xml", + "usr/local/[jboss]/server/default/conf/jboss-minimal.xml", + "usr/local/[jboss]/server/default/conf/jboss-service.xml", + "usr/local/[jboss]/server/default/conf/jndi.properties", + "usr/local/[jboss]/server/default/conf/log4j.xml", + "usr/local/[jboss]/server/default/conf/login-config.xml", + "usr/local/[jboss]/server/default/conf/standardjaws.xml", + "usr/local/[jboss]/server/default/conf/standardjboss.xml", + "usr/local/[jboss]/server/default/conf/server.log.properties", + "usr/local/[jboss]/server/default/deploy/jboss-logging.xml", + "private/tmp/[jboss]/server/default/conf/jboss-minimal.xml", + "private/tmp/[jboss]/server/default/conf/jboss-service.xml", + "private/tmp/[jboss]/server/default/conf/jndi.properties", + "private/tmp/[jboss]/server/default/conf/log4j.xml", + "private/tmp/[jboss]/server/default/conf/login-config.xml", + "private/tmp/[jboss]/server/default/conf/standardjaws.xml", + "private/tmp/[jboss]/server/default/conf/standardjboss.xml", + "private/tmp/[jboss]/server/default/conf/server.log.properties", + "private/tmp/[jboss]/server/default/deploy/jboss-logging.xml", + "tmp/[jboss]/server/default/conf/jboss-minimal.xml", + "tmp/[jboss]/server/default/conf/jboss-service.xml", + "tmp/[jboss]/server/default/conf/jndi.properties", + "tmp/[jboss]/server/default/conf/log4j.xml", + "tmp/[jboss]/server/default/conf/login-config.xml", + "tmp/[jboss]/server/default/conf/standardjaws.xml", + "tmp/[jboss]/server/default/conf/standardjboss.xml", + "tmp/[jboss]/server/default/conf/server.log.properties", + "tmp/[jboss]/server/default/deploy/jboss-logging.xml", + "program files/[jboss]/server/default/conf/jboss-minimal.xml", + "program files/[jboss]/server/default/conf/jboss-service.xml", + "program files/[jboss]/server/default/conf/jndi.properties", + "program files/[jboss]/server/default/conf/log4j.xml", + "program files/[jboss]/server/default/conf/login-config.xml", + "program files/[jboss]/server/default/conf/standardjaws.xml", + "program files/[jboss]/server/default/conf/standardjboss.xml", + "program files/[jboss]/server/default/conf/server.log.properties", + "program files/[jboss]/server/default/deploy/jboss-logging.xml", + "[jboss]/server/default/conf/jboss-minimal.xml", + "[jboss]/server/default/conf/jboss-service.xml", + "[jboss]/server/default/conf/jndi.properties", + "[jboss]/server/default/conf/log4j.xml", + "[jboss]/server/default/conf/login-config.xml", + "[jboss]/server/default/conf/standardjaws.xml", + "[jboss]/server/default/conf/standardjboss.xml", + "[jboss]/server/default/conf/server.log.properties", + "[jboss]/server/default/deploy/jboss-logging.xml", + "opt/[jboss]/server/default/log/server.log", + "opt/[jboss]/server/default/log/boot.log", + "usr/local/[jboss]/server/default/log/server.log", + "usr/local/[jboss]/server/default/log/boot.log", + "private/tmp/[jboss]/server/default/log/server.log", + "private/tmp/[jboss]/server/default/log/boot.log", + "tmp/[jboss]/server/default/log/server.log", + "tmp/[jboss]/server/default/log/boot.log", + "program files/[jboss]/server/default/log/server.log", + "program files/[jboss]/server/default/log/boot.log", + "[jboss]/server/default/log/server.log", + "[jboss]/server/default/log/boot.log", + "var/lighttpd.log", + "var/logs/access.log", + "usr/local/apache2/logs/lighttpd.error.log", + "usr/local/apache2/logs/lighttpd.log", + "usr/local/apache/logs/lighttpd.error.log", + "usr/local/apache/logs/lighttpd.log", + "usr/local/lighttpd/log/lighttpd.error.log", + "usr/local/lighttpd/log/access.log", + "usr/home/user/var/log/lighttpd.error.log", + "usr/home/user/var/log/apache.log", + "home/user/lighttpd/lighttpd.conf", + "usr/home/user/lighttpd/lighttpd.conf", + "etc/lighttpd/lighthttpd.conf", + "usr/local/etc/lighttpd.conf", + "usr/local/lighttpd/conf/lighttpd.conf", + "usr/local/etc/lighttpd.conf.new", + "var/www/.lighttpdpassword", + "logs/access_log", + "logs/error_log", + "etc/nginx/nginx.conf", + "usr/local/etc/nginx/nginx.conf", + "usr/local/nginx/conf/nginx.conf", + "usr/local/zeus/web/global.cfg", + "usr/local/zeus/web/log/errors", + "opt/lsws/conf/httpd_conf.xml", + "usr/local/lsws/conf/httpd_conf.xml", + "opt/lsws/logs/error.log", + "opt/lsws/logs/access.log", + "usr/local/lsws/logs/error.log", + "usr/local/logs/access.log", + "usr/local/samba/lib/log.user", + "usr/local/logs/samba.log", + "etc/samba/netlogon", + "etc/smbpasswd", + "etc/smb.conf", + "etc/samba/dhcp.conf", + "etc/samba/smb.conf", + "etc/samba/samba.conf", + "etc/samba/smb.conf.user", + "etc/samba/smbpasswd", + "etc/samba/smbusers", + "etc/samba/private/smbpasswd", + "usr/local/etc/smb.conf", + "usr/local/samba/lib/smb.conf.user", + "etc/dhcp3/dhclient.conf", + "etc/dhcp3/dhcpd.conf", + "etc/dhcp/dhclient.conf", + "program files/vidalia bundle/polipo/polipo.conf", + "etc/tor/tor-tsocks.conf", + "etc/stunnel/stunnel.conf", + "etc/tsocks.conf", + "etc/tinyproxy/tinyproxy.conf", + "etc/miredo-server.conf", + "etc/miredo.conf", + "etc/miredo/miredo-server.conf", + "etc/miredo/miredo.conf", + "etc/wicd/dhclient.conf.template.default", + "etc/wicd/manager-settings.conf", + "etc/wicd/wired-settings.conf", + "etc/wicd/wireless-settings.conf", + "etc/ipfw.rules", + "etc/ipfw.conf", + "etc/firewall.rules", + "winnt/system32/logfiles/firewall/pfirewall.log", + "winnt/system32/logfiles/firewall/pfirewall.log.old", + "windows/system32/logfiles/firewall/pfirewall.log", + "windows/system32/logfiles/firewall/pfirewall.log.old", + "etc/clamav/clamd.conf", + "etc/clamav/freshclam.conf", + "etc/x11/xorg.conf", + "etc/x11/xorg.conf-vesa", + "etc/x11/xorg.conf-vmware", + "etc/x11/xorg.conf.beforevmwaretoolsinstall", + "etc/x11/xorg.conf.orig", + "etc/bluetooth/input.conf", + "etc/bluetooth/main.conf", + "etc/bluetooth/network.conf", + "etc/bluetooth/rfcomm.conf", + "etc/bash_completion.d/debconf", + "root/.bash_logout", + "root/.bash_history", + "root/.bash_config", + "root/.bashrc", + "etc/bash.bashrc", + "var/adm/syslog", + "var/adm/sulog", + "var/adm/utmp", + "var/adm/utmpx", + "var/adm/wtmp", + "var/adm/wtmpx", + "var/adm/lastlog/username", + "usr/spool/lp/log", + "var/adm/lp/lpd-errs", + "usr/lib/cron/log", + "var/adm/loginlog", + "var/adm/pacct", + "var/adm/dtmp", + "var/adm/acct/sum/loginlog", + "var/adm/x0msgs", + "var/adm/crash/vmcore", + "var/adm/crash/unix", + "etc/newsyslog.conf", + "var/adm/qacct", + "var/adm/ras/errlog", + "var/adm/ras/bootlog", + "var/adm/cron/log", + "etc/utmp", + "etc/security/lastlog", + "etc/security/failedlogin", + "usr/spool/mqueue/syslog", + "var/adm/messages", + "var/adm/aculogs", + "var/adm/aculog", + "var/adm/vold.log", + "var/adm/log/asppp.log", + "var/lp/logs/lpsched", + "var/lp/logs/lpnet", + "var/lp/logs/requests", + "var/cron/log", + "var/saf/_log", + "var/saf/port/log", + "tmp/access.log", + "etc/sensors.conf", + "etc/sensors3.conf", + "etc/host.conf", + "etc/pam.conf", + "etc/resolv.conf", + "etc/apt/apt.conf", + "etc/inetd.conf", + "etc/syslog.conf", + "etc/sysctl.conf", + "etc/sysctl.d/10-console-messages.conf", + "etc/sysctl.d/10-network-security.conf", + "etc/sysctl.d/10-process-security.conf", + "etc/sysctl.d/wine.sysctl.conf", + "etc/security/access.conf", + "etc/security/group.conf", + "etc/security/limits.conf", + "etc/security/namespace.conf", + "etc/security/pam_env.conf", + "etc/security/sepermit.conf", + "etc/security/time.conf", + "etc/ssh/sshd_config", + "etc/adduser.conf", + "etc/deluser.conf", + "etc/avahi/avahi-daemon.conf", + "etc/ca-certificates.conf", + "etc/ca-certificates.conf.dpkg-old", + "etc/casper.conf", + "etc/chkrootkit.conf", + "etc/debconf.conf", + "etc/dns2tcpd.conf", + "etc/e2fsck.conf", + "etc/esound/esd.conf", + "etc/etter.conf", + "etc/fuse.conf", + "etc/foremost.conf", + "etc/hdparm.conf", + "etc/kernel-img.conf", + "etc/kernel-pkg.conf", + "etc/ld.so.conf", + "etc/ltrace.conf", + "etc/mail/sendmail.conf", + "etc/manpath.config", + "etc/kbd/config", + "etc/ldap/ldap.conf", + "etc/logrotate.conf", + "etc/mtools.conf", + "etc/smi.conf", + "etc/updatedb.conf", + "etc/pulse/client.conf", + "usr/share/adduser/adduser.conf", + "etc/hostname", + "etc/networks", + "etc/timezone", + "etc/modules", + "etc/passwd", + "etc/shadow", + "etc/fstab", + "etc/motd", + "etc/hosts", + "etc/group", + "etc/alias", + "etc/crontab", + "etc/crypttab", + "etc/exports", + "etc/mtab", + "etc/hosts.allow", + "etc/hosts.deny", + "etc/os-release", + "etc/password.master", + "etc/profile", + "etc/default/grub", + "etc/resolvconf/update-libc.d/sendmail", + "etc/inittab", + "etc/issue", + "etc/issue.net", + "etc/login.defs", + "etc/sudoers", + "etc/sysconfig/network-scripts/ifcfg-eth0", + "etc/redhat-release", + "etc/scw-release", + "etc/system-release-cpe", + "etc/debian_version", + "etc/fedora-release", + "etc/mandrake-release", + "etc/slackware-release", + "etc/suse-release", + "etc/security/group", + "etc/security/passwd", + "etc/security/user", + "etc/security/environ", + "etc/security/limits", + "etc/security/opasswd", + "boot/grub/grub.cfg", + "boot/grub/menu.lst", + "root/.ksh_history", + "root/.xauthority", + "usr/lib/security/mkuser.default", + "var/lib/squirrelmail/prefs/squirrelmail.log", + "etc/squirrelmail/apache.conf", + "etc/squirrelmail/config_local.php", + "etc/squirrelmail/default_pref", + "etc/squirrelmail/index.php", + "etc/squirrelmail/config_default.php", + "etc/squirrelmail/config.php", + "etc/squirrelmail/filters_setup.php", + "etc/squirrelmail/sqspell_config.php", + "etc/squirrelmail/config/config.php", + "etc/httpd/conf.d/squirrelmail.conf", + "usr/share/squirrelmail/config/config.php", + "private/etc/squirrelmail/config/config.php", + "srv/www/htdos/squirrelmail/config/config.php", + "var/www/squirrelmail/config/config.php", + "var/www/html/squirrelmail/config/config.php", + "var/www/html/squirrelmail-1.2.9/config/config.php", + "usr/share/squirrelmail/plugins/squirrel_logger/setup.php", + "usr/local/squirrelmail/www/readme", + "windows/system32/drivers/etc/hosts", + "windows/system32/drivers/etc/lmhosts.sam", + "windows/system32/drivers/etc/networks", + "windows/system32/drivers/etc/protocol", + "windows/system32/drivers/etc/services", + "/boot.ini", + "windows/debug/netsetup.log", + "windows/comsetup.log", + "windows/repair/setup.log", + "windows/setupact.log", + "windows/setupapi.log", + "windows/setuperr.log", + "windows/updspapi.log", + "windows/wmsetup.log", + "windows/windowsupdate.log", + "windows/odbc.ini", + "usr/local/psa/admin/htdocs/domains/databases/phpmyadmin/libraries/config.default.php", + "etc/apache2/conf.d/phpmyadmin.conf", + "etc/phpmyadmin/config.inc.php", + "etc/openldap/ldap.conf", + "etc/cups/acroread.conf", + "etc/cups/cupsd.conf", + "etc/cups/cupsd.conf.default", + "etc/cups/pdftops.conf", + "etc/cups/printers.conf", + "windows/system32/macromed/flash/flashinstall.log", + "windows/system32/macromed/flash/install.log", + "etc/cvs-cron.conf", + "etc/cvs-pserver.conf", + "etc/subversion/config", + "etc/modprobe.d/vmware-tools.conf", + "etc/updatedb.conf.beforevmwaretoolsinstall", + "etc/vmware-tools/config", + "etc/vmware-tools/tpvmlp.conf", + "etc/vmware-tools/vmware-tools-libraries.conf", + "var/log", + "var/log/sw-cp-server/error_log", + "var/log/sso/sso.log", + "var/log/dpkg.log", + "var/log/btmp", + "var/log/utmp", + "var/log/wtmp", + "var/log/mysql/mysql-bin.log", + "var/log/mysql/mysql-bin.index", + "var/log/mysql/data/mysql-bin.index", + "var/log/mysql.log", + "var/log/mysql.err", + "var/log/mysqlderror.log", + "var/log/mysql/mysql.log", + "var/log/mysql/mysql-slow.log", + "var/log/mysql-bin.index", + "var/log/data/mysql-bin.index", + "var/log/postgresql/postgresql.log", + "var/log/postgres/pg_backup.log", + "var/log/postgres/postgres.log", + "var/log/postgresql.log", + "var/log/pgsql/pgsql.log", + "var/log/postgresql/postgresql-8.1-main.log", + "var/log/postgresql/postgresql-8.3-main.log", + "var/log/postgresql/postgresql-8.4-main.log", + "var/log/postgresql/postgresql-9.0-main.log", + "var/log/postgresql/postgresql-9.1-main.log", + "var/log/pgsql8.log", + "var/log/postgresql/postgres.log", + "var/log/pgsql_log", + "var/log/postgresql/main.log", + "var/log/cron", + "var/log/postgres.log", + "var/log/proftpd", + "var/log/proftpd/xferlog.legacy", + "var/log/proftpd.access_log", + "var/log/proftpd.xferlog", + "var/log/vsftpd.log", + "var/log/xferlog", + "var/log/pure-ftpd/pure-ftpd.log", + "var/log/pureftpd.log", + "var/log/muddleftpd", + "var/log/muddleftpd.conf", + "var/log/ftp-proxy/ftp-proxy.log", + "var/log/ftp-proxy", + "var/log/ftplog", + "var/log/exim_mainlog", + "var/log/exim/mainlog", + "var/log/maillog", + "var/log/exim_paniclog", + "var/log/exim/paniclog", + "var/log/exim/rejectlog", + "var/log/exim_rejectlog", + "var/log/webmin/miniserv.log", + "var/log/httpd/access_log", + "var/log/httpd/error_log", + "var/log/httpd/access.log", + "var/log/httpd/error.log", + "var/log/apache/access_log", + "var/log/apache/access.log", + "var/log/apache/error_log", + "var/log/apache/error.log", + "var/log/apache2/access_log", + "var/log/apache2/access.log", + "var/log/apache2/error_log", + "var/log/apache2/error.log", + "var/log/access_log", + "var/log/access.log", + "var/log/error_log", + "var/log/error.log", + "var/log/tomcat6/catalina.out", + "var/log/lighttpd.error.log", + "var/log/lighttpd.access.log", + "var/logs/access.log", + "var/log/lighttpd/", + "var/log/lighttpd/error.log", + "var/log/lighttpd/access.www.log", + "var/log/lighttpd/error.www.log", + "var/log/lighttpd/access.log", + "var/log/lighttpd/{domain}/access.log", + "var/log/lighttpd/{domain}/error.log", + "var/log/nginx/access_log", + "var/log/nginx/error_log", + "var/log/nginx/access.log", + "var/log/nginx/error.log", + "var/log/nginx.access_log", + "var/log/nginx.error_log", + "var/log/samba/log.smbd", + "var/log/samba/log.nmbd", + "var/log/samba.log", + "var/log/samba.log1", + "var/log/samba.log2", + "var/log/log.smb", + "var/log/ipfw.log", + "var/log/ipfw", + "var/log/ipfw/ipfw.log", + "var/log/ipfw.today", + "var/log/poplog", + "var/log/authlog", + "var/log/news.all", + "var/log/news/news.all", + "var/log/news/news.crit", + "var/log/news/news.err", + "var/log/news/news.notice", + "var/log/news/suck.err", + "var/log/news/suck.notice", + "var/log/messages", + "var/log/messages.1", + "var/log/user.log", + "var/log/user.log.1", + "var/log/auth.log", + "var/log/pm-powersave.log", + "var/log/xorg.0.log", + "var/log/daemon.log", + "var/log/daemon.log.1", + "var/log/kern.log", + "var/log/kern.log.1", + "var/log/mail.err", + "var/log/mail.info", + "var/log/mail.warn", + "var/log/ufw.log", + "var/log/boot.log", + "var/log/syslog", + "var/log/syslog.1", + "var/log/squirrelmail.log", + "var/log/apache2/squirrelmail.log", + "var/log/apache2/squirrelmail.err.log", + "var/log/mail.log", + "var/log/vmware/hostd.log", + "var/log/vmware/hostd-1.log", + "/wp-config.php", + "/wp-config.bak", + "/wp-config.old", + "/wp-config.temp", + "/wp-config.tmp", + "/wp-config.txt", + "/config.yml", + "/config_dev.yml", + "/config_prod.yml", + "/config_test.yml", + "/parameters.yml", + "/routing.yml", + "/security.yml", + "/services.yml", + "sites/default/default.settings.php", + "sites/default/settings.php", + "sites/default/settings.local.php", + "app/etc/local.xml", + "/sftp-config.json", + "/web.config", + "includes/config.php", + "includes/configure.php", + "/config.inc.php", + "/localsettings.php", + "inc/config.php", + "typo3conf/localconf.php", + "config/app.php", + "config/custom.php", + "config/database.php", + "/configuration.php", + "/config.php", + "var/mail/www-data", + "etc/network/", + "etc/init/", + "inetpub/wwwroot/global.asa", + "system32/inetsrv/config/applicationhost.config", + "system32/inetsrv/config/administration.config", + "system32/inetsrv/config/redirection.config", + "system32/config/default", + "system32/config/sam", + "system32/config/system", + "system32/config/software", + "winnt/repair/sam._", + "/package.json", + "/package-lock.json", + "/gruntfile.js", + "/npm-debug.log", + "/ormconfig.json", + "/tsconfig.json", + "/webpack.config.js", + "/yarn.lock", + "proc/0", + "proc/1", + "proc/2", + "proc/3", + "proc/4", + "proc/5", + "proc/6", + "proc/7", + "proc/8", + "proc/9", + "proc/acpi", + "proc/asound", + "proc/bootconfig", + "proc/buddyinfo", + "proc/bus", + "proc/cgroups", + "proc/cmdline", + "proc/config.gz", + "proc/consoles", + "proc/cpuinfo", + "proc/crypto", + "proc/devices", + "proc/diskstats", + "proc/dma", + "proc/docker", + "proc/driver", + "proc/dynamic_debug", + "proc/execdomains", + "proc/fb", + "proc/filesystems", + "proc/fs", + "proc/interrupts", + "proc/iomem", + "proc/ioports", + "proc/ipmi", + "proc/irq", + "proc/kallsyms", + "proc/kcore", + "proc/keys", + "proc/keys", + "proc/key-users", + "proc/kmsg", + "proc/kpagecgroup", + "proc/kpagecount", + "proc/kpageflags", + "proc/latency_stats", + "proc/loadavg", + "proc/locks", + "proc/mdstat", + "proc/meminfo", + "proc/misc", + "proc/modules", + "proc/mounts", + "proc/mpt", + "proc/mtd", + "proc/mtrr", + "proc/net", + "proc/net/tcp", + "proc/net/udp", + "proc/pagetypeinfo", + "proc/partitions", + "proc/pressure", + "proc/sched_debug", + "proc/schedstat", + "proc/scsi", + "proc/self", + "proc/self/cmdline", + "proc/self/environ", + "proc/self/fd/0", + "proc/self/fd/1", + "proc/self/fd/10", + "proc/self/fd/11", + "proc/self/fd/12", + "proc/self/fd/13", + "proc/self/fd/14", + "proc/self/fd/15", + "proc/self/fd/2", + "proc/self/fd/3", + "proc/self/fd/4", + "proc/self/fd/5", + "proc/self/fd/6", + "proc/self/fd/7", + "proc/self/fd/8", + "proc/self/fd/9", + "proc/self/mounts", + "proc/self/stat", + "proc/self/status", + "proc/slabinfo", + "proc/softirqs", + "proc/stat", + "proc/swaps", + "proc/sys", + "proc/sysrq-trigger", + "proc/sysvipc", + "proc/thread-self", + "proc/timer_list", + "proc/timer_stats", + "proc/tty", + "proc/uptime", + "proc/version", + "proc/version_signature", + "proc/vmallocinfo", + "proc/vmstat", + "proc/zoneinfo", + "sys/block", + "sys/bus", + "sys/class", + "sys/dev", + "sys/devices", + "sys/firmware", + "sys/fs", + "sys/hypervisor", + "sys/kernel", + "sys/module", + "sys/power", + "windows\\win.ini", + "default\\ntuser.dat", + "/var/run/secrets/kubernetes.io/serviceaccount" + ] + }, + "operator": "phrase_match" + } + ], + "transformers": [ + "lowercase", + "normalizePath" + ] + }, + { + "id": "crs-931-110", + "name": "RFI: Common RFI Vulnerable Parameter Name used w/ URL Payload", + "tags": { + "type": "rfi", + "crs_id": "931110", + "category": "attack_attempt", + "cwe": "98", + "capec": "1000/152/175/253/193", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + } + ], + "regex": "(?:\\binclude\\s*\\([^)]*|mosConfig_absolute_path|_CONF\\[path\\]|_SERVER\\[DOCUMENT_ROOT\\]|GALLERY_BASEDIR|path\\[docroot\\]|appserv_root|config\\[root_dir\\])=(?:file|ftps?|https?)://", + "options": { + "min_length": 15 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "crs-931-120", + "name": "RFI: URL Payload Used w/Trailing Question Mark Character (?)", + "tags": { + "type": "rfi", + "crs_id": "931120", + "category": "attack_attempt", + "cwe": "98", + "capec": "1000/152/175/253/193" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "^(?i:file|ftps?)://.*?\\?+$", + "options": { + "case_sensitive": true, + "min_length": 4 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "crs-932-160", + "name": "Remote Command Execution: Unix Shell Code Found", + "tags": { + "type": "command_injection", + "crs_id": "932160", + "category": "attack_attempt", + "cwe": "77", + "capec": "1000/152/248/88", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "list": [ + "${cdpath}", + "${dirstack}", + "${home}", + "${hostname}", + "${ifs}", + "${oldpwd}", + "${ostype}", + "${path}", + "${pwd}", + "$cdpath", + "$dirstack", + "$home", + "$hostname", + "$ifs", + "$oldpwd", + "$ostype", + "$path", + "$pwd", + "dev/fd/", + "dev/null", + "dev/stderr", + "dev/stdin", + "dev/stdout", + "dev/tcp/", + "dev/udp/", + "dev/zero", + "etc/master.passwd", + "etc/pwd.db", + "etc/shells", + "etc/spwd.db", + "proc/self/", + "bin/7z", + "bin/7za", + "bin/7zr", + "bin/ab", + "bin/agetty", + "bin/ansible-playbook", + "bin/apt", + "bin/apt-get", + "bin/ar", + "bin/aria2c", + "bin/arj", + "bin/arp", + "bin/as", + "bin/ascii-xfr", + "bin/ascii85", + "bin/ash", + "bin/aspell", + "bin/at", + "bin/atobm", + "bin/awk", + "bin/base32", + "bin/base64", + "bin/basenc", + "bin/bash", + "bin/bpftrace", + "bin/bridge", + "bin/bundler", + "bin/bunzip2", + "bin/busctl", + "bin/busybox", + "bin/byebug", + "bin/bzcat", + "bin/bzcmp", + "bin/bzdiff", + "bin/bzegrep", + "bin/bzexe", + "bin/bzfgrep", + "bin/bzgrep", + "bin/bzip2", + "bin/bzip2recover", + "bin/bzless", + "bin/bzmore", + "bin/bzz", + "bin/c89", + "bin/c99", + "bin/cancel", + "bin/capsh", + "bin/cat", + "bin/cc", + "bin/certbot", + "bin/check_by_ssh", + "bin/check_cups", + "bin/check_log", + "bin/check_memory", + "bin/check_raid", + "bin/check_ssl_cert", + "bin/check_statusfile", + "bin/chmod", + "bin/choom", + "bin/chown", + "bin/chroot", + "bin/clang", + "bin/clang++", + "bin/cmp", + "bin/cobc", + "bin/column", + "bin/comm", + "bin/composer", + "bin/core_perl/zipdetails", + "bin/cowsay", + "bin/cowthink", + "bin/cp", + "bin/cpan", + "bin/cpio", + "bin/cpulimit", + "bin/crash", + "bin/crontab", + "bin/csh", + "bin/csplit", + "bin/csvtool", + "bin/cupsfilter", + "bin/curl", + "bin/cut", + "bin/dash", + "bin/date", + "bin/dd", + "bin/dev/fd/", + "bin/dev/null", + "bin/dev/stderr", + "bin/dev/stdin", + "bin/dev/stdout", + "bin/dev/tcp/", + "bin/dev/udp/", + "bin/dev/zero", + "bin/dialog", + "bin/diff", + "bin/dig", + "bin/dmesg", + "bin/dmidecode", + "bin/dmsetup", + "bin/dnf", + "bin/docker", + "bin/dosbox", + "bin/dpkg", + "bin/du", + "bin/dvips", + "bin/easy_install", + "bin/eb", + "bin/echo", + "bin/ed", + "bin/efax", + "bin/emacs", + "bin/env", + "bin/eqn", + "bin/es", + "bin/esh", + "bin/etc/group", + "bin/etc/master.passwd", + "bin/etc/passwd", + "bin/etc/pwd.db", + "bin/etc/shadow", + "bin/etc/shells", + "bin/etc/spwd.db", + "bin/ex", + "bin/exiftool", + "bin/expand", + "bin/expect", + "bin/expr", + "bin/facter", + "bin/fetch", + "bin/file", + "bin/find", + "bin/finger", + "bin/fish", + "bin/flock", + "bin/fmt", + "bin/fold", + "bin/fping", + "bin/ftp", + "bin/gawk", + "bin/gcc", + "bin/gcore", + "bin/gdb", + "bin/gem", + "bin/genie", + "bin/genisoimage", + "bin/ghc", + "bin/ghci", + "bin/gimp", + "bin/ginsh", + "bin/git", + "bin/grc", + "bin/grep", + "bin/gtester", + "bin/gunzip", + "bin/gzexe", + "bin/gzip", + "bin/hd", + "bin/head", + "bin/hexdump", + "bin/highlight", + "bin/hping3", + "bin/iconv", + "bin/id", + "bin/iftop", + "bin/install", + "bin/ionice", + "bin/ip", + "bin/irb", + "bin/ispell", + "bin/jjs", + "bin/join", + "bin/journalctl", + "bin/jq", + "bin/jrunscript", + "bin/knife", + "bin/ksh", + "bin/ksshell", + "bin/latex", + "bin/ld", + "bin/ldconfig", + "bin/less", + "bin/lftp", + "bin/ln", + "bin/loginctl", + "bin/logsave", + "bin/look", + "bin/lp", + "bin/ls", + "bin/ltrace", + "bin/lua", + "bin/lualatex", + "bin/luatex", + "bin/lwp-download", + "bin/lwp-request", + "bin/lz", + "bin/lz4", + "bin/lz4c", + "bin/lz4cat", + "bin/lzcat", + "bin/lzcmp", + "bin/lzdiff", + "bin/lzegrep", + "bin/lzfgrep", + "bin/lzgrep", + "bin/lzless", + "bin/lzma", + "bin/lzmadec", + "bin/lzmainfo", + "bin/lzmore", + "bin/mail", + "bin/make", + "bin/man", + "bin/mawk", + "bin/mkfifo", + "bin/mknod", + "bin/more", + "bin/mosquitto", + "bin/mount", + "bin/msgattrib", + "bin/msgcat", + "bin/msgconv", + "bin/msgfilter", + "bin/msgmerge", + "bin/msguniq", + "bin/mtr", + "bin/mv", + "bin/mysql", + "bin/nano", + "bin/nasm", + "bin/nawk", + "bin/nc", + "bin/ncat", + "bin/neofetch", + "bin/nice", + "bin/nl", + "bin/nm", + "bin/nmap", + "bin/node", + "bin/nohup", + "bin/npm", + "bin/nroff", + "bin/nsenter", + "bin/octave", + "bin/od", + "bin/openssl", + "bin/openvpn", + "bin/openvt", + "bin/opkg", + "bin/paste", + "bin/pax", + "bin/pdb", + "bin/pdflatex", + "bin/pdftex", + "bin/pdksh", + "bin/perf", + "bin/perl", + "bin/pg", + "bin/php", + "bin/php-cgi", + "bin/php5", + "bin/php7", + "bin/pic", + "bin/pico", + "bin/pidstat", + "bin/pigz", + "bin/pip", + "bin/pkexec", + "bin/pkg", + "bin/pr", + "bin/printf", + "bin/proc/self/", + "bin/pry", + "bin/ps", + "bin/psed", + "bin/psftp", + "bin/psql", + "bin/ptx", + "bin/puppet", + "bin/pxz", + "bin/python", + "bin/python2", + "bin/python3", + "bin/rake", + "bin/rbash", + "bin/rc", + "bin/readelf", + "bin/red", + "bin/redcarpet", + "bin/restic", + "bin/rev", + "bin/rlogin", + "bin/rlwrap", + "bin/rpm", + "bin/rpmquery", + "bin/rsync", + "bin/ruby", + "bin/run-mailcap", + "bin/run-parts", + "bin/rview", + "bin/rvim", + "bin/sash", + "bin/sbin/capsh", + "bin/sbin/logsave", + "bin/sbin/service", + "bin/sbin/start-stop-daemon", + "bin/scp", + "bin/screen", + "bin/script", + "bin/sed", + "bin/service", + "bin/setarch", + "bin/sftp", + "bin/sg", + "bin/sh", + "bin/shuf", + "bin/sleep", + "bin/slsh", + "bin/smbclient", + "bin/snap", + "bin/socat", + "bin/soelim", + "bin/sort", + "bin/split", + "bin/sqlite3", + "bin/ss", + "bin/ssh", + "bin/ssh-keygen", + "bin/ssh-keyscan", + "bin/sshpass", + "bin/start-stop-daemon", + "bin/stdbuf", + "bin/strace", + "bin/strings", + "bin/su", + "bin/sysctl", + "bin/systemctl", + "bin/systemd-resolve", + "bin/tac", + "bin/tail", + "bin/tar", + "bin/task", + "bin/taskset", + "bin/tbl", + "bin/tclsh", + "bin/tcpdump", + "bin/tcsh", + "bin/tee", + "bin/telnet", + "bin/tex", + "bin/tftp", + "bin/tic", + "bin/time", + "bin/timedatectl", + "bin/timeout", + "bin/tmux", + "bin/top", + "bin/troff", + "bin/tshark", + "bin/ul", + "bin/uname", + "bin/uncompress", + "bin/unexpand", + "bin/uniq", + "bin/unlz4", + "bin/unlzma", + "bin/unpigz", + "bin/unrar", + "bin/unshare", + "bin/unxz", + "bin/unzip", + "bin/unzstd", + "bin/update-alternatives", + "bin/uudecode", + "bin/uuencode", + "bin/valgrind", + "bin/vi", + "bin/view", + "bin/vigr", + "bin/vim", + "bin/vimdiff", + "bin/vipw", + "bin/virsh", + "bin/volatility", + "bin/wall", + "bin/watch", + "bin/wc", + "bin/wget", + "bin/whiptail", + "bin/who", + "bin/whoami", + "bin/whois", + "bin/wireshark", + "bin/wish", + "bin/xargs", + "bin/xelatex", + "bin/xetex", + "bin/xmodmap", + "bin/xmore", + "bin/xpad", + "bin/xxd", + "bin/xz", + "bin/xzcat", + "bin/xzcmp", + "bin/xzdec", + "bin/xzdiff", + "bin/xzegrep", + "bin/xzfgrep", + "bin/xzgrep", + "bin/xzless", + "bin/xzmore", + "bin/yarn", + "bin/yelp", + "bin/yes", + "bin/yum", + "bin/zathura", + "bin/zip", + "bin/zipcloak", + "bin/zipcmp", + "bin/zipdetails", + "bin/zipgrep", + "bin/zipinfo", + "bin/zipmerge", + "bin/zipnote", + "bin/zipsplit", + "bin/ziptool", + "bin/zsh", + "bin/zsoelim", + "bin/zstd", + "bin/zstdcat", + "bin/zstdgrep", + "bin/zstdless", + "bin/zstdmt", + "bin/zypper" + ] + }, + "operator": "phrase_match" + } + ], + "transformers": [ + "lowercase", + "cmdLine" + ] + }, + { + "id": "crs-932-171", + "name": "Remote Command Execution: Shellshock (CVE-2014-6271)", + "tags": { + "type": "command_injection", + "crs_id": "932171", + "category": "attack_attempt", + "cwe": "77", + "capec": "1000/152/248/88", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "^\\(\\s*\\)\\s+{", + "options": { + "case_sensitive": true, + "min_length": 4 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "crs-932-180", + "name": "Restricted File Upload Attempt", + "tags": { + "type": "command_injection", + "crs_id": "932180", + "category": "attack_attempt", + "cwe": "706", + "capec": "1000/225/122/17/177", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "x-filename" + ] + }, + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "x_filename" + ] + }, + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "x-file-name" + ] + } + ], + "list": [ + ".htaccess", + ".htdigest", + ".htpasswd", + "wp-config.php", + "config.yml", + "config_dev.yml", + "config_prod.yml", + "config_test.yml", + "parameters.yml", + "routing.yml", + "security.yml", + "services.yml", + "default.settings.php", + "settings.php", + "settings.local.php", + "local.xml", + ".env" + ] + }, + "operator": "phrase_match" + } + ], + "transformers": [ + "lowercase" + ] + }, + { + "id": "crs-933-111", + "name": "PHP Injection Attack: PHP Script File Upload Found", + "tags": { + "type": "unrestricted_file_upload", + "crs_id": "933111", + "category": "attack_attempt", + "cwe": "434", + "capec": "1000/225/122/17/650", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "x-filename" + ] + }, + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "x_filename" + ] + }, + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "x.filename" + ] + }, + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "x-file-name" + ] + } + ], + "regex": ".*\\.(?:php\\d*|phtml)\\..*$", + "options": { + "case_sensitive": true, + "min_length": 5 + } + }, + "operator": "match_regex" + } + ], + "transformers": [ + "lowercase" + ] + }, + { + "id": "crs-933-130", + "name": "PHP Injection Attack: Global Variables Found", + "tags": { + "type": "php_code_injection", + "crs_id": "933130", + "category": "attack_attempt", + "cwe": "94", + "capec": "1000/225/122/17/650", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "list": [ + "$globals", + "$_cookie", + "$_env", + "$_files", + "$_get", + "$_post", + "$_request", + "$_server", + "$_session", + "$argc", + "$argv", + "$http_\\u200bresponse_\\u200bheader", + "$php_\\u200berrormsg", + "$http_cookie_vars", + "$http_env_vars", + "$http_get_vars", + "$http_post_files", + "$http_post_vars", + "$http_raw_post_data", + "$http_request_vars", + "$http_server_vars" + ] + }, + "operator": "phrase_match" + } + ], + "transformers": [ + "lowercase" + ] + }, + { + "id": "crs-933-131", + "name": "PHP Injection Attack: HTTP Headers Values Found", + "tags": { + "type": "php_code_injection", + "crs_id": "933131", + "category": "attack_attempt", + "cwe": "94", + "capec": "1000/225/122/17/650" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "(?:HTTP_(?:ACCEPT(?:_(?:ENCODING|LANGUAGE|CHARSET))?|(?:X_FORWARDED_FO|REFERE)R|(?:USER_AGEN|HOS)T|CONNECTION|KEEP_ALIVE)|PATH_(?:TRANSLATED|INFO)|ORIG_PATH_INFO|QUERY_STRING|REQUEST_URI|AUTH_TYPE)", + "options": { + "case_sensitive": true, + "min_length": 9 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "crs-933-140", + "name": "PHP Injection Attack: I/O Stream Found", + "tags": { + "type": "php_code_injection", + "crs_id": "933140", + "category": "attack_attempt", + "cwe": "94", + "capec": "1000/225/122/17/650", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "php://(?:std(?:in|out|err)|(?:in|out)put|fd|memory|temp|filter)", + "options": { + "min_length": 8 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "crs-933-150", + "name": "PHP Injection Attack: High-Risk PHP Function Name Found", + "tags": { + "type": "php_code_injection", + "crs_id": "933150", + "category": "attack_attempt", + "cwe": "94", + "capec": "1000/225/122/17/650", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "list": [ + "__halt_compiler", + "apache_child_terminate", + "base64_decode", + "bzdecompress", + "call_user_func", + "call_user_func_array", + "call_user_method", + "call_user_method_array", + "convert_uudecode", + "file_get_contents", + "file_put_contents", + "fsockopen", + "get_class_methods", + "get_class_vars", + "get_defined_constants", + "get_defined_functions", + "get_defined_vars", + "gzdecode", + "gzinflate", + "gzuncompress", + "include_once", + "invokeargs", + "pcntl_exec", + "pcntl_fork", + "pfsockopen", + "posix_getcwd", + "posix_getpwuid", + "posix_getuid", + "posix_uname", + "reflectionfunction", + "require_once", + "shell_exec", + "str_rot13", + "sys_get_temp_dir", + "wp_remote_fopen", + "wp_remote_get", + "wp_remote_head", + "wp_remote_post", + "wp_remote_request", + "wp_safe_remote_get", + "wp_safe_remote_head", + "wp_safe_remote_post", + "wp_safe_remote_request", + "zlib_decode" + ] + }, + "operator": "phrase_match" + } + ], + "transformers": [ + "lowercase" + ] + }, + { + "id": "crs-933-160", + "name": "PHP Injection Attack: High-Risk PHP Function Call Found", + "tags": { + "type": "php_code_injection", + "crs_id": "933160", + "category": "attack_attempt", + "cwe": "94", + "capec": "1000/225/122/17/650" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "\\b(?:s(?:e(?:t(?:_(?:e(?:xception|rror)_handler|magic_quotes_runtime|include_path)|defaultstub)|ssion_s(?:et_save_handler|tart))|qlite_(?:(?:(?:unbuffered|single|array)_)?query|create_(?:aggregate|function)|p?open|exec)|tr(?:eam_(?:context_create|socket_client)|ipc?slashes|rev)|implexml_load_(?:string|file)|ocket_c(?:onnect|reate)|h(?:ow_sourc|a1_fil)e|pl_autoload_register|ystem)|p(?:r(?:eg_(?:replace(?:_callback(?:_array)?)?|match(?:_all)?|split)|oc_(?:(?:terminat|clos|nic)e|get_status|open)|int_r)|o(?:six_(?:get(?:(?:e[gu]|g)id|login|pwnam)|mk(?:fifo|nod)|ttyname|kill)|pen)|hp(?:_(?:strip_whitespac|unam)e|version|info)|g_(?:(?:execut|prepar)e|connect|query)|a(?:rse_(?:ini_file|str)|ssthru)|utenv)|r(?:unkit_(?:function_(?:re(?:defin|nam)e|copy|add)|method_(?:re(?:defin|nam)e|copy|add)|constant_(?:redefine|add))|e(?:(?:gister_(?:shutdown|tick)|name)_function|ad(?:(?:gz)?file|_exif_data|dir))|awurl(?:de|en)code)|i(?:mage(?:createfrom(?:(?:jpe|pn)g|x[bp]m|wbmp|gif)|(?:jpe|pn)g|g(?:d2?|if)|2?wbmp|xbm)|s_(?:(?:(?:execut|write?|read)ab|fi)le|dir)|ni_(?:get(?:_all)?|set)|terator_apply|ptcembed)|g(?:et(?:_(?:c(?:urrent_use|fg_va)r|meta_tags)|my(?:[gpu]id|inode)|(?:lastmo|cw)d|imagesize|env)|z(?:(?:(?:defla|wri)t|encod|fil)e|compress|open|read)|lob)|a(?:rray_(?:u(?:intersect(?:_u?assoc)?|diff(?:_u?assoc)?)|intersect_u(?:assoc|key)|diff_u(?:assoc|key)|filter|reduce|map)|ssert(?:_options)?|tob)|h(?:tml(?:specialchars(?:_decode)?|_entity_decode|entities)|(?:ash(?:_(?:update|hmac))?|ighlight)_file|e(?:ader_register_callback|x2bin))|f(?:i(?:le(?:(?:[acm]tim|inod)e|(?:_exist|perm)s|group)?|nfo_open)|tp_(?:nb_(?:ge|pu)|connec|ge|pu)t|(?:unction_exis|pu)ts|write|open)|o(?:b_(?:get_(?:c(?:ontents|lean)|flush)|end_(?:clean|flush)|clean|flush|start)|dbc_(?:result(?:_all)?|exec(?:ute)?|connect)|pendir)|m(?:b_(?:ereg(?:_(?:replace(?:_callback)?|match)|i(?:_replace)?)?|parse_str)|(?:ove_uploaded|d5)_file|ethod_exists|ysql_query|kdir)|e(?:x(?:if_(?:t(?:humbnail|agname)|imagetype|read_data)|ec)|scapeshell(?:arg|cmd)|rror_reporting|val)|c(?:url_(?:file_create|exec|init)|onvert_uuencode|reate_function|hr)|u(?:n(?:serialize|pack)|rl(?:de|en)code|[ak]?sort)|b(?:(?:son_(?:de|en)|ase64_en)code|zopen|toa)|(?:json_(?:de|en)cod|debug_backtrac|tmpfil)e|var_dump)(?:\\s|/\\*.*\\*/|//.*|#.*|\\\"|')*\\((?:(?:\\s|/\\*.*\\*/|//.*|#.*)*(?:\\$\\w+|[A-Z\\d]\\w*|\\w+\\(.*\\)|\\\\?\"(?:[^\"]|\\\\\"|\"\"|\"\\+\")*\\\\?\"|\\\\?'(?:[^']|''|'\\+')*\\\\?')(?:\\s|/\\*.*\\*/|//.*|#.*)*(?:(?:::|\\.|->)(?:\\s|/\\*.*\\*/|//.*|#.*)*\\w+(?:\\(.*\\))?)?,)*(?:(?:\\s|/\\*.*\\*/|//.*|#.*)*(?:\\$\\w+|[A-Z\\d]\\w*|\\w+\\(.*\\)|\\\\?\"(?:[^\"]|\\\\\"|\"\"|\"\\+\")*\\\\?\"|\\\\?'(?:[^']|''|'\\+')*\\\\?')(?:\\s|/\\*.*\\*/|//.*|#.*)*(?:(?:::|\\.|->)(?:\\s|/\\*.*\\*/|//.*|#.*)*\\w+(?:\\(.*\\))?)?)?\\)", + "options": { + "case_sensitive": true, + "min_length": 5 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "crs-933-170", + "name": "PHP Injection Attack: Serialized Object Injection", + "tags": { + "type": "php_code_injection", + "crs_id": "933170", + "category": "attack_attempt", + "cwe": "502", + "capec": "1000/152/586", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "[oOcC]:\\d+:\\\".+?\\\":\\d+:{[\\W\\w]*}", + "options": { + "case_sensitive": true, + "min_length": 12 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "crs-933-200", + "name": "PHP Injection Attack: Wrapper scheme detected", + "tags": { + "type": "php_code_injection", + "crs_id": "933200", + "category": "attack_attempt", + "cwe": "502", + "capec": "1000/152/586" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "(?:(?:bzip|ssh)2|z(?:lib|ip)|(?:ph|r)ar|expect|glob|ogg)://", + "options": { + "case_sensitive": true, + "min_length": 6 + } + }, + "operator": "match_regex" + } + ], + "transformers": [ + "removeNulls" + ] + }, + { + "id": "crs-934-100", + "name": "Node.js Injection Attack 1/2", + "tags": { + "type": "js_code_injection", + "crs_id": "934100", + "category": "attack_attempt", + "cwe": "94", + "capec": "1000/152/242" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "\\b(?:(?:l(?:(?:utimes|chmod)(?:Sync)?|(?:stat|ink)Sync)|w(?:rite(?:(?:File|v)(?:Sync)?|Sync)|atchFile)|u(?:n(?:watchFile|linkSync)|times(?:Sync)?)|s(?:(?:ymlink|tat)Sync|pawn(?:File|Sync))|ex(?:ec(?:File(?:Sync)?|Sync)|istsSync)|a(?:ppendFile|ccess)(?:Sync)?|(?:Caveat|Inode)s|open(?:dir)?Sync|new\\s+Function|Availability|\\beval)\\s*\\(|m(?:ain(?:Module\\s*(?:\\W*\\s*(?:constructor|require)|\\[)|\\s*(?:\\W*\\s*(?:constructor|require)|\\[))|kd(?:temp(?:Sync)?|irSync)\\s*\\(|odule\\.exports\\s*=)|c(?:(?:(?:h(?:mod|own)|lose)Sync|reate(?:Write|Read)Stream|p(?:Sync)?)\\s*\\(|o(?:nstructor\\s*(?:\\W*\\s*_load|\\[)|pyFile(?:Sync)?\\s*\\())|f(?:(?:(?:s(?:(?:yncS)?|tatS)|datas(?:yncS)?)ync|ch(?:mod|own)(?:Sync)?)\\s*\\(|u(?:nction\\s*\\(\\s*\\)\\s*{|times(?:Sync)?\\s*\\())|r(?:e(?:(?:ad(?:(?:File|link|dir)?Sync|v(?:Sync)?)|nameSync)\\s*\\(|quire\\s*(?:\\W*\\s*main|\\[))|m(?:Sync)?\\s*\\()|process\\s*(?:\\W*\\s*(?:mainModule|binding)|\\[)|t(?:his\\.constructor|runcateSync\\s*\\()|_(?:\\$\\$ND_FUNC\\$\\$_|_js_function)|global\\s*(?:\\W*\\s*process|\\[)|String\\s*\\.\\s*fromCharCode|binding\\s*\\[)", + "options": { + "case_sensitive": true, + "min_length": 3 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "crs-934-101", + "name": "Node.js Injection Attack 2/2", + "tags": { + "type": "js_code_injection", + "crs_id": "934101", + "category": "attack_attempt", + "confidence": "1", + "cwe": "94", + "capec": "1000/152/242" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "\\b(?:w(?:atch|rite)|(?:spaw|ope)n|exists|close|fork|read)\\s*\\(", + "options": { + "case_sensitive": true, + "min_length": 5 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "crs-941-110", + "name": "XSS Filter - Category 1: Script Tag Vector", + "tags": { + "type": "xss", + "crs_id": "941110", + "category": "attack_attempt", + "cwe": "80", + "capec": "1000/152/242/63/591", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + }, + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "referer" + ] + }, + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "]*>[\\s\\S]*?", + "options": { + "case_sensitive": false, + "min_length": 8 + } + }, + "operator": "match_regex" + } + ], + "transformers": [ + "removeNulls", + "urlDecodeUni" + ] + }, + { + "id": "crs-941-120", + "name": "XSS Filter - Category 2: Event Handler Vector", + "tags": { + "type": "xss", + "crs_id": "941120", + "category": "attack_attempt", + "cwe": "83", + "capec": "1000/152/242/63/591/243", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + }, + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "referer" + ] + }, + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "\\bon(?:d(?:r(?:ag(?:en(?:ter|d)|leave|start|over)?|op)|urationchange|blclick)|s(?:e(?:ek(?:ing|ed)|arch|lect)|u(?:spend|bmit)|talled|croll|how)|m(?:ouse(?:(?:lea|mo)ve|o(?:ver|ut)|enter|down|up)|essage)|p(?:a(?:ge(?:hide|show)|(?:st|us)e)|lay(?:ing)?|rogress|aste|ointer(?:cancel|down|enter|leave|move|out|over|rawupdate|up))|c(?:anplay(?:through)?|o(?:ntextmenu|py)|hange|lick|ut)|a(?:nimation(?:iteration|start|end)|(?:fterprin|bor)t|uxclick|fterscriptexecute)|t(?:o(?:uch(?:cancel|start|move|end)|ggle)|imeupdate)|f(?:ullscreen(?:change|error)|ocus(?:out|in)?|inish)|(?:(?:volume|hash)chang|o(?:ff|n)lin)e|b(?:efore(?:unload|print)|lur)|load(?:ed(?:meta)?data|start|end)?|r(?:es(?:ize|et)|atechange)|key(?:press|down|up)|w(?:aiting|heel)|in(?:valid|put)|e(?:nded|rror)|unload)[\\s\\x0B\\x09\\x0C\\x3B\\x2C\\x28\\x3B]*?=[^=]", + "options": { + "min_length": 8 + } + }, + "operator": "match_regex" + } + ], + "transformers": [ + "removeNulls", + "urlDecodeUni" + ] + }, + { + "id": "crs-941-140", + "name": "XSS Filter - Category 4: Javascript URI Vector", + "tags": { + "type": "xss", + "crs_id": "941140", + "category": "attack_attempt", + "cwe": "84", + "capec": "1000/152/242/63/591/244", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + }, + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "referer" + ] + }, + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "[a-z]+=(?:[^:=]+:.+;)*?[^:=]+:url\\(javascript", + "options": { + "min_length": 18 + } + }, + "operator": "match_regex" + } + ], + "transformers": [ + "removeNulls", + "urlDecodeUni" + ] + }, + { + "id": "crs-941-170", + "name": "NoScript XSS InjectionChecker: Attribute Injection", + "tags": { + "type": "xss", + "crs_id": "941170", + "category": "attack_attempt", + "cwe": "83", + "capec": "1000/152/242/63/591/243", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + }, + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "referer" + ] + }, + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "(?:\\W|^)(?:javascript:(?:[\\s\\S]+[=\\x5c\\(\\[\\.<]|[\\s\\S]*?(?:\\bname\\b|\\x5c[ux]\\d)))|@\\W*?i\\W*?m\\W*?p\\W*?o\\W*?r\\W*?t\\W*?(?:/\\*[\\s\\S]*?)?(?:[\\\"']|\\W*?u\\W*?r\\W*?l[\\s\\S]*?\\()|[^-]*?-\\W*?m\\W*?o\\W*?z\\W*?-\\W*?b\\W*?i\\W*?n\\W*?d\\W*?i\\W*?n\\W*?g[^:]*?:\\W*?u\\W*?r\\W*?l[\\s\\S]*?\\(", + "options": { + "min_length": 6 + } + }, + "operator": "match_regex" + } + ], + "transformers": [ + "removeNulls", + "urlDecodeUni" + ] + }, + { + "id": "crs-941-180", + "name": "Node-Validator Deny List Keywords", + "tags": { + "type": "xss", + "crs_id": "941180", + "category": "attack_attempt", + "cwe": "79", + "capec": "1000/152/242/63/591" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "list": [ + "document.cookie", + "document.write", + ".parentnode", + ".innerhtml", + "window.location", + "-moz-binding" + ] + }, + "operator": "phrase_match" + } + ], + "transformers": [ + "removeNulls", + "lowercase" + ] + }, + { + "id": "crs-941-200", + "name": "IE XSS Filters - Attack Detected via vmlframe tag", + "tags": { + "type": "xss", + "crs_id": "941200", + "category": "attack_attempt", + "cwe": "80", + "capec": "1000/152/242/63/591", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "(?i:<.*[:]?vmlframe.*?[\\s/+]*?src[\\s/+]*=)", + "options": { + "case_sensitive": true, + "min_length": 13 + } + }, + "operator": "match_regex" + } + ], + "transformers": [ + "removeNulls" + ] + }, + { + "id": "crs-941-210", + "name": "IE XSS Filters - Obfuscated Attack Detected via javascript injection", + "tags": { + "type": "xss", + "crs_id": "941210", + "category": "attack_attempt", + "cwe": "80", + "capec": "1000/152/242/63/591", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "(?i:(?:j|&#x?0*(?:74|4A|106|6A);?)(?:\\t|\\n|\\r|&(?:#x?0*(?:9|13|10|A|D);?|tab;|newline;))*(?:a|&#x?0*(?:65|41|97|61);?)(?:\\t|\\n|\\r|&(?:#x?0*(?:9|13|10|A|D);?|tab;|newline;))*(?:v|&#x?0*(?:86|56|118|76);?)(?:\\t|\\n|\\r|&(?:#x?0*(?:9|13|10|A|D);?|tab;|newline;))*(?:a|&#x?0*(?:65|41|97|61);?)(?:\\t|\\n|\\r|&(?:#x?0*(?:9|13|10|A|D);?|tab;|newline;))*(?:s|&#x?0*(?:83|53|115|73);?)(?:\\t|\\n|\\r|&(?:#x?0*(?:9|13|10|A|D);?|tab;|newline;))*(?:c|&#x?0*(?:67|43|99|63);?)(?:\\t|\\n|\\r|&(?:#x?0*(?:9|13|10|A|D);?|tab;|newline;))*(?:r|&#x?0*(?:82|52|114|72);?)(?:\\t|\\n|\\r|&(?:#x?0*(?:9|13|10|A|D);?|tab;|newline;))*(?:i|&#x?0*(?:73|49|105|69);?)(?:\\t|\\n|\\r|&(?:#x?0*(?:9|13|10|A|D);?|tab;|newline;))*(?:p|&#x?0*(?:80|50|112|70);?)(?:\\t|\\n|\\r|&(?:#x?0*(?:9|13|10|A|D);?|tab;|newline;))*(?:t|&#x?0*(?:84|54|116|74);?)(?:\\t|\\n|\\r|&(?:#x?0*(?:9|13|10|A|D);?|tab;|newline;))*(?::|&(?:#x?0*(?:58|3A);?|colon;)).)", + "options": { + "case_sensitive": true, + "min_length": 12 + } + }, + "operator": "match_regex" + } + ], + "transformers": [ + "removeNulls" + ] + }, + { + "id": "crs-941-220", + "name": "IE XSS Filters - Obfuscated Attack Detected via vbscript injection", + "tags": { + "type": "xss", + "crs_id": "941220", + "category": "attack_attempt", + "cwe": "80", + "capec": "1000/152/242/63/591", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "(?i:(?:v|&#x?0*(?:86|56|118|76);?)(?:\\t|&(?:#x?0*(?:9|13|10|A|D);?|tab;|newline;))*(?:b|&#x?0*(?:66|42|98|62);?)(?:\\t|&(?:#x?0*(?:9|13|10|A|D);?|tab;|newline;))*(?:s|&#x?0*(?:83|53|115|73);?)(?:\\t|&(?:#x?0*(?:9|13|10|A|D);?|tab;|newline;))*(?:c|&#x?0*(?:67|43|99|63);?)(?:\\t|&(?:#x?0*(?:9|13|10|A|D);?|tab;|newline;))*(?:r|&#x?0*(?:82|52|114|72);?)(?:\\t|&(?:#x?0*(?:9|13|10|A|D);?|tab;|newline;))*(?:i|&#x?0*(?:73|49|105|69);?)(?:\\t|&(?:#x?0*(?:9|13|10|A|D);?|tab;|newline;))*(?:p|&#x?0*(?:80|50|112|70);?)(?:\\t|&(?:#x?0*(?:9|13|10|A|D);?|tab;|newline;))*(?:t|&#x?0*(?:84|54|116|74);?)(?:\\t|&(?:#x?0*(?:9|13|10|A|D);?|tab;|newline;))*(?::|&(?:#x?0*(?:58|3A);?|colon;)).)", + "options": { + "case_sensitive": true, + "min_length": 10 + } + }, + "operator": "match_regex" + } + ], + "transformers": [ + "removeNulls" + ] + }, + { + "id": "crs-941-230", + "name": "IE XSS Filters - Attack Detected via embed tag", + "tags": { + "type": "xss", + "crs_id": "941230", + "category": "attack_attempt", + "cwe": "83", + "capec": "1000/152/242/63/591/243", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "]", + "options": { + "min_length": 8 + } + }, + "operator": "match_regex" + } + ], + "transformers": [ + "removeNulls" + ] + }, + { + "id": "crs-941-300", + "name": "IE XSS Filters - Attack Detected via object tag", + "tags": { + "type": "xss", + "crs_id": "941300", + "category": "attack_attempt", + "cwe": "83", + "capec": "1000/152/242/63/591/243", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": ")|<.*\\+AD4-", + "options": { + "case_sensitive": true, + "min_length": 6 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "crs-941-360", + "name": "JSFuck / Hieroglyphy obfuscation detected", + "tags": { + "type": "xss", + "crs_id": "941360", + "category": "attack_attempt", + "cwe": "87", + "capec": "1000/152/242/63/591/199" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "![!+ ]\\[\\]", + "options": { + "case_sensitive": true, + "min_length": 4 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "crs-941-390", + "name": "Javascript method detected", + "tags": { + "type": "xss", + "crs_id": "941390", + "category": "attack_attempt", + "confidence": "1", + "cwe": "79", + "capec": "1000/152/242/63/591" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "\\b(?i:eval|settimeout|setinterval|new\\s+Function|alert|prompt)[\\s+]*\\([^\\)]", + "options": { + "case_sensitive": true, + "min_length": 5 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "crs-942-100", + "name": "SQL Injection Attack Detected via libinjection", + "tags": { + "type": "sql_injection", + "crs_id": "942100", + "category": "attack_attempt", + "cwe": "89", + "capec": "1000/152/248/66" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ] + }, + "operator": "is_sqli" + } + ], + "transformers": [ + "removeNulls" + ] + }, + { + "id": "crs-942-160", + "name": "Detects blind sqli tests using sleep() or benchmark()", + "tags": { + "type": "sql_injection", + "crs_id": "942160", + "category": "attack_attempt", + "cwe": "89", + "capec": "1000/152/248/66/7", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "(?i:sleep\\(\\s*?\\d*?\\s*?\\)|benchmark\\(.*?\\,.*?\\))", + "options": { + "case_sensitive": true, + "min_length": 7 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "crs-942-240", + "name": "Detects MySQL charset switch and MSSQL DoS attempts", + "tags": { + "type": "sql_injection", + "crs_id": "942240", + "category": "attack_attempt", + "cwe": "89", + "capec": "1000/152/248/66/7", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "(?:[\\\"'`](?:;*?\\s*?waitfor\\s+(?:delay|time)\\s+[\\\"'`]|;.*?:\\s*?goto)|alter\\s*?\\w+.*?cha(?:racte)?r\\s+set\\s+\\w+)", + "options": { + "min_length": 7 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "crs-942-250", + "name": "Detects MATCH AGAINST, MERGE and EXECUTE IMMEDIATE injections", + "tags": { + "type": "sql_injection", + "crs_id": "942250", + "category": "attack_attempt", + "cwe": "89", + "capec": "1000/152/248/66" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "(?i:merge.*?using\\s*?\\(|execute\\s*?immediate\\s*?[\\\"'`]|match\\s*?[\\w(?:),+-]+\\s*?against\\s*?\\()", + "options": { + "case_sensitive": true, + "min_length": 11 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "crs-942-270", + "name": "Basic SQL injection", + "tags": { + "type": "sql_injection", + "crs_id": "942270", + "category": "attack_attempt", + "cwe": "89", + "capec": "1000/152/248/66" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "union.*?select.*?from", + "options": { + "min_length": 15 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "crs-942-280", + "name": "SQL Injection with delay functions", + "tags": { + "type": "sql_injection", + "crs_id": "942280", + "category": "attack_attempt", + "cwe": "89", + "capec": "1000/152/248/66/7", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "(?:;\\s*?shutdown\\s*?(?:[#;{]|\\/\\*|--)|waitfor\\s*?delay\\s?[\\\"'`]+\\s?\\d|select\\s*?pg_sleep)", + "options": { + "min_length": 10 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "crs-942-290", + "name": "Finds basic MongoDB SQL injection attempts", + "tags": { + "type": "nosql_injection", + "crs_id": "942290", + "category": "attack_attempt", + "cwe": "943", + "capec": "1000/152/248/676" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "(?i:(?:\\[?\\$(?:(?:s(?:lic|iz)|wher)e|e(?:lemMatch|xists|q)|n(?:o[rt]|in?|e)|l(?:ike|te?)|t(?:ext|ype)|a(?:ll|nd)|jsonSchema|between|regex|x?or|div|mod)\\]?)\\b)", + "options": { + "case_sensitive": true, + "min_length": 3 + } + }, + "operator": "match_regex" + } + ], + "transformers": [ + "keys_only" + ] + }, + { + "id": "crs-942-360", + "name": "Detects concatenated basic SQL injection and SQLLFI attempts", + "tags": { + "type": "sql_injection", + "crs_id": "942360", + "category": "attack_attempt", + "cwe": "89", + "capec": "1000/152/248/66/470" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "(?:^[\\W\\d]+\\s*?(?:alter\\s*(?:a(?:(?:pplication\\s*rol|ggregat)e|s(?:ymmetric\\s*ke|sembl)y|u(?:thorization|dit)|vailability\\s*group)|c(?:r(?:yptographic\\s*provider|edential)|o(?:l(?:latio|um)|nversio)n|ertificate|luster)|s(?:e(?:rv(?:ice|er)|curity|quence|ssion|arch)|y(?:mmetric\\s*key|nonym)|togroup|chema)|m(?:a(?:s(?:ter\\s*key|k)|terialized)|e(?:ssage\\s*type|thod)|odule)|l(?:o(?:g(?:file\\s*group|in)|ckdown)|a(?:ngua|r)ge|ibrary)|t(?:(?:abl(?:espac)?|yp)e|r(?:igger|usted)|hreshold|ext)|p(?:a(?:rtition|ckage)|ro(?:cedur|fil)e|ermission)|d(?:i(?:mension|skgroup)|atabase|efault|omain)|r(?:o(?:l(?:lback|e)|ute)|e(?:sourc|mot)e)|f(?:u(?:lltext|nction)|lashback|oreign)|e(?:xte(?:nsion|rnal)|(?:ndpoi|ve)nt)|in(?:dex(?:type)?|memory|stance)|b(?:roker\\s*priority|ufferpool)|x(?:ml\\s*schema|srobject)|w(?:ork(?:load)?|rapper)|hi(?:erarchy|stogram)|o(?:perator|utline)|(?:nicknam|queu)e|us(?:age|er)|group|java|view)|union\\s*(?:(?:distin|sele)ct|all))\\b|\\b(?:(?:(?:trunc|cre|upd)at|renam)e|(?:inser|selec)t|de(?:lete|sc)|alter|load)\\s+(?:group_concat|load_file|char)\\b\\s*\\(?|[\\s(]load_file\\s*?\\(|[\\\"'`]\\s+regexp\\W)", + "options": { + "min_length": 5 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "crs-942-500", + "name": "MySQL in-line comment detected", + "tags": { + "type": "sql_injection", + "crs_id": "942500", + "category": "attack_attempt", + "cwe": "89", + "capec": "1000/152/248/66" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "(?i:/\\*[!+](?:[\\w\\s=_\\-(?:)]+)?\\*/)", + "options": { + "case_sensitive": true, + "min_length": 5 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "crs-943-100", + "name": "Possible Session Fixation Attack: Setting Cookie Values in HTML", + "tags": { + "type": "http_protocol_violation", + "crs_id": "943100", + "category": "attack_attempt", + "cwe": "384", + "capec": "1000/225/21/593/61", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "(?i:\\.cookie\\b.*?;\\W*?(?:expires|domain)\\W*?=|\\bhttp-equiv\\W+set-cookie\\b)", + "options": { + "case_sensitive": true, + "min_length": 15 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "crs-944-100", + "name": "Remote Command Execution: Suspicious Java class detected", + "tags": { + "type": "java_code_injection", + "crs_id": "944100", + "category": "attack_attempt", + "cwe": "94", + "capec": "1000/152/242", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "java\\.lang\\.(?:runtime|processbuilder)", + "options": { + "case_sensitive": true, + "min_length": 17 + } + }, + "operator": "match_regex" + } + ], + "transformers": [ + "lowercase" + ] + }, + { + "id": "crs-944-110", + "name": "Remote Command Execution: Java process spawn (CVE-2017-9805)", + "tags": { + "type": "java_code_injection", + "category": "attack_attempt", + "cwe": "94", + "capec": "1000/152/242" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "(?:unmarshaller|base64data|java\\.).*(?:runtime|processbuilder)", + "options": { + "case_sensitive": false, + "min_length": 13 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "crs-944-130", + "name": "Suspicious Java class detected", + "tags": { + "type": "java_code_injection", + "crs_id": "944130", + "category": "attack_attempt", + "cwe": "94", + "capec": "1000/152/242" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "list": [ + "com.opensymphony.xwork2", + "com.sun.org.apache", + "java.io.bufferedinputstream", + "java.io.bufferedreader", + "java.io.bytearrayinputstream", + "java.io.bytearrayoutputstream", + "java.io.chararrayreader", + "java.io.datainputstream", + "java.io.file", + "java.io.fileoutputstream", + "java.io.filepermission", + "java.io.filewriter", + "java.io.filterinputstream", + "java.io.filteroutputstream", + "java.io.filterreader", + "java.io.inputstream", + "java.io.inputstreamreader", + "java.io.linenumberreader", + "java.io.objectoutputstream", + "java.io.outputstream", + "java.io.pipedoutputstream", + "java.io.pipedreader", + "java.io.printstream", + "java.io.pushbackinputstream", + "java.io.reader", + "java.io.stringreader", + "java.lang.class", + "java.lang.integer", + "java.lang.number", + "java.lang.object", + "java.lang.process", + "java.lang.reflect", + "java.lang.runtime", + "java.lang.string", + "java.lang.stringbuilder", + "java.lang.system", + "javax.script.scriptenginemanager", + "org.apache.commons", + "org.apache.struts", + "org.apache.struts2", + "org.omg.corba", + "java.beans.xmldecode" + ] + }, + "operator": "phrase_match" + } + ], + "transformers": [ + "lowercase" + ] + }, + { + "id": "crs-944-260", + "name": "Remote Command Execution: Malicious class-loading payload", + "tags": { + "type": "java_code_injection", + "crs_id": "944260", + "category": "attack_attempt", + "cwe": "94", + "capec": "1000/152/242", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "(?:class\\.module\\.classLoader\\.resources\\.context\\.parent\\.pipeline|springframework\\.context\\.support\\.FileSystemXmlApplicationContext)", + "options": { + "case_sensitive": true, + "min_length": 58 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "dog-000-001", + "name": "Look for Cassandra injections", + "tags": { + "type": "nosql_injection", + "category": "attack_attempt", + "cwe": "943", + "capec": "1000/152/248/676" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + }, + { + "address": "server.request.headers.no_cookies" + } + ], + "regex": "\\ballow\\s+filtering\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [ + "removeComments" + ] + }, + { + "id": "dog-000-002", + "name": "OGNL - Look for formatting injection patterns", + "tags": { + "type": "java_code_injection", + "category": "attack_attempt", + "cwe": "94", + "capec": "1000/152/242" + }, + "conditions": [ + { + "operator": "match_regex", + "parameters": { + "inputs": [ + { + "address": "server.request.uri.raw" + }, + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + }, + { + "address": "server.request.headers.no_cookies" + } + ], + "regex": "[#%$]{(?:[^}]+[^\\w\\s}\\-_][^}]+|\\d+-\\d+)}", + "options": { + "case_sensitive": true + } + } + } + ], + "transformers": [] + }, + { + "id": "dog-000-003", + "name": "OGNL - Detect OGNL exploitation primitives", + "tags": { + "type": "java_code_injection", + "category": "attack_attempt", + "cwe": "94", + "capec": "1000/152/242", + "confidence": "1" + }, + "conditions": [ + { + "operator": "match_regex", + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "[@#]ognl", + "options": { + "case_sensitive": true + } + } + } + ], + "transformers": [] + }, + { + "id": "dog-000-004", + "name": "Spring4Shell - Attempts to exploit the Spring4shell vulnerability", + "tags": { + "type": "exploit_detection", + "category": "attack_attempt", + "cwe": "94", + "capec": "1000/152/242", + "confidence": "1" + }, + "conditions": [ + { + "operator": "match_regex", + "parameters": { + "inputs": [ + { + "address": "server.request.body" + } + ], + "regex": "^class\\.module\\.classLoader\\.", + "options": { + "case_sensitive": false + } + } + } + ], + "transformers": [ + "keys_only" + ] + }, + { + "id": "dog-000-005", + "name": "Node.js: Prototype pollution through __proto__", + "tags": { + "type": "js_code_injection", + "category": "attack_attempt", + "cwe": "1321", + "capec": "1000/152/242", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + } + ], + "regex": "^__proto__$" + }, + "operator": "match_regex" + } + ], + "transformers": [ + "keys_only" + ] + }, + { + "id": "dog-000-006", + "name": "Node.js: Prototype pollution through constructor.prototype", + "tags": { + "type": "js_code_injection", + "category": "attack_attempt", + "cwe": "1321", + "capec": "1000/152/242", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + } + ], + "regex": "^constructor$" + }, + "operator": "match_regex" + }, + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + } + ], + "regex": "^prototype$" + }, + "operator": "match_regex" + } + ], + "transformers": [ + "keys_only" + ] + }, + { + "id": "dog-000-007", + "name": "Server side template injection: Velocity & Freemarker", + "tags": { + "type": "java_code_injection", + "category": "attack_attempt", + "cwe": "1336", + "capec": "1000/152/242/19", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "#(?:set|foreach|macro|parse|if)\\(.*\\)|<#assign.*>" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "dog-913-001", + "name": "BurpCollaborator OOB domain", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "tool_name": "BurpCollaborator", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "\\b(?:burpcollaborator\\.net|oastify\\.com)\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "dog-913-002", + "name": "Qualys OOB domain", + "tags": { + "type": "commercial_scanner", + "category": "attack_attempt", + "tool_name": "Qualys", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "\\bqualysperiscope\\.com\\b|\\.oscomm\\." + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "dog-913-003", + "name": "Probely OOB domain", + "tags": { + "type": "commercial_scanner", + "category": "attack_attempt", + "tool_name": "Probely", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "\\bprbly\\.win\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "dog-913-004", + "name": "Known malicious out-of-band interaction domain", + "tags": { + "type": "security_scanner", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "\\b(?:webhook\\.site|\\.canarytokens\\.com|vii\\.one|act1on3\\.ru|gdsburp\\.com|arcticwolf\\.net|oob\\.li|htbiw\\.com|h4\\.vc|mochan\\.cloud|imshopping\\.com|bootstrapnodejs\\.com|mooo-ng\\.com|securitytrails\\.com|canyouhackit\\.io|7bae\\.xyz)\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "dog-913-005", + "name": "Known suspicious out-of-band interaction domain", + "tags": { + "type": "security_scanner", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "\\b(?:\\.ngrok\\.io|requestbin\\.com|requestbin\\.net)\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "dog-913-006", + "name": "Rapid7 OOB domain", + "tags": { + "type": "commercial_scanner", + "category": "attack_attempt", + "tool_name": "Rapid7", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "\\bappspidered\\.rapid7\\." + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "dog-913-007", + "name": "Interact.sh OOB domain", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "tool_name": "interact.sh", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "\\b(?:interact\\.sh|oast\\.(?:pro|live|site|online|fun|me)|indusfacefinder\\.in|where\\.land|syhunt\\.net|tssrt\\.de|boardofcyber\\.io|assetnote-callback\\.com|praetorianlabs\\.dev|netspi\\.sh)\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "dog-913-008", + "name": "Netsparker OOB domain", + "tags": { + "type": "commercial_scanner", + "category": "attack_attempt", + "tool_name": "Netsparker", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "\\b(?:\\.|(?:\\\\|&#)(?:0*46|x0*2e);)?r87(?:\\.|(?:\\\\|&#)(?:0*46|x0*2e);)(?:me|com)\\b", + "options": { + "case_sensitive": false, + "min_length": 7 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "dog-913-009", + "name": "WhiteHat Security OOB domain", + "tags": { + "type": "commercial_scanner", + "category": "attack_attempt", + "tool_name": "WhiteHatSecurity", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "\\bwhsec(?:\\.|(?:\\\\|&#)(?:0*46|x0*2e);)us\\b", + "options": { + "case_sensitive": false, + "min_length": 8 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "dog-913-010", + "name": "Nessus OOB domain", + "tags": { + "type": "commercial_scanner", + "category": "attack_attempt", + "tool_name": "Nessus", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "\\b\\.nessus\\.org\\b", + "options": { + "case_sensitive": false, + "min_length": 8 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "dog-913-011", + "name": "Watchtowr OOB domain", + "tags": { + "type": "commercial_scanner", + "category": "attack_attempt", + "tool_name": "Watchtowr", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "\\bwatchtowr\\.com\\b", + "options": { + "case_sensitive": false, + "min_length": 8 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "dog-913-012", + "name": "AppCheck NG OOB domain", + "tags": { + "type": "commercial_scanner", + "category": "attack_attempt", + "tool_name": "AppCheckNG", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "\\bptst\\.io\\b", + "options": { + "case_sensitive": false, + "min_length": 7 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "dog-931-001", + "name": "RFI: URL Payload to well known RFI target", + "tags": { + "type": "rfi", + "category": "attack_attempt", + "cwe": "98", + "capec": "1000/152/175/253/193", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "^(?i:file|ftps?|https?).*/rfiinc\\.txt\\?+$", + "options": { + "case_sensitive": true, + "min_length": 17 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "dog-932-100", + "name": "Shell spawn executing network command", + "tags": { + "type": "command_injection", + "category": "attack_attempt", + "cwe": "77", + "capec": "1000/152/248/88", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "(?:(?:['\"\\x60({|;&]|(?:^|['\"\\x60({|;&])(?:cmd(?:\\.exe)?\\s+(?:/\\w(?::\\w+)?\\s+)*))(?:ping|curl|wget|telnet)|\\bnslookup)[\\s,]", + "options": { + "case_sensitive": true, + "min_length": 5 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "dog-934-001", + "name": "XXE - XML file loads external entity", + "tags": { + "type": "xxe", + "category": "attack_attempt", + "cwe": "91", + "capec": "1000/152/248/250", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.body" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "(?:<\\?xml[^>]*>.*)]+SYSTEM\\s+[^>]+>", + "options": { + "case_sensitive": false, + "min_length": 24 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "dog-941-001", + "name": "XSS in source property", + "tags": { + "type": "xss", + "category": "attack_attempt", + "cwe": "83", + "capec": "1000/152/242/63/591/243", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + }, + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "referer" + ] + }, + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "<(?:iframe|esi:include)(?:(?:\\s|/)*\\w+=[\"'\\w]+)*(?:\\s|/)*src(?:doc)?=[\"']?(?:data:|javascript:|http:|dns:|//)[^\\s'\"]+['\"]?", + "options": { + "min_length": 14 + } + }, + "operator": "match_regex" + } + ], + "transformers": [ + "removeNulls", + "urlDecodeUni" + ] + }, + { + "id": "dog-942-001", + "name": "Blind XSS callback domains", + "tags": { + "type": "xss", + "category": "attack_attempt", + "cwe": "83", + "capec": "1000/152/242/63/591/243", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "https?:\\/\\/(?:.*\\.)?(?:bxss\\.(?:in|me)|xss\\.ht|js\\.rip)", + "options": { + "case_sensitive": false + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "nfd-000-001", + "name": "Detect common directory discovery scans", + "tags": { + "type": "security_scanner", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "1" + }, + "conditions": [ + { + "operator": "match_regex", + "parameters": { + "inputs": [ + { + "address": "server.response.status" + } + ], + "regex": "^404$", + "options": { + "case_sensitive": true + } + } + }, + { + "operator": "phrase_match", + "parameters": { + "inputs": [ + { + "address": "server.request.uri.raw" + } + ], + "list": [ + "/wordpress/", + "/etc/", + "/login.php", + "/install.php", + "/administrator", + "/admin.php", + "/wp-config", + "/phpmyadmin", + "/fckeditor", + "/mysql", + "/manager/html", + ".htaccess", + "/config.php", + "/configuration", + "/cgi-bin/php", + "/search.php", + "/tinymce", + "/tiny_mce", + "/settings.php", + "../../..", + "/install/", + "/download.php", + "/webdav", + "/forum.php", + "/user.php", + "/style.php", + "/jmx-console", + "/modules.php", + "/include.php", + "/default.asp", + "/help.php", + "/database.yml", + "/database.yml.pgsql", + "/database.yml.sqlite3", + "/database.yml.sqlite", + "/database.yml.mysql", + ".%2e/", + "/view.php", + "/header.php", + "/search.asp", + "%5c%5c", + "/server/php/", + "/invoker/jmxinvokerservlet", + "/phpmyadmin/index.php", + "/data/admin/allowurl.txt", + "/verify.php", + "/misc/ajax.js", + "/.idea", + "/module.php", + "/backup.rar", + "/backup.tar", + "/backup.zip", + "/backup.7z", + "/backup.gz", + "/backup.tgz", + "/backup.tar.gz", + "waitfor%20delay", + "/calendar.php", + "/news.php", + "/dompdf.php", + "))))))))))))))))", + "/web.config", + "tree.php", + "/cgi-bin-sdb/printenv", + "/comments.php", + "/detail.asp", + "/license.txt", + "/admin.asp", + "/auth.php", + "/list.php", + "/content.php", + "/mod.php", + "/mini.php", + "/install.pgsql", + "/install.mysql", + "/install.sqlite", + "/install.sqlite3", + "/install.txt", + "/install.md", + "/doku.php", + "/main.asp", + "/myadmin", + "/force-download.php", + "/iisprotect/admin", + "/.gitignore", + "/print.php", + "/common.php", + "/mainfile.php", + "/functions.php", + "/scripts/setup.php", + "/faq.php", + "/op/op.login.php", + "/home.php", + "/includes/hnmain.inc.php3", + "/preview.php", + "/dump.rar", + "/dump.tar", + "/dump.zip", + "/dump.7z", + "/dump.gz", + "/dump.tgz", + "/dump.tar.gz", + "/thumbnail.php", + "/sendcard.php", + "/global.asax", + "/directory.php", + "/footer.php", + "/error.asp", + "/forum.asp", + "/save.php", + "/htmlsax3.php", + "/adm/krgourl.php", + "/includes/converter.inc.php", + "/nucleus/libs/pluginadmin.php", + "/base_qry_common.php", + "/fileadmin", + "/bitrix/admin/", + "/adm.php", + "/util/barcode.php", + "/action.php", + "/rss.asp", + "/downloads.php", + "/page.php", + "/snarf_ajax.php", + "/fck/editor", + "/sendmail.php", + "/detail.php", + "/iframe.php", + "/swfupload.swf", + "/jenkins/login", + "/phpmyadmin/main.php", + "/phpmyadmin/scripts/setup.php", + "/user/index.php", + "/checkout.php", + "/process.php", + "/ks_inc/ajax.js", + "/export.php", + "/register.php", + "/cart.php", + "/console.php", + "/friend.php", + "/readmsg.php", + "/install.asp", + "/dagent/downloadreport.asp", + "/system/index.php", + "/core/changelog.txt", + "/js/util.js", + "/interna.php", + "/gallery.php", + "/links.php", + "/data/admin/ver.txt", + "/language/zh-cn.xml", + "/productdetails.asp", + "/admin/template/article_more/config.htm", + "/components/com_moofaq/includes/file_includer.php", + "/licence.txt", + "/rss.xsl", + "/vtigerservice.php", + "/mysql/main.php", + "/passwiki.php", + "/scr/soustab.php", + "/global.php", + "/email.php", + "/user.asp", + "/msd", + "/products.php", + "/cultbooking.php", + "/cron.php", + "/static/js/admincp.js", + "/comment.php", + "/maintainers", + "/modules/plain/adminpart/addplain.php", + "/wp-content/plugins/ungallery/source_vuln.php", + "/upgrade.txt", + "/category.php", + "/index_logged.php", + "/members.asp", + "/script/html.js", + "/images/ad.js", + "/awstats/awstats.pl", + "/includes/esqueletos/skel_null.php", + "/modules/profile/user.php", + "/window_top.php", + "/openbrowser.php", + "/thread.php", + "tinfoil_xss", + "/includes/include.php", + "/urheber.php", + "/header.inc.php", + "/mysqldumper", + "/display.php", + "/website.php", + "/stats.php", + "/assets/plugins/mp3_id/mp3_id.php", + "/siteminderagent/forms/smpwservices.fcc" + ] + } + } + ], + "transformers": [ + "lowercase" + ] + }, + { + "id": "nfd-000-002", + "name": "Detect failed attempt to fetch readme files", + "tags": { + "type": "security_scanner", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "1" + }, + "conditions": [ + { + "operator": "match_regex", + "parameters": { + "inputs": [ + { + "address": "server.response.status" + } + ], + "regex": "^404$", + "options": { + "case_sensitive": true + } + } + }, + { + "operator": "match_regex", + "parameters": { + "inputs": [ + { + "address": "server.request.uri.raw" + } + ], + "regex": "readme\\.[\\.a-z0-9]+$", + "options": { + "case_sensitive": false + } + } + } + ], + "transformers": [] + }, + { + "id": "nfd-000-003", + "name": "Detect failed attempt to fetch Java EE resource files", + "tags": { + "type": "security_scanner", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "1" + }, + "conditions": [ + { + "operator": "match_regex", + "parameters": { + "inputs": [ + { + "address": "server.response.status" + } + ], + "regex": "^404$", + "options": { + "case_sensitive": true + } + } + }, + { + "operator": "match_regex", + "parameters": { + "inputs": [ + { + "address": "server.request.uri.raw" + } + ], + "regex": "^(?:.*web\\-inf)(?:.*web\\.xml).*$", + "options": { + "case_sensitive": false + } + } + } + ], + "transformers": [] + }, + { + "id": "nfd-000-004", + "name": "Detect failed attempt to fetch code files", + "tags": { + "type": "security_scanner", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "1" + }, + "conditions": [ + { + "operator": "match_regex", + "parameters": { + "inputs": [ + { + "address": "server.response.status" + } + ], + "regex": "^404$", + "options": { + "case_sensitive": true + } + } + }, + { + "operator": "match_regex", + "parameters": { + "inputs": [ + { + "address": "server.request.uri.raw" + } + ], + "regex": "\\.(java|pyc?|rb|class)\\b", + "options": { + "case_sensitive": false + } + } + } + ], + "transformers": [] + }, + { + "id": "nfd-000-005", + "name": "Detect failed attempt to fetch source code archives", + "tags": { + "type": "security_scanner", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "1" + }, + "conditions": [ + { + "operator": "match_regex", + "parameters": { + "inputs": [ + { + "address": "server.response.status" + } + ], + "regex": "^404$", + "options": { + "case_sensitive": true + } + } + }, + { + "operator": "match_regex", + "parameters": { + "inputs": [ + { + "address": "server.request.uri.raw" + } + ], + "regex": "\\.(sql|log|ndb|gz|zip|tar\\.gz|tar|regVV|reg|conf|bz2|ini|db|war|bat|inc|btr|server|ds|conf|config|admin|master|sln|bak)\\b(?:[^.]|$)", + "options": { + "case_sensitive": false + } + } + } + ], + "transformers": [] + }, + { + "id": "nfd-000-006", + "name": "Detect failed attempt to fetch sensitive files", + "tags": { + "type": "security_scanner", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "1" + }, + "conditions": [ + { + "operator": "match_regex", + "parameters": { + "inputs": [ + { + "address": "server.response.status" + } + ], + "regex": "^404$", + "options": { + "case_sensitive": true + } + } + }, + { + "operator": "match_regex", + "parameters": { + "inputs": [ + { + "address": "server.request.uri.raw" + } + ], + "regex": "\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)", + "options": { + "case_sensitive": false + } + } + } + ], + "transformers": [] + }, + { + "id": "nfd-000-007", + "name": "Detect failed attempt to fetch archives", + "tags": { + "type": "security_scanner", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "1" + }, + "conditions": [ + { + "operator": "match_regex", + "parameters": { + "inputs": [ + { + "address": "server.response.status" + } + ], + "regex": "^404$", + "options": { + "case_sensitive": true + } + } + }, + { + "operator": "match_regex", + "parameters": { + "inputs": [ + { + "address": "server.request.uri.raw" + } + ], + "regex": "/[\\d\\-_]*\\.(rar|tar|zip|7z|gz|tgz|tar.gz)", + "options": { + "case_sensitive": false + } + } + } + ], + "transformers": [] + }, + { + "id": "nfd-000-008", + "name": "Detect failed attempt to trigger incorrect application behavior", + "tags": { + "type": "security_scanner", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "1" + }, + "conditions": [ + { + "operator": "match_regex", + "parameters": { + "inputs": [ + { + "address": "server.response.status" + } + ], + "regex": "^404$", + "options": { + "case_sensitive": true + } + } + }, + { + "operator": "match_regex", + "parameters": { + "inputs": [ + { + "address": "server.request.uri.raw" + } + ], + "regex": "(/(administrator/components/com.*\\.php|response\\.write\\(.+\\))|select\\(.+\\)from|\\(.*sleep\\(.+\\)|(%[a-zA-Z0-9]{2}[a-zA-Z]{0,1})+\\))", + "options": { + "case_sensitive": false + } + } + } + ], + "transformers": [] + }, + { + "id": "nfd-000-009", + "name": "Detect failed attempt to leak the structure of the application", + "tags": { + "type": "security_scanner", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "1" + }, + "conditions": [ + { + "operator": "match_regex", + "parameters": { + "inputs": [ + { + "address": "server.response.status" + } + ], + "regex": "^404$", + "options": { + "case_sensitive": true + } + } + }, + { + "operator": "match_regex", + "parameters": { + "inputs": [ + { + "address": "server.request.uri.raw" + } + ], + "regex": "/(login\\.rol|LICENSE|[\\w-]+\\.(plx|pwd))$", + "options": { + "case_sensitive": false + } + } + } + ], + "transformers": [] + }, + { + "id": "nfd-000-010", + "name": "Detect failed attempts to find API documentation", + "tags": { + "type": "security_scanner", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "0" + }, + "conditions": [ + { + "operator": "match_regex", + "parameters": { + "inputs": [ + { + "address": "server.response.status" + } + ], + "regex": "^404$", + "options": { + "case_sensitive": true + } + } + }, + { + "operator": "match_regex", + "parameters": { + "inputs": [ + { + "address": "server.request.uri.raw" + } + ], + "regex": "(?:/swagger\\b|/api[-/]docs?\\b)", + "options": { + "case_sensitive": false + } + } + } + ], + "transformers": [] + }, + { + "id": "sqr-000-001", + "name": "SSRF: Try to access the credential manager of the main cloud services", + "tags": { + "type": "ssrf", + "category": "attack_attempt", + "cwe": "918", + "capec": "1000/225/115/664", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "(?i)^\\W*((http|ftp)s?://)?\\W*((::f{4}:)?(169|(0x)?0*a9|0+251)\\.?(254|(0x)?0*fe|0+376)[0-9a-fx\\.:]+|metadata\\.google\\.internal|metadata\\.goog)\\W*/", + "options": { + "min_length": 4 + } + }, + "operator": "match_regex" + } + ], + "transformers": [ + "removeNulls" + ] + }, + { + "id": "sqr-000-002", + "name": "Server-side Javascript injection: Try to detect obvious JS injection", + "tags": { + "type": "js_code_injection", + "category": "attack_attempt", + "cwe": "94", + "capec": "1000/152/242" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "require\\(['\"][\\w\\.]+['\"]\\)|process\\.\\w+\\([\\w\\.]*\\)|\\.toString\\(\\)", + "options": { + "min_length": 4 + } + }, + "operator": "match_regex" + } + ], + "transformers": [ + "removeNulls" + ] + }, + { + "id": "sqr-000-008", + "name": "Windows: Detect attempts to exfiltrate .ini files", + "tags": { + "type": "command_injection", + "category": "attack_attempt", + "cwe": "78", + "capec": "1000/152/248/88", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "(?i)[&|]\\s*type\\s+%\\w+%\\\\+\\w+\\.ini\\s*[&|]" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "sqr-000-009", + "name": "Linux: Detect attempts to exfiltrate passwd files", + "tags": { + "type": "command_injection", + "category": "attack_attempt", + "cwe": "78", + "capec": "1000/152/248/88", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "(?i)[&|]\\s*cat\\s*\\/etc\\/[\\w\\.\\/]*passwd\\s*[&|]" + }, + "operator": "match_regex" + } + ], + "transformers": [ + "cmdLine" + ] + }, + { + "id": "sqr-000-010", + "name": "Windows: Detect attempts to timeout a shell", + "tags": { + "type": "command_injection", + "category": "attack_attempt", + "cwe": "78", + "capec": "1000/152/248/88", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "(?i)[&|]\\s*timeout\\s+/t\\s+\\d+\\s*[&|]" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "sqr-000-011", + "name": "SSRF: Try to access internal OMI service (CVE-2021-38647)", + "tags": { + "type": "ssrf", + "category": "attack_attempt", + "cwe": "918", + "capec": "1000/225/115/664", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "http(s?):\\/\\/([A-Za-z0-9\\.\\-\\_]+|\\[[A-Fa-f0-9\\:]+\\]|):5986\\/wsman", + "options": { + "min_length": 4 + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "sqr-000-012", + "name": "SSRF: Detect SSRF attempt on internal service", + "tags": { + "type": "ssrf", + "category": "attack_attempt", + "cwe": "918", + "capec": "1000/225/115/664", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "^(jar:)?(http|https):\\/\\/([0-9oq]{1,5}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}|[0-9]{1,10})(:[0-9]{1,5})?(\\/[^:@]*)?$" + }, + "operator": "match_regex" + } + ], + "transformers": [ + "lowercase" + ] + }, + { + "id": "sqr-000-013", + "name": "SSRF: Detect SSRF attempts using IPv6 or octal/hexdecimal obfuscation", + "tags": { + "type": "ssrf", + "category": "attack_attempt", + "cwe": "918", + "capec": "1000/225/115/664", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "^(jar:)?(http|https):\\/\\/((\\[)?[:0-9a-f\\.x]{2,}(\\])?)(:[0-9]{1,5})?(\\/[^:@]*)?$" + }, + "operator": "match_regex" + } + ], + "transformers": [ + "lowercase" + ] + }, + { + "id": "sqr-000-014", + "name": "SSRF: Detect SSRF domain redirection bypass", + "tags": { + "type": "ssrf", + "category": "attack_attempt", + "cwe": "918", + "capec": "1000/225/115/664", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "(http|https):\\/\\/(?:.*\\.)?(?:burpcollaborator\\.net|localtest\\.me|mail\\.ebc\\.apple\\.com|bugbounty\\.dod\\.network|.*\\.[nx]ip\\.io|oastify\\.com|oast\\.(?:pro|live|site|online|fun|me)|sslip\\.io|requestbin\\.com|requestbin\\.net|hookbin\\.com|webhook\\.site|canarytokens\\.com|interact\\.sh|ngrok\\.io|bugbounty\\.click|prbly\\.win|qualysperiscope\\.com|vii\\.one|act1on3\\.ru)" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "sqr-000-015", + "name": "SSRF: Detect SSRF attempt using non HTTP protocol", + "tags": { + "type": "ssrf", + "category": "attack_attempt", + "cwe": "918", + "capec": "1000/225/115/664", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "^(jar:)?((file|netdoc):\\/\\/[\\\\\\/]+|(dict|gopher|ldap|sftp|tftp):\\/\\/.*:[0-9]{1,5})" + }, + "operator": "match_regex" + } + ], + "transformers": [ + "lowercase" + ] + }, + { + "id": "sqr-000-017", + "name": "Log4shell: Attempt to exploit log4j CVE-2021-44228", + "tags": { + "type": "exploit_detection", + "category": "attack_attempt", + "cwe": "94", + "capec": "1000/152/242", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.uri.raw" + }, + { + "address": "server.request.query" + }, + { + "address": "server.request.body" + }, + { + "address": "server.request.path_params" + }, + { + "address": "server.request.headers.no_cookies" + }, + { + "address": "grpc.server.request.message" + }, + { + "address": "graphql.server.all_resolvers" + }, + { + "address": "graphql.server.resolver" + } + ], + "regex": "\\${[^j]*j[^n]*n[^d]*d[^i]*i[^:]*:[^}]*}" + }, + "operator": "match_regex" + } + ], + "transformers": [ + "unicode_normalize" + ] + }, + { + "id": "ua0-600-0xx", + "name": "Joomla exploitation tool", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Joomla exploitation tool", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "JDatabaseDriverMysqli" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-10x", + "name": "Nessus", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Nessus", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)^Nessus(/|([ :]+SOAP))" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-12x", + "name": "Arachni", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Arachni", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "^Arachni\\/v" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-13x", + "name": "Jorgee", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Jorgee", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)\\bJorgee\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-14x", + "name": "Probely", + "tags": { + "type": "commercial_scanner", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Probely", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)\\bProbely\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-15x", + "name": "Metis", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Metis", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)\\bmetis\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-16x", + "name": "SQL power injector", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "SQLPowerInjector", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "sql power injector" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-18x", + "name": "N-Stealth", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "N-Stealth", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)\\bn-stealth\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-19x", + "name": "Brutus", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Brutus", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)\\bbrutus\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-1xx", + "name": "Shellshock exploitation tool", + "tags": { + "type": "security_scanner", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "\\(\\) \\{ :; *\\}" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-20x", + "name": "Netsparker", + "tags": { + "type": "commercial_scanner", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Netsparker", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "\\bnetsparker\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-22x", + "name": "JAASCois", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "JAASCois", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)\\bjaascois\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-26x", + "name": "Nsauditor", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Nsauditor", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)\\bnsauditor\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-27x", + "name": "Paros", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Paros", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)Mozilla/.* Paros/" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-28x", + "name": "DirBuster", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "DirBuster", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)\\bdirbuster\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-29x", + "name": "Pangolin", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Pangolin", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)\\bpangolin\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-2xx", + "name": "Qualys", + "tags": { + "type": "commercial_scanner", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Qualys", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)\\bqualys\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-30x", + "name": "SQLNinja", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "SQLNinja", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)\\bsqlninja\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-31x", + "name": "Nikto", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Nikto", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "\\(Nikto/[\\d\\.]+\\)" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-33x", + "name": "BlackWidow", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "BlackWidow", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)\\bblack\\s?widow\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-34x", + "name": "Grendel-Scan", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Grendel-Scan", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)\\bgrendel-scan\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-35x", + "name": "Havij", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Havij", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)\\bhavij\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-36x", + "name": "w3af", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "w3af", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)\\bw3af\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-37x", + "name": "Nmap", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Nmap", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "nmap (nse|scripting engine)" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-39x", + "name": "Nessus Scripted", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Nessus", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)^'?[a-z0-9_]+\\.nasl'?$" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-3xx", + "name": "Evil Scanner", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "EvilScanner", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)\\bevilScanner\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-40x", + "name": "WebFuck", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "WebFuck", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)\\bWebFuck\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-42x", + "name": "OpenVAS", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "OpenVAS", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)OpenVAS\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-43x", + "name": "Spider-Pig", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Spider-Pig", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "Powered by Spider-Pig by tinfoilsecurity\\.com" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-44x", + "name": "Zgrab", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Zgrab", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "Mozilla/\\d+.\\d+ zgrab" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-45x", + "name": "Zmeu", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Zmeu", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)\\bZmEu\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-47x", + "name": "GoogleSecurityScanner", + "tags": { + "type": "commercial_scanner", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "GoogleSecurityScanner", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)\\bGoogleSecurityScanner\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-48x", + "name": "Commix", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Commix", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "^commix\\/" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-49x", + "name": "Gobuster", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Gobuster", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "^gobuster\\/" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-4xx", + "name": "CGIchk", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "CGIchk", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)\\bcgichk\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-51x", + "name": "FFUF", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "FFUF", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)^Fuzz Faster U Fool\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-52x", + "name": "Nuclei", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Nuclei", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)^Nuclei\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-53x", + "name": "Tsunami", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Tsunami", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)\\bTsunamiSecurityScanner\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-54x", + "name": "Nimbostratus", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Nimbostratus", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)\\bnimbostratus-bot\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-55x", + "name": "Datadog test scanner: user-agent", + "tags": { + "type": "security_scanner", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Datadog Canary Test", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + }, + { + "address": "grpc.server.request.metadata", + "key_path": [ + "dd-canary" + ] + } + ], + "regex": "^dd-test-scanner-log(?:$|/|\\s)" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-56x", + "name": "Datadog test scanner - blocking version: user-agent", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Datadog Canary Test", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + }, + { + "address": "grpc.server.request.metadata", + "key_path": [ + "dd-canary" + ] + } + ], + "regex": "^dd-test-scanner-log-block(?:$|/|\\s)" + }, + "operator": "match_regex" + } + ], + "transformers": [], + "on_match": [ + "block" + ] + }, + { + "id": "ua0-600-57x", + "name": "AlertLogic", + "tags": { + "type": "commercial_scanner", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "AlertLogic", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "\\bAlertLogic-MDR-" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-58x", + "name": "wfuzz", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "wfuzz", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "\\bwfuzz\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-59x", + "name": "Detectify", + "tags": { + "type": "commercial_scanner", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Detectify", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "\\bdetectify\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-5xx", + "name": "Blind SQL Injection Brute Forcer", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "BSQLBF", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)\\bbsqlbf\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-60x", + "name": "masscan", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "masscan", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "^masscan/" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-61x", + "name": "WPScan", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "WPScan", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "^wpscan\\b" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-62x", + "name": "Aon pentesting services", + "tags": { + "type": "commercial_scanner", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Aon", + "confidence": "0" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "^Aon/" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-63x", + "name": "FeroxBuster", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "feroxbuster", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "^feroxbuster/" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-6xx", + "name": "Stealthy scanner", + "tags": { + "type": "security_scanner", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "mozilla/4\\.0 \\(compatible(; msie (?:6\\.0; (?:win32|Windows NT 5\\.0)|4\\.0; Windows NT))?\\)", + "options": { + "case_sensitive": false + } + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-7xx", + "name": "SQLmap", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "SQLmap", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "^sqlmap/" + }, + "operator": "match_regex" + } + ], + "transformers": [] + }, + { + "id": "ua0-600-9xx", + "name": "Skipfish", + "tags": { + "type": "attack_tool", + "category": "attack_attempt", + "cwe": "200", + "capec": "1000/118/169", + "tool_name": "Skipfish", + "confidence": "1" + }, + "conditions": [ + { + "parameters": { + "inputs": [ + { + "address": "server.request.headers.no_cookies", + "key_path": [ + "user-agent" + ] + } + ], + "regex": "(?i)mozilla/5\\.0 sf/" + }, + "operator": "match_regex" + } + ], + "transformers": [] + } + ], + "processors": [ + { + "id": "extract-content", + "generator": "extract_schema", + "conditions": [ + { + "operator": "equals", + "parameters": { + "inputs": [ + { + "address": "waf.context.processor", + "key_path": [ + "extract-schema" + ] + } + ], + "type": "boolean", + "value": true + } + } + ], + "parameters": { + "mappings": [ + { + "inputs": [ + { + "address": "server.request.body" + } + ], + "output": "_dd.appsec.s.req.body" + }, + { + "inputs": [ + { + "address": "server.request.cookies" + } + ], + "output": "_dd.appsec.s.req.cookies" + }, + { + "inputs": [ + { + "address": "server.request.query" + } + ], + "output": "_dd.appsec.s.req.query" + }, + { + "inputs": [ + { + "address": "server.request.path_params" + } + ], + "output": "_dd.appsec.s.req.params" + }, + { + "inputs": [ + { + "address": "server.response.body" + } + ], + "output": "_dd.appsec.s.res.body" + }, + { + "inputs": [ + { + "address": "graphql.server.all_resolvers" + } + ], + "output": "_dd.appsec.s.graphql.all_resolvers" + }, + { + "inputs": [ + { + "address": "graphql.server.resolver" + } + ], + "output": "_dd.appsec.s.graphql.resolver" + } + ], + "scanners": [ + { + "tags": { + "category": "payment" + } + }, + { + "tags": { + "category": "pii" + } + } + ] + }, + "evaluate": false, + "output": true + }, + { + "id": "extract-headers", + "generator": "extract_schema", + "conditions": [ + { + "operator": "equals", + "parameters": { + "inputs": [ + { + "address": "waf.context.processor", + "key_path": [ + "extract-schema" + ] + } + ], + "type": "boolean", + "value": true + } + } + ], + "parameters": { + "mappings": [ + { + "inputs": [ + { + "address": "server.request.headers.no_cookies" + } + ], + "output": "_dd.appsec.s.req.headers" + }, + { + "inputs": [ + { + "address": "server.response.headers.no_cookies" + } + ], + "output": "_dd.appsec.s.res.headers" + } + ], + "scanners": [ + { + "tags": { + "category": "credentials" + } + }, + { + "tags": { + "category": "pii" + } + } + ] + }, + "evaluate": false, + "output": true + } + ], + "scanners": [ + { + "id": "JU1sRk3mSzqSUJn6GrVn7g", + "name": "American Express Card Scanner (4+4+4+3 digits)", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:card|cc|credit|debit|payment|amex|visa|mastercard|maestro|discover|jcb|diner|amex|visa|mastercard|maestro|discover|jcb|diner)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b3[47]\\d{2}(?:(?:\\s\\d{4}\\s\\d{4}\\s\\d{3})|(?:\\,\\d{4}\\,\\d{4}\\,\\d{3})|(?:-\\d{4}-\\d{4}-\\d{3})|(?:\\.\\d{4}\\.\\d{4}\\.\\d{3}))\\b", + "options": { + "case_sensitive": false, + "min_length": 16 + } + } + }, + "tags": { + "type": "card", + "card_type": "amex", + "category": "payment" + } + }, + { + "id": "edmH513UTQWcRiQ9UnzHlw-mod", + "name": "American Express Card Scanner (4+6|5+5|6 digits)", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:card|cc|credit|debit|payment|amex|visa|mastercard|maestro|discover|jcb|diner)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b3[47]\\d{2}(?:(?:\\s\\d{5,6}\\s\\d{5,6})|(?:\\.\\d{5,6}\\.\\d{5,6})|(?:-\\d{5,6}-\\d{5,6})|(?:,\\d{5,6},\\d{5,6}))\\b", + "options": { + "case_sensitive": false, + "min_length": 17 + } + } + }, + "tags": { + "type": "card", + "card_type": "amex", + "category": "payment" + } + }, + { + "id": "e6K4h_7qTLaMiAbaNXoSZA", + "name": "American Express Card Scanner (8+7 digits)", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:card|cc|credit|debit|payment|amex|visa|mastercard|maestro|discover|jcb|diner)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b3[47]\\d{6}(?:(?:\\s\\d{7})|(?:\\,\\d{7})|(?:-\\d{7})|(?:\\.\\d{7}))\\b", + "options": { + "case_sensitive": false, + "min_length": 16 + } + } + }, + "tags": { + "type": "card", + "card_type": "amex", + "category": "payment" + } + }, + { + "id": "K2rZflWzRhGM9HiTc6whyQ", + "name": "American Express Card Scanner (1x15 digits)", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:card|cc|credit|debit|payment|amex|visa|mastercard|maestro|discover|jcb|diner)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b3[47]\\d{13}\\b", + "options": { + "case_sensitive": false, + "min_length": 15 + } + } + }, + "tags": { + "type": "card", + "card_type": "amex", + "category": "payment" + } + }, + { + "id": "9d7756e343cefa22a5c098e1092590f806eb5446", + "name": "Basic Authentication Scanner", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\bauthorization\\b", + "options": { + "case_sensitive": false, + "min_length": 13 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "^basic\\s+[A-Za-z0-9+/=]+", + "options": { + "case_sensitive": false, + "min_length": 7 + } + } + }, + "tags": { + "type": "basic_auth", + "category": "credentials" + } + }, + { + "id": "mZy8XjZLReC9smpERXWnnw", + "name": "Bearer Authentication Scanner", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\bauthorization\\b", + "options": { + "case_sensitive": false, + "min_length": 13 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "^bearer\\s+[-a-z0-9._~+/]{4,}", + "options": { + "case_sensitive": false, + "min_length": 11 + } + } + }, + "tags": { + "type": "bearer_token", + "category": "credentials" + } + }, + { + "id": "450239afc250a19799b6c03dc0e16fd6a4b2a1af", + "name": "Canadian Social Insurance Number Scanner", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:social[\\s_]?(?:insurance(?:\\s+number)?)?|SIN|Canadian[\\s_]?(?:social[\\s_]?(?:insurance)?|insurance[\\s_]?number)?)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b\\d{3}-\\d{3}-\\d{3}\\b", + "options": { + "case_sensitive": false, + "min_length": 11 + } + } + }, + "tags": { + "type": "canadian_sin", + "category": "pii" + } + }, + { + "id": "87a879ff33693b46c8a614d8211f5a2c289beca0", + "name": "Digest Authentication Scanner", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\bauthorization\\b", + "options": { + "case_sensitive": false, + "min_length": 13 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "^digest\\s+", + "options": { + "case_sensitive": false, + "min_length": 7 + } + } + }, + "tags": { + "type": "digest_auth", + "category": "credentials" + } + }, + { + "id": "qWumeP1GQUa_E4ffAnT-Yg", + "name": "American Express Card Scanner (1x14 digits)", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:card|cc|credit|debit|payment|amex|visa|mastercard|maestro|discover|jcb|diner)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "(?:30[0-59]\\d|3[689]\\d{2})(?:\\d{10})", + "options": { + "case_sensitive": false, + "min_length": 14 + } + } + }, + "tags": { + "type": "card", + "card_type": "diners", + "category": "payment" + } + }, + { + "id": "NlTWWM5LS6W0GSqBLuvtRw", + "name": "Diners Card Scanner (4+4+4+2 digits)", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:card|cc|credit|debit|payment|amex|visa|mastercard|maestro|discover|jcb|diner)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:30[0-59]\\d|3[689]\\d{2})(?:(?:\\s\\d{4}\\s\\d{4}\\s\\d{2})|(?:\\,\\d{4}\\,\\d{4}\\,\\d{2})|(?:-\\d{4}-\\d{4}-\\d{2})|(?:\\.\\d{4}\\.\\d{4}\\.\\d{2}))\\b", + "options": { + "case_sensitive": false, + "min_length": 17 + } + } + }, + "tags": { + "type": "card", + "card_type": "diners", + "category": "payment" + } + }, + { + "id": "Xr5VdbQSTXitYGGiTfxBpw", + "name": "Diners Card Scanner (4+6+4 digits)", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:card|cc|credit|debit|payment|amex|visa|mastercard|maestro|discover|jcb|diner)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:30[0-59]\\d|3[689]\\d{2})(?:(?:\\s\\d{6}\\s\\d{4})|(?:\\.\\d{6}\\.\\d{4})|(?:-\\d{6}-\\d{4})|(?:,\\d{6},\\d{4}))\\b", + "options": { + "case_sensitive": false, + "min_length": 16 + } + } + }, + "tags": { + "type": "card", + "card_type": "diners", + "category": "payment" + } + }, + { + "id": "gAbunN_WQNytxu54DjcbAA-mod", + "name": "Diners Card Scanner (8+6 digits)", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:card|cc|credit|debit|payment|amex|visa|mastercard|maestro|discover|jcb|diner)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:30[0-59]\\d{5}|3[689]\\d{6})\\s?(?:(?:\\s\\d{6})|(?:\\,\\d{6})|(?:-\\d{6})|(?:\\.\\d{6}))\\b", + "options": { + "case_sensitive": false, + "min_length": 14 + } + } + }, + "tags": { + "type": "card", + "card_type": "diners", + "category": "payment" + } + }, + { + "id": "9cs4qCfEQBeX17U7AepOvQ", + "name": "MasterCard Scanner (2x8 digits)", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:card|cc|credit|debit|payment|amex|visa|mastercard|maestro|discover|jcb|diner)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:6221(?:2[6-9]|[3-9][0-9])\\d{2}(?:,\\d{8}|\\s\\d{8}|-\\d{8}|\\.\\d{8})|6229(?:[01][0-9]|2[0-5])\\d{2}(?:,\\d{8}|\\s\\d{8}|-\\d{8}|\\.\\d{8})|(?:6011|65\\d{2}|64[4-9]\\d|622[2-8])\\d{4}(?:,\\d{8}|\\s\\d{8}|-\\d{8}|\\.\\d{8}))\\b", + "options": { + "case_sensitive": false, + "min_length": 16 + } + } + }, + "tags": { + "type": "card", + "card_type": "discover", + "category": "payment" + } + }, + { + "id": "YBIDWJIvQWW_TFOyU0CGJg", + "name": "Discover Card Scanner (4x4 digits)", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:card|cc|credit|debit|payment|amex|visa|mastercard|maestro|discover|jcb|diner)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:(?:(?:6221(?:2[6-9]|[3-9][0-9])\\d{2}(?:,\\d{4}){2})|(?:6221\\s(?:2[6-9]|[3-9][0-9])\\d{2}(?:\\s\\d{4}){2})|(?:6221\\.(?:2[6-9]|[3-9][0-9])\\d{2}(?:\\.\\d{4}){2})|(?:6221-(?:2[6-9]|[3-9][0-9])\\d{2}(?:-\\d{4}){2}))|(?:(?:6229(?:[01][0-9]|2[0-5])\\d{2}(?:,\\d{4}){2})|(?:6229\\s(?:[01][0-9]|2[0-5])\\d{2}(?:\\s\\d{4}){2})|(?:6229\\.(?:[01][0-9]|2[0-5])\\d{2}(?:\\.\\d{4}){2})|(?:6229-(?:[01][0-9]|2[0-5])\\d{2}(?:-\\d{4}){2}))|(?:(?:6011|65\\d{2}|64[4-9]\\d|622[2-8])(?:(?:\\s\\d{4}){3}|(?:\\.\\d{4}){3}|(?:-\\d{4}){3}|(?:,\\d{4}){3})))\\b", + "options": { + "case_sensitive": false, + "min_length": 16 + } + } + }, + "tags": { + "type": "card", + "card_type": "discover", + "category": "payment" + } + }, + { + "id": "12cpbjtVTMaMutFhh9sojQ", + "name": "Discover Card Scanner (1x16 digits)", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:card|cc|credit|debit|payment|amex|visa|mastercard|maestro|discover|jcb|diner)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:6221(?:2[6-9]|[3-9][0-9])\\d{10}|6229(?:[01][0-9]|2[0-5])\\d{10}|(?:6011|65\\d{2}|64[4-9]\\d|622[2-8])\\d{12})\\b", + "options": { + "case_sensitive": false, + "min_length": 16 + } + } + }, + "tags": { + "type": "card", + "card_type": "discover", + "category": "payment" + } + }, + { + "id": "PuXiVTCkTHOtj0Yad1ppsw", + "name": "Standard E-mail Address", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:(?:e[-\\s]?)?mail|address|sender|\\bto\\b|from|recipient)\\b", + "options": { + "case_sensitive": false, + "min_length": 2 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b[\\w!#$%&'*+/=?`{|}~^-]+(?:\\.[\\w!#$%&'*+/=?`{|}~^-]+)*(%40|@)(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}\\b", + "options": { + "case_sensitive": false, + "min_length": 5 + } + } + }, + "tags": { + "type": "email", + "category": "pii" + } + }, + { + "id": "8VS2RKxzR8a_95L5fuwaXQ", + "name": "IBAN", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:iban|account|sender|receiver)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:NO\\d{2}(?:[ \\-]?\\d{4}){2}[ \\-]?\\d{3}|BE\\d{2}(?:[ \\-]?\\d{4}){3}|(?:DK|FO|FI|GL|SD)\\d{2}(?:[ \\-]?\\d{4}){3}[ \\-]?\\d{2}|NL\\d{2}[ \\-]?[A-Z]{4}(?:[ \\-]?\\d{4}){2}[ \\-]?\\d{2}|MK\\d{2}[ \\-]?\\d{3}[A-Z0-9](?:[ \\-]?[A-Z0-9]{4}){2}[ \\-]?[A-Z0-9]\\d{2}|SI\\d{17}|(?:AT|BA|EE|LT|XK)\\d{18}|(?:LU|KZ|EE|LT)\\d{5}[A-Z0-9]{13}|LV\\d{2}[A-Z]{4}[A-Z0-9]{13}|(?:LI|CH)\\d{2}[ \\-]?\\d{4}[ \\-]?\\d[A-Z0-9]{3}(?:[ \\-]?[A-Z0-9]{4}){2}[ \\-]?[A-Z0-9]|HR\\d{2}(?:[ \\-]?\\d{4}){4}[ \\-]?\\d|GE\\d{2}[ \\-]?[A-Z0-9]{2}\\d{2}\\d{14}|VA\\d{20}|BG\\d{2}[A-Z]{4}\\d{6}[A-Z0-9]{8}|BH\\d{2}[A-Z]{4}[A-Z0-9]{14}|GB\\d{2}[A-Z]{4}(?:[ \\-]?\\d{4}){3}[ \\-]?\\d{2}|IE\\d{2}[ \\-]?[A-Z0-9]{4}(?:[ \\-]?\\d{4}){3}[ \\-]?\\d{2}|(?:CR|DE|ME|RS)\\d{2}(?:[ \\-]?\\d{4}){4}[ \\-]?\\d{2}|(?:AE|TL|IL)\\d{2}(?:[ \\-]?\\d{4}){4}[ \\-]?\\d{3}|GI\\d{2}[ \\-]?[A-Z]{4}(?:[ \\-]?[A-Z0-9]{4}){3}[ \\-]?[A-Z0-9]{3}|IQ\\d{2}[ \\-]?[A-Z]{4}(?:[ \\-]?\\d{4}){3}[ \\-]?\\d{3}|MD\\d{2}(?:[ \\-]?[A-Z0-9]{4}){5}|SA\\d{2}[ \\-]?\\d{2}[A-Z0-9]{2}(?:[ \\-]?[A-Z0-9]{4}){4}|RO\\d{2}[ \\-]?[A-Z]{4}(?:[ \\-]?[A-Z0-9]{4}){4}|(?:PK|VG)\\d{2}[ \\-]?[A-Z0-9]{4}(?:[ \\-]?\\d{4}){4}|AD\\d{2}(?:[ \\-]?\\d{4}){2}(?:[ \\-]?[A-Z0-9]{4}){3}|(?:CZ|SK|ES|SE|TN)\\d{2}(?:[ \\-]?\\d{4}){5}|(?:LY|PT|ST)\\d{2}(?:[ \\-]?\\d{4}){5}[ \\-]?\\d|TR\\d{2}[ \\-]?\\d{4}[ \\-]?\\d[A-Z0-9]{3}(?:[ \\-]?[A-Z0-9]{4}){3}[ \\-]?[A-Z0-9]{2}|IS\\d{2}(?:[ \\-]?\\d{4}){5}[ \\-]?\\d{2}|(?:IT|SM)\\d{2}[ \\-]?[A-Z]\\d{3}[ \\-]?\\d{4}[ \\-]?\\d{3}[A-Z0-9](?:[ \\-]?[A-Z0-9]{4}){2}[ \\-]?[A-Z0-9]{3}|GR\\d{2}[ \\-]?\\d{4}[ \\-]?\\d{3}[A-Z0-9](?:[ \\-]?[A-Z0-9]{4}){3}[A-Z0-9]{3}|(?:FR|MC)\\d{2}(?:[ \\-]?\\d{4}){2}[ \\-]?\\d{2}[A-Z0-9]{2}(?:[ \\-]?[A-Z0-9]{4}){2}[ \\-]?[A-Z0-9]\\d{2}|MR\\d{2}(?:[ \\-]?\\d{4}){5}[ \\-]?\\d{3}|(?:SV|DO)\\d{2}[ \\-]?[A-Z]{4}(?:[ \\-]?\\d{4}){5}|BY\\d{2}[ \\-]?[A-Z]{4}[ \\-]?\\d{4}(?:[ \\-]?[A-Z0-9]{4}){4}|GT\\d{2}(?:[ \\-]?[A-Z0-9]{4}){6}|AZ\\d{2}[ \\-]?[A-Z0-9]{4}(?:[ \\-]?\\d{5}){4}|LB\\d{2}[ \\-]?\\d{4}(?:[ \\-]?[A-Z0-9]{5}){4}|(?:AL|CY)\\d{2}(?:[ \\-]?\\d{4}){2}(?:[ \\-]?[A-Z0-9]{4}){4}|(?:HU|PL)\\d{2}(?:[ \\-]?\\d{4}){6}|QA\\d{2}[ \\-]?[A-Z]{4}(?:[ \\-]?[A-Z0-9]{4}){5}[ \\-]?[A-Z0-9]|PS\\d{2}[ \\-]?[A-Z0-9]{4}(?:[ \\-]?\\d{4}){5}[ \\-]?\\d|UA\\d{2}[ \\-]?\\d{4}[ \\-]?\\d{2}[A-Z0-9]{2}(?:[ \\-]?[A-Z0-9]{4}){4}[ \\-]?[A-Z0-9]|BR\\d{2}(?:[ \\-]?\\d{4}){5}[ \\-]?\\d{3}[A-Z0-9][ \\-]?[A-Z0-9]|EG\\d{2}(?:[ \\-]?\\d{4}){6}\\d|MU\\d{2}[ \\-]?[A-Z]{4}(?:[ \\-]?\\d{4}){4}\\d{3}[A-Z][ \\-]?[A-Z]{2}|(?:KW|JO)\\d{2}[ \\-]?[A-Z]{4}(?:[ \\-]?[A-Z0-9]{4}){5}[ \\-]?[A-Z0-9]{2}|MT\\d{2}[ \\-]?[A-Z]{4}[ \\-]?\\d{4}[ \\-]?\\d[A-Z0-9]{3}(?:[ \\-]?[A-Z0-9]{3}){4}[ \\-]?[A-Z0-9]{3}|SC\\d{2}[ \\-]?[A-Z]{4}(?:[ \\-]?\\d{4}){5}[ \\-]?[A-Z]{3}|LC\\d{2}[ \\-]?[A-Z]{4}(?:[ \\-]?[A-Z0-9]{4}){6})\\b", + "options": { + "case_sensitive": false, + "min_length": 15 + } + } + }, + "tags": { + "type": "iban", + "category": "payment" + } + }, + { + "id": "h6WJcecQTwqvN9KeEtwDvg", + "name": "JCB Card Scanner (1x16 digits)", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:card|cc|credit|debit|payment|amex|visa|mastercard|maestro|discover|jcb|diner)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b35(?:2[89]|[3-9][0-9])(?:\\d{12})\\b", + "options": { + "case_sensitive": false, + "min_length": 16 + } + } + }, + "tags": { + "type": "card", + "card_type": "jcb", + "category": "payment" + } + }, + { + "id": "gcEaMu_VSJ2-bGCEkgyC0w", + "name": "JCB Card Scanner (2x8 digits)", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:card|cc|credit|debit|payment|amex|visa|mastercard|maestro|discover|jcb|diner)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b35(?:2[89]|[3-9][0-9])\\d{4}(?:(?:,\\d{8})|(?:-\\d{8})|(?:\\s\\d{8})|(?:\\.\\d{8}))\\b", + "options": { + "case_sensitive": false, + "min_length": 17 + } + } + }, + "tags": { + "type": "card", + "card_type": "jcb", + "category": "payment" + } + }, + { + "id": "imTliuhXT5GAeRNhqChXQQ", + "name": "JCB Card Scanner (4x4 digits)", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:card|cc|credit|debit|payment|amex|visa|mastercard|maestro|discover|jcb|diner)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b35(?:2[89]|[3-9][0-9])(?:(?:\\s\\d{4}){3}|(?:\\.\\d{4}){3}|(?:-\\d{4}){3}|(?:,\\d{4}){3})\\b", + "options": { + "case_sensitive": false, + "min_length": 16 + } + } + }, + "tags": { + "type": "card", + "card_type": "jcb", + "category": "payment" + } + }, + { + "id": "9osY3xc9Q7ONAV0zw9Uz4A", + "name": "JSON Web Token", + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\bey[I-L][\\w=-]+\\.ey[I-L][\\w=-]+(\\.[\\w.+\\/=-]+)?\\b", + "options": { + "case_sensitive": false, + "min_length": 20 + } + } + }, + "tags": { + "type": "json_web_token", + "category": "credentials" + } + }, + { + "id": "d1Q9D3YMRxuVKf6CZInJPw", + "name": "Maestro Card Scanner (1x16 digits)", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:card|cc|credit|debit|payment|amex|visa|mastercard|maestro|discover|jcb|diner)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:5[06-9]\\d{2}|6\\d{3})(?:\\d{12})\\b", + "options": { + "case_sensitive": false, + "min_length": 16 + } + } + }, + "tags": { + "type": "card", + "card_type": "maestro", + "category": "payment" + } + }, + { + "id": "M3YIQKKjRVmoeQuM3pjzrw", + "name": "Maestro Card Scanner (2x8 digits)", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:card|cc|credit|debit|payment|amex|visa|mastercard|maestro|discover|jcb|diner)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:5[06-9]\\d{6}|6\\d{7})(?:\\s\\d{8}|\\.\\d{8}|-\\d{8}|,\\d{8})\\b", + "options": { + "case_sensitive": false, + "min_length": 17 + } + } + }, + "tags": { + "type": "card", + "card_type": "maestro", + "category": "payment" + } + }, + { + "id": "hRxiQBlSSVKcjh5U7LZYLA", + "name": "Maestro Card Scanner (4x4 digits)", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:card|cc|credit|debit|payment|amex|visa|mastercard|maestro|discover|jcb|diner)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:5[06-9]\\d{2}|6\\d{3})(?:(?:\\s\\d{4}){3}|(?:\\.\\d{4}){3}|(?:-\\d{4}){3}|(?:,\\d{4}){3})\\b", + "options": { + "case_sensitive": false, + "min_length": 16 + } + } + }, + "tags": { + "type": "card", + "card_type": "maestro", + "category": "payment" + } + }, + { + "id": "NwhIYNS4STqZys37WlaIKA", + "name": "MasterCard Scanner (2x8 digits)", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:card|cc|credit|debit|payment|amex|visa|mastercard|maestro|discover|jcb|diner)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:(?:5[1-5]\\d{2})|(?:222[1-9])|(?:22[3-9]\\d)|(?:2[3-6]\\d{2})|(?:27[0-1]\\d)|(?:2720))(?:(?:\\d{4}(?:(?:,\\d{8})|(?:-\\d{8})|(?:\\s\\d{8})|(?:\\.\\d{8}))))\\b", + "options": { + "case_sensitive": false, + "min_length": 16 + } + } + }, + "tags": { + "type": "card", + "card_type": "mastercard", + "category": "payment" + } + }, + { + "id": "axxJkyjhRTOuhjwlsA35Vw", + "name": "MasterCard Scanner (4x4 digits)", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:card|cc|credit|debit|payment|amex|visa|mastercard|maestro|discover|jcb|diner)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:(?:5[1-5]\\d{2})|(?:222[1-9])|(?:22[3-9]\\d)|(?:2[3-6]\\d{2})|(?:27[0-1]\\d)|(?:2720))(?:(?:\\s\\d{4}){3}|(?:\\.\\d{4}){3}|(?:-\\d{4}){3}|(?:,\\d{4}){3})\\b", + "options": { + "case_sensitive": false, + "min_length": 16 + } + } + }, + "tags": { + "type": "card", + "card_type": "mastercard", + "category": "payment" + } + }, + { + "id": "76EhmoK3TPqJcpM-fK0pLw", + "name": "MasterCard Scanner (1x16 digits)", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:card|cc|credit|debit|payment|amex|visa|mastercard|maestro|discover|jcb|diner)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:(?:5[1-5]\\d{2})|(?:222[1-9])|(?:22[3-9]\\d)|(?:2[3-6]\\d{2})|(?:27[0-1]\\d)|(?:2720))(?:\\d{12})\\b", + "options": { + "case_sensitive": false, + "min_length": 16 + } + } + }, + "tags": { + "type": "card", + "card_type": "mastercard", + "category": "payment" + } + }, + { + "id": "de0899e0cbaaa812bb624cf04c912071012f616d-mod", + "name": "UK National Insurance Number Scanner", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "^nin$|\\binsurance\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b[A-Z]{2}[\\s-]?\\d{6}[\\s-]?[A-Z]?\\b", + "options": { + "case_sensitive": false, + "min_length": 8 + } + } + }, + "tags": { + "type": "uk_nin", + "category": "pii" + } + }, + { + "id": "d962f7ddb3f55041e39195a60ff79d4814a7c331", + "name": "US Passport Scanner", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\bpassport\\b", + "options": { + "case_sensitive": false, + "min_length": 8 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b[0-9A-Z]{9}\\b|\\b[0-9]{6}[A-Z][0-9]{2}\\b", + "options": { + "case_sensitive": false, + "min_length": 8 + } + } + }, + "tags": { + "type": "passport_number", + "category": "pii" + } + }, + { + "id": "7771fc3b-b205-4b93-bcef-28608c5c1b54", + "name": "United States Social Security Number Scanner", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:SSN|(?:(?:social)?[\\s_]?(?:security)?[\\s_]?(?:number)?)?)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b\\d{3}[-\\s\\.]{1}\\d{2}[-\\s\\.]{1}\\d{4}\\b", + "options": { + "case_sensitive": false, + "min_length": 11 + } + } + }, + "tags": { + "type": "us_ssn", + "category": "pii" + } + }, + { + "id": "ac6d683cbac77f6e399a14990793dd8fd0fca333", + "name": "US Vehicle Identification Number Scanner", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:vehicle[_\\s-]*identification[_\\s-]*number|vin)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b[A-HJ-NPR-Z0-9]{17}\\b", + "options": { + "case_sensitive": false, + "min_length": 17 + } + } + }, + "tags": { + "type": "vin", + "category": "pii" + } + }, + { + "id": "wJIgOygRQhKkR69b_9XbRQ", + "name": "Visa Card Scanner (2x8 digits)", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:card|cc|credit|debit|payment|amex|visa|mastercard|maestro|discover|jcb|diner)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b4\\d{3}(?:(?:\\d{4}(?:(?:,\\d{8})|(?:-\\d{8})|(?:\\s\\d{8})|(?:\\.\\d{8}))))\\b", + "options": { + "case_sensitive": false, + "min_length": 16 + } + } + }, + "tags": { + "type": "card", + "card_type": "visa", + "category": "payment" + } + }, + { + "id": "0o71SJxXQNK7Q6gMbBesFQ", + "name": "Visa Card Scanner (4x4 digits)", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:card|cc|credit|debit|payment|amex|visa|mastercard|maestro|discover|jcb|diner)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "\\b4\\d{3}(?:(?:,\\d{4}){3}|(?:\\s\\d{4}){3}|(?:\\.\\d{4}){3}|(?:-\\d{4}){3})\\b", + "options": { + "case_sensitive": false, + "min_length": 16 + } + } + }, + "tags": { + "type": "card", + "card_type": "visa", + "category": "payment" + } + }, + { + "id": "QrHD6AfgQm6z-j0wStxTvA", + "name": "Visa Card Scanner (1x15 & 1x16 & 1x19 digits)", + "key": { + "operator": "match_regex", + "parameters": { + "regex": "\\b(?:card|cc|credit|debit|payment|amex|visa|mastercard|maestro|discover|jcb|diner)\\b", + "options": { + "case_sensitive": false, + "min_length": 3 + } + } + }, + "value": { + "operator": "match_regex", + "parameters": { + "regex": "4[0-9]{12}(?:[0-9]{3})?", + "options": { + "case_sensitive": false, + "min_length": 13 + } + } + }, + "tags": { + "type": "card", + "card_type": "visa", + "category": "payment" + } + } + ] +} \ No newline at end of file diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/trace_utils/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/trace_utils/__init__.py new file mode 100644 index 0000000..25559d7 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/appsec/trace_utils/__init__.py @@ -0,0 +1,11 @@ +from ddtrace.appsec._trace_utils import block_request # noqa: F401 +from ddtrace.appsec._trace_utils import block_request_if_user_blocked # noqa: F401 +from ddtrace.appsec._trace_utils import should_block_user # noqa: F401 +from ddtrace.appsec._trace_utils import track_custom_event # noqa: F401 +from ddtrace.appsec._trace_utils import track_user_login_failure_event # noqa: F401 +from ddtrace.appsec._trace_utils import track_user_login_success_event # noqa: F401 +from ddtrace.appsec._trace_utils import track_user_signup_event # noqa: F401 +import ddtrace.internal.core + + +ddtrace.internal.core.on("set_user_for_asm", block_request_if_user_blocked, "block_user") diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/auto.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/auto.py new file mode 100644 index 0000000..27f8086 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/auto.py @@ -0,0 +1,21 @@ +""" +.. _ddtraceauto: + +Importing ``ddtrace.auto`` installs Datadog instrumentation in the runtime. It should be used +when :ref:`ddtrace-run` is not an option. Using it with :ref:`ddtrace-run` +is unsupported and may lead to undefined behavior:: + + # myapp.py + + import ddtrace.auto # install instrumentation as early as possible + import mystuff + + def main(): + print("It's my app!") + + main() + +If you'd like more granular control over instrumentation setup, you can call the `patch*` functions +directly. +""" +import ddtrace.bootstrap.sitecustomize # noqa:F401 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/bootstrap/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/bootstrap/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/bootstrap/preload.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/bootstrap/preload.py new file mode 100644 index 0000000..aecab4e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/bootstrap/preload.py @@ -0,0 +1,114 @@ +""" +Bootstrapping code that is run when using the `ddtrace-run` Python entrypoint +Add all monkey-patching that needs to run by default here +""" +import os # noqa:I001 + +from ddtrace import config # noqa:F401 +from ddtrace.debugging._config import di_config # noqa:F401 +from ddtrace.debugging._config import ed_config # noqa:F401 +from ddtrace.settings.profiling import config as profiling_config # noqa:F401 +from ddtrace.internal.logger import get_logger # noqa:F401 +from ddtrace.internal.module import ModuleWatchdog # noqa:F401 +from ddtrace.internal.runtime.runtime_metrics import RuntimeWorker # noqa:F401 +from ddtrace.internal.tracemethods import _install_trace_methods # noqa:F401 +from ddtrace.internal.utils.formats import asbool # noqa:F401 +from ddtrace.internal.utils.formats import parse_tags_str # noqa:F401 +from ddtrace.settings.asm import config as asm_config # noqa:F401 +from ddtrace import tracer + + +import typing as t + +# Register operations to be performned after the preload is complete. In +# general, we might need to perform some cleanup operations after the +# initialisation of the library, while also execute some more code after that. +# _____ ___ _________ _____ ______ _____ ___ _ _ _____ +# |_ _|| \/ || ___ \| _ || ___ \|_ _| / _ \ | \ | ||_ _| +# | | | . . || |_/ /| | | || |_/ / | | / /_\ \| \| | | | +# | | | |\/| || __/ | | | || / | | | _ || . ` | | | +# _| |_ | | | || | \ \_/ /| |\ \ | | | | | || |\ | | | +# \___/ \_| |_/\_| \___/ \_| \_| \_/ \_| |_/\_| \_/ \_/ +# Do not register any functions that import ddtrace modules that have not been +# imported yet. +post_preload = [] + + +def register_post_preload(func: t.Callable) -> None: + post_preload.append(func) + + +log = get_logger(__name__) + + +if profiling_config.enabled: + log.debug("profiler enabled via environment variable") + try: + import ddtrace.profiling.auto # noqa: F401 + except Exception: + log.error("failed to enable profiling", exc_info=True) + +if di_config.enabled or ed_config.enabled: + from ddtrace.debugging import DynamicInstrumentation + + DynamicInstrumentation.enable() + +if config._runtime_metrics_enabled: + RuntimeWorker.enable() + +if asbool(os.getenv("DD_IAST_ENABLED", False)): + from ddtrace.appsec._iast._utils import _is_python_version_supported + + if _is_python_version_supported(): + from ddtrace.appsec._iast._ast.ast_patching import _should_iast_patch + from ddtrace.appsec._iast._loader import _exec_iast_patched_module + + log.debug("IAST enabled") + ModuleWatchdog.register_pre_exec_module_hook(_should_iast_patch, _exec_iast_patched_module) + +if config._remote_config_enabled: + from ddtrace.internal.remoteconfig.worker import remoteconfig_poller + + remoteconfig_poller.enable() + config.enable_remote_configuration() + +if asm_config._asm_enabled or config._remote_config_enabled: + from ddtrace.appsec._remoteconfiguration import enable_appsec_rc + + enable_appsec_rc() + +if config._otel_enabled: + + @ModuleWatchdog.after_module_imported("opentelemetry.trace") + def _(_): + from opentelemetry.trace import set_tracer_provider + + from ddtrace.opentelemetry import TracerProvider + + set_tracer_provider(TracerProvider()) + + +if asbool(os.getenv("DD_TRACE_ENABLED", default=True)): + from ddtrace import patch_all + + @register_post_preload + def _(): + # We need to clean up after we have imported everything we need from + # ddtrace, but before we register the patch-on-import hooks for the + # integrations. + modules_to_patch = os.getenv("DD_PATCH_MODULES") + modules_to_str = parse_tags_str(modules_to_patch) + modules_to_bool = {k: asbool(v) for k, v in modules_to_str.items()} + patch_all(**modules_to_bool) + + if config.trace_methods: + _install_trace_methods(config.trace_methods) + +if "DD_TRACE_GLOBAL_TAGS" in os.environ: + env_tags = os.getenv("DD_TRACE_GLOBAL_TAGS") + tracer.set_tags(parse_tags_str(env_tags)) + + +@register_post_preload +def _(): + tracer._generate_diagnostic_logs() diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/bootstrap/sitecustomize.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/bootstrap/sitecustomize.py new file mode 100644 index 0000000..b7e1d59 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/bootstrap/sitecustomize.py @@ -0,0 +1,171 @@ +""" +Bootstrapping code that is run when using the `ddtrace-run` Python entrypoint +Add all monkey-patching that needs to run by default here +""" +# _____ ___ _________ _____ ______ _____ ___ _ _ _____ +# |_ _|| \/ || ___ \| _ || ___ \|_ _| / _ \ | \ | ||_ _| +# | | | . . || |_/ /| | | || |_/ / | | / /_\ \| \| | | | +# | | | |\/| || __/ | | | || / | | | _ || . ` | | | +# _| |_ | | | || | \ \_/ /| |\ \ | | | | | || |\ | | | +# \___/ \_| |_/\_| \___/ \_| \_| \_/ \_| |_/\_| \_/ \_/ +# DO NOT MODIFY THIS FILE! +# Only do so if you know what you're doing. This file contains boilerplate code +# to allow injecting a custom sitecustomize.py file into the Python process to +# perform the correct initialisation for the library. All the actual +# initialisation logic should be placed in preload.py. +from ddtrace import LOADED_MODULES # isort:skip + +import logging # noqa:I001 +import os # noqa:F401 +import sys +import warnings # noqa:F401 + +from ddtrace import config # noqa:F401 +from ddtrace._logger import _configure_log_injection +from ddtrace.internal.logger import get_logger # noqa:F401 +from ddtrace.internal.module import ModuleWatchdog # noqa:F401 +from ddtrace.internal.module import is_module_installed +from ddtrace.internal.utils.formats import asbool # noqa:F401 + +# Debug mode from the tracer will do the same here, so only need to do this otherwise. +if config.logs_injection: + _configure_log_injection() + + +log = get_logger(__name__) + + +if "gevent" in sys.modules or "gevent.monkey" in sys.modules: + import gevent.monkey # noqa:F401 + + if gevent.monkey.is_module_patched("threading"): + warnings.warn( # noqa: B028 + "Loading ddtrace after gevent.monkey.patch_all() is not supported and is " + "likely to break the application. Use ddtrace-run to fix this, or " + "import ddtrace.auto before calling gevent.monkey.patch_all().", + RuntimeWarning, + ) + + +def cleanup_loaded_modules(): + def drop(module_name): + # type: (str) -> None + del sys.modules[module_name] + + MODULES_REQUIRING_CLEANUP = ("gevent",) + do_cleanup = os.getenv("DD_UNLOAD_MODULES_FROM_SITECUSTOMIZE", default="auto").lower() + if do_cleanup == "auto": + do_cleanup = any(is_module_installed(m) for m in MODULES_REQUIRING_CLEANUP) + + if not asbool(do_cleanup): + return + + # Unload all the modules that we have imported, except for the ddtrace one. + # NB: this means that every `import threading` anywhere in `ddtrace/` code + # uses a copy of that module that is distinct from the copy that user code + # gets when it does `import threading`. The same applies to every module + # not in `KEEP_MODULES`. + KEEP_MODULES = frozenset( + [ + "atexit", + "copyreg", # pickling issues for tracebacks with gevent + "ddtrace", + "concurrent", + "typing", + "re", # referenced by the typing module + "sre_constants", # imported by re at runtime + "logging", + "attr", + "google", + "google.protobuf", # the upb backend in >= 4.21 does not like being unloaded + ] + ) + for m in list(_ for _ in sys.modules if _ not in LOADED_MODULES): + if any(m == _ or m.startswith(_ + ".") for _ in KEEP_MODULES): + continue + + drop(m) + + # TODO: The better strategy is to identify the core modues in LOADED_MODULES + # that should not be unloaded, and then unload as much as possible. + UNLOAD_MODULES = frozenset( + [ + # imported in Python >= 3.10 and patched by gevent + "time", + # we cannot unload the whole concurrent hierarchy, but this + # submodule makes use of threading so it is critical to unload when + # gevent is used. + "concurrent.futures", + ] + ) + for u in UNLOAD_MODULES: + for m in list(sys.modules): + if m == u or m.startswith(u + "."): + drop(m) + + # Because we are not unloading it, the logging module requires a reference + # to the newly imported threading module to allow it to retrieve the correct + # thread object information, like the thread name. We register a post-import + # hook on the threading module to perform this update. + @ModuleWatchdog.after_module_imported("threading") + def _(threading): + logging.threading = threading + + +try: + import ddtrace.bootstrap.preload as preload # Perform the actual initialisation + + cleanup_loaded_modules() + + # Check for and import any sitecustomize that would have normally been used + # had ddtrace-run not been used. + bootstrap_dir = os.path.dirname(__file__) + if bootstrap_dir in sys.path: + index = sys.path.index(bootstrap_dir) + del sys.path[index] + + # NOTE: this reference to the module is crucial in Python 2. + # Without it the current module gets gc'd and all subsequent references + # will be `None`. + ddtrace_sitecustomize = sys.modules["sitecustomize"] + del sys.modules["sitecustomize"] + + # Cache this module under it's fully qualified package name + if "ddtrace.bootstrap.sitecustomize" not in sys.modules: + sys.modules["ddtrace.bootstrap.sitecustomize"] = ddtrace_sitecustomize + + try: + import sitecustomize # noqa:F401 + except ImportError: + # If an additional sitecustomize is not found then put the ddtrace + # sitecustomize back. + log.debug("additional sitecustomize not found") + sys.modules["sitecustomize"] = ddtrace_sitecustomize + else: + log.debug("additional sitecustomize found in: %s", sys.path) + finally: + # Always reinsert the ddtrace bootstrap directory to the path so + # that introspection and debugging the application makes sense. + # Note that this does not interfere with imports since a user + # sitecustomize, if it exists, will be imported. + sys.path.insert(index, bootstrap_dir) + else: + try: + import sitecustomize # noqa:F401 + except ImportError: + log.debug("additional sitecustomize not found") + else: + log.debug("additional sitecustomize found in: %s", sys.path) + + config._ddtrace_bootstrapped = True + # Loading status used in tests to detect if the `sitecustomize` has been + # properly loaded without exceptions. This must be the last action in the module + # when the execution ends with a success. + loaded = True + + for f in preload.post_preload: + f() + +except Exception: + loaded = False + log.warning("error configuring Datadog tracing", exc_info=True) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/commands/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/constants.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/constants.py new file mode 100644 index 0000000..ee1a024 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/constants.py @@ -0,0 +1,46 @@ +SAMPLE_RATE_METRIC_KEY = "_sample_rate" +SAMPLING_PRIORITY_KEY = "_sampling_priority_v1" +ANALYTICS_SAMPLE_RATE_KEY = "_dd1.sr.eausr" +SAMPLING_AGENT_DECISION = "_dd.agent_psr" +SAMPLING_RULE_DECISION = "_dd.rule_psr" +SAMPLING_LIMIT_DECISION = "_dd.limit_psr" +_SINGLE_SPAN_SAMPLING_MECHANISM = "_dd.span_sampling.mechanism" +_SINGLE_SPAN_SAMPLING_RATE = "_dd.span_sampling.rule_rate" +_SINGLE_SPAN_SAMPLING_MAX_PER_SEC = "_dd.span_sampling.max_per_second" +_SINGLE_SPAN_SAMPLING_MAX_PER_SEC_NO_LIMIT = -1 + +ORIGIN_KEY = "_dd.origin" +USER_ID_KEY = "_dd.p.usr.id" +HOSTNAME_KEY = "_dd.hostname" +RUNTIME_FAMILY = "_dd.runtime_family" +ENV_KEY = "env" +VERSION_KEY = "version" +SERVICE_KEY = "service.name" +BASE_SERVICE_KEY = "_dd.base_service" +SERVICE_VERSION_KEY = "service.version" +SPAN_KIND = "span.kind" +SPAN_MEASURED_KEY = "_dd.measured" +KEEP_SPANS_RATE_KEY = "_dd.tracer_kr" +MULTIPLE_IP_HEADERS = "_dd.multiple-ip-headers" + +APPSEC_ENV = "DD_APPSEC_ENABLED" + +IAST_ENV = "DD_IAST_ENABLED" + +MANUAL_DROP_KEY = "manual.drop" +MANUAL_KEEP_KEY = "manual.keep" + +ERROR_MSG = "error.message" # a string representing the error message +ERROR_TYPE = "error.type" # a string representing the type of the error +ERROR_STACK = "error.stack" # a human readable version of the stack. + +PID = "process_id" + +# Use this to explicitly inform the backend that a trace should be rejected and not stored. +USER_REJECT = -1 +# Used by the builtin sampler to inform the backend that a trace should be rejected and not stored. +AUTO_REJECT = 0 +# Used by the builtin sampler to inform the backend that a trace should be kept and stored. +AUTO_KEEP = 1 +# Use this to explicitly inform the backend that a trace should be kept and stored. +USER_KEEP = 2 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/context.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/context.py new file mode 100644 index 0000000..c67f329 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/context.py @@ -0,0 +1,269 @@ +import base64 +import re +import threading +from typing import TYPE_CHECKING # noqa:F401 +from typing import Any # noqa:F401 +from typing import Optional +from typing import Text # noqa:F401 + +from ddtrace.tracing._span_link import SpanLink # noqa:F401 + +from .constants import ORIGIN_KEY +from .constants import SAMPLING_PRIORITY_KEY +from .constants import USER_ID_KEY +from .internal.compat import NumericType +from .internal.constants import W3C_TRACEPARENT_KEY +from .internal.constants import W3C_TRACESTATE_KEY +from .internal.logger import get_logger +from .internal.utils.http import w3c_get_dd_list_member as _w3c_get_dd_list_member + + +if TYPE_CHECKING: # pragma: no cover + from typing import Tuple # noqa:F401,I001 + + from .span import Span # noqa:F401 + + from .span import _MetaDictType + from .span import _MetricDictType + + _ContextState = Tuple[ + Optional[int], # trace_id + Optional[int], # span_id + _MetaDictType, # _meta + _MetricDictType, # _metrics + list[SpanLink], + dict[str, Any], + ] + + +_DD_ORIGIN_INVALID_CHARS_REGEX = re.compile(r"[^\x20-\x7E]+") + +log = get_logger(__name__) + + +class Context(object): + """Represents the state required to propagate a trace across execution + boundaries. + """ + + __slots__ = ["trace_id", "span_id", "_lock", "_meta", "_metrics", "_span_links", "_baggage"] + + def __init__( + self, + trace_id=None, # type: Optional[int] + span_id=None, # type: Optional[int] + dd_origin=None, # type: Optional[str] + sampling_priority=None, # type: Optional[float] + meta=None, # type: Optional[_MetaDictType] + metrics=None, # type: Optional[_MetricDictType] + lock=None, # type: Optional[threading.RLock] + span_links=None, # type: Optional[list[SpanLink]] + baggage=None, # type: Optional[dict[str, Any]] + ): + self._meta = meta if meta is not None else {} # type: _MetaDictType + self._metrics = metrics if metrics is not None else {} # type: _MetricDictType + self._baggage = baggage if baggage is not None else {} # type: dict[str, Any] + + self.trace_id = trace_id # type: Optional[int] + self.span_id = span_id # type: Optional[int] + + if dd_origin is not None and _DD_ORIGIN_INVALID_CHARS_REGEX.search(dd_origin) is None: + self._meta[ORIGIN_KEY] = dd_origin + if sampling_priority is not None: + self._metrics[SAMPLING_PRIORITY_KEY] = sampling_priority + if span_links is not None: + self._span_links = span_links + else: + self._span_links = [] + + if lock is not None: + self._lock = lock + else: + # DEV: A `forksafe.RLock` is not necessary here since Contexts + # are recreated by the tracer after fork + # https://github.com/DataDog/dd-trace-py/blob/a1932e8ddb704d259ea8a3188d30bf542f59fd8d/ddtrace/tracer.py#L489-L508 + self._lock = threading.RLock() + + def __getstate__(self): + # type: () -> _ContextState + return ( + self.trace_id, + self.span_id, + self._meta, + self._metrics, + self._span_links, + self._baggage + # Note: self._lock is not serializable + ) + + def __setstate__(self, state): + # type: (_ContextState) -> None + self.trace_id, self.span_id, self._meta, self._metrics, self._span_links, self._baggage = state + # We cannot serialize and lock, so we must recreate it unless we already have one + self._lock = threading.RLock() + + def _with_span(self, span): + # type: (Span) -> Context + """Return a shallow copy of the context with the given span.""" + return self.__class__( + trace_id=span.trace_id, + span_id=span.span_id, + meta=self._meta, + metrics=self._metrics, + lock=self._lock, + baggage=self._baggage, + ) + + def _update_tags(self, span): + # type: (Span) -> None + with self._lock: + for tag in self._meta: + span._meta.setdefault(tag, self._meta[tag]) + for metric in self._metrics: + span._metrics.setdefault(metric, self._metrics[metric]) + + @property + def sampling_priority(self) -> Optional[NumericType]: + """Return the context sampling priority for the trace.""" + return self._metrics.get(SAMPLING_PRIORITY_KEY) + + @sampling_priority.setter + def sampling_priority(self, value: Optional[NumericType]) -> None: + with self._lock: + if value is None: + if SAMPLING_PRIORITY_KEY in self._metrics: + del self._metrics[SAMPLING_PRIORITY_KEY] + return + self._metrics[SAMPLING_PRIORITY_KEY] = value + + @property + def _traceparent(self): + # type: () -> str + tp = self._meta.get(W3C_TRACEPARENT_KEY) + if self.span_id is None or self.trace_id is None: + # if we only have a traceparent then we'll forward it + # if we don't have a span id or trace id value we can't build a valid traceparent + return tp or "" + + # determine the trace_id value + if tp: + # grab the original traceparent trace id, not the converted value + trace_id = tp.split("-")[1] + else: + trace_id = "{:032x}".format(self.trace_id) + + return "00-{}-{:016x}-{}".format(trace_id, self.span_id, self._traceflags) + + @property + def _traceflags(self): + # type: () -> str + return "01" if self.sampling_priority and self.sampling_priority > 0 else "00" + + @property + def _tracestate(self): + # type: () -> str + dd_list_member = _w3c_get_dd_list_member(self) + + # if there's a preexisting tracestate we need to update it to preserve other vendor data + ts = self._meta.get(W3C_TRACESTATE_KEY, "") + if ts and dd_list_member: + # cut out the original dd list member from tracestate so we can replace it with the new one we created + ts_w_out_dd = re.sub("dd=(.+?)(?:,|$)", "", ts) + if ts_w_out_dd: + ts = "dd={},{}".format(dd_list_member, ts_w_out_dd) + else: + ts = "dd={}".format(dd_list_member) + # if there is no original tracestate value then tracestate is just the dd list member we created + elif dd_list_member: + ts = "dd={}".format(dd_list_member) + return ts + + @property + def dd_origin(self): + # type: () -> Optional[Text] + """Get the origin of the trace.""" + return self._meta.get(ORIGIN_KEY) + + @dd_origin.setter + def dd_origin(self, value): + # type: (Optional[Text]) -> None + """Set the origin of the trace.""" + with self._lock: + if value is None: + if ORIGIN_KEY in self._meta: + del self._meta[ORIGIN_KEY] + return + self._meta[ORIGIN_KEY] = value + + @property + def dd_user_id(self): + # type: () -> Optional[Text] + """Get the user ID of the trace.""" + user_id = self._meta.get(USER_ID_KEY) + if user_id: + return str(base64.b64decode(user_id), encoding="utf-8") + return None + + @dd_user_id.setter + def dd_user_id(self, value): + # type: (Optional[Text]) -> None + """Set the user ID of the trace.""" + with self._lock: + if value is None: + if USER_ID_KEY in self._meta: + del self._meta[USER_ID_KEY] + return + self._meta[USER_ID_KEY] = str(base64.b64encode(bytes(value, encoding="utf-8")), encoding="utf-8") + + def _set_baggage_item(self, key, value): + # type: (str, Any) -> None + """Sets a baggage item in this span context. + Note that this operation mutates the baggage of this span context + """ + self._baggage[key] = value + + def _with_baggage_item(self, key, value): + # type: (str, Any) -> Context + """Returns a copy of this span with a new baggage item. + Useful for instantiating new child span contexts. + """ + new_baggage = dict(self._baggage) + new_baggage[key] = value + + ctx = self.__class__(trace_id=self.trace_id, span_id=self.span_id) + ctx._meta = self._meta + ctx._metrics = self._metrics + ctx._baggage = new_baggage + return ctx + + def _get_baggage_item(self, key): + # type: (str) -> Optional[Any] + """Gets a baggage item in this span context.""" + return self._baggage.get(key, None) + + def __eq__(self, other): + # type: (Any) -> bool + if isinstance(other, Context): + with self._lock: + return ( + self.trace_id == other.trace_id + and self.span_id == other.span_id + and self._meta == other._meta + and self._metrics == other._metrics + and self._span_links == other._span_links + and self._baggage == other._baggage + ) + return False + + def __repr__(self): + # type: () -> str + return "Context(trace_id=%s, span_id=%s, _meta=%s, _metrics=%s, _span_links=%s, _baggage=%s)" % ( + self.trace_id, + self.span_id, + self._meta, + self._metrics, + self._span_links, + self._baggage, + ) + + __str__ = __repr__ diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/__init__.py new file mode 100644 index 0000000..8d86099 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/__init__.py @@ -0,0 +1,4 @@ +from ..internal.utils.importlib import func_name # noqa:F401 +from ..internal.utils.importlib import module_name # noqa:F401 +from ..internal.utils.importlib import require_modules # noqa:F401 +from ..tracing import trace_handlers # noqa:F401 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiobotocore/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiobotocore/__init__.py new file mode 100644 index 0000000..5930f54 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiobotocore/__init__.py @@ -0,0 +1,38 @@ +""" +The aiobotocore integration will trace all AWS calls made with the ``aiobotocore`` +library. This integration is not enabled by default. + +Enabling +~~~~~~~~ + +The aiobotocore integration is not enabled by default. Use +:func:`patch()` to enable the integration:: + + from ddtrace import patch + patch(aiobotocore=True) + +Configuration +~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.aiobotocore['tag_no_params'] + + This opts out of the default behavior of adding span tags for a narrow set of API parameters. + + To not collect any API parameters, ``ddtrace.config.aiobotocore.tag_no_params = True`` or by setting the environment + variable ``DD_AWS_TAG_NO_PARAMS=true``. + + + Default: ``False`` + +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["aiobotocore.client"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + + __all__ = ["patch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiobotocore/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiobotocore/patch.py new file mode 100644 index 0000000..6b08d60 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiobotocore/patch.py @@ -0,0 +1,180 @@ +import os + +import aiobotocore.client + +from ddtrace import config +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.utils.version import parse_version +from ddtrace.vendor import wrapt + +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from ...ext import aws +from ...ext import http +from ...internal.schema import schematize_cloud_api_operation +from ...internal.schema import schematize_service_name +from ...internal.utils import ArgumentError +from ...internal.utils import get_argument_value +from ...internal.utils.formats import asbool +from ...internal.utils.formats import deep_getattr +from ...pin import Pin +from ..trace_utils import unwrap + + +aiobotocore_version_str = getattr(aiobotocore, "__version__", "") +AIOBOTOCORE_VERSION = parse_version(aiobotocore_version_str) + +if AIOBOTOCORE_VERSION <= (0, 10, 0): + # aiobotocore>=0.11.0 + from aiobotocore.endpoint import ClientResponseContentProxy +elif AIOBOTOCORE_VERSION >= (0, 11, 0) and AIOBOTOCORE_VERSION < (2, 3, 0): + from aiobotocore._endpoint_helpers import ClientResponseContentProxy + + +ARGS_NAME = ("action", "params", "path", "verb") +TRACED_ARGS = {"params", "path", "verb"} + + +config._add( + "aiobotocore", + { + "tag_no_params": asbool(os.getenv("DD_AWS_TAG_NO_PARAMS", default=False)), + }, +) + + +def get_version(): + # type: () -> str + return aiobotocore_version_str + + +def patch(): + if getattr(aiobotocore.client, "_datadog_patch", False): + return + aiobotocore.client._datadog_patch = True + + wrapt.wrap_function_wrapper("aiobotocore.client", "AioBaseClient._make_api_call", _wrapped_api_call) + Pin(service=config.service or "aws").onto(aiobotocore.client.AioBaseClient) + + +def unpatch(): + if getattr(aiobotocore.client, "_datadog_patch", False): + aiobotocore.client._datadog_patch = False + unwrap(aiobotocore.client.AioBaseClient, "_make_api_call") + + +class WrappedClientResponseContentProxy(wrapt.ObjectProxy): + def __init__(self, body, pin, parent_span): + super(WrappedClientResponseContentProxy, self).__init__(body) + self._self_pin = pin + self._self_parent_span = parent_span + + async def read(self, *args, **kwargs): + # async read that must be child of the parent span operation + operation_name = "{}.read".format(self._self_parent_span.name) + + with self._self_pin.tracer.start_span(operation_name, child_of=self._self_parent_span) as span: + span.set_tag_str(COMPONENT, config.aiobotocore.integration_name) + + # set span.kind tag equal to type of request + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + # inherit parent attributes + span.resource = self._self_parent_span.resource + span.span_type = self._self_parent_span.span_type + span._meta = dict(self._self_parent_span._meta) + span._metrics = dict(self._self_parent_span.metrics) + + result = await self.__wrapped__.read(*args, **kwargs) + span.set_tag("Length", len(result)) + + return result + + # wrapt doesn't proxy `async with` context managers + async def __aenter__(self): + # call the wrapped method but return the object proxy + await self.__wrapped__.__aenter__() + return self + + async def __aexit__(self, *args, **kwargs): + response = await self.__wrapped__.__aexit__(*args, **kwargs) + return response + + +async def _wrapped_api_call(original_func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + result = await original_func(*args, **kwargs) + return result + + endpoint_name = deep_getattr(instance, "_endpoint._endpoint_prefix") + + service = pin.service if pin.service != "aws" else "{}.{}".format(pin.service, endpoint_name) + with pin.tracer.trace( + schematize_cloud_api_operation( + "{}.command".format(endpoint_name), cloud_provider="aws", cloud_service=endpoint_name + ), + service=schematize_service_name(service), + span_type=SpanTypes.HTTP, + ) as span: + span.set_tag_str(COMPONENT, config.aiobotocore.integration_name) + + # set span.kind tag equal to type of request + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + span.set_tag(SPAN_MEASURED_KEY) + + try: + operation = get_argument_value(args, kwargs, 0, "operation_name") + params = get_argument_value(args, kwargs, 1, "params") + + span.resource = "{}.{}".format(endpoint_name, operation.lower()) + + if params and not config.aiobotocore["tag_no_params"]: + aws._add_api_param_span_tags(span, endpoint_name, params) + except ArgumentError: + operation = None + span.resource = endpoint_name + + region_name = deep_getattr(instance, "meta.region_name") + + meta = { + "aws.agent": "aiobotocore", + "aws.operation": operation, + "aws.region": region_name, + "region": region_name, + } + span.set_tags(meta) + + result = await original_func(*args, **kwargs) + + body = result.get("Body") + + # ClientResponseContentProxy removed in aiobotocore 2.3.x: https://github.com/aio-libs/aiobotocore/pull/934/ + if hasattr(body, "ClientResponseContentProxy") and isinstance(body, ClientResponseContentProxy): + result["Body"] = WrappedClientResponseContentProxy(body, pin, span) + + response_meta = result["ResponseMetadata"] + response_headers = response_meta["HTTPHeaders"] + + span.set_tag(http.STATUS_CODE, response_meta["HTTPStatusCode"]) + if 500 <= response_meta["HTTPStatusCode"] < 600: + span.error = 1 + + span.set_tag("retry_attempts", response_meta["RetryAttempts"]) + + request_id = response_meta.get("RequestId") + if request_id: + span.set_tag_str("aws.requestid", request_id) + + request_id2 = response_headers.get("x-amz-id-2") + if request_id2: + span.set_tag_str("aws.requestid2", request_id2) + + # set analytics sample rate + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.aiobotocore.get_analytics_sample_rate()) + + return result diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiohttp/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiohttp/__init__.py new file mode 100644 index 0000000..ae5a71e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiohttp/__init__.py @@ -0,0 +1,99 @@ +""" +The ``aiohttp`` integration traces requests made with the client or to the server. + +The client is automatically instrumented while the server must be manually instrumented using middleware. + +Client +****** + +Enabling +~~~~~~~~ + +The client integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(aiohttp=True) + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.aiohttp_client['distributed_tracing'] + + Include distributed tracing headers in requests sent from the aiohttp client. + + This option can also be set with the ``DD_AIOHTTP_CLIENT_DISTRIBUTED_TRACING`` + environment variable. + + Default: ``True`` + +.. py:data:: ddtrace.config.aiohttp_client['split_by_domain'] + + Whether or not to use the domain name of requests as the service name. + + Default: ``False`` + + +Server +****** + +Enabling +~~~~~~~~ + +Automatic instrumentation is not available for the server, instead +the provided ``trace_app`` function must be used:: + + from aiohttp import web + from ddtrace import tracer, patch + from ddtrace.contrib.aiohttp import trace_app + + # create your application + app = web.Application() + app.router.add_get('/', home_handler) + + # trace your application handlers + trace_app(app, tracer, service='async-api') + web.run_app(app, port=8000) + +Integration settings are attached to your application under the ``datadog_trace`` +namespace. You can read or update them as follows:: + + # disables distributed tracing for all received requests + app['datadog_trace']['distributed_tracing_enabled'] = False + +Available settings are: + +* ``tracer`` (default: ``ddtrace.tracer``): set the default tracer instance that is used to + trace `aiohttp` internals. By default the `ddtrace` tracer is used. +* ``service`` (default: ``aiohttp-web``): set the service name used by the tracer. Usually + this configuration must be updated with a meaningful name. +* ``distributed_tracing_enabled`` (default: ``True``): enable distributed tracing during + the middleware execution, so that a new span is created with the given ``trace_id`` and + ``parent_id`` injected via request headers. + +When a request span is created, a new ``Context`` for this logical execution is attached +to the ``request`` object, so that it can be used in the application code:: + + async def home_handler(request): + ctx = request['datadog_context'] + # do something with the tracing Context + +:ref:`All HTTP tags ` are supported for this integration. + +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["aiohttp"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .middlewares import trace_app + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "trace_app", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiohttp/middlewares.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiohttp/middlewares.py new file mode 100644 index 0000000..87f6a10 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiohttp/middlewares.py @@ -0,0 +1,182 @@ +from aiohttp import web +from aiohttp.web_urldispatcher import SystemRoute + +from ddtrace import config +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema.span_attribute_schema import SpanDirection + +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from ...ext import http +from ...internal.schema import schematize_url_operation +from .. import trace_utils +from ..asyncio import context_provider + + +CONFIG_KEY = "datadog_trace" +REQUEST_CONTEXT_KEY = "datadog_context" +REQUEST_CONFIG_KEY = "__datadog_trace_config" +REQUEST_SPAN_KEY = "__datadog_request_span" + + +async def trace_middleware(app, handler): + """ + ``aiohttp`` middleware that traces the handler execution. + Because handlers are run in different tasks for each request, we attach the Context + instance both to the Task and to the Request objects. In this way: + + * the Task is used by the internal automatic instrumentation + * the ``Context`` attached to the request can be freely used in the application code + """ + + async def attach_context(request): + # application configs + tracer = app[CONFIG_KEY]["tracer"] + service = app[CONFIG_KEY]["service"] + distributed_tracing = app[CONFIG_KEY]["distributed_tracing_enabled"] + # Create a new context based on the propagated information. + trace_utils.activate_distributed_headers( + tracer, + int_config=config.aiohttp, + request_headers=request.headers, + override=distributed_tracing, + ) + + # trace the handler + request_span = tracer.trace( + schematize_url_operation("aiohttp.request", protocol="http", direction=SpanDirection.INBOUND), + service=service, + span_type=SpanTypes.WEB, + ) + request_span.set_tag(SPAN_MEASURED_KEY) + + request_span.set_tag_str(COMPONENT, config.aiohttp.integration_name) + + # set span.kind tag equal to type of request + request_span.set_tag_str(SPAN_KIND, SpanKind.SERVER) + + # Configure trace search sample rate + # DEV: aiohttp is special case maintains separate configuration from config api + analytics_enabled = app[CONFIG_KEY]["analytics_enabled"] + if (config.analytics_enabled and analytics_enabled is not False) or analytics_enabled is True: + request_span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, app[CONFIG_KEY].get("analytics_sample_rate", True)) + + # attach the context and the root span to the request; the Context + # may be freely used by the application code + request[REQUEST_CONTEXT_KEY] = request_span.context + request[REQUEST_SPAN_KEY] = request_span + request[REQUEST_CONFIG_KEY] = app[CONFIG_KEY] + try: + response = await handler(request) + if isinstance(response, web.StreamResponse): + request.task.add_done_callback(lambda _: finish_request_span(request, response)) + return response + except Exception: + request_span.set_traceback() + raise + + return attach_context + + +def finish_request_span(request, response): + # safe-guard: discard if we don't have a request span + request_span = request.get(REQUEST_SPAN_KEY, None) + if not request_span: + return + + # default resource name + resource = str(response.status) + + if request.match_info.route.resource: + # collect the resource name based on http resource type + res_info = request.match_info.route.resource.get_info() + + if res_info.get("path"): + resource = res_info.get("path") + elif res_info.get("formatter"): + resource = res_info.get("formatter") + elif res_info.get("prefix"): + resource = res_info.get("prefix") + + # prefix the resource name by the http method + resource = "{} {}".format(request.method, resource) + + request_span.resource = resource + + # DEV: aiohttp is special case maintains separate configuration from config api + trace_query_string = request[REQUEST_CONFIG_KEY].get("trace_query_string") + if trace_query_string is None: + trace_query_string = config.http.trace_query_string + if trace_query_string: + request_span.set_tag_str(http.QUERY_STRING, request.query_string) + + # The match info object provided by aiohttp's default (and only) router + # has a `route` attribute, but routers are susceptible to being replaced/hand-rolled + # so we can only support this case. + route = None + if hasattr(request.match_info, "route"): + aiohttp_route = request.match_info.route + if not isinstance(aiohttp_route, SystemRoute): + # SystemRoute objects exist to throw HTTP errors and have no path + route = aiohttp_route.resource.canonical + + trace_utils.set_http_meta( + request_span, + config.aiohttp, + method=request.method, + url=str(request.url), # DEV: request.url is a yarl's URL object + status_code=response.status, + request_headers=request.headers, + response_headers=response.headers, + route=route, + ) + + request_span.finish() + + +async def on_prepare(request, response): + """ + The on_prepare signal is used to close the request span that is created during + the trace middleware execution. + """ + # NB isinstance is not appropriate here because StreamResponse is a parent of the other + # aiohttp response types + if type(response) is web.StreamResponse and not response.task.done(): + return + finish_request_span(request, response) + + +def trace_app(app, tracer, service="aiohttp-web"): + """ + Tracing function that patches the ``aiohttp`` application so that it will be + traced using the given ``tracer``. + + :param app: aiohttp application to trace + :param tracer: tracer instance to use + :param service: service name of tracer + """ + + # safe-guard: don't trace an application twice + if getattr(app, "__datadog_trace", False): + return + app.__datadog_trace = True + + # configure datadog settings + app[CONFIG_KEY] = { + "tracer": tracer, + "service": config._get_service(default=service), + "distributed_tracing_enabled": None, + "analytics_enabled": None, + "analytics_sample_rate": 1.0, + } + + # the tracer must work with asynchronous Context propagation + tracer.configure(context_provider=context_provider) + + # add the async tracer middleware as a first middleware + # and be sure that the on_prepare signal is the last one + app.middlewares.insert(0, trace_middleware) + app.on_response_prepare.append(on_prepare) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiohttp/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiohttp/patch.py new file mode 100644 index 0000000..3830ee4 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiohttp/patch.py @@ -0,0 +1,158 @@ +import os + +import aiohttp +from yarl import URL + +from ddtrace import config +from ddtrace.constants import SPAN_KIND +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.logger import get_logger +from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.internal.utils import get_argument_value +from ddtrace.internal.utils.formats import asbool +from ddtrace.vendor import wrapt + +from ...ext import SpanKind +from ...ext import SpanTypes +from ...internal.schema import schematize_url_operation +from ...pin import Pin +from ...propagation.http import HTTPPropagator +from ..trace_utils import ext_service +from ..trace_utils import extract_netloc_and_query_info_from_url +from ..trace_utils import set_http_meta +from ..trace_utils import unwrap +from ..trace_utils import with_traced_module as with_traced_module_sync +from ..trace_utils import wrap +from ..trace_utils_async import with_traced_module + + +log = get_logger(__name__) + + +# Server config +config._add( + "aiohttp", + dict(distributed_tracing=True), +) + +config._add( + "aiohttp_client", + dict( + distributed_tracing=asbool(os.getenv("DD_AIOHTTP_CLIENT_DISTRIBUTED_TRACING", True)), + default_http_tag_query_string=os.getenv("DD_HTTP_CLIENT_TAG_QUERY_STRING", "true"), + split_by_domain=asbool(os.getenv("DD_AIOHTTP_CLIENT_SPLIT_BY_DOMAIN", default=False)), + ), +) + + +def get_version(): + # type: () -> str + return aiohttp.__version__ + + +class _WrappedConnectorClass(wrapt.ObjectProxy): + def __init__(self, obj, pin): + super().__init__(obj) + pin.onto(self) + + async def connect(self, req, *args, **kwargs): + pin = Pin.get_from(self) + with pin.tracer.trace("%s.connect" % self.__class__.__name__) as span: + # set component tag equal to name of integration + span.set_tag(COMPONENT, config.aiohttp.integration_name) + result = await self.__wrapped__.connect(req, *args, **kwargs) + return result + + async def _create_connection(self, req, *args, **kwargs): + pin = Pin.get_from(self) + with pin.tracer.trace("%s._create_connection" % self.__class__.__name__) as span: + # set component tag equal to name of integration + span.set_tag(COMPONENT, config.aiohttp.integration_name) + result = await self.__wrapped__._create_connection(req, *args, **kwargs) + return result + + +@with_traced_module +async def _traced_clientsession_request(aiohttp, pin, func, instance, args, kwargs): + method = get_argument_value(args, kwargs, 0, "method") # type: str + url = URL(get_argument_value(args, kwargs, 1, "url")) # type: URL + params = kwargs.get("params") + headers = kwargs.get("headers") or {} + + with pin.tracer.trace( + schematize_url_operation("aiohttp.request", protocol="http", direction=SpanDirection.OUTBOUND), + span_type=SpanTypes.HTTP, + service=ext_service(pin, config.aiohttp_client), + ) as span: + if config.aiohttp_client.split_by_domain: + span.service = url.host + + if pin._config["distributed_tracing"]: + HTTPPropagator.inject(span.context, headers) + kwargs["headers"] = headers + + span.set_tag_str(COMPONENT, config.aiohttp_client.integration_name) + + # set span.kind tag equal to type of request + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + # Params can be included separate of the URL so the URL has to be constructed + # with the passed params. + url_str = str(url.update_query(params) if params else url) + host, query = extract_netloc_and_query_info_from_url(url_str) + set_http_meta( + span, + config.aiohttp_client, + method=method, + url=str(url), + target_host=host, + query=query, + request_headers=headers, + ) + resp = await func(*args, **kwargs) # type: aiohttp.ClientResponse + set_http_meta( + span, config.aiohttp_client, response_headers=resp.headers, status_code=resp.status, status_msg=resp.reason + ) + return resp + + +@with_traced_module_sync +def _traced_clientsession_init(aiohttp, pin, func, instance, args, kwargs): + func(*args, **kwargs) + instance._connector = _WrappedConnectorClass(instance._connector, pin) + + +def _patch_client(aiohttp): + Pin().onto(aiohttp) + pin = Pin(_config=config.aiohttp_client.copy()) + pin.onto(aiohttp.ClientSession) + + wrap("aiohttp", "ClientSession.__init__", _traced_clientsession_init(aiohttp)) + wrap("aiohttp", "ClientSession._request", _traced_clientsession_request(aiohttp)) + + +def patch(): + import aiohttp + + if getattr(aiohttp, "_datadog_patch", False): + return + + _patch_client(aiohttp) + + aiohttp._datadog_patch = True + + +def _unpatch_client(aiohttp): + unwrap(aiohttp.ClientSession, "__init__") + unwrap(aiohttp.ClientSession, "_request") + + +def unpatch(): + import aiohttp + + if not getattr(aiohttp, "_datadog_patch", False): + return + + _unpatch_client(aiohttp) + + aiohttp._datadog_patch = False diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiohttp_jinja2/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiohttp_jinja2/__init__.py new file mode 100644 index 0000000..96b626a --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiohttp_jinja2/__init__.py @@ -0,0 +1,27 @@ +""" +The ``aiohttp_jinja2`` integration adds tracing of template rendering. + + +Enabling +~~~~~~~~ + +The integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(aiohttp_jinja2=True) +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["aiohttp_jinja2"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiohttp_jinja2/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiohttp_jinja2/patch.py new file mode 100644 index 0000000..e774362 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiohttp_jinja2/patch.py @@ -0,0 +1,75 @@ +import aiohttp_jinja2 + +from ddtrace import Pin +from ddtrace import config +from ddtrace.internal.constants import COMPONENT + +from ...ext import SpanTypes +from ...internal.utils import get_argument_value +from ..trace_utils import unwrap +from ..trace_utils import with_traced_module +from ..trace_utils import wrap + + +config._add( + "aiohttp_jinja2", + dict(), +) + + +def get_version(): + # type: () -> str + return getattr(aiohttp_jinja2, "__version__", "") + + +@with_traced_module +def traced_render_template(aiohttp_jinja2, pin, func, instance, args, kwargs): + # original signature: + # render_template(template_name, request, context, *, app_key=APP_KEY, encoding='utf-8') + template_name = get_argument_value(args, kwargs, 0, "template_name") + request = get_argument_value(args, kwargs, 1, "request") + get_env_kwargs = {} + if "app_key" in kwargs: + get_env_kwargs["app_key"] = kwargs["app_key"] + env = aiohttp_jinja2.get_env(request.app, **get_env_kwargs) + + # the prefix is available only on PackageLoader + template_prefix = getattr(env.loader, "package_path", "") + template_meta = "%s/%s" % (template_prefix, template_name) + + with pin.tracer.trace("aiohttp.template", span_type=SpanTypes.TEMPLATE) as span: + span.set_tag_str(COMPONENT, config.aiohttp_jinja2.integration_name) + + span.set_tag_str("aiohttp.template", template_meta) + return func(*args, **kwargs) + + +def _patch(aiohttp_jinja2): + Pin().onto(aiohttp_jinja2) + wrap("aiohttp_jinja2", "render_template", traced_render_template(aiohttp_jinja2)) + + +def patch(): + import aiohttp_jinja2 + + if getattr(aiohttp_jinja2, "_datadog_patch", False): + return + + _patch(aiohttp_jinja2) + + aiohttp_jinja2._datadog_patch = True + + +def _unpatch(aiohttp_jinja2): + unwrap(aiohttp_jinja2, "render_template") + + +def unpatch(): + import aiohttp_jinja2 + + if not getattr(aiohttp_jinja2, "_datadog_patch", False): + return + + _unpatch(aiohttp_jinja2) + + aiohttp_jinja2._datadog_patch = False diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiomysql/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiomysql/__init__.py new file mode 100644 index 0000000..a104996 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiomysql/__init__.py @@ -0,0 +1,50 @@ +""" +The aiomysql integration instruments the aiomysql library to trace MySQL queries. + +Enabling +~~~~~~~~ + +The integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(aiomysql=True) + + +Instance Configuration +~~~~~~~~~~~~~~~~~~~~~~ + +To configure the integration on an per-connection basis use the +``Pin`` API:: + + from ddtrace import Pin + import asyncio + import aiomysql + + # This will report a span with the default settings + conn = await aiomysql.connect(host="127.0.0.1", port=3306, + user="root", password="", db="mysql", + loop=loop) + + # Use a pin to override the service name for this connection. + Pin.override(conn, service="mysql-users") + + + cur = await conn.cursor() + await cur.execute("SELECT 6*7 AS the_answer;") +""" + +from ...internal.utils.importlib import require_modules + + +required_modules = ["aiomysql"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiomysql/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiomysql/patch.py new file mode 100644 index 0000000..56a9636 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiomysql/patch.py @@ -0,0 +1,161 @@ +import aiomysql + +from ddtrace import Pin +from ddtrace import config +from ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import SPAN_KIND +from ddtrace.constants import SPAN_MEASURED_KEY +from ddtrace.contrib import dbapi +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema import schematize_database_operation +from ddtrace.internal.utils.wrappers import unwrap +from ddtrace.vendor import wrapt + +from ...ext import SpanKind +from ...ext import SpanTypes +from ...ext import db +from ...ext import net +from ...internal.schema import schematize_service_name + + +config._add( + "aiomysql", + dict(_default_service=schematize_service_name("mysql")), +) + + +def get_version(): + # type: () -> str + return getattr(aiomysql, "__version__", "") + + +CONN_ATTR_BY_TAG = { + net.TARGET_HOST: "host", + net.TARGET_PORT: "port", + db.USER: "user", + db.NAME: "db", +} + + +async def patched_connect(connect_func, _, args, kwargs): + conn = await connect_func(*args, **kwargs) + tags = {} + for tag, attr in CONN_ATTR_BY_TAG.items(): + if hasattr(conn, attr): + tags[tag] = getattr(conn, attr) + tags[db.SYSTEM] = "mysql" + + c = AIOTracedConnection(conn) + Pin(tags=tags).onto(c) + return c + + +class AIOTracedCursor(wrapt.ObjectProxy): + """TracedCursor wraps a aiomysql cursor and traces its queries.""" + + def __init__(self, cursor, pin): + super(AIOTracedCursor, self).__init__(cursor) + pin.onto(self) + self._self_datadog_name = schematize_database_operation("mysql.query", database_provider="mysql") + + async def _trace_method(self, method, resource, extra_tags, *args, **kwargs): + pin = Pin.get_from(self) + if not pin or not pin.enabled(): + result = await method(*args, **kwargs) + return result + service = pin.service + + with pin.tracer.trace( + self._self_datadog_name, service=service, resource=resource, span_type=SpanTypes.SQL + ) as s: + s.set_tag_str(COMPONENT, config.aiomysql.integration_name) + + # set span.kind to the type of request being performed + s.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + s.set_tag(SPAN_MEASURED_KEY) + s.set_tags(pin.tags) + s.set_tags(extra_tags) + + # set analytics sample rate + s.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.aiomysql.get_analytics_sample_rate()) + + try: + result = await method(*args, **kwargs) + return result + finally: + s.set_metric(db.ROWCOUNT, self.rowcount) + s.set_metric("db.rownumber", self.rownumber) + + async def executemany(self, query, *args, **kwargs): + result = await self._trace_method( + self.__wrapped__.executemany, query, {"sql.executemany": "true"}, query, *args, **kwargs + ) + return result + + async def execute(self, query, *args, **kwargs): + result = await self._trace_method(self.__wrapped__.execute, query, {}, query, *args, **kwargs) + return result + + # Explicitly define `__aenter__` and `__aexit__` since they do not get proxied properly + async def __aenter__(self): + # The base class just returns `self`, but we want the wrapped cursor so we return ourselves + return self + + async def __aexit__(self, *args, **kwargs): + return await self.__wrapped__.__aexit__(*args, **kwargs) + + +class AIOTracedConnection(wrapt.ObjectProxy): + def __init__(self, conn, pin=None, cursor_cls=AIOTracedCursor): + super(AIOTracedConnection, self).__init__(conn) + name = dbapi._get_vendor(conn) + db_pin = pin or Pin(service=name) + db_pin.onto(self) + # wrapt requires prefix of `_self` for attributes that are only in the + # proxy (since some of our source objects will use `__slots__`) + self._self_cursor_cls = cursor_cls + + def cursor(self, *args, **kwargs): + ctx_manager = self.__wrapped__.cursor(*args, **kwargs) + pin = Pin.get_from(self) + if not pin: + return ctx_manager + + # The result of `cursor()` is an `aiomysql.utils._ContextManager` + # which wraps a coroutine (a future) and adds async context manager + # helper functions to it. + # https://github.com/aio-libs/aiomysql/blob/8a32f052a16dc3886af54b98f4d91d95862bfb8e/aiomysql/connection.py#L461 + # https://github.com/aio-libs/aiomysql/blob/7fa5078da31bbc95f5e32a934a4b2b4207c67ede/aiomysql/utils.py#L30-L79 + # We cannot swap out the result on the future/context manager so + # instead we have to create a new coroutine that returns our + # wrapped cursor + # We also cannot turn `def cursor` into `async def cursor` because + # otherwise we will change the result to be a coroutine instead of + # an `aiomysql.utils._ContextManager` which wraps a coroutine. This + # will cause issues with `async with conn.cursor() as cur:` usage. + async def _wrap_cursor(): + cursor = await ctx_manager + return self._self_cursor_cls(cursor, pin) + + return type(ctx_manager)(_wrap_cursor()) + + # Explicitly define `__aenter__` and `__aexit__` since they do not get proxied properly + async def __aenter__(self): + return await self.__wrapped__.__aenter__() + + async def __aexit__(self, *args, **kwargs): + return await self.__wrapped__.__aexit__(*args, **kwargs) + + +def patch(): + if getattr(aiomysql, "__datadog_patch", False): + return + aiomysql.__datadog_patch = True + wrapt.wrap_function_wrapper(aiomysql.connection, "_connect", patched_connect) + + +def unpatch(): + if getattr(aiomysql, "__datadog_patch", False): + aiomysql.__datadog_patch = False + unwrap(aiomysql.connection, "_connect") diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiopg/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiopg/__init__.py new file mode 100644 index 0000000..df4fbb2 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiopg/__init__.py @@ -0,0 +1,28 @@ +""" +Instrument aiopg to report a span for each executed Postgres queries:: + + from ddtrace import Pin, patch + import aiopg + + # If not patched yet, you can patch aiopg specifically + patch(aiopg=True) + + # This will report a span with the default settings + async with aiopg.connect(DSN) as db: + with (await db.cursor()) as cursor: + await cursor.execute("SELECT * FROM users WHERE id = 1") + + # Use a pin to specify metadata related to this connection + Pin.override(db, service='postgres-users') +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["aiopg"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + + __all__ = ["patch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiopg/connection.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiopg/connection.py new file mode 100644 index 0000000..3373405 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiopg/connection.py @@ -0,0 +1,121 @@ +import asyncio + +from aiopg import __version__ +from aiopg.utils import _ContextManager + +from ddtrace import config +from ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import SPAN_KIND +from ddtrace.constants import SPAN_MEASURED_KEY +from ddtrace.contrib import dbapi +from ddtrace.ext import SpanKind +from ddtrace.ext import SpanTypes +from ddtrace.ext import db +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema import schematize_database_operation +from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.utils.version import parse_version +from ddtrace.pin import Pin +from ddtrace.vendor import wrapt + + +AIOPG_VERSION = parse_version(__version__) + + +class AIOTracedCursor(wrapt.ObjectProxy): + """TracedCursor wraps a psql cursor and traces its queries.""" + + def __init__(self, cursor, pin): + super(AIOTracedCursor, self).__init__(cursor) + pin.onto(self) + self._datadog_name = schematize_database_operation("postgres.query", database_provider="postgresql") + + @asyncio.coroutine + def _trace_method(self, method, resource, extra_tags, *args, **kwargs): + pin = Pin.get_from(self) + if not pin or not pin.enabled(): + result = yield from method(*args, **kwargs) + return result + service = pin.service + + with pin.tracer.trace(self._datadog_name, service=service, resource=resource, span_type=SpanTypes.SQL) as s: + s.set_tag_str(COMPONENT, config.aiopg.integration_name) + s.set_tag_str(db.SYSTEM, "postgresql") + + # set span.kind to the type of request being performed + s.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + s.set_tag(SPAN_MEASURED_KEY) + s.set_tags(pin.tags) + s.set_tags(extra_tags) + + # set analytics sample rate + s.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.aiopg.get_analytics_sample_rate()) + + try: + result = yield from method(*args, **kwargs) + return result + finally: + s.set_metric(db.ROWCOUNT, self.rowcount) + + @asyncio.coroutine + def executemany(self, query, *args, **kwargs): + # FIXME[matt] properly handle kwargs here. arg names can be different + # with different libs. + result = yield from self._trace_method( + self.__wrapped__.executemany, query, {"sql.executemany": "true"}, query, *args, **kwargs + ) + return result + + @asyncio.coroutine + def execute(self, query, *args, **kwargs): + result = yield from self._trace_method(self.__wrapped__.execute, query, {}, query, *args, **kwargs) + return result + + @asyncio.coroutine + def callproc(self, proc, args): + result = yield from self._trace_method(self.__wrapped__.callproc, proc, {}, proc, args) + return result + + def __aiter__(self): + return self.__wrapped__.__aiter__() + + +class AIOTracedConnection(wrapt.ObjectProxy): + """TracedConnection wraps a Connection with tracing code.""" + + def __init__(self, conn, pin=None, cursor_cls=AIOTracedCursor): + super(AIOTracedConnection, self).__init__(conn) + vendor = dbapi._get_vendor(conn) + name = schematize_service_name(vendor) + db_pin = pin or Pin(service=name) + db_pin.onto(self) + # wrapt requires prefix of `_self` for attributes that are only in the + # proxy (since some of our source objects will use `__slots__`) + self._self_cursor_cls = cursor_cls + + # unfortunately we also need to patch this method as otherwise "self" + # ends up being the aiopg connection object + if AIOPG_VERSION >= (0, 16, 0): + + def cursor(self, *args, **kwargs): + # Only one cursor per connection is allowed, as per DB API spec + self.close_cursor() + self._last_usage = self._loop.time() + + coro = self._cursor(*args, **kwargs) + return _ContextManager(coro) + + else: + + def cursor(self, *args, **kwargs): + coro = self._cursor(*args, **kwargs) + return _ContextManager(coro) + + @asyncio.coroutine + def _cursor(self, *args, **kwargs): + cursor = yield from self.__wrapped__._cursor(*args, **kwargs) + pin = Pin.get_from(self) + if not pin: + return cursor + return self._self_cursor_cls(cursor, pin) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiopg/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiopg/patch.py new file mode 100644 index 0000000..1e15d54 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aiopg/patch.py @@ -0,0 +1,62 @@ +# 3p +import asyncio + +import aiopg.connection +import psycopg2.extensions + +from ddtrace.contrib.aiopg.connection import AIOTracedConnection +from ddtrace.contrib.psycopg.connection import patch_conn as psycopg_patch_conn +from ddtrace.contrib.psycopg.extensions import _patch_extensions +from ddtrace.contrib.psycopg.extensions import _unpatch_extensions +from ddtrace.internal.utils.wrappers import unwrap as _u +from ddtrace.vendor import wrapt + + +def get_version(): + # type: () -> str + return getattr(aiopg, "__version__", "") + + +def patch(): + """Patch monkey patches psycopg's connection function + so that the connection's functions are traced. + """ + if getattr(aiopg, "_datadog_patch", False): + return + aiopg._datadog_patch = True + + wrapt.wrap_function_wrapper(aiopg.connection, "_connect", patched_connect) + _patch_extensions(_aiopg_extensions) # do this early just in case + + +def unpatch(): + if getattr(aiopg, "_datadog_patch", False): + aiopg._datadog_patch = False + _u(aiopg.connection, "_connect") + _unpatch_extensions(_aiopg_extensions) + + +@asyncio.coroutine +def patched_connect(connect_func, _, args, kwargs): + conn = yield from connect_func(*args, **kwargs) + return psycopg_patch_conn(conn, traced_conn_cls=AIOTracedConnection) + + +def _extensions_register_type(func, _, args, kwargs): + def _unroll_args(obj, scope=None): + return obj, scope + + obj, scope = _unroll_args(*args, **kwargs) + + # register_type performs a c-level check of the object + # type so we must be sure to pass in the actual db connection + if scope and isinstance(scope, wrapt.ObjectProxy): + scope = scope.__wrapped__._conn + + return func(obj, scope) if scope else func(obj) + + +# extension hooks +_aiopg_extensions = [ + (psycopg2.extensions.register_type, psycopg2.extensions, "register_type", _extensions_register_type), +] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aioredis/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aioredis/__init__.py new file mode 100644 index 0000000..46d5e71 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aioredis/__init__.py @@ -0,0 +1,83 @@ +from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning +from ddtrace.vendor.debtcollector import deprecate + + +deprecate( + "The aioredis integration is deprecated.", + message="Please use the redis integration with redis>=4.2.0 instead.", + category=DDTraceDeprecationWarning, +) +""" +The aioredis integration instruments aioredis requests. Version 1.3 and above are fully +supported. + + +Enabling +~~~~~~~~ + +The aioredis integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch() ` to manually enable the integration:: + + from ddtrace import patch + patch(aioredis=True) + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.aioredis["service"] + + The service name reported by default for aioredis instances. + + This option can also be set with the ``DD_AIOREDIS_SERVICE`` environment + variable. + + Default: ``"redis"`` + +.. py:data:: ddtrace.config.aioredis["cmd_max_length"] + + Max allowable size for the aioredis command span tag. + Anything beyond the max length will be replaced with ``"..."``. + + This option can also be set with the ``DD_AIOREDIS_CMD_MAX_LENGTH`` environment + variable. + + Default: ``1000`` + +.. py:data:: ddtrace.config.aioedis["resource_only_command"] + + The span resource will only include the command executed. To include all + arguments in the span resource, set this value to ``False``. + + This option can also be set with the ``DD_REDIS_RESOURCE_ONLY_COMMAND`` environment + variable. + + Default: ``True`` + + +Instance Configuration +~~~~~~~~~~~~~~~~~~~~~~ + +To configure the aioredis integration on a per-instance basis use the +``Pin`` API:: + + import aioredis + from ddtrace import Pin + + myaioredis = aioredis.Aioredis() + Pin.override(myaioredis, service="myaioredis") +""" +from ...internal.utils.importlib import require_modules # noqa:E402 + + +required_modules = ["aioredis"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aioredis/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aioredis/patch.py new file mode 100644 index 0000000..9941a90 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aioredis/patch.py @@ -0,0 +1,233 @@ +import asyncio +import os +import sys + +import aioredis + +from ddtrace import config +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.utils.wrappers import unwrap as _u +from ddtrace.pin import Pin +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w + +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from ...ext import db +from ...ext import net +from ...ext import redis as redisx +from ...internal.schema import schematize_cache_operation +from ...internal.schema import schematize_service_name +from ...internal.utils.formats import CMD_MAX_LEN +from ...internal.utils.formats import asbool +from ...internal.utils.formats import stringify_cache_args +from .. import trace_utils +from ..trace_utils_redis import ROW_RETURNING_COMMANDS +from ..trace_utils_redis import _run_redis_command_async +from ..trace_utils_redis import _trace_redis_cmd +from ..trace_utils_redis import _trace_redis_execute_pipeline +from ..trace_utils_redis import determine_row_count + + +try: + from aioredis.commands.transaction import _RedisBuffer +except ImportError: + _RedisBuffer = None + +config._add( + "aioredis", + dict( + _default_service=schematize_service_name("redis"), + cmd_max_length=int(os.getenv("DD_AIOREDIS_CMD_MAX_LENGTH", CMD_MAX_LEN)), + resource_only_command=asbool(os.getenv("DD_REDIS_RESOURCE_ONLY_COMMAND", True)), + ), +) + +aioredis_version_str = getattr(aioredis, "__version__", "") +aioredis_version = tuple([int(i) for i in aioredis_version_str.split(".")]) + + +def get_version(): + # type: () -> str + return aioredis_version_str + + +def patch(): + if getattr(aioredis, "_datadog_patch", False): + return + aioredis._datadog_patch = True + pin = Pin() + if aioredis_version >= (2, 0): + _w("aioredis.client", "Redis.execute_command", traced_execute_command) + _w("aioredis.client", "Redis.pipeline", traced_pipeline) + _w("aioredis.client", "Pipeline.execute", traced_execute_pipeline) + pin.onto(aioredis.client.Redis) + else: + _w("aioredis", "Redis.execute", traced_13_execute_command) + _w("aioredis", "Redis.pipeline", traced_13_pipeline) + _w("aioredis.commands.transaction", "Pipeline.execute", traced_13_execute_pipeline) + pin.onto(aioredis.Redis) + + +def unpatch(): + if not getattr(aioredis, "_datadog_patch", False): + return + + aioredis._datadog_patch = False + if aioredis_version >= (2, 0): + _u(aioredis.client.Redis, "execute_command") + _u(aioredis.client.Redis, "pipeline") + _u(aioredis.client.Pipeline, "execute") + else: + _u(aioredis.Redis, "execute") + _u(aioredis.Redis, "pipeline") + _u(aioredis.commands.transaction.Pipeline, "execute") + + +async def traced_execute_command(func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return await func(*args, **kwargs) + + with _trace_redis_cmd(pin, config.aioredis, instance, args) as span: + return await _run_redis_command_async(span=span, func=func, args=args, kwargs=kwargs) + + +def traced_pipeline(func, instance, args, kwargs): + pipeline = func(*args, **kwargs) + pin = Pin.get_from(instance) + if pin: + pin.onto(pipeline) + return pipeline + + +async def traced_execute_pipeline(func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return await func(*args, **kwargs) + + cmds = [stringify_cache_args(c, cmd_max_len=config.aioredis.cmd_max_length) for c, _ in instance.command_stack] + with _trace_redis_execute_pipeline(pin, config.aioredis, cmds, instance): + return await func(*args, **kwargs) + + +def traced_13_pipeline(func, instance, args, kwargs): + pipeline = func(*args, **kwargs) + pin = Pin.get_from(instance) + if pin: + pin.onto(pipeline) + return pipeline + + +def traced_13_execute_command(func, instance, args, kwargs): + # If we have a _RedisBuffer then we are in a pipeline + if isinstance(instance.connection, _RedisBuffer): + return func(*args, **kwargs) + + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return func(*args, **kwargs) + + # Don't activate the span since this operation is performed as a future which concludes sometime later on in + # execution so subsequent operations in the stack are not necessarily semantically related + # (we don't want this span to be the parent of all other spans created before the future is resolved) + parent = pin.tracer.current_span() + query = stringify_cache_args(args, cmd_max_len=config.aioredis.cmd_max_length) + span = pin.tracer.start_span( + schematize_cache_operation(redisx.CMD, cache_provider="redis"), + service=trace_utils.ext_service(pin, config.aioredis), + resource=query.split(" ")[0] if config.aioredis.resource_only_command else query, + span_type=SpanTypes.REDIS, + activate=False, + child_of=parent, + ) + # set span.kind to the type of request being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + span.set_tag_str(COMPONENT, config.aioredis.integration_name) + span.set_tag_str(db.SYSTEM, redisx.APP) + span.set_tag(SPAN_MEASURED_KEY) + span.set_tag_str(redisx.RAWCMD, query) + if pin.tags: + span.set_tags(pin.tags) + + span.set_tags( + { + net.TARGET_HOST: instance.address[0], + net.TARGET_PORT: instance.address[1], + redisx.DB: instance.db or 0, + } + ) + span.set_metric(redisx.ARGS_LEN, len(args)) + # set analytics sample rate if enabled + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.aioredis.get_analytics_sample_rate()) + + def _finish_span(future): + try: + # Accessing the result will raise an exception if: + # - The future was cancelled (CancelledError) + # - There was an error executing the future (`future.exception()`) + # - The future is in an invalid state + redis_command = span.resource.split(" ")[0] + future.result() + if redis_command in ROW_RETURNING_COMMANDS: + determine_row_count(redis_command=redis_command, span=span, result=future.result()) + # CancelledError exceptions extend from BaseException as of Python 3.8, instead of usual Exception + except BaseException: + span.set_exc_info(*sys.exc_info()) + if redis_command in ROW_RETURNING_COMMANDS: + span.set_metric(db.ROWCOUNT, 0) + finally: + span.finish() + + task = func(*args, **kwargs) + # Execute command returns a coroutine when no free connections are available + # https://github.com/aio-libs/aioredis-py/blob/v1.3.1/aioredis/pool.py#L191 + task = asyncio.ensure_future(task) + task.add_done_callback(_finish_span) + return task + + +async def traced_13_execute_pipeline(func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return await func(*args, **kwargs) + + cmds = [] + for _, cmd, cmd_args, _ in instance._pipeline: + parts = [cmd] + parts.extend(cmd_args) + cmds.append(stringify_cache_args(parts, cmd_max_len=config.aioredis.cmd_max_length)) + + resource = cmds_string = "\n".join(cmds) + if config.aioredis.resource_only_command: + resource = "\n".join([cmd.split(" ")[0] for cmd in cmds]) + + with pin.tracer.trace( + schematize_cache_operation(redisx.CMD, cache_provider="redis"), + resource=resource, + service=trace_utils.ext_service(pin, config.aioredis), + span_type=SpanTypes.REDIS, + ) as span: + # set span.kind to the type of request being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + span.set_tag_str(COMPONENT, config.aioredis.integration_name) + span.set_tag_str(db.SYSTEM, redisx.APP) + span.set_tags( + { + net.TARGET_HOST: instance._pool_or_conn.address[0], + net.TARGET_PORT: instance._pool_or_conn.address[1], + redisx.DB: instance._pool_or_conn.db or 0, + } + ) + + span.set_tag(SPAN_MEASURED_KEY) + span.set_tag_str(redisx.RAWCMD, cmds_string) + span.set_metric(redisx.PIPELINE_LEN, len(instance._pipeline)) + # set analytics sample rate if enabled + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.aioredis.get_analytics_sample_rate()) + + return await func(*args, **kwargs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/algoliasearch/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/algoliasearch/__init__.py new file mode 100644 index 0000000..8b4c96d --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/algoliasearch/__init__.py @@ -0,0 +1,36 @@ +""" +The Algoliasearch__ integration will add tracing to your Algolia searches. + +:: + + import ddtrace.auto + + from algoliasearch import algoliasearch + client = alogliasearch.Client(, ) + index = client.init_index() + index.search("your query", args={"attributesToRetrieve": "attribute1,attribute1"}) + +Configuration +~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.algoliasearch['collect_query_text'] + + Whether to pass the text of your query onto Datadog. Since this may contain sensitive data it's off by default + + Default: ``False`` + +.. __: https://www.algolia.com +""" + +from ...internal.utils.importlib import require_modules + + +required_modules = ["algoliasearch", "algoliasearch.version"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/algoliasearch/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/algoliasearch/patch.py new file mode 100644 index 0000000..b576e4d --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/algoliasearch/patch.py @@ -0,0 +1,168 @@ +from ddtrace import config +from ddtrace.ext import SpanKind +from ddtrace.ext import SpanTypes +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema import schematize_cloud_api_operation +from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.utils.wrappers import unwrap as _u +from ddtrace.pin import Pin +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w + +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from .. import trace_utils + + +DD_PATCH_ATTR = "_datadog_patch" + +SERVICE_NAME = schematize_service_name("algoliasearch") +APP_NAME = "algoliasearch" + +try: + VERSION = "0.0.0" + import algoliasearch + from algoliasearch.version import VERSION + + algoliasearch_version = tuple([int(i) for i in VERSION.split(".")]) + + # Default configuration + config._add("algoliasearch", dict(_default_service=SERVICE_NAME, collect_query_text=False)) +except ImportError: + algoliasearch_version = (0, 0) + + +def get_version(): + # type: () -> str + return VERSION + + +def patch(): + if algoliasearch_version == (0, 0): + return + + if getattr(algoliasearch, DD_PATCH_ATTR, False): + return + + algoliasearch._datadog_patch = True + + pin = Pin() + + if algoliasearch_version < (2, 0) and algoliasearch_version >= (1, 0): + _w(algoliasearch.index, "Index.search", _patched_search) + pin.onto(algoliasearch.index.Index) + elif algoliasearch_version >= (2, 0) and algoliasearch_version < (3, 0): + from algoliasearch import search_index + + _w(algoliasearch, "search_index.SearchIndex.search", _patched_search) + pin.onto(search_index.SearchIndex) + else: + return + + +def unpatch(): + if algoliasearch_version == (0, 0): + return + + if getattr(algoliasearch, DD_PATCH_ATTR, False): + setattr(algoliasearch, DD_PATCH_ATTR, False) + + if algoliasearch_version < (2, 0) and algoliasearch_version >= (1, 0): + _u(algoliasearch.index.Index, "search") + elif algoliasearch_version >= (2, 0) and algoliasearch_version < (3, 0): + from algoliasearch import search_index + + _u(search_index.SearchIndex, "search") + else: + return + + +# DEV: this maps serves the dual purpose of enumerating the algoliasearch.search() query_args that +# will be sent along as tags, as well as converting arguments names into tag names compliant with +# tag naming recommendations set out here: https://docs.datadoghq.com/tagging/ +QUERY_ARGS_DD_TAG_MAP = { + "page": "page", + "hitsPerPage": "hits_per_page", + "attributesToRetrieve": "attributes_to_retrieve", + "attributesToHighlight": "attributes_to_highlight", + "attributesToSnippet": "attributes_to_snippet", + "minWordSizefor1Typo": "min_word_size_for_1_typo", + "minWordSizefor2Typos": "min_word_size_for_2_typos", + "getRankingInfo": "get_ranking_info", + "aroundLatLng": "around_lat_lng", + "numericFilters": "numeric_filters", + "tagFilters": "tag_filters", + "queryType": "query_type", + "optionalWords": "optional_words", + "distinct": "distinct", +} + + +def _patched_search(func, instance, wrapt_args, wrapt_kwargs): + """ + wrapt_args is called the way it is to distinguish it from the 'args' + argument to the algoliasearch.index.Index.search() method. + """ + + if algoliasearch_version < (2, 0) and algoliasearch_version >= (1, 0): + function_query_arg_name = "args" + elif algoliasearch_version >= (2, 0) and algoliasearch_version < (3, 0): + function_query_arg_name = "request_options" + else: + return func(*wrapt_args, **wrapt_kwargs) + + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return func(*wrapt_args, **wrapt_kwargs) + + with pin.tracer.trace( + schematize_cloud_api_operation("algoliasearch.search", cloud_provider="algoliasearch", cloud_service="search"), + service=trace_utils.ext_service(pin, config.algoliasearch), + span_type=SpanTypes.HTTP, + ) as span: + span.set_tag_str(COMPONENT, config.algoliasearch.integration_name) + + # set span.kind to the type of request being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + span.set_tag(SPAN_MEASURED_KEY) + + if not span.sampled: + return func(*wrapt_args, **wrapt_kwargs) + + if config.algoliasearch.collect_query_text: + span.set_tag_str("query.text", wrapt_kwargs.get("query", wrapt_args[0])) + + query_args = wrapt_kwargs.get(function_query_arg_name, wrapt_args[1] if len(wrapt_args) > 1 else None) + + if query_args and isinstance(query_args, dict): + for query_arg, tag_name in QUERY_ARGS_DD_TAG_MAP.items(): + value = query_args.get(query_arg) + if value is not None: + span.set_tag("query.args.{}".format(tag_name), value) + + # Result would look like this + # { + # 'hits': [ + # { + # .... your search results ... + # } + # ], + # 'processingTimeMS': 1, + # 'nbHits': 1, + # 'hitsPerPage': 20, + # 'exhaustiveNbHits': true, + # 'params': 'query=xxx', + # 'nbPages': 1, + # 'query': 'xxx', + # 'page': 0 + # } + result = func(*wrapt_args, **wrapt_kwargs) + + if isinstance(result, dict): + if result.get("processingTimeMS", None) is not None: + span.set_metric("processing_time_ms", int(result["processingTimeMS"])) + + if result.get("nbHits", None) is not None: + span.set_metric("number_of_hits", int(result["nbHits"])) + + return result diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aredis/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aredis/__init__.py new file mode 100644 index 0000000..b00475c --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aredis/__init__.py @@ -0,0 +1,79 @@ +""" +The aredis integration traces aredis requests. + + +Enabling +~~~~~~~~ + +The aredis integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(aredis=True) + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.aredis["service"] + + The service name reported by default for aredis traces. + + This option can also be set with the ``DD_AREDIS_SERVICE`` environment + variable. + + Default: ``"redis"`` + +.. py:data:: ddtrace.config.aredis["cmd_max_length"] + + Max allowable size for the aredis command span tag. + Anything beyond the max length will be replaced with ``"..."``. + + This option can also be set with the ``DD_AREDIS_CMD_MAX_LENGTH`` environment + variable. + + Default: ``1000`` + +.. py:data:: ddtrace.config.aredis["resource_only_command"] + + The span resource will only include the command executed. To include all + arguments in the span resource, set this value to ``False``. + + This option can also be set with the ``DD_REDIS_RESOURCE_ONLY_COMMAND`` environment + variable. + + Default: ``True`` + + +Instance Configuration +~~~~~~~~~~~~~~~~~~~~~~ + +To configure particular aredis instances use the :class:`Pin ` API:: + + import aredis + from ddtrace import Pin + + client = aredis.StrictRedis(host="localhost", port=6379) + + # Override service name for this instance + Pin.override(client, service="my-custom-queue") + + # Traces reported for this client will now have "my-custom-queue" + # as the service name. + async def example(): + await client.get("my-key") +""" + +from ...internal.utils.importlib import require_modules + + +required_modules = ["aredis", "aredis.client"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + + __all__ = ["patch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aredis/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aredis/patch.py new file mode 100644 index 0000000..1c0dc8c --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aredis/patch.py @@ -0,0 +1,86 @@ +import os + +import aredis + +from ddtrace import config +from ddtrace.vendor import wrapt + +from ...internal.schema import schematize_service_name +from ...internal.utils.formats import CMD_MAX_LEN +from ...internal.utils.formats import asbool +from ...internal.utils.formats import stringify_cache_args +from ...internal.utils.wrappers import unwrap +from ...pin import Pin +from ..trace_utils_redis import _run_redis_command_async +from ..trace_utils_redis import _trace_redis_cmd +from ..trace_utils_redis import _trace_redis_execute_pipeline + + +config._add( + "aredis", + dict( + _default_service=schematize_service_name("redis"), + cmd_max_length=int(os.getenv("DD_AREDIS_CMD_MAX_LENGTH", CMD_MAX_LEN)), + resource_only_command=asbool(os.getenv("DD_REDIS_RESOURCE_ONLY_COMMAND", True)), + ), +) + + +def get_version(): + # type: () -> str + return getattr(aredis, "__version__", "") + + +def patch(): + """Patch the instrumented methods""" + if getattr(aredis, "_datadog_patch", False): + return + aredis._datadog_patch = True + + _w = wrapt.wrap_function_wrapper + + _w("aredis.client", "StrictRedis.execute_command", traced_execute_command) + _w("aredis.client", "StrictRedis.pipeline", traced_pipeline) + _w("aredis.pipeline", "StrictPipeline.execute", traced_execute_pipeline) + _w("aredis.pipeline", "StrictPipeline.immediate_execute_command", traced_execute_command) + Pin(service=None).onto(aredis.StrictRedis) + + +def unpatch(): + if getattr(aredis, "_datadog_patch", False): + aredis._datadog_patch = False + + unwrap(aredis.client.StrictRedis, "execute_command") + unwrap(aredis.client.StrictRedis, "pipeline") + unwrap(aredis.pipeline.StrictPipeline, "execute") + unwrap(aredis.pipeline.StrictPipeline, "immediate_execute_command") + + +# +# tracing functions +# +async def traced_execute_command(func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return await func(*args, **kwargs) + + with _trace_redis_cmd(pin, config.aredis, instance, args) as span: + return await _run_redis_command_async(span=span, func=func, args=args, kwargs=kwargs) + + +async def traced_pipeline(func, instance, args, kwargs): + pipeline = await func(*args, **kwargs) + pin = Pin.get_from(instance) + if pin: + pin.onto(pipeline) + return pipeline + + +async def traced_execute_pipeline(func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return await func(*args, **kwargs) + + cmds = [stringify_cache_args(c, cmd_max_len=config.aredis.cmd_max_length) for c, _ in instance.command_stack] + with _trace_redis_execute_pipeline(pin, config.aredis, cmds, instance): + return await func(*args, **kwargs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asgi/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asgi/__init__.py new file mode 100644 index 0000000..59902f5 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asgi/__init__.py @@ -0,0 +1,75 @@ +""" +The asgi__ middleware for tracing all requests to an ASGI-compliant application. + +To configure tracing manually:: + + from ddtrace.contrib.asgi import TraceMiddleware + + # app = + app = TraceMiddleware(app) + +Then use ddtrace-run when serving your application. For example, if serving with Uvicorn:: + + ddtrace-run uvicorn app:app + +On Python 3.6 and below, you must enable the legacy ``AsyncioContextProvider`` before using the middleware:: + + from ddtrace.contrib.asyncio.provider import AsyncioContextProvider + from ddtrace import tracer # Or whichever tracer instance you plan to use + tracer.configure(context_provider=AsyncioContextProvider()) + +The middleware also supports using a custom function for handling exceptions for a trace:: + + from ddtrace.contrib.asgi import TraceMiddleware + + def custom_handle_exception_span(exc, span): + span.set_tag("http.status_code", 501) + + # app = + app = TraceMiddleware(app, handle_exception_span=custom_handle_exception_span) + + +To retrieve the request span from the scope of an ASGI request use the ``span_from_scope`` +function:: + + from ddtrace.contrib.asgi import span_from_scope + + def handle_request(scope, send): + span = span_from_scope(scope) + if span: + span.set_tag(...) + ... + + +Configuration +~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.asgi['distributed_tracing'] + + Whether to use distributed tracing headers from requests received by your Asgi app. + + Default: ``True`` + +.. py:data:: ddtrace.config.asgi['service_name'] + + The service name reported for your ASGI app. + + Can also be configured via the ``DD_SERVICE`` environment variable. + + Default: ``'asgi'`` + +.. __: https://asgi.readthedocs.io/ +""" + +from ...internal.utils.importlib import require_modules + + +required_modules = [] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .middleware import TraceMiddleware + from .middleware import get_version + from .middleware import span_from_scope + + __all__ = ["TraceMiddleware", "span_from_scope", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asgi/middleware.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asgi/middleware.py new file mode 100644 index 0000000..e87944e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asgi/middleware.py @@ -0,0 +1,286 @@ +import sys +from typing import Any +from typing import Mapping +from typing import Optional +from urllib import parse + +import ddtrace +from ddtrace import Span +from ddtrace import config +from ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import SPAN_KIND +from ddtrace.ext import SpanKind +from ddtrace.ext import SpanTypes +from ddtrace.ext import http +from ddtrace.internal.compat import is_valid_ip +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.constants import HTTP_REQUEST_BLOCKED +from ddtrace.internal.schema import schematize_url_operation +from ddtrace.internal.schema.span_attribute_schema import SpanDirection + +from ...internal import core +from ...internal.logger import get_logger +from .. import trace_utils +from .utils import guarantee_single_callable + + +log = get_logger(__name__) + +config._add( + "asgi", + dict(service_name=config._get_service(default="asgi"), request_span_name="asgi.request", distributed_tracing=True), +) + +ASGI_VERSION = "asgi.version" +ASGI_SPEC_VERSION = "asgi.spec_version" + + +def get_version() -> str: + return "" + + +def bytes_to_str(str_or_bytes): + return str_or_bytes.decode(errors="ignore") if isinstance(str_or_bytes, bytes) else str_or_bytes + + +def _extract_versions_from_scope(scope, integration_config): + tags = {} + + http_version = scope.get("http_version") + if http_version: + tags[http.VERSION] = http_version + + scope_asgi = scope.get("asgi") + + if scope_asgi and "version" in scope_asgi: + tags[ASGI_VERSION] = scope_asgi["version"] + + if scope_asgi and "spec_version" in scope_asgi: + tags[ASGI_SPEC_VERSION] = scope_asgi["spec_version"] + + return tags + + +def _extract_headers(scope): + headers = scope.get("headers") + if headers: + # headers: (Iterable[[byte string, byte string]]) + return dict((bytes_to_str(k), bytes_to_str(v)) for (k, v) in headers) + return {} + + +def _default_handle_exception_span(exc, span): + """Default handler for exception for span""" + span.set_tag(http.STATUS_CODE, 500) + + +def span_from_scope(scope: Mapping[str, Any]) -> Optional[Span]: + """Retrieve the top-level ASGI span from the scope.""" + return scope.get("datadog", {}).get("request_spans", [None])[0] + + +async def _blocked_asgi_app(scope, receive, send): + await send({"type": "http.response.start", "status": 403, "headers": []}) + await send({"type": "http.response.body", "body": b""}) + + +class TraceMiddleware: + """ + ASGI application middleware that traces the requests. + Args: + app: The ASGI application. + tracer: Custom tracer. Defaults to the global tracer. + """ + + default_ports = {"http": 80, "https": 443, "ws": 80, "wss": 443} + + def __init__( + self, + app, + tracer=None, + integration_config=config.asgi, + handle_exception_span=_default_handle_exception_span, + span_modifier=None, + ): + self.app = guarantee_single_callable(app) + self.tracer = tracer or ddtrace.tracer + self.integration_config = integration_config + self.handle_exception_span = handle_exception_span + self.span_modifier = span_modifier + + async def __call__(self, scope, receive, send): + if scope["type"] != "http": + return await self.app(scope, receive, send) + try: + headers = _extract_headers(scope) + except Exception: + log.warning("failed to decode headers for distributed tracing", exc_info=True) + headers = {} + else: + trace_utils.activate_distributed_headers( + self.tracer, int_config=self.integration_config, request_headers=headers + ) + resource = " ".join((scope["method"], scope["path"])) + operation_name = self.integration_config.get("request_span_name", "asgi.request") + operation_name = schematize_url_operation(operation_name, direction=SpanDirection.INBOUND, protocol="http") + pin = ddtrace.pin.Pin(service="asgi", tracer=self.tracer) + with pin.tracer.trace( + name=operation_name, + service=trace_utils.int_service(None, self.integration_config), + resource=resource, + span_type=SpanTypes.WEB, + ) as span, core.context_with_data( + "asgi.__call__", + remote_addr=scope.get("REMOTE_ADDR"), + headers=headers, + headers_case_sensitive=True, + environ=scope, + middleware=self, + span=span, + ) as ctx: + span.set_tag_str(COMPONENT, self.integration_config.integration_name) + ctx.set_item("req_span", span) + + # set span.kind to the type of request being performed + span.set_tag_str(SPAN_KIND, SpanKind.SERVER) + + if "datadog" not in scope: + scope["datadog"] = {"request_spans": [span]} + else: + scope["datadog"]["request_spans"].append(span) + + if self.span_modifier: + self.span_modifier(span, scope) + + sample_rate = self.integration_config.get_analytics_sample_rate(use_global_config=True) + if sample_rate is not None: + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, sample_rate) + + host_header = None + for key, value in scope["headers"]: + if key == b"host": + try: + host_header = value.decode("ascii") + except UnicodeDecodeError: + log.warning( + "failed to decode host header, host from http headers will not be considered", exc_info=True + ) + break + method = scope.get("method") + server = scope.get("server") + scheme = scope.get("scheme", "http") + parsed_query = parse.parse_qs(bytes_to_str(scope.get("query_string", b""))) + full_path = scope.get("root_path", "") + scope.get("path", "") + if host_header: + url = "{}://{}{}".format(scheme, host_header, full_path) + elif server and len(server) == 2: + port = server[1] + default_port = self.default_ports.get(scheme, None) + server_host = server[0] + (":" + str(port) if port is not None and port != default_port else "") + url = "{}://{}{}".format(scheme, server_host, full_path) + else: + url = None + query_string = scope.get("query_string") + if query_string: + query_string = bytes_to_str(query_string) + if url: + url = f"{url}?{query_string}" + if not self.integration_config.trace_query_string: + query_string = None + body = None + result = core.dispatch_with_results("asgi.request.parse.body", (receive, headers)).await_receive_and_body + if result: + receive, body = await result.value + + client = scope.get("client") + if isinstance(client, list) and len(client) and is_valid_ip(client[0]): + peer_ip = client[0] + else: + peer_ip = None + + trace_utils.set_http_meta( + span, + self.integration_config, + method=method, + url=url, + query=query_string, + request_headers=headers, + raw_uri=url, + parsed_query=parsed_query, + request_body=body, + peer_ip=peer_ip, + headers_are_case_sensitive=True, + ) + tags = _extract_versions_from_scope(scope, self.integration_config) + span.set_tags(tags) + + async def wrapped_send(message): + try: + response_headers = _extract_headers(message) + except Exception: + log.warning("failed to extract response headers", exc_info=True) + response_headers = None + + if span and message.get("type") == "http.response.start" and "status" in message: + status_code = message["status"] + trace_utils.set_http_meta( + span, self.integration_config, status_code=status_code, response_headers=response_headers + ) + core.dispatch("asgi.start_response", ("asgi",)) + core.dispatch("asgi.finalize_response", (message.get("body"), response_headers)) + + if core.get_item(HTTP_REQUEST_BLOCKED): + raise trace_utils.InterruptException("wrapped_send") + try: + return await send(message) + finally: + # Per asgi spec, "more_body" is used if there is still data to send + # Close the span if "http.response.body" has no more data left to send in the + # response. + if ( + message.get("type") == "http.response.body" + and not message.get("more_body", False) + # If the span has an error status code delay finishing the span until the + # traceback and exception message is available + and span.error == 0 + ): + span.finish() + + async def wrapped_blocked_send(message): + result = core.dispatch_with_results("asgi.block.started", (ctx, url)).status_headers_content + if result: + status, headers, content = result.value + else: + status, headers, content = 403, [], b"" + if span and message.get("type") == "http.response.start": + message["headers"] = headers + message["status"] = int(status) + core.dispatch("asgi.finalize_response", (None, headers)) + elif message.get("type") == "http.response.body": + message["body"] = ( + content if isinstance(content, bytes) else content.encode("utf-8", errors="ignore") + ) + message["more_body"] = False + core.dispatch("asgi.finalize_response", (content, None)) + try: + return await send(message) + finally: + trace_utils.set_http_meta( + span, self.integration_config, status_code=status, response_headers=headers + ) + if message.get("type") == "http.response.body" and span.error == 0: + span.finish() + + try: + core.dispatch("asgi.start_request", ("asgi",)) + return await self.app(scope, receive, wrapped_send) + except trace_utils.InterruptException: + return await _blocked_asgi_app(scope, receive, wrapped_blocked_send) + except Exception as exc: + (exc_type, exc_val, exc_tb) = sys.exc_info() + span.set_exc_info(exc_type, exc_val, exc_tb) + self.handle_exception_span(exc, span) + raise + finally: + if span in scope["datadog"]["request_spans"]: + scope["datadog"]["request_spans"].remove(span) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asgi/utils.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asgi/utils.py new file mode 100644 index 0000000..73f5d17 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asgi/utils.py @@ -0,0 +1,82 @@ +""" +Compatibility functions vendored from asgiref + +Source: https://github.com/django/asgiref +Version: 3.2.10 +License: + +Copyright (c) Django Software Foundation and individual contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of Django nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" +import asyncio +import inspect + + +def is_double_callable(application): + """ + Tests to see if an application is a legacy-style (double-callable) application. + """ + # Look for a hint on the object first + if getattr(application, "_asgi_single_callable", False): + return False + if getattr(application, "_asgi_double_callable", False): + return True + # Uninstanted classes are double-callable + if inspect.isclass(application): + return True + # Instanted classes depend on their __call__ + if hasattr(application, "__call__"): # noqa: B004 + # We only check to see if its __call__ is a coroutine function - + # if it's not, it still might be a coroutine function itself. + if asyncio.iscoroutinefunction(application.__call__): + return False + # Non-classes we just check directly + return not asyncio.iscoroutinefunction(application) + + +def double_to_single_callable(application): + """ + Transforms a double-callable ASGI application into a single-callable one. + """ + + async def new_application(scope, receive, send): + instance = application(scope) + return await instance(receive, send) + + return new_application + + +def guarantee_single_callable(application): + """ + Takes either a single- or double-callable application and always returns it + in single-callable style. Use this to add backwards compatibility for ASGI + 2.0 applications to your server/test harness/etc. + """ + if is_double_callable(application): + application = double_to_single_callable(application) + return application diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncio/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncio/__init__.py new file mode 100644 index 0000000..0e515e2 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncio/__init__.py @@ -0,0 +1,65 @@ +""" +This integration provides context management for tracing the execution flow +of concurrent execution of ``asyncio.Task``. + +This integration is only necessary in Python < 3.7 (where contextvars is not supported). +For Python > 3.7 this works automatically without configuration. + +For asynchronous execution tracing to work properly the tracer must +be configured as follows:: + + import asyncio + from ddtrace import tracer + from ddtrace.contrib.asyncio import context_provider + + # enable asyncio support + tracer.configure(context_provider=context_provider) + + async def some_work(): + with tracer.trace('asyncio.some_work'): + # do something + + # launch your coroutines as usual + loop = asyncio.get_event_loop() + loop.run_until_complete(some_work()) + loop.close() + +In addition, helpers are provided to simplify how the tracing ``Context`` is +handled between scheduled coroutines and ``Future`` invoked in separated +threads: + + * ``set_call_context(task, ctx)``: attach the context to the given ``Task`` + so that it will be available from the ``tracer.current_trace_context()`` + * ``ensure_future(coro_or_future, *, loop=None)``: wrapper for the + ``asyncio.ensure_future`` that attaches the current context to a new + ``Task`` instance + * ``run_in_executor(loop, executor, func, *args)``: wrapper for the + ``loop.run_in_executor`` that attaches the current context to the new + thread so that the trace can be resumed regardless when it's executed + * ``create_task(coro)``: creates a new asyncio ``Task`` that inherits the + current active ``Context`` so that generated traces in the new task are + attached to the main trace +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["asyncio"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from ...internal.compat import CONTEXTVARS_IS_AVAILABLE + from ...provider import DefaultContextProvider + from .provider import AsyncioContextProvider + + if CONTEXTVARS_IS_AVAILABLE: + context_provider = DefaultContextProvider() + else: + context_provider = AsyncioContextProvider() + + from .helpers import ensure_future + from .helpers import run_in_executor + from .helpers import set_call_context + from .patch import get_version + from .patch import patch + + __all__ = ["context_provider", "set_call_context", "ensure_future", "run_in_executor", "patch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncio/compat.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncio/compat.py new file mode 100644 index 0000000..4389a36 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncio/compat.py @@ -0,0 +1,15 @@ +import asyncio + + +if hasattr(asyncio, "current_task"): + + def asyncio_current_task(): + try: + return asyncio.current_task() + except RuntimeError: + return None + +else: + + def asyncio_current_task(): + return asyncio.Task.current_task() diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncio/helpers.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncio/helpers.py new file mode 100644 index 0000000..f1803ee --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncio/helpers.py @@ -0,0 +1,81 @@ +""" +This module includes a list of convenience methods that +can be used to simplify some operations while handling +Context and Spans in instrumented ``asyncio`` code. +""" +import asyncio + +import ddtrace + +from .provider import AsyncioContextProvider +from .wrappers import wrapped_create_task + + +def set_call_context(task, ctx): + """ + Updates the ``Context`` for the given Task. Useful when you need to + pass the context among different tasks. + + This method is available for backward-compatibility. Use the + ``AsyncioContextProvider`` API to set the current active ``Context``. + """ + setattr(task, AsyncioContextProvider._CONTEXT_ATTR, ctx) + + +def ensure_future(coro_or_future, *, loop=None, tracer=None): + """Wrapper that sets a context to the newly created Task. + + If the current task already has a Context, it will be attached to the new Task so the Trace list will be preserved. + """ + tracer = tracer or ddtrace.tracer + current_ctx = tracer.current_trace_context() + task = asyncio.ensure_future(coro_or_future, loop=loop) + set_call_context(task, current_ctx) + return task + + +def run_in_executor(loop, executor, func, *args, tracer=None): + """Wrapper function that sets a context to the newly created Thread. + + If the current task has a Context, it will be attached as an empty Context with the current_span activated to + inherit the ``trace_id`` and the ``parent_id``. + + Because the Executor can run the Thread immediately or after the + coroutine is executed, we may have two different scenarios: + * the Context is copied in the new Thread and the trace is sent twice + * the coroutine flushes the Context and when the Thread copies the + Context it is already empty (so it will be a root Span) + + To support both situations, we create a new Context that knows only what was + the latest active Span when the new thread was created. In this new thread, + we fallback to the thread-local ``Context`` storage. + + """ + tracer = tracer or ddtrace.tracer + current_ctx = tracer.current_trace_context() + + # prepare the future using an executor wrapper + future = loop.run_in_executor(executor, _wrap_executor, func, args, tracer, current_ctx) + return future + + +def _wrap_executor(fn, args, tracer, ctx): + """ + This function is executed in the newly created Thread so the right + ``Context`` can be set in the thread-local storage. This operation + is safe because the ``Context`` class is thread-safe and can be + updated concurrently. + """ + # the AsyncioContextProvider knows that this is a new thread + # so it is legit to pass the Context in the thread-local storage; + # fn() will be executed outside the asyncio loop as a synchronous code + tracer.context_provider.activate(ctx) + return fn(*args) + + +def create_task(*args, **kwargs): + """This function spawns a task with a Context that inherits the + `trace_id` and the `parent_id` from the current active one if available. + """ + loop = asyncio.get_event_loop() + return wrapped_create_task(loop.create_task, None, args, kwargs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncio/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncio/patch.py new file mode 100644 index 0000000..24620a7 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncio/patch.py @@ -0,0 +1,22 @@ +import asyncio + + +def get_version(): + # type: () -> str + return "" + + +def patch(): + """Patches current loop `create_task()` method to enable spawned tasks to + parent to the base task context. + """ + if getattr(asyncio, "_datadog_patch", False): + return + asyncio._datadog_patch = True + + +def unpatch(): + """Remove tracing from patched modules.""" + + if getattr(asyncio, "_datadog_patch", False): + asyncio._datadog_patch = False diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncio/provider.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncio/provider.py new file mode 100644 index 0000000..356753c --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncio/provider.py @@ -0,0 +1,74 @@ +import asyncio + +from ...provider import BaseContextProvider +from ...provider import DatadogContextMixin +from ...span import Span + + +class AsyncioContextProvider(BaseContextProvider, DatadogContextMixin): + """Manages the active context for asyncio execution. Framework + instrumentation that is built on top of the ``asyncio`` library, should + use this provider when contextvars are not available (Python versions + less than 3.7). + + This Context Provider inherits from ``DefaultContextProvider`` because + it uses a thread-local storage when the ``Context`` is propagated to + a different thread, than the one that is running the async loop. + """ + + # Task attribute used to set/get the context + _CONTEXT_ATTR = "__datadog_context" + + def activate(self, context, loop=None): + """Sets the scoped ``Context`` for the current running ``Task``.""" + loop = self._get_loop(loop) + if not loop: + super(AsyncioContextProvider, self).activate(context) + return context + + # the current unit of work (if tasks are used) + task = asyncio.Task.current_task(loop=loop) + if task: + setattr(task, self._CONTEXT_ATTR, context) + return context + + def _get_loop(self, loop=None): + """Helper to try and resolve the current loop""" + try: + return loop or asyncio.get_event_loop() + except RuntimeError: + # Detects if a loop is available in the current thread; + # DEV: This happens when a new thread is created from the out that is running the async loop + # DEV: It's possible that a different Executor is handling a different Thread that + # works with blocking code. In that case, we fallback to a thread-local Context. + pass + return None + + def _has_active_context(self, loop=None): + """Helper to determine if we have a currently active context""" + loop = self._get_loop(loop=loop) + if loop is None: + return super(AsyncioContextProvider, self)._has_active_context() + + # the current unit of work (if tasks are used) + task = asyncio.Task.current_task(loop=loop) + if task is None: + return False + + ctx = getattr(task, self._CONTEXT_ATTR, None) + return ctx is not None + + def active(self, loop=None): + """Returns the active context for the execution.""" + loop = self._get_loop(loop=loop) + if not loop: + return super(AsyncioContextProvider, self).active() + + # the current unit of work (if tasks are used) + task = asyncio.Task.current_task(loop=loop) + if task is None: + return None + ctx = getattr(task, self._CONTEXT_ATTR, None) + if isinstance(ctx, Span): + return self._update_active(ctx) + return ctx diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncio/wrappers.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncio/wrappers.py new file mode 100644 index 0000000..ddbabc4 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncio/wrappers.py @@ -0,0 +1,25 @@ +from .compat import asyncio_current_task +from .provider import AsyncioContextProvider + + +def wrapped_create_task(wrapped, instance, args, kwargs): + """Wrapper for ``create_task(coro)`` that propagates the current active + ``Context`` to the new ``Task``. This function is useful to connect traces + of detached executions. + + Note: we can't just link the task contexts due to the following scenario: + * begin task A + * task A starts task B1..B10 + * finish task B1-B9 (B10 still on trace stack) + * task A starts task C + * now task C gets parented to task B10 since it's still on the stack, + however was not actually triggered by B10 + """ + new_task = wrapped(*args, **kwargs) + current_task = asyncio_current_task() + + ctx = getattr(current_task, AsyncioContextProvider._CONTEXT_ATTR, None) + if ctx: + setattr(new_task, AsyncioContextProvider._CONTEXT_ATTR, ctx) + + return new_task diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncpg/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncpg/__init__.py new file mode 100644 index 0000000..d67edee --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncpg/__init__.py @@ -0,0 +1,57 @@ +""" +The ``asyncpg`` integration traces database requests made using connection +and cursor objects. + + +Enabling +~~~~~~~~ + +The integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(asyncpg=True) + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.asyncpg['service'] + + The service name reported by default for asyncpg connections. + + This option can also be set with the ``DD_ASYNCPG_SERVICE`` + environment variable. + + Default: ``postgres`` + + +Instance Configuration +~~~~~~~~~~~~~~~~~~~~~~ + +Service +^^^^^^^ + +To configure the service name used by the asyncpg integration on a per-instance +basis use the ``Pin`` API:: + + import asyncpg + from ddtrace import Pin + + conn = asyncpg.connect("postgres://localhost:5432") + Pin.override(conn, service="custom-service") +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["asyncpg"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncpg/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncpg/patch.py new file mode 100644 index 0000000..c7686a0 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/asyncpg/patch.py @@ -0,0 +1,163 @@ +from typing import TYPE_CHECKING # noqa:I001 +from types import ModuleType +import asyncpg + +from ddtrace import Pin +from ddtrace import config +from ddtrace.internal.constants import COMPONENT +from ddtrace.vendor import wrapt + +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from ...ext import db +from ...ext import net +from ...internal.logger import get_logger +from ...internal.schema import schematize_database_operation +from ...internal.schema import schematize_service_name +from ...internal.utils import get_argument_value +from ..trace_utils import ext_service +from ..trace_utils import unwrap +from ..trace_utils import wrap +from ..trace_utils_async import with_traced_module + + +if TYPE_CHECKING: # pragma: no cover + from typing import Dict # noqa:F401 + from typing import Union # noqa:F401 + + from asyncpg.prepared_stmt import PreparedStatement # noqa:F401 + + +DBMS_NAME = "postgresql" + + +config._add( + "asyncpg", + dict( + _default_service=schematize_service_name("postgres"), + ), +) + + +log = get_logger(__name__) + + +def get_version(): + # type: () -> str + return getattr(asyncpg, "__version__", "") + + +def _get_connection_tags(conn): + # type: (asyncpg.Connection) -> Dict[str, str] + addr = conn._addr + params = conn._params + host = port = "" + if isinstance(addr, tuple) and len(addr) == 2: + host, port = addr + return { + net.TARGET_HOST: host, + net.TARGET_PORT: port, + db.USER: params.user, + db.NAME: params.database, + } + + +class _TracedConnection(wrapt.ObjectProxy): + def __init__(self, conn, pin): + super(_TracedConnection, self).__init__(conn) + tags = _get_connection_tags(conn) + tags[db.SYSTEM] = DBMS_NAME + conn_pin = pin.clone(tags=tags) + # Keep the pin on the protocol + conn_pin.onto(self._protocol) + + def __setddpin__(self, pin): + pin.onto(self._protocol) + + def __getddpin__(self): + return Pin.get_from(self._protocol) + + +@with_traced_module +async def _traced_connect(asyncpg, pin, func, instance, args, kwargs): + """Traced asyncpg.connect(). + + connect() is instrumented and patched to return a connection proxy. + """ + with pin.tracer.trace( + "postgres.connect", span_type=SpanTypes.SQL, service=ext_service(pin, config.asyncpg) + ) as span: + span.set_tag_str(COMPONENT, config.asyncpg.integration_name) + span.set_tag_str(db.SYSTEM, DBMS_NAME) + + # set span.kind to the type of request being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + # Need an ObjectProxy since Connection uses slots + conn = _TracedConnection(await func(*args, **kwargs), pin) + span.set_tags(_get_connection_tags(conn)) + return conn + + +async def _traced_query(pin, method, query, args, kwargs): + with pin.tracer.trace( + schematize_database_operation("postgres.query", database_provider="postgresql"), + resource=query, + service=ext_service(pin, config.asyncpg), + span_type=SpanTypes.SQL, + ) as span: + span.set_tag_str(COMPONENT, config.asyncpg.integration_name) + span.set_tag_str(db.SYSTEM, DBMS_NAME) + + # set span.kind to the type of request being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + span.set_tag(SPAN_MEASURED_KEY) + span.set_tags(pin.tags) + return await method(*args, **kwargs) + + +@with_traced_module +async def _traced_protocol_execute(asyncpg, pin, func, instance, args, kwargs): + state = get_argument_value(args, kwargs, 0, "state") # type: Union[str, PreparedStatement] + query = state if isinstance(state, str) else state.query + return await _traced_query(pin, func, query, args, kwargs) + + +def _patch(asyncpg: ModuleType) -> None: + wrap(asyncpg, "connect", _traced_connect(asyncpg)) + for method in ("execute", "bind_execute", "query", "bind_execute_many"): + wrap(asyncpg.protocol, "Protocol.%s" % method, _traced_protocol_execute(asyncpg)) + + +def patch(): + # type: () -> None + import asyncpg + + if getattr(asyncpg, "_datadog_patch", False): + return + + Pin().onto(asyncpg) + _patch(asyncpg) + + asyncpg._datadog_patch = True + + +def _unpatch(asyncpg: ModuleType) -> None: + unwrap(asyncpg, "connect") + for method in ("execute", "bind_execute", "query", "bind_execute_many"): + unwrap(asyncpg.protocol.Protocol, method) + + +def unpatch(): + # type: () -> None + import asyncpg + + if not getattr(asyncpg, "_datadog_patch", False): + return + + _unpatch(asyncpg) + + asyncpg._datadog_patch = False diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aws_lambda/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aws_lambda/__init__.py new file mode 100644 index 0000000..6b43cd6 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aws_lambda/__init__.py @@ -0,0 +1,46 @@ +""" +The aws_lambda integration currently enables traces to be sent +before an impending timeout in an AWS Lambda function instrumented with the +`Datadog Lambda Python `_ package. + +Enabling +~~~~~~~~ + +The aws_lambda integration is enabled automatically for AWS Lambda +functions which have been instrumented with Datadog. + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +This integration is configured automatically. The `datadog_lambda` package +calls ``patch_all`` when ``DD_TRACE_ENABLED`` is set to ``true``. +It's not recommended to call ``patch`` for it manually. Since it would not do +anything for other environments that do not meet the criteria above. + + +Configuration +~~~~~~~~~~~~~ + +.. important:: + + You can configure some features with environment variables. + +.. py:data:: DD_APM_FLUSH_DEADLINE_MILLISECONDS + + Used to determine when to submit spans before a timeout occurs. + When the remaining time in an AWS Lambda invocation is less than `DD_APM_FLUSH_DEADLINE_MILLISECONDS`, + the tracer will attempt to submit the current active spans and all finished spans. + + Default: 100 + + +For additional configuration refer to +`Instrumenting Python Serverless Applications by Datadog `_. +""" +from .patch import get_version +from .patch import patch +from .patch import unpatch + + +__all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aws_lambda/_cold_start.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aws_lambda/_cold_start.py new file mode 100644 index 0000000..75aeda4 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aws_lambda/_cold_start.py @@ -0,0 +1,18 @@ +__cold_start = True +__lambda_container_initialized = False + + +def set_cold_start(): + """Set the value of the cold start global. + + This should be executed once per AWS Lambda execution before the execution. + """ + global __cold_start + global __lambda_container_initialized + __cold_start = not __lambda_container_initialized + __lambda_container_initialized = True + + +def is_cold_start(): + """Returns the value of the global cold_start.""" + return __cold_start diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aws_lambda/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aws_lambda/patch.py new file mode 100644 index 0000000..53c2fcf --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/aws_lambda/patch.py @@ -0,0 +1,272 @@ +from importlib import import_module +import os +import signal + +from ddtrace import tracer +from ddtrace.constants import ERROR_MSG +from ddtrace.constants import ERROR_TYPE +from ddtrace.internal.logger import get_logger +from ddtrace.internal.serverless import in_aws_lambda +from ddtrace.internal.utils import get_argument_value +from ddtrace.internal.wrapping import unwrap +from ddtrace.internal.wrapping import wrap + +from ._cold_start import is_cold_start +from ._cold_start import set_cold_start + + +def get_version(): + # type: () -> str + return "" + + +class DDLambdaLogger: + """Uses `DDLogger` to log only on cold start invocations.""" + + def __init__(self): + self.logger = get_logger(__name__) + self.is_cold_start = is_cold_start() + + def exception(self, msg, *args, exc_info=True, **kwargs): + if self.is_cold_start: + self.logger.error(msg, *args, exc_info=exc_info, **kwargs) + + def warning(self, msg, *args, **kwargs): + if self.is_cold_start: + self.logger.warning(msg, *args, **kwargs) + + +log = DDLambdaLogger() + + +class TimeoutChannel: + def __init__(self, context): + self.crashed = False + self.context = context + + def _handle_signal(self, sig, f): + """ + Returns a signal of type `sig` with function `f`, if there are + no previously defined signals. + + Else, wraps the given signal with the previously defined one, + so no signals are overridden. + """ + old_signal = signal.getsignal(sig) + + def wrap_signals(*args, **kwargs): + if old_signal is not None: + old_signal(*args, **kwargs) + f(*args, **kwargs) + + # Return the incoming signal if any of the following cases happens: + # - old signal does not exist, + # - old signal is the same as the incoming, or + # - old signal is our wrapper. + # This avoids multiple signal calling and infinite wrapping. + if not callable(old_signal) or old_signal == f or old_signal == wrap_signals: + return signal.signal(sig, f) + + return signal.signal(sig, wrap_signals) + + def _start(self): + self._handle_signal(signal.SIGALRM, self._crash_flush) + + remaining_time_in_millis = self.context.get_remaining_time_in_millis() + apm_flush_deadline = int(os.environ.get("DD_APM_FLUSH_DEADLINE_MILLISECONDS", 100)) + apm_flush_deadline = 100 if apm_flush_deadline < 0 else apm_flush_deadline + + # TODO: Update logic to calculate an approximate of how long it will + # take us to flush the spans on the queue. + remaining_time_in_seconds = max(((remaining_time_in_millis - apm_flush_deadline) / 1000), 0) + signal.setitimer(signal.ITIMER_REAL, remaining_time_in_seconds) + + def _crash_flush(self, _, __): + """ + Tags the current root span with an Impending Timeout error. + Finishes spans with ancestors from the current span. + """ + self._remove_alarm_signal() + self.crashed = True + + root_span = tracer.current_root_span() + if root_span is not None: + root_span.error = 1 + root_span.set_tag_str(ERROR_MSG, "Datadog detected an Impending Timeout") + root_span.set_tag_str(ERROR_TYPE, "Impending Timeout") + else: + log.warning("An impending timeout was reached, but no root span was found. No error will be tagged.") + + current_span = tracer.current_span() + if current_span is not None: + current_span.finish_with_ancestors() + + def _remove_alarm_signal(self): + """Removes the handler set for the signal `SIGALRM`.""" + signal.alarm(0) + signal.signal(signal.SIGALRM, signal.SIG_DFL) + + def stop(self): + self._remove_alarm_signal() + + +class DatadogInstrumentation(object): + """Patches an AWS Lambda handler function for Datadog instrumentation.""" + + def __call__(self, func, args, kwargs): + self.func = func + self._before(args, kwargs) + try: + self.response = self.func(*args, **kwargs) + return self.response + except Exception: + raise + finally: + self._after() + + def _set_context(self, args, kwargs): + """Sets the context attribute.""" + # The context is the second argument in a handler + # signature and it is always sent. + # + # note: AWS Lambda context is an object, the event is a dict. + # `get_remaining_time_in_millis` is guaranteed to be + # present in the context. + _context = get_argument_value(args, kwargs, 1, "context") + if hasattr(_context, "get_remaining_time_in_millis"): + self.context = _context + else: + # Handler was possibly manually wrapped, and the first + # argument is the `datadog-lambda` decorator object. + self.context = get_argument_value(args, kwargs, 2, "context") + + def _before(self, args, kwargs): + set_cold_start() + self._set_context(args, kwargs) + self.timeoutChannel = TimeoutChannel(self.context) + + self.timeoutChannel._start() + + def _after(self): + if not self.timeoutChannel.crashed: + self.timeoutChannel.stop() + + +def _modify_module_name(module_name): + """Returns a valid modified module to get imported.""" + return ".".join(module_name.split("/")) + + +def _get_handler_and_module(): + """Returns the user AWS Lambda handler and module.""" + path = os.environ.get("DD_LAMBDA_HANDLER", None) + _datadog_instrumentation = DatadogInstrumentation() + + if path is None: + from datadog_lambda.wrapper import datadog_lambda_wrapper + + wrapper_module = datadog_lambda_wrapper + wrapper_handler = datadog_lambda_wrapper.__call__ + + return wrapper_handler, wrapper_module, _datadog_instrumentation + else: + parts = path.rsplit(".", 1) + (mod_name, handler_name) = parts + modified_mod_name = _modify_module_name(mod_name) + handler_module = import_module(modified_mod_name) + handler = getattr(handler_module, handler_name) + + if callable(handler): + class_name = type(handler).__name__ + is_function = not isinstance(handler, type) and hasattr(handler, "__code__") and class_name == "function" + # handler is a function + # + # note: this is a best effort to identify function based handlers + # this will not cover all cases + if is_function: + return handler, handler_module, _datadog_instrumentation + + # handler must be either a class or an instance of a class + # + # note: if handler is a class instance with `__code__` defined, + # we will prioritize the `__call__` method, ignoring `__code__`. + class_module = getattr(handler_module, class_name) + class_handler = class_module.__call__ + + if isinstance(handler, type): + # class handler is a metaclass + if hasattr(class_handler, "__func__"): + class_handler = class_handler.__func__ + + return class_handler, class_module, _datadog_instrumentation + else: + raise TypeError("Handler type is not supported to patch.") + + +def _has_patch_module(): + """ + Ensures that the `aws_lambda` integration can be patched. + + It checks either the user has the DD_LAMBDA_HANDLER set correctly. + Or if the `datadog_lambda` package is installed. + """ + path = os.environ.get("DD_LAMBDA_HANDLER", None) + if path is None: + try: + import_module("datadog_lambda.wrapper") + except Exception: + return False + else: + parts = path.rsplit(".", 1) + if len(parts) != 2: + return False + return True + + +def patch(): + """Patches an AWS Lambda using the `datadog-lambda-py` Lambda layer.""" + + # It's expected to only patch only in AWS Lambda environments. + # The need to check if a patch module exists is to avoid patching + # when `ddtrace` is present but not `datadog-lambda`. + if not in_aws_lambda() and not _has_patch_module(): + return + + try: + handler, handler_module, wrapper = _get_handler_and_module() + + if getattr(handler_module, "_datadog_patch", False): + return + + wrap(handler, wrapper) + + handler_module._datadog_patch = True + except AttributeError: + # User code might contain `ddtrace.patch_all()` or `ddtrace.patch(aws_lambda=True)` + # which might cause a circular dependency. Skipping. + return + except Exception: + log.exception("Error patching handler. Timeout spans will not be generated.") + + return + + +def unpatch(): + if not in_aws_lambda() and not _has_patch_module(): + return + + try: + handler, handler_module, wrapper = _get_handler_and_module() + + if not getattr(handler_module, "_datadog_patch", False): + return + + unwrap(handler, wrapper) + + handler_module._datadog_patch = False + except AttributeError: + return + except Exception: + log.exception("Error unpatching handler.") + + return diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/boto/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/boto/__init__.py new file mode 100644 index 0000000..0478bc7 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/boto/__init__.py @@ -0,0 +1,41 @@ +""" +Boto integration will trace all AWS calls made via boto2. + +Enabling +~~~~~~~~ + +The boto integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(boto=True) + +Configuration +~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.boto['tag_no_params'] + + This opts out of the default behavior of collecting a narrow set of API + parameters as span tags. + + To not collect any API parameters, ``ddtrace.config.boto.tag_no_params = + True`` or by setting the environment variable ``DD_AWS_TAG_NO_PARAMS=true``. + + + Default: ``False`` + +""" + +from ...internal.utils.importlib import require_modules + + +required_modules = ["boto.connection"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + + __all__ = ["patch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/boto/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/boto/patch.py new file mode 100644 index 0000000..4db66fb --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/boto/patch.py @@ -0,0 +1,212 @@ +import inspect +import os + +from boto import __version__ +import boto.connection + +from ddtrace import config +from ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import SPAN_KIND +from ddtrace.constants import SPAN_MEASURED_KEY +from ddtrace.ext import SpanKind +from ddtrace.ext import SpanTypes +from ddtrace.ext import aws +from ddtrace.ext import http +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.utils.wrappers import unwrap +from ddtrace.pin import Pin +from ddtrace.vendor import wrapt + +from ...internal.schema import schematize_cloud_api_operation +from ...internal.schema import schematize_service_name +from ...internal.utils import get_argument_value +from ...internal.utils.formats import asbool + + +# Original boto client class +_Boto_client = boto.connection.AWSQueryConnection + +AWS_QUERY_ARGS_NAME = ("operation_name", "params", "path", "verb") +AWS_AUTH_ARGS_NAME = ( + "method", + "path", + "headers", + "data", + "host", + "auth_path", + "sender", +) +AWS_QUERY_TRACED_ARGS = {"operation_name", "params", "path"} +AWS_AUTH_TRACED_ARGS = {"path", "data", "host"} + + +config._add( + "boto", + { + "tag_no_params": asbool(os.getenv("DD_AWS_TAG_NO_PARAMS", default=False)), + }, +) + + +def get_version(): + # type: () -> str + return __version__ + + +def patch(): + if getattr(boto.connection, "_datadog_patch", False): + return + boto.connection._datadog_patch = True + + # AWSQueryConnection and AWSAuthConnection are two different classes called by + # different services for connection. + # For example EC2 uses AWSQueryConnection and S3 uses AWSAuthConnection + wrapt.wrap_function_wrapper("boto.connection", "AWSQueryConnection.make_request", patched_query_request) + wrapt.wrap_function_wrapper("boto.connection", "AWSAuthConnection.make_request", patched_auth_request) + Pin(service="aws").onto(boto.connection.AWSQueryConnection) + Pin(service="aws").onto(boto.connection.AWSAuthConnection) + + +def unpatch(): + if getattr(boto.connection, "_datadog_patch", False): + boto.connection._datadog_patch = False + unwrap(boto.connection.AWSQueryConnection, "make_request") + unwrap(boto.connection.AWSAuthConnection, "make_request") + + +# ec2, sqs, kinesis +def patched_query_request(original_func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return original_func(*args, **kwargs) + + endpoint_name = instance.host.split(".")[0] + + with pin.tracer.trace( + schematize_cloud_api_operation( + "{}.command".format(endpoint_name), cloud_provider="aws", cloud_service=endpoint_name + ), + service=schematize_service_name("{}.{}".format(pin.service, endpoint_name)), + span_type=SpanTypes.HTTP, + ) as span: + span.set_tag_str(COMPONENT, config.boto.integration_name) + + # set span.kind to the type of request being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + span.set_tag(SPAN_MEASURED_KEY) + + operation_name = None + if args: + operation_name = get_argument_value(args, kwargs, 0, "action") + params = get_argument_value(args, kwargs, 1, "params") + + span.resource = "%s.%s" % (endpoint_name, operation_name.lower()) + + if params and not config.boto["tag_no_params"]: + aws._add_api_param_span_tags(span, endpoint_name, params) + else: + span.resource = endpoint_name + + # Obtaining region name + region_name = _get_instance_region_name(instance) + + meta = { + aws.AGENT: "boto", + aws.OPERATION: operation_name, + } + if region_name: + meta[aws.REGION] = region_name + meta[aws.AWSREGION] = region_name + + span.set_tags(meta) + + # Original func returns a boto.connection.HTTPResponse object + result = original_func(*args, **kwargs) + span.set_tag(http.STATUS_CODE, result.status) + span.set_tag_str(http.METHOD, result._method) + + # set analytics sample rate + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.boto.get_analytics_sample_rate()) + + return result + + +# s3, lambda +def patched_auth_request(original_func, instance, args, kwargs): + # Catching the name of the operation that called make_request() + operation_name = None + + # Go up the stack until we get the first non-ddtrace module + # DEV: For `lambda.list_functions()` this should be: + # - ddtrace.contrib.boto.patch + # - ddtrace.vendor.wrapt.wrappers + # - boto.awslambda.layer1 (make_request) + # - boto.awslambda.layer1 (list_functions) + # But can vary depending on Python versions; that's why we use an heuristic + frame = inspect.currentframe().f_back + operation_name = None + while frame: + if frame.f_code.co_name == "make_request": + operation_name = frame.f_back.f_code.co_name + break + frame = frame.f_back + + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return original_func(*args, **kwargs) + + endpoint_name = instance.host.split(".")[0] + + with pin.tracer.trace( + schematize_cloud_api_operation( + "{}.command".format(endpoint_name), cloud_provider="aws", cloud_service=endpoint_name + ), + service=schematize_service_name("{}.{}".format(pin.service, endpoint_name)), + span_type=SpanTypes.HTTP, + ) as span: + span.set_tag(SPAN_MEASURED_KEY) + if args: + http_method = get_argument_value(args, kwargs, 0, "method") + span.resource = "%s.%s" % (endpoint_name, http_method.lower()) + else: + span.resource = endpoint_name + + # Obtaining region name + region_name = _get_instance_region_name(instance) + + meta = { + aws.AGENT: "boto", + aws.OPERATION: operation_name, + } + if region_name: + meta[aws.REGION] = region_name + meta[aws.AWSREGION] = region_name + + span.set_tags(meta) + + # Original func returns a boto.connection.HTTPResponse object + result = original_func(*args, **kwargs) + span.set_tag(http.STATUS_CODE, result.status) + span.set_tag_str(http.METHOD, result._method) + + # set analytics sample rate + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.boto.get_analytics_sample_rate()) + + span.set_tag_str(COMPONENT, config.boto.integration_name) + + # set span.kind to the type of request being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + return result + + +def _get_instance_region_name(instance): + region = getattr(instance, "region", None) + + if not region: + return None + if isinstance(region, str): + return region.split(":")[1] + else: + return region.name diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/__init__.py new file mode 100644 index 0000000..561ca4e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/__init__.py @@ -0,0 +1,125 @@ +""" +The Botocore integration will trace all AWS calls made with the botocore +library. Libraries like Boto3 that use Botocore will also be patched. + +Enabling +~~~~~~~~ + +The botocore integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(botocore=True) + +To patch only specific botocore modules, pass a list of the module names instead:: + + from ddtrace import patch + patch(botocore=['s3', 'sns']) + +Configuration +~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.botocore['distributed_tracing'] + + Whether to inject distributed tracing data to requests in SQS, SNS, EventBridge, Kinesis Streams and Lambda. + + Can also be enabled with the ``DD_BOTOCORE_DISTRIBUTED_TRACING`` environment variable. + + Example:: + + from ddtrace import config + + # Enable distributed tracing + config.botocore['distributed_tracing'] = True + + + Default: ``True`` + + +.. py:data:: ddtrace.config.botocore['invoke_with_legacy_context'] + + This preserves legacy behavior when tracing directly invoked Python and Node Lambda + functions instrumented with datadog-lambda-python < v41 or datadog-lambda-js < v3.58.0. + + Legacy support for older libraries is available with + ``ddtrace.config.botocore.invoke_with_legacy_context = True`` or by setting the environment + variable ``DD_BOTOCORE_INVOKE_WITH_LEGACY_CONTEXT=true``. + + + Default: ``False`` + + +.. py:data:: ddtrace.config.botocore['operations'][].error_statuses = "" + + Definition of which HTTP status codes to consider for making a span as an error span. + + By default response status codes of ``'500-599'`` are considered as errors for all endpoints. + + Example marking 404, and 5xx as errors for ``s3.headobject`` API calls:: + + from ddtrace import config + + config.botocore['operations']['s3.headobject'].error_statuses = '404,500-599' + + + See :ref:`HTTP - Custom Error Codes` documentation for more examples. + +.. py:data:: ddtrace.config.botocore['tag_no_params'] + + This opts out of the default behavior of collecting a narrow set of API parameters as span tags. + + To not collect any API parameters, ``ddtrace.config.botocore.tag_no_params = True`` or by setting the environment + variable ``DD_AWS_TAG_NO_PARAMS=true``. + + + Default: ``False`` + + +.. py:data:: ddtrace.config.botocore['instrument_internals'] + + This opts into collecting spans for some internal functions, including ``parsers.ResponseParser.parse``. + + Can also be enabled with the ``DD_BOTOCORE_INSTRUMENT_INTERNALS`` environment variable. + + Default: ``False`` + + +.. py:data:: ddtrace.config.botocore['span_prompt_completion_sample_rate'] + + Configure the sample rate for the collection of bedrock prompts and completions as span tags. + + Alternatively, you can set this option with the ``DD_BEDROCK_SPAN_PROMPT_COMPLETION_SAMPLE_RATE`` environment + variable. + + Default: ``1.0`` + + +.. py:data:: (beta) ddtrace.config.botocore["span_char_limit"] + + Configure the maximum number of characters for bedrock span tags for prompt/response text. + + Text exceeding the maximum number of characters is truncated to the character limit + and has ``...`` appended to the end. + + Alternatively, you can set this option with the ``DD_BEDROCK_SPAN_CHAR_LIMIT`` environment + variable. + + Default: ``128`` + +""" + + +from ...internal.utils.importlib import require_modules + + +required_modules = ["botocore.client"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import patch_submodules + + __all__ = ["patch", "patch_submodules", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/patch.py new file mode 100644 index 0000000..043ae5e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/patch.py @@ -0,0 +1,273 @@ +""" +Trace queries to aws api done via botocore client +""" +import collections +import os +from typing import List # noqa:F401 +from typing import Set # noqa:F401 +from typing import Union # noqa:F401 + +from botocore import __version__ +import botocore.client +import botocore.exceptions + +from ddtrace import config +from ddtrace.contrib.trace_utils import with_traced_module +from ddtrace.internal.llmobs.integrations import BedrockIntegration +from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.settings.config import Config +from ddtrace.vendor import wrapt + +from ...constants import SPAN_KIND +from ...ext import SpanKind +from ...ext import SpanTypes +from ...ext import http +from ...internal.constants import COMPONENT +from ...internal.logger import get_logger +from ...internal.schema import schematize_cloud_api_operation +from ...internal.schema import schematize_cloud_faas_operation +from ...internal.schema import schematize_cloud_messaging_operation +from ...internal.schema import schematize_service_name +from ...internal.utils import get_argument_value +from ...internal.utils.formats import asbool +from ...internal.utils.formats import deep_getattr +from ...pin import Pin +from ..trace_utils import unwrap +from .services.bedrock import patched_bedrock_api_call +from .services.kinesis import patched_kinesis_api_call +from .services.sqs import inject_trace_to_sqs_or_sns_batch_message +from .services.sqs import inject_trace_to_sqs_or_sns_message +from .services.sqs import patched_sqs_api_call +from .services.stepfunctions import inject_trace_to_stepfunction_input +from .services.stepfunctions import patched_stepfunction_api_call +from .utils import inject_trace_to_client_context +from .utils import inject_trace_to_eventbridge_detail +from .utils import set_patched_api_call_span_tags +from .utils import set_response_metadata_tags + + +_PATCHED_SUBMODULES = set() # type: Set[str] + +# Original botocore client class +_Botocore_client = botocore.client.BaseClient + +ARGS_NAME = ("action", "params", "path", "verb") +TRACED_ARGS = {"params", "path", "verb"} + +log = get_logger(__name__) + + +# Botocore default settings +config._add( + "botocore", + { + "distributed_tracing": asbool(os.getenv("DD_BOTOCORE_DISTRIBUTED_TRACING", default=True)), + "invoke_with_legacy_context": asbool(os.getenv("DD_BOTOCORE_INVOKE_WITH_LEGACY_CONTEXT", default=False)), + "llmobs_enabled": asbool(os.getenv("DD_BEDROCK_LLMOBS_ENABLED", False)), + "operations": collections.defaultdict(Config._HTTPServerConfig), + "span_prompt_completion_sample_rate": float(os.getenv("DD_BEDROCK_SPAN_PROMPT_COMPLETION_SAMPLE_RATE", 1.0)), + "llmobs_prompt_completion_sample_rate": float( + os.getenv("DD_LANGCHAIN_LLMOBS_PROMPT_COMPLETION_SAMPLE_RATE", 1.0) + ), + "span_char_limit": int(os.getenv("DD_BEDROCK_SPAN_CHAR_LIMIT", 128)), + "tag_no_params": asbool(os.getenv("DD_AWS_TAG_NO_PARAMS", default=False)), + "instrument_internals": asbool(os.getenv("DD_BOTOCORE_INSTRUMENT_INTERNALS", default=False)), + "propagation_enabled": asbool(os.getenv("DD_BOTOCORE_PROPAGATION_ENABLED", default=False)), + "empty_poll_enabled": asbool(os.getenv("DD_BOTOCORE_EMPTY_POLL_ENABLED", default=True)), + }, +) + + +def get_version(): + # type: () -> str + return __version__ + + +def patch(): + if getattr(botocore.client, "_datadog_patch", False): + return + botocore.client._datadog_patch = True + + botocore._datadog_integration = BedrockIntegration(integration_config=config.botocore) + wrapt.wrap_function_wrapper("botocore.client", "BaseClient._make_api_call", patched_api_call(botocore)) + Pin(service="aws").onto(botocore.client.BaseClient) + wrapt.wrap_function_wrapper("botocore.parsers", "ResponseParser.parse", patched_lib_fn) + Pin(service="aws").onto(botocore.parsers.ResponseParser) + _PATCHED_SUBMODULES.clear() + + +def unpatch(): + _PATCHED_SUBMODULES.clear() + if getattr(botocore.client, "_datadog_patch", False): + botocore.client._datadog_patch = False + unwrap(botocore.parsers.ResponseParser, "parse") + unwrap(botocore.client.BaseClient, "_make_api_call") + + +def patch_submodules(submodules): + # type: (Union[List[str], bool]) -> None + if isinstance(submodules, bool) and submodules: + _PATCHED_SUBMODULES.clear() + elif isinstance(submodules, list): + submodules = [sub_module.lower() for sub_module in submodules] + _PATCHED_SUBMODULES.update(submodules) + + +def patched_lib_fn(original_func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled() or not config.botocore["instrument_internals"]: + return original_func(*args, **kwargs) + with pin.tracer.trace("{}.{}".format(original_func.__module__, original_func.__name__)) as span: + span.set_tag_str(COMPONENT, config.botocore.integration_name) + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + return original_func(*args, **kwargs) + + +@with_traced_module +def patched_api_call(botocore, pin, original_func, instance, args, kwargs): + if not pin or not pin.enabled(): + return original_func(*args, **kwargs) + + endpoint_name = deep_getattr(instance, "_endpoint._endpoint_prefix") + + if _PATCHED_SUBMODULES and endpoint_name not in _PATCHED_SUBMODULES: + return original_func(*args, **kwargs) + + trace_operation = schematize_cloud_api_operation( + "{}.command".format(endpoint_name), cloud_provider="aws", cloud_service=endpoint_name + ) + + operation = get_argument_value(args, kwargs, 0, "operation_name", True) + params = get_argument_value(args, kwargs, 1, "api_params", True) + + function_vars = { + "endpoint_name": endpoint_name, + "operation": operation, + "params": params, + "pin": pin, + "trace_operation": trace_operation, + "integration": botocore._datadog_integration, + } + + if endpoint_name == "kinesis": + return patched_kinesis_api_call( + original_func=original_func, + instance=instance, + args=args, + kwargs=kwargs, + function_vars=function_vars, + ) + elif endpoint_name == "sqs": + return patched_sqs_api_call( + original_func=original_func, + instance=instance, + args=args, + kwargs=kwargs, + function_vars=function_vars, + ) + elif endpoint_name == "bedrock-runtime" and operation.startswith("InvokeModel"): + return patched_bedrock_api_call( + original_func=original_func, + instance=instance, + args=args, + kwargs=kwargs, + function_vars=function_vars, + ) + elif endpoint_name == "states": + return patched_stepfunction_api_call( + original_func=original_func, instance=instance, args=args, kwargs=kwargs, function_vars=function_vars + ) + else: + # this is the default patched api call + return patched_api_call_fallback( + original_func=original_func, + instance=instance, + args=args, + kwargs=kwargs, + function_vars=function_vars, + ) + + +def patched_api_call_fallback(original_func, instance, args, kwargs, function_vars): + # default patched api call that is used generally for several services / operations + params = function_vars.get("params") + trace_operation = function_vars.get("trace_operation") + pin = function_vars.get("pin") + endpoint_name = function_vars.get("endpoint_name") + operation = function_vars.get("operation") + + with pin.tracer.trace( + trace_operation, + service=schematize_service_name("{}.{}".format(pin.service, endpoint_name)), + span_type=SpanTypes.HTTP, + ) as span: + set_patched_api_call_span_tags(span, instance, args, params, endpoint_name, operation) + + if args: + if config.botocore["distributed_tracing"]: + try: + if endpoint_name == "lambda" and operation == "Invoke": + inject_trace_to_client_context(params, span) + span.name = schematize_cloud_faas_operation( + trace_operation, cloud_provider="aws", cloud_service="lambda" + ) + if endpoint_name == "events" and operation == "PutEvents": + inject_trace_to_eventbridge_detail(params, span) + span.name = schematize_cloud_messaging_operation( + trace_operation, + cloud_provider="aws", + cloud_service="events", + direction=SpanDirection.OUTBOUND, + ) + if endpoint_name == "sns" and operation == "Publish": + inject_trace_to_sqs_or_sns_message( + params, + span, + endpoint_service=endpoint_name, + ) + span.name = schematize_cloud_messaging_operation( + trace_operation, + cloud_provider="aws", + cloud_service="sns", + direction=SpanDirection.OUTBOUND, + ) + if endpoint_name == "sns" and operation == "PublishBatch": + inject_trace_to_sqs_or_sns_batch_message( + params, + span, + endpoint_service=endpoint_name, + ) + span.name = schematize_cloud_messaging_operation( + trace_operation, + cloud_provider="aws", + cloud_service="sns", + direction=SpanDirection.OUTBOUND, + ) + if endpoint_name == "states" and ( + operation == "StartExecution" or operation == "StartSyncExecution" + ): + inject_trace_to_stepfunction_input(params, span) + span.name = schematize_cloud_messaging_operation( + trace_operation, + cloud_provider="aws", + cloud_service="stepfunctions", + direction=SpanDirection.OUTBOUND, + ) + except Exception: + log.warning("Unable to inject trace context", exc_info=True) + + try: + result = original_func(*args, **kwargs) + set_response_metadata_tags(span, result) + return result + + except botocore.exceptions.ClientError as e: + # `ClientError.response` contains the result, so we can still grab response metadata + set_response_metadata_tags(span, e.response) + + # If we have a status code, and the status code is not an error, + # then ignore the exception being raised + status_code = span.get_tag(http.STATUS_CODE) + if status_code and not config.botocore.operations[span.resource].is_error_code(int(status_code)): + span._ignore_exception(botocore.exceptions.ClientError) + raise diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/services/bedrock.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/services/bedrock.py new file mode 100644 index 0000000..4009400 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/services/bedrock.py @@ -0,0 +1,341 @@ +import json +import sys +from typing import Any +from typing import Dict +from typing import List +from typing import Optional + +from ddtrace import Span +from ddtrace.internal.llmobs.integrations import BedrockIntegration +from ddtrace.internal.logger import get_logger +from ddtrace.vendor import wrapt + +from ....internal.schema import schematize_service_name + + +log = get_logger(__name__) + + +_AI21 = "ai21" +_AMAZON = "amazon" +_ANTHROPIC = "anthropic" +_COHERE = "cohere" +_META = "meta" +_STABILITY = "stability" + + +class TracedBotocoreStreamingBody(wrapt.ObjectProxy): + """ + This class wraps the StreamingBody object returned by botocore api calls, specifically for Bedrock invocations. + Since the response body is in the form of a stream object, we need to wrap it in order to tag the response data + and finish the span as the user consumes the streamed response. + Currently, the corresponding span finishes only if: + 1) the user fully consumes the stream body + 2) error during reading + This means that if the stream is not consumed, there is a small risk of memory leak due to unfinished spans. + """ + + def __init__(self, wrapped, span, integration, prompt=None): + """ + The TracedBotocoreStreamingBody wrapper stores a reference to the + underlying Span object, BedrockIntegration object, and the response body that will saved and tagged. + """ + super().__init__(wrapped) + self._datadog_span = span + self._datadog_integration = integration + self._body = [] + self._prompt = prompt + + def read(self, amt=None): + """Wraps around method to tags the response data and finish the span as the user consumes the stream.""" + try: + body = self.__wrapped__.read(amt=amt) + self._body.append(json.loads(body)) + if self.__wrapped__.tell() == int(self.__wrapped__._content_length): + formatted_response = _extract_response(self._datadog_span, self._body[0]) + self._process_response(formatted_response) + self._datadog_span.finish() + return body + except Exception: + _handle_exception(self._datadog_span, self._datadog_integration, self._prompt, sys.exc_info()) + raise + + def readlines(self): + """Wraps around method to tags the response data and finish the span as the user consumes the stream.""" + try: + lines = self.__wrapped__.readlines() + for line in lines: + self._body.append(json.loads(line)) + formatted_response = _extract_response(self._datadog_span, self._body[0]) + self._process_response(formatted_response) + self._datadog_span.finish() + return lines + except Exception: + _handle_exception(self._datadog_span, self._datadog_integration, self._prompt, sys.exc_info()) + raise + + def __iter__(self): + """Wraps around method to tags the response data and finish the span as the user consumes the stream.""" + try: + for line in self.__wrapped__: + self._body.append(json.loads(line["chunk"]["bytes"])) + yield line + metadata = _extract_streamed_response_metadata(self._datadog_span, self._body) + formatted_response = _extract_streamed_response(self._datadog_span, self._body) + self._process_response(formatted_response, metadata=metadata) + self._datadog_span.finish() + except Exception: + _handle_exception(self._datadog_span, self._datadog_integration, self._prompt, sys.exc_info()) + raise + + def _process_response(self, formatted_response: Dict[str, Any], metadata: Dict[str, Any] = None) -> None: + """ + Sets the response tags on the span given the formatted response body and any metadata. + Also generates an LLM record if enabled. + """ + if metadata is not None: + for k, v in metadata.items(): + self._datadog_span.set_tag_str("bedrock.{}".format(k), str(v)) + for i in range(len(formatted_response["text"])): + if self._datadog_integration.is_pc_sampled_span(self._datadog_span): + self._datadog_span.set_tag_str( + "bedrock.response.choices.{}.text".format(i), + self._datadog_integration.trunc(str(formatted_response["text"][i])), + ) + self._datadog_span.set_tag_str( + "bedrock.response.choices.{}.finish_reason".format(i), str(formatted_response["finish_reason"][i]) + ) + if self._datadog_integration.is_pc_sampled_llmobs(self._datadog_span): + self._datadog_integration.generate_llm_record( + self._datadog_span, formatted_response=formatted_response, prompt=self._prompt + ) + + +def _handle_exception(span, integration, prompt, exc_info): + """Helper method to finish the span on stream read error.""" + span.set_exc_info(*exc_info) + span.finish() + if integration.is_pc_sampled_llmobs(span): + integration.generate_llm_record(span, formatted_response=None, prompt=prompt, err=1) + + +def _extract_request_params(params: Dict[str, Any], provider: str) -> Dict[str, Any]: + """ + Extracts request parameters including prompt, temperature, top_p, max_tokens, and stop_sequences. + """ + request_body = json.loads(params.get("body")) + if provider == _AI21: + return { + "prompt": request_body.get("prompt"), + "temperature": request_body.get("temperature", None), + "top_p": request_body.get("topP", None), + "max_tokens": request_body.get("maxTokens", None), + "stop_sequences": request_body.get("stopSequences", []), + } + elif provider == _AMAZON: + text_generation_config = request_body.get("textGenerationConfig", {}) + return { + "prompt": request_body.get("inputText"), + "temperature": text_generation_config.get("temperature", None), + "top_p": text_generation_config.get("topP", None), + "max_tokens": text_generation_config.get("maxTokenCount", None), + "stop_sequences": text_generation_config.get("stopSequences", []), + } + elif provider == _ANTHROPIC: + return { + "prompt": request_body.get("prompt"), + "temperature": request_body.get("temperature", None), + "top_p": request_body.get("top_p", None), + "top_k": request_body.get("top_k", None), + "max_tokens": request_body.get("max_tokens_to_sample", None), + "stop_sequences": request_body.get("stop_sequences", []), + } + elif provider == _COHERE: + return { + "prompt": request_body.get("prompt"), + "temperature": request_body.get("temperature", None), + "top_p": request_body.get("p", None), + "top_k": request_body.get("k", None), + "max_tokens": request_body.get("max_tokens", None), + "stop_sequences": request_body.get("stop_sequences", []), + "stream": request_body.get("stream", None), + "n": request_body.get("num_generations", None), + } + elif provider == _META: + return { + "prompt": request_body.get("prompt"), + "temperature": request_body.get("temperature", None), + "top_p": request_body.get("top_p", None), + "max_tokens": request_body.get("max_gen_len", None), + } + elif provider == _STABILITY: + # TODO: request/response formats are different for image-based models. Defer for now + return {} + return {} + + +def _extract_response(span: Span, body: Dict[str, Any]) -> Dict[str, List[str]]: + """ + Extracts text and finish_reason from the response body, which has different formats for different providers. + """ + text, finish_reason = "", "" + provider = span.get_tag("bedrock.request.model_provider") + try: + if provider == _AI21: + text = body.get("completions")[0].get("data").get("text") + finish_reason = body.get("completions")[0].get("finishReason") + elif provider == _AMAZON: + text = body.get("results")[0].get("outputText") + finish_reason = body.get("results")[0].get("completionReason") + elif provider == _ANTHROPIC: + text = body.get("completion") + finish_reason = body.get("stop_reason") + elif provider == _COHERE: + text = [generation["text"] for generation in body.get("generations")] + finish_reason = [generation["finish_reason"] for generation in body.get("generations")] + for i in range(len(text)): + span.set_tag_str("bedrock.response.choices.{}.id".format(i), str(body.get("generations")[i]["id"])) + elif provider == _META: + text = body.get("generation") + finish_reason = body.get("stop_reason") + elif provider == _STABILITY: + # TODO: request/response formats are different for image-based models. Defer for now + pass + except (IndexError, AttributeError): + log.warning("Unable to extract text/finish_reason from response body. Defaulting to empty text/finish_reason.") + + if not isinstance(text, list): + text = [text] + if not isinstance(finish_reason, list): + finish_reason = [finish_reason] + + return {"text": text, "finish_reason": finish_reason} + + +def _extract_streamed_response(span: Span, streamed_body: List[Dict[str, Any]]) -> Dict[str, List[str]]: + """ + Extracts text,finish_reason from the streamed response body, which has different formats for different providers. + """ + text, finish_reason = "", "" + provider = span.get_tag("bedrock.request.model_provider") + try: + if provider == _AI21: + pass # note: ai21 does not support streamed responses + elif provider == _AMAZON: + text = "".join([chunk["outputText"] for chunk in streamed_body]) + finish_reason = streamed_body[-1]["completionReason"] + elif provider == _ANTHROPIC: + text = "".join([chunk["completion"] for chunk in streamed_body]) + finish_reason = streamed_body[-1]["stop_reason"] + elif provider == _COHERE and streamed_body: + if "is_finished" in streamed_body[0]: # streamed response + if "index" in streamed_body[0]: # n >= 2 + n = int(span.get_tag("bedrock.request.n")) + text = [ + "".join([chunk["text"] for chunk in streamed_body[:-1] if chunk["index"] == i]) + for i in range(n) + ] + finish_reason = [streamed_body[-1]["finish_reason"] for _ in range(n)] + else: + text = "".join([chunk["text"] for chunk in streamed_body[:-1]]) + finish_reason = streamed_body[-1]["finish_reason"] + else: + text = [chunk["text"] for chunk in streamed_body[0]["generations"]] + finish_reason = [chunk["finish_reason"] for chunk in streamed_body[0]["generations"]] + for i in range(len(text)): + span.set_tag_str( + "bedrock.response.choices.{}.id".format(i), + str(streamed_body[0]["generations"][i].get("id", None)), + ) + elif provider == _META: + text = "".join([chunk["generation"] for chunk in streamed_body]) + finish_reason = streamed_body[-1]["stop_reason"] + elif provider == _STABILITY: + # TODO: figure out extraction for image-based models + pass + except (IndexError, AttributeError): + log.warning("Unable to extract text/finish_reason from response body. Defaulting to empty text/finish_reason.") + + if not isinstance(text, list): + text = [text] + if not isinstance(finish_reason, list): + finish_reason = [finish_reason] + + return {"text": text, "finish_reason": finish_reason} + + +def _extract_streamed_response_metadata(span: Span, streamed_body: List[Dict[str, Any]]) -> Dict[str, Any]: + """Extracts metadata from the streamed response body.""" + provider = span.get_tag("bedrock.request.model_provider") + metadata = {} + if provider == _AI21: + pass # ai21 does not support streamed responses + elif provider in [_AMAZON, _ANTHROPIC, _COHERE, _META] and streamed_body: + metadata = streamed_body[-1].get("amazon-bedrock-invocationMetrics", {}) + elif provider == _STABILITY: + # TODO: figure out extraction for image-based models + pass + return { + "response.duration": metadata.get("invocationLatency", None), + "usage.prompt_tokens": metadata.get("inputTokenCount", None), + "usage.completion_tokens": metadata.get("outputTokenCount", None), + } + + +def handle_bedrock_request(span: Span, integration: BedrockIntegration, params: Dict[str, Any]) -> None: + """Perform request param extraction and tagging.""" + model_provider, model_name = params.get("modelId").split(".") + request_params = _extract_request_params(params, model_provider) + + span.set_tag_str("bedrock.request.model_provider", model_provider) + span.set_tag_str("bedrock.request.model", model_name) + prompt = None + for k, v in request_params.items(): + if k == "prompt" and integration.is_pc_sampled_span(span): + v = integration.trunc(str(v)) + if k == "prompt" and integration.is_pc_sampled_llmobs(span): + prompt = v + span.set_tag_str("bedrock.request.{}".format(k), str(v)) + return prompt + + +def handle_bedrock_response( + span: Span, integration: BedrockIntegration, result: Dict[str, Any], prompt: Optional[str] = None +) -> Dict[str, Any]: + """Perform response param extraction and tagging.""" + metadata = result["ResponseMetadata"] + http_headers = metadata["HTTPHeaders"] + span.set_tag_str("bedrock.response.id", str(metadata.get("RequestId", ""))) + span.set_tag_str("bedrock.response.duration", str(http_headers.get("x-amzn-bedrock-invocation-latency", ""))) + span.set_tag_str("bedrock.usage.prompt_tokens", str(http_headers.get("x-amzn-bedrock-input-token-count", ""))) + span.set_tag_str("bedrock.usage.completion_tokens", str(http_headers.get("x-amzn-bedrock-output-token-count", ""))) + + # Wrap the StreamingResponse in a traced object so that we can tag response data as the user consumes it. + body = result["body"] + result["body"] = TracedBotocoreStreamingBody(body, span, integration, prompt=prompt) + return result + + +def patched_bedrock_api_call(original_func, instance, args, kwargs, function_vars): + params = function_vars.get("params") + trace_operation = function_vars.get("trace_operation") + operation = function_vars.get("operation") + pin = function_vars.get("pin") + endpoint_name = function_vars.get("endpoint_name") + integration = function_vars.get("integration") + # This span will be finished separately as the user fully consumes the stream body, or on error. + bedrock_span = pin.tracer.start_span( + trace_operation, + service=schematize_service_name("{}.{}".format(pin.service, endpoint_name)), + resource=operation, + activate=False, + ) + prompt = None + try: + prompt = handle_bedrock_request(bedrock_span, integration, params) + result = original_func(*args, **kwargs) + result = handle_bedrock_response(bedrock_span, integration, result, prompt=prompt) + return result + except Exception: + _handle_exception(bedrock_span, integration, prompt, sys.exc_info()) + raise diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/services/kinesis.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/services/kinesis.py new file mode 100644 index 0000000..2785545 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/services/kinesis.py @@ -0,0 +1,182 @@ +import json +from typing import Any # noqa:F401 +from typing import Dict # noqa:F401 +from typing import List # noqa:F401 +from typing import Optional # noqa:F401 + +import botocore.client +import botocore.exceptions + +from ddtrace import Span # noqa:F401 +from ddtrace import config +from ddtrace.internal import core +from ddtrace.internal.schema.span_attribute_schema import SpanDirection + +from ....ext import SpanTypes +from ....ext import http +from ....internal.compat import time_ns +from ....internal.logger import get_logger +from ....internal.schema import schematize_cloud_messaging_operation +from ....internal.schema import schematize_service_name +from ....pin import Pin # noqa:F401 +from ....propagation.http import HTTPPropagator +from ..utils import extract_DD_context +from ..utils import get_kinesis_data_object +from ..utils import set_patched_api_call_span_tags +from ..utils import set_response_metadata_tags + + +log = get_logger(__name__) + + +MAX_KINESIS_DATA_SIZE = 1 << 20 # 1MB + + +class TraceInjectionSizeExceed(Exception): + pass + + +def inject_trace_to_kinesis_stream_data(record, span): + # type: (Dict[str, Any], Span) -> None + """ + :record: contains args for the current botocore action, Kinesis record is at index 1 + :span: the span which provides the trace context to be propagated + Inject trace headers into the Kinesis record's Data field in addition to the existing + data. Only possible if the existing data is JSON string or base64 encoded JSON string + Max data size per record is 1MB (https://aws.amazon.com/kinesis/data-streams/faqs/) + """ + if "Data" not in record: + log.warning("Unable to inject context. The kinesis stream has no data") + return + + data = record["Data"] + line_break, data_obj = get_kinesis_data_object(data) + if data_obj is not None: + data_obj["_datadog"] = {} + HTTPPropagator.inject(span.context, data_obj["_datadog"]) + data_json = json.dumps(data_obj) + + # if original string had a line break, add it back + if line_break is not None: + data_json += line_break + + # check if data size will exceed max size with headers + data_size = len(data_json) + if data_size >= MAX_KINESIS_DATA_SIZE: + raise TraceInjectionSizeExceed( + "Data including trace injection ({}) exceeds ({})".format(data_size, MAX_KINESIS_DATA_SIZE) + ) + + record["Data"] = data_json + + +def inject_trace_to_kinesis_stream(params, span): + # type: (List[Any], Span) -> None + """ + :params: contains the params for the current botocore action + :span: the span which provides the trace context to be propagated + Max data size per record is 1MB (https://aws.amazon.com/kinesis/data-streams/faqs/) + """ + core.dispatch("botocore.kinesis.start", [params]) + if "Records" in params: + records = params["Records"] + + if records: + record = records[0] + inject_trace_to_kinesis_stream_data(record, span) + elif "Data" in params: + inject_trace_to_kinesis_stream_data(params, span) + + +def patched_kinesis_api_call(original_func, instance, args, kwargs, function_vars): + params = function_vars.get("params") + trace_operation = function_vars.get("trace_operation") + pin = function_vars.get("pin") + endpoint_name = function_vars.get("endpoint_name") + operation = function_vars.get("operation") + + message_received = False + func_run = False + func_run_err = None + child_of = None + start_ns = None + result = None + + if operation == "GetRecords": + try: + start_ns = time_ns() + func_run = True + core.dispatch(f"botocore.{endpoint_name}.{operation}.pre", [params]) + result = original_func(*args, **kwargs) + core.dispatch(f"botocore.{endpoint_name}.{operation}.post", [params, result]) + except Exception as e: + func_run_err = e + if result is not None and "Records" in result and len(result["Records"]) >= 1: + message_received = True + if config.botocore.propagation_enabled: + child_of = extract_DD_context(result["Records"]) + + """ + We only want to create a span for the following cases: + - not func_run: The function is not `getRecords` and we need to run it + - func_run and message_received: Received a message when polling + - config.empty_poll_enabled: We want to trace empty poll operations + - func_run_err: There was an error when calling the `getRecords` function + """ + if (func_run and message_received) or config.botocore.empty_poll_enabled or not func_run or func_run_err: + with pin.tracer.start_span( + trace_operation, + service=schematize_service_name("{}.{}".format(pin.service, endpoint_name)), + span_type=SpanTypes.HTTP, + child_of=child_of if child_of is not None else pin.tracer.context_provider.active(), + activate=True, + ) as span: + set_patched_api_call_span_tags(span, instance, args, params, endpoint_name, operation) + + # we need this since we may have ran the wrapped operation before starting the span + # we need to ensure the span start time is correct + if start_ns is not None and func_run: + span.start_ns = start_ns + + if args and config.botocore["distributed_tracing"]: + try: + if endpoint_name == "kinesis" and operation in {"PutRecord", "PutRecords"}: + inject_trace_to_kinesis_stream(params, span) + span.name = schematize_cloud_messaging_operation( + trace_operation, + cloud_provider="aws", + cloud_service="kinesis", + direction=SpanDirection.OUTBOUND, + ) + except Exception: + log.warning("Unable to inject trace context", exc_info=True) + + try: + if not func_run: + core.dispatch(f"botocore.{endpoint_name}.{operation}.pre", [params]) + result = original_func(*args, **kwargs) + core.dispatch(f"botocore.{endpoint_name}.{operation}.post", [params, result]) + + # raise error if it was encountered before the span was started + if func_run_err: + raise func_run_err + + set_response_metadata_tags(span, result) + return result + + except botocore.exceptions.ClientError as e: + # `ClientError.response` contains the result, so we can still grab response metadata + set_response_metadata_tags(span, e.response) + + # If we have a status code, and the status code is not an error, + # then ignore the exception being raised + status_code = span.get_tag(http.STATUS_CODE) + if status_code and not config.botocore.operations[span.resource].is_error_code(int(status_code)): + span._ignore_exception(botocore.exceptions.ClientError) + raise + # return results in the case that we ran the function, but no records were returned and empty + # poll spans are disabled + elif func_run: + if func_run_err: + raise func_run_err + return result diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/services/sqs.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/services/sqs.py new file mode 100644 index 0000000..21f9636 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/services/sqs.py @@ -0,0 +1,227 @@ +import json +from typing import Any # noqa:F401 +from typing import Dict # noqa:F401 +from typing import Optional # noqa:F401 + +import botocore.client +import botocore.exceptions + +from ddtrace import Span # noqa:F401 +from ddtrace import config +from ddtrace.internal import core +from ddtrace.internal.schema.span_attribute_schema import SpanDirection + +from ....ext import SpanTypes +from ....ext import http +from ....internal.compat import time_ns +from ....internal.logger import get_logger +from ....internal.schema import schematize_cloud_messaging_operation +from ....internal.schema import schematize_service_name +from ....pin import Pin # noqa:F401 +from ....propagation.http import HTTPPropagator +from ..utils import extract_DD_context +from ..utils import set_patched_api_call_span_tags +from ..utils import set_response_metadata_tags + + +log = get_logger(__name__) + + +def _encode_data(trace_data): + """ + This method exists solely to enable us to patch the value in tests, since + moto doesn't support auto-encoded SNS -> SQS as binary with RawDelivery enabled + """ + return json.dumps(trace_data) + + +def inject_trace_data_to_message_attributes(trace_data, entry, endpoint_service=None): + # type: (Dict[str, str], Dict[str, Any], Optional[str]) -> None + """ + :trace_data: trace headers and DSM pathway to be stored in the entry's MessageAttributes + :entry: an SQS or SNS record + :endpoint_service: endpoint of message, "sqs" or "sns" + Inject trace headers and DSM info into the SQS or SNS record's MessageAttributes + """ + if "MessageAttributes" not in entry: + entry["MessageAttributes"] = {} + # Max of 10 message attributes. + if len(entry["MessageAttributes"]) < 10: + if endpoint_service == "sqs": + # Use String since changing this to Binary would be a breaking + # change as other tracers expect this to be a String. + entry["MessageAttributes"]["_datadog"] = {"DataType": "String", "StringValue": _encode_data(trace_data)} + elif endpoint_service == "sns": + # Use Binary since SNS subscription filter policies fail silently + # with JSON strings https://github.com/DataDog/datadog-lambda-js/pull/269 + # AWS will encode our value if it sees "Binary" + entry["MessageAttributes"]["_datadog"] = {"DataType": "Binary", "BinaryValue": _encode_data(trace_data)} + else: + log.debug( + "skipping trace injection, endpoint service is not SNS or SQS.", + extra=dict(endpoint_service=endpoint_service), + ) + else: + # In the event a record has 10 or more msg attributes we cannot add our _datadog msg attribute + log.warning("skipping trace injection, max number (10) of MessageAttributes exceeded") + + +def inject_trace_to_sqs_or_sns_batch_message(params, span, endpoint_service=None): + # type: (Any, Span, Optional[str]) -> None + """ + :params: contains the params for the current botocore action + :span: the span which provides the trace context to be propagated + :endpoint_service: endpoint of message, "sqs" or "sns" + Inject trace headers info into MessageAttributes for all SQS or SNS records inside a batch + """ + + trace_data = {} + HTTPPropagator.inject(span.context, trace_data) + + # An entry here is an SNS or SQS record, and depending on how it was published, + # it could either show up under Entries (in case of PutRecords), + # or PublishBatchRequestEntries (in case of PublishBatch). + entries = params.get("Entries", params.get("PublishBatchRequestEntries", [])) + if len(entries) != 0: + for entry in entries: + core.dispatch("botocore.sqs_sns.start", [endpoint_service, trace_data, params]) + inject_trace_data_to_message_attributes(trace_data, entry, endpoint_service) + else: + log.warning("Skipping injecting Datadog attributes to records, no records available") + + +def inject_trace_to_sqs_or_sns_message(params, span, endpoint_service=None): + # type: (Any, Span, Optional[str]) -> None + """ + :params: contains the params for the current botocore action + :span: the span which provides the trace context to be propagated + :endpoint_service: endpoint of message, "sqs" or "sns" + Inject trace headers info into MessageAttributes for the SQS or SNS record + """ + trace_data = {} + HTTPPropagator.inject(span.context, trace_data) + + core.dispatch("botocore.sqs_sns.start", [endpoint_service, trace_data, params]) + inject_trace_data_to_message_attributes(trace_data, params, endpoint_service) + + +def patched_sqs_api_call(original_func, instance, args, kwargs, function_vars): + params = function_vars.get("params") + trace_operation = function_vars.get("trace_operation") + pin = function_vars.get("pin") + endpoint_name = function_vars.get("endpoint_name") + operation = function_vars.get("operation") + + message_received = False + func_run = False + func_run_err = None + child_of = None + start_ns = None + result = None + + if operation == "ReceiveMessage": + # Ensure we have Datadog MessageAttribute enabled + if "MessageAttributeNames" not in params: + params.update({"MessageAttributeNames": ["_datadog"]}) + elif "_datadog" not in params["MessageAttributeNames"]: + params.update({"MessageAttributeNames": list(params["MessageAttributeNames"]) + ["_datadog"]}) + + try: + start_ns = time_ns() + func_run = True + # run the function before in order to extract possible parent context before starting span + + core.dispatch(f"botocore.{endpoint_name}.{operation}.pre", [params]) + result = original_func(*args, **kwargs) + core.dispatch(f"botocore.{endpoint_name}.{operation}.post", [params, result]) + except Exception as e: + func_run_err = e + if result is not None and "Messages" in result and len(result["Messages"]) >= 1: + message_received = True + if config.botocore.propagation_enabled: + child_of = extract_DD_context(result["Messages"]) + + """ + We only want to create a span for the following cases: + - not func_run: The function is not `ReceiveMessage` and we need to run it + - func_run and message_received: Received a message when polling + - config.empty_poll_enabled: We want to trace empty poll operations + - func_run_err: There was an error when calling the `ReceiveMessage` function + """ + if (func_run and message_received) or config.botocore.empty_poll_enabled or not func_run or func_run_err: + with pin.tracer.start_span( + trace_operation, + service=schematize_service_name("{}.{}".format(pin.service, endpoint_name)), + span_type=SpanTypes.HTTP, + child_of=child_of if child_of is not None else pin.tracer.context_provider.active(), + activate=True, + ) as span: + set_patched_api_call_span_tags(span, instance, args, params, endpoint_name, operation) + + # we need this since we may have ran the wrapped operation before starting the span + # we need to ensure the span start time is correct + if start_ns is not None and func_run: + span.start_ns = start_ns + + if args and config.botocore["distributed_tracing"]: + try: + if endpoint_name == "sqs" and operation == "SendMessage": + inject_trace_to_sqs_or_sns_message( + params, + span, + endpoint_service=endpoint_name, + ) + span.name = schematize_cloud_messaging_operation( + trace_operation, + cloud_provider="aws", + cloud_service="sqs", + direction=SpanDirection.OUTBOUND, + ) + if endpoint_name == "sqs" and operation == "SendMessageBatch": + inject_trace_to_sqs_or_sns_batch_message( + params, + span, + endpoint_service=endpoint_name, + ) + span.name = schematize_cloud_messaging_operation( + trace_operation, + cloud_provider="aws", + cloud_service="sqs", + direction=SpanDirection.OUTBOUND, + ) + if endpoint_name == "sqs" and operation == "ReceiveMessage": + span.name = schematize_cloud_messaging_operation( + trace_operation, + cloud_provider="aws", + cloud_service="sqs", + direction=SpanDirection.INBOUND, + ) + except Exception: + log.warning("Unable to inject trace context", exc_info=True) + try: + if not func_run: + core.dispatch(f"botocore.{endpoint_name}.{operation}.pre", [params]) + result = original_func(*args, **kwargs) + core.dispatch(f"botocore.{endpoint_name}.{operation}.post", [params, result]) + + set_response_metadata_tags(span, result) + # raise error if it was encountered before the span was started + if func_run_err: + raise func_run_err + return result + except botocore.exceptions.ClientError as e: + # `ClientError.response` contains the result, so we can still grab response metadata + set_response_metadata_tags(span, e.response) + + # If we have a status code, and the status code is not an error, + # then ignore the exception being raised + status_code = span.get_tag(http.STATUS_CODE) + if status_code and not config.botocore.operations[span.resource].is_error_code(int(status_code)): + span._ignore_exception(botocore.exceptions.ClientError) + raise + # return results in the case that we ran the function, but no records were returned and empty + # poll spans are disabled + elif func_run: + if func_run_err: + raise func_run_err + return result diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/services/stepfunctions.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/services/stepfunctions.py new file mode 100644 index 0000000..7aa32a5 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/services/stepfunctions.py @@ -0,0 +1,108 @@ +import json +from typing import Any # noqa:F401 +from typing import Dict # noqa:F401 + +import botocore.exceptions + +from ddtrace import Span # noqa:F401 +from ddtrace import config +from ddtrace.ext import http +from ddtrace.propagation.http import HTTPPropagator + +from ....ext import SpanTypes +from ....internal.logger import get_logger +from ....internal.schema import SpanDirection +from ....internal.schema import schematize_cloud_messaging_operation +from ....internal.schema import schematize_service_name +from ..utils import set_patched_api_call_span_tags +from ..utils import set_response_metadata_tags + + +log = get_logger(__name__) + + +def inject_trace_to_stepfunction_input(params, span): + # type: (Any, Span) -> None + """ + :params: contains the params for the current botocore action + :span: the span which provides the trace context to be propagated + + Inject the trace headers into the StepFunction input if the input is a JSON string + """ + if "input" not in params: + log.warning("Unable to inject context. The StepFunction input had no input.") + return + + if params["input"] is None: + log.warning("Unable to inject context. The StepFunction input was None.") + return + + elif isinstance(params["input"], dict): + if "_datadog" in params["input"]: + log.warning("Input already has trace context.") + return + params["input"]["_datadog"] = {} + HTTPPropagator.inject(span.context, params["input"]["_datadog"]) + return + + elif isinstance(params["input"], str): + try: + input_obj = json.loads(params["input"]) + except ValueError: + log.warning("Input is not a valid JSON string") + return + + if isinstance(input_obj, dict): + input_obj["_datadog"] = {} + HTTPPropagator.inject(span.context, input_obj["_datadog"]) + input_json = json.dumps(input_obj) + + params["input"] = input_json + return + else: + log.warning("Unable to inject context. The StepFunction input was not a dict.") + return + + else: + log.warning("Unable to inject context. The StepFunction input was not a dict or a JSON string.") + + +def patched_stepfunction_api_call(original_func, instance, args, kwargs: Dict, function_vars: Dict): + params = function_vars.get("params") + trace_operation = function_vars.get("trace_operation") + pin = function_vars.get("pin") + endpoint_name = function_vars.get("endpoint_name") + operation = function_vars.get("operation") + + with pin.tracer.trace( + trace_operation, + service=schematize_service_name("{}.{}".format(pin.service, endpoint_name)), + span_type=SpanTypes.HTTP, + ) as span: + set_patched_api_call_span_tags(span, instance, args, params, endpoint_name, operation) + + if args: + if config.botocore["distributed_tracing"]: + try: + if endpoint_name == "states" and operation in {"StartExecution", "StartSyncExecution"}: + inject_trace_to_stepfunction_input(params, span) + span.name = schematize_cloud_messaging_operation( + trace_operation, + cloud_provider="aws", + cloud_service="stepfunctions", + direction=SpanDirection.OUTBOUND, + ) + except Exception: + log.warning("Unable to inject trace context", exc_info=True) + + try: + return original_func(*args, **kwargs) + except botocore.exceptions.ClientError as e: + set_response_metadata_tags(span, e.response) + + # If we have a status code, and the status code is not an error, + # then ignore the exception being raised + status_code = span.get_tag(http.STATUS_CODE) + if status_code and not config.botocore.operations[span.resource].is_error_code(int(status_code)): + span._ignore_exception(botocore.exceptions.ClientError) + raise diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/utils.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/utils.py new file mode 100644 index 0000000..46a5e4b --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/botocore/utils.py @@ -0,0 +1,248 @@ +""" +Trace queries monitoring to aws api done via botocore client +""" +import base64 +import json +from typing import Any # noqa:F401 +from typing import Dict # noqa:F401 +from typing import Optional # noqa:F401 +from typing import Tuple # noqa:F401 + +from ddtrace import Span # noqa:F401 +from ddtrace import config + +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import aws +from ...ext import http +from ...internal.constants import COMPONENT +from ...internal.logger import get_logger +from ...internal.utils.formats import deep_getattr +from ...propagation.http import HTTPPropagator + + +log = get_logger(__name__) + +MAX_EVENTBRIDGE_DETAIL_SIZE = 1 << 18 # 256KB + +LINE_BREAK = "\n" + + +def get_json_from_str(data_str): + # type: (str) -> Tuple[str, Optional[Dict[str, Any]]] + data_obj = json.loads(data_str) + + if data_str.endswith(LINE_BREAK): + return LINE_BREAK, data_obj + return None, data_obj + + +def get_kinesis_data_object(data): + # type: (str) -> Tuple[str, Optional[Dict[str, Any]]] + """ + :data: the data from a kinesis stream + The data from a kinesis stream comes as a string (could be json, base64 encoded, etc.) + We support injecting our trace context in the following three cases: + - json string + - byte encoded json string + - base64 encoded json string + If it's none of these, then we leave the message as it is. + """ + + # check if data is a json string + try: + return get_json_from_str(data) + except Exception: + log.debug("Kinesis data is not a JSON string. Trying Byte encoded JSON string.") + + # check if data is an encoded json string + try: + data_str = data.decode("ascii") + return get_json_from_str(data_str) + except Exception: + log.debug("Kinesis data is not a JSON string encoded. Trying Base64 encoded JSON string.") + + # check if data is a base64 encoded json string + try: + data_str = base64.b64decode(data).decode("ascii") + return get_json_from_str(data_str) + except Exception: + log.debug("Unable to parse payload, unable to inject trace context.") + + return None, None + + +def inject_trace_to_eventbridge_detail(params, span): + # type: (Any, Span) -> None + """ + :params: contains the params for the current botocore action + :span: the span which provides the trace context to be propagated + Inject trace headers into the EventBridge record if the record's Detail object contains a JSON string + Max size per event is 256KB (https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-putevent-size.html) + """ + if "Entries" not in params: + log.warning("Unable to inject context. The Event Bridge event had no Entries.") + return + + for entry in params["Entries"]: + detail = {} + if "Detail" in entry: + try: + detail = json.loads(entry["Detail"]) + except ValueError: + log.warning("Detail is not a valid JSON string") + continue + + detail["_datadog"] = {} + HTTPPropagator.inject(span.context, detail["_datadog"]) + detail_json = json.dumps(detail) + + # check if detail size will exceed max size with headers + detail_size = len(detail_json) + if detail_size >= MAX_EVENTBRIDGE_DETAIL_SIZE: + log.warning("Detail with trace injection (%s) exceeds limit (%s)", detail_size, MAX_EVENTBRIDGE_DETAIL_SIZE) + continue + + entry["Detail"] = detail_json + + +def modify_client_context(client_context_object, trace_headers): + if config.botocore["invoke_with_legacy_context"]: + trace_headers = {"_datadog": trace_headers} + + if "custom" in client_context_object: + client_context_object["custom"].update(trace_headers) + else: + client_context_object["custom"] = trace_headers + + +def inject_trace_to_client_context(params, span): + trace_headers = {} + HTTPPropagator.inject(span.context, trace_headers) + client_context_object = {} + if "ClientContext" in params: + try: + client_context_json = base64.b64decode(params["ClientContext"]).decode("utf-8") + client_context_object = json.loads(client_context_json) + except Exception: + log.warning("malformed client_context=%s", params["ClientContext"], exc_info=True) + return + modify_client_context(client_context_object, trace_headers) + try: + json_context = json.dumps(client_context_object).encode("utf-8") + except Exception: + log.warning("unable to encode modified client context as json: %s", client_context_object, exc_info=True) + return + params["ClientContext"] = base64.b64encode(json_context).decode("utf-8") + + +def set_patched_api_call_span_tags(span, instance, args, params, endpoint_name, operation): + span.set_tag_str(COMPONENT, config.botocore.integration_name) + # set span.kind to the type of request being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + span.set_tag(SPAN_MEASURED_KEY) + + if args: + # DEV: join is the fastest way of concatenating strings that is compatible + # across Python versions (see + # https://stackoverflow.com/questions/1316887/what-is-the-most-efficient-string-concatenation-method-in-python) + span.resource = ".".join((endpoint_name, operation.lower())) + span.set_tag("aws_service", endpoint_name) + + if params and not config.botocore["tag_no_params"]: + aws._add_api_param_span_tags(span, endpoint_name, params) + + else: + span.resource = endpoint_name + + region_name = deep_getattr(instance, "meta.region_name") + + span.set_tag_str("aws.agent", "botocore") + if operation is not None: + span.set_tag_str("aws.operation", operation) + if region_name is not None: + span.set_tag_str("aws.region", region_name) + span.set_tag_str("region", region_name) + + # set analytics sample rate + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.botocore.get_analytics_sample_rate()) + + +def set_response_metadata_tags(span, result): + # type: (Span, Dict[str, Any]) -> None + if not result or not result.get("ResponseMetadata"): + return + response_meta = result["ResponseMetadata"] + + if "HTTPStatusCode" in response_meta: + status_code = response_meta["HTTPStatusCode"] + span.set_tag(http.STATUS_CODE, status_code) + + # Mark this span as an error if requested + if config.botocore.operations[span.resource].is_error_code(int(status_code)): + span.error = 1 + + if "RetryAttempts" in response_meta: + span.set_tag("retry_attempts", response_meta["RetryAttempts"]) + + if "RequestId" in response_meta: + span.set_tag_str("aws.requestid", response_meta["RequestId"]) + + +def extract_DD_context(messages): + ctx = None + if len(messages) >= 1: + message = messages[0] + context_json = extract_trace_context_json(message) + if context_json is not None: + child_of = HTTPPropagator.extract(context_json) + if child_of.trace_id is not None: + ctx = child_of + return ctx + + +def extract_trace_context_json(message): + context_json = None + try: + if message and message.get("Type") == "Notification": + # This is potentially a DSM SNS notification + if ( + "MessageAttributes" in message + and "_datadog" in message["MessageAttributes"] + and message["MessageAttributes"]["_datadog"]["Type"] == "Binary" + ): + context_json = json.loads(base64.b64decode(message["MessageAttributes"]["_datadog"]["Value"]).decode()) + elif ( + "MessageAttributes" in message + and "_datadog" in message["MessageAttributes"] + and "StringValue" in message["MessageAttributes"]["_datadog"] + ): + # The message originated from SQS + context_json = json.loads(message["MessageAttributes"]["_datadog"]["StringValue"]) + elif ( + "MessageAttributes" in message + and "_datadog" in message["MessageAttributes"] + and "BinaryValue" in message["MessageAttributes"]["_datadog"] + ): + # Raw message delivery + context_json = json.loads(message["MessageAttributes"]["_datadog"]["BinaryValue"].decode()) + # this is a kinesis message + elif "Data" in message: + # Raw message delivery + _, data = get_kinesis_data_object(message["Data"]) + if "_datadog" in data: + context_json = data["_datadog"] + + if context_json is None: + # AWS SNS holds attributes within message body + if "Body" in message: + try: + body = json.loads(message["Body"]) + return extract_trace_context_json(body) + except ValueError: + log.debug("Unable to parse AWS message body.") + except Exception: + log.debug("Unable to parse AWS message attributes for Datadog Context.") + return context_json diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/bottle/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/bottle/__init__.py new file mode 100644 index 0000000..0e8f8d4 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/bottle/__init__.py @@ -0,0 +1,47 @@ +""" +The bottle integration traces the Bottle web framework. Add the following +plugin to your app:: + + import bottle + from ddtrace import tracer + from ddtrace.contrib.bottle import TracePlugin + + app = bottle.Bottle() + plugin = TracePlugin(service="my-web-app") + app.install(plugin) + +:ref:`All HTTP tags ` are supported for this integration. + +Configuration +~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.bottle['distributed_tracing'] + + Whether to parse distributed tracing headers from requests received by your bottle app. + + Can also be enabled with the ``DD_BOTTLE_DISTRIBUTED_TRACING`` environment variable. + + Default: ``True`` + + +Example:: + + from ddtrace import config + + # Enable distributed tracing + config.bottle['distributed_tracing'] = True + +""" + +from ...internal.utils.importlib import require_modules + + +required_modules = ["bottle"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .trace import TracePlugin + + __all__ = ["TracePlugin", "patch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/bottle/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/bottle/patch.py new file mode 100644 index 0000000..d22f076 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/bottle/patch.py @@ -0,0 +1,41 @@ +import os + +import bottle + +from ddtrace import config +from ddtrace.vendor import wrapt + +from ...internal.utils.formats import asbool +from .trace import TracePlugin + + +# Configure default configuration +config._add( + "bottle", + dict( + distributed_tracing=asbool(os.getenv("DD_BOTTLE_DISTRIBUTED_TRACING", default=True)), + ), +) + + +def get_version(): + # type: () -> str + return getattr(bottle, "__version__", "") + + +def patch(): + """Patch the bottle.Bottle class""" + if getattr(bottle, "_datadog_patch", False): + return + + bottle._datadog_patch = True + wrapt.wrap_function_wrapper("bottle", "Bottle.__init__", traced_init) + + +def traced_init(wrapped, instance, args, kwargs): + wrapped(*args, **kwargs) + + service = config._get_service(default="bottle") + + plugin = TracePlugin(service=service) + instance.install(plugin) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/bottle/trace.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/bottle/trace.py new file mode 100644 index 0000000..fd41c59 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/bottle/trace.py @@ -0,0 +1,107 @@ +from bottle import HTTPError +from bottle import HTTPResponse +from bottle import request +from bottle import response + +import ddtrace +from ddtrace import config +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema.span_attribute_schema import SpanDirection + +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from ...internal.schema import schematize_url_operation +from ...internal.utils.formats import asbool +from .. import trace_utils + + +class TracePlugin(object): + name = "trace" + api = 2 + + def __init__(self, service="bottle", tracer=None, distributed_tracing=None): + self.service = config.service or service + self.tracer = tracer or ddtrace.tracer + if distributed_tracing is not None: + config.bottle.distributed_tracing = distributed_tracing + + @property + def distributed_tracing(self): + return config.bottle.distributed_tracing + + @distributed_tracing.setter + def distributed_tracing(self, distributed_tracing): + config.bottle["distributed_tracing"] = asbool(distributed_tracing) + + def apply(self, callback, route): + def wrapped(*args, **kwargs): + if not self.tracer or not self.tracer.enabled: + return callback(*args, **kwargs) + + resource = "{} {}".format(request.method, route.rule) + + trace_utils.activate_distributed_headers( + self.tracer, int_config=config.bottle, request_headers=request.headers + ) + + with self.tracer.trace( + schematize_url_operation("bottle.request", protocol="http", direction=SpanDirection.INBOUND), + service=self.service, + resource=resource, + span_type=SpanTypes.WEB, + ) as s: + s.set_tag_str(COMPONENT, config.bottle.integration_name) + + # set span.kind to the type of request being performed + s.set_tag_str(SPAN_KIND, SpanKind.SERVER) + + s.set_tag(SPAN_MEASURED_KEY) + # set analytics sample rate with global config enabled + s.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.bottle.get_analytics_sample_rate(use_global_config=True)) + + code = None + result = None + try: + result = callback(*args, **kwargs) + return result + except (HTTPError, HTTPResponse) as e: + # you can interrupt flows using abort(status_code, 'message')... + # we need to respect the defined status_code. + # we also need to handle when response is raised as is the + # case with a 4xx status + code = e.status_code + raise + except Exception: + # bottle doesn't always translate unhandled exceptions, so + # we mark it here. + code = 500 + raise + finally: + if isinstance(result, HTTPResponse): + response_code = result.status_code + elif code: + response_code = code + else: + # bottle local response has not yet been updated so this + # will be default + response_code = response.status_code + + method = request.method + url = request.urlparts._replace(query="").geturl() + full_route = "/".join([request.script_name.rstrip("/"), route.rule.lstrip("/")]) + trace_utils.set_http_meta( + s, + config.bottle, + method=method, + url=url, + status_code=response_code, + query=request.query_string, + request_headers=request.headers, + response_headers=response.headers, + route=full_route, + ) + + return wrapped diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/cassandra/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/cassandra/__init__.py new file mode 100644 index 0000000..ea9376c --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/cassandra/__init__.py @@ -0,0 +1,34 @@ +"""Instrument Cassandra to report Cassandra queries. + +``import ddtrace.auto`` will automatically patch your Cluster instance to make it work. +:: + + from ddtrace import Pin, patch + from cassandra.cluster import Cluster + + # If not patched yet, you can patch cassandra specifically + patch(cassandra=True) + + # This will report spans with the default instrumentation + cluster = Cluster(contact_points=["127.0.0.1"], port=9042) + session = cluster.connect("my_keyspace") + # Example of instrumented query + session.execute("select id from my_table limit 10;") + + # Use a pin to specify metadata related to this cluster + cluster = Cluster(contact_points=['10.1.1.3', '10.1.1.4', '10.1.1.5'], port=9042) + Pin.override(cluster, service='cassandra-backend') + session = cluster.connect("my_keyspace") + session.execute("select id from my_table limit 10;") +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["cassandra.cluster"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import patch + from .session import get_version + + __all__ = ["patch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/cassandra/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/cassandra/patch.py new file mode 100644 index 0000000..d82e620 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/cassandra/patch.py @@ -0,0 +1,5 @@ +from .session import patch +from .session import unpatch + + +__all__ = ["patch", "unpatch"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/cassandra/session.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/cassandra/session.py new file mode 100644 index 0000000..c8f821c --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/cassandra/session.py @@ -0,0 +1,294 @@ +""" +Trace queries along a session to a cassandra cluster +""" +import sys + +from cassandra import __version__ + + +try: + import cassandra.cluster as cassandra_cluster +except AttributeError: + from cassandra import cluster as cassandra_cluster + +from ddtrace import config +from ddtrace.internal.constants import COMPONENT + +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import ERROR_MSG +from ...constants import ERROR_TYPE +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from ...ext import cassandra as cassx +from ...ext import db +from ...ext import net +from ...internal.compat import maybe_stringify +from ...internal.logger import get_logger +from ...internal.schema import schematize_database_operation +from ...internal.schema import schematize_service_name +from ...internal.utils import get_argument_value +from ...internal.utils.formats import deep_getattr +from ...pin import Pin +from ...vendor import wrapt + + +log = get_logger(__name__) + +RESOURCE_MAX_LENGTH = 5000 +SERVICE = schematize_service_name("cassandra") +CURRENT_SPAN = "_ddtrace_current_span" +PAGE_NUMBER = "_ddtrace_page_number" + + +# Original connect connect function +_connect = cassandra_cluster.Cluster.connect + + +def get_version(): + # type: () -> str + return __version__ + + +def patch(): + """patch will add tracing to the cassandra library.""" + cassandra_cluster.Cluster.connect = wrapt.FunctionWrapper(_connect, traced_connect) + Pin(service=SERVICE).onto(cassandra_cluster.Cluster) + + +def unpatch(): + cassandra_cluster.Cluster.connect = _connect + + +def traced_connect(func, instance, args, kwargs): + session = func(*args, **kwargs) + if not isinstance(session.execute, wrapt.FunctionWrapper): + # FIXME[matt] this should probably be private. + session.execute_async = wrapt.FunctionWrapper(session.execute_async, traced_execute_async) + return session + + +def _close_span_on_success(result, future): + span = getattr(future, CURRENT_SPAN, None) + if not span: + log.debug("traced_set_final_result was not able to get the current span from the ResponseFuture") + return + try: + span.set_tags(_extract_result_metas(cassandra_cluster.ResultSet(future, result))) + except Exception: + log.debug("an exception occurred while setting tags", exc_info=True) + finally: + span.finish() + delattr(future, CURRENT_SPAN) + + +def traced_set_final_result(func, instance, args, kwargs): + result = get_argument_value(args, kwargs, 0, "response") + _close_span_on_success(result, instance) + return func(*args, **kwargs) + + +def _close_span_on_error(exc, future): + span = getattr(future, CURRENT_SPAN, None) + if not span: + log.debug("traced_set_final_exception was not able to get the current span from the ResponseFuture") + return + try: + # handling the exception manually because we + # don't have an ongoing exception here + span.error = 1 + span.set_tag_str(ERROR_MSG, exc.args[0]) + span.set_tag_str(ERROR_TYPE, exc.__class__.__name__) + except Exception: + log.debug("traced_set_final_exception was not able to set the error, failed with error", exc_info=True) + finally: + span.finish() + delattr(future, CURRENT_SPAN) + + +def traced_set_final_exception(func, instance, args, kwargs): + exc = get_argument_value(args, kwargs, 0, "response") + _close_span_on_error(exc, instance) + return func(*args, **kwargs) + + +def traced_start_fetching_next_page(func, instance, args, kwargs): + has_more_pages = getattr(instance, "has_more_pages", True) + if not has_more_pages: + return func(*args, **kwargs) + session = getattr(instance, "session", None) + cluster = getattr(session, "cluster", None) + pin = Pin.get_from(cluster) + if not pin or not pin.enabled(): + return func(*args, **kwargs) + + # In case the current span is not finished we make sure to finish it + old_span = getattr(instance, CURRENT_SPAN, None) + if old_span: + log.debug("previous span was not finished before fetching next page") + old_span.finish() + + query = getattr(instance, "query", None) + + span = _start_span_and_set_tags(pin, query, session, cluster) + + page_number = getattr(instance, PAGE_NUMBER, 1) + 1 + setattr(instance, PAGE_NUMBER, page_number) + setattr(instance, CURRENT_SPAN, span) + try: + return func(*args, **kwargs) + except Exception: + with span: + span.set_exc_info(*sys.exc_info()) + raise + + +def traced_execute_async(func, instance, args, kwargs): + cluster = getattr(instance, "cluster", None) + pin = Pin.get_from(cluster) + if not pin or not pin.enabled(): + return func(*args, **kwargs) + + query = get_argument_value(args, kwargs, 0, "query") + + span = _start_span_and_set_tags(pin, query, instance, cluster) + + try: + result = func(*args, **kwargs) + setattr(result, CURRENT_SPAN, span) + setattr(result, PAGE_NUMBER, 1) + result._set_final_result = wrapt.FunctionWrapper(result._set_final_result, traced_set_final_result) + result._set_final_exception = wrapt.FunctionWrapper(result._set_final_exception, traced_set_final_exception) + result.start_fetching_next_page = wrapt.FunctionWrapper( + result.start_fetching_next_page, traced_start_fetching_next_page + ) + + # Since we cannot be sure that the previous methods were overwritten + # before the call ended, we add callbacks that will be run + # synchronously if the call already returned and we remove them right + # after. + result.add_callbacks( + _close_span_on_success, _close_span_on_error, callback_args=(result,), errback_args=(result,) + ) + result.clear_callbacks() + return result + except Exception: + with span: + span.set_exc_info(*sys.exc_info()) + raise + + +def _start_span_and_set_tags(pin, query, session, cluster): + service = pin.service + tracer = pin.tracer + span_name = schematize_database_operation("cassandra.query", database_provider="cassandra") + span = tracer.trace(span_name, service=service, span_type=SpanTypes.CASSANDRA) + + span.set_tag_str(COMPONENT, config.cassandra.integration_name) + span.set_tag_str(db.SYSTEM, "cassandra") + + # set span.kind to the type of request being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + span.set_tag(SPAN_MEASURED_KEY) + _sanitize_query(span, query) + span.set_tags(_extract_session_metas(session)) # FIXME[matt] do once? + span.set_tags(_extract_cluster_metas(cluster)) + # set analytics sample rate if enabled + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.cassandra.get_analytics_sample_rate()) + return span + + +def _extract_session_metas(session): + metas = {} + + if getattr(session, "keyspace", None): + # FIXME the keyspace can be overridden explicitly in the query itself + # e.g. 'select * from trace.hash_to_resource' + metas[cassx.KEYSPACE] = session.keyspace.lower() + + return metas + + +def _extract_cluster_metas(cluster): + metas = {} + if deep_getattr(cluster, "metadata.cluster_name"): + metas[cassx.CLUSTER] = cluster.metadata.cluster_name + if getattr(cluster, "port", None): + metas[net.TARGET_PORT] = cluster.port + + return metas + + +def _extract_result_metas(result): + metas = {} + if result is None: + return metas + + future = getattr(result, "response_future", None) + + if future: + # get the host + host = maybe_stringify(getattr(future, "coordinator_host", None)) + if host: + host, _, port = host.partition(":") + metas[net.TARGET_HOST] = host + if port: + metas[net.TARGET_PORT] = int(port) + elif hasattr(future, "_current_host"): + address = deep_getattr(future, "_current_host.address") + if address: + metas[net.TARGET_HOST] = address + + query = getattr(future, "query", None) + if getattr(query, "consistency_level", None): + metas[cassx.CONSISTENCY_LEVEL] = query.consistency_level + if getattr(query, "keyspace", None): + metas[cassx.KEYSPACE] = query.keyspace.lower() + + page_number = getattr(future, PAGE_NUMBER, 1) + has_more_pages = future.has_more_pages + is_paginated = has_more_pages or page_number > 1 + metas[cassx.PAGINATED] = is_paginated + if is_paginated: + metas[cassx.PAGE_NUMBER] = page_number + + if hasattr(result, "current_rows"): + result_rows = result.current_rows or [] + metas[db.ROWCOUNT] = len(result_rows) + + return metas + + +def _sanitize_query(span, query): + # TODO (aaditya): fix this hacky type check. we need it to avoid circular imports + t = type(query).__name__ + + resource = None + if t in ("SimpleStatement", "PreparedStatement"): + # reset query if a string is available + resource = getattr(query, "query_string", query) + elif t == "BatchStatement": + resource = "BatchStatement" + # Each element in `_statements_and_parameters` is: + # (is_prepared, statement, parameters) + # ref:https://github.com/datastax/python-driver/blob/13d6d72be74f40fcef5ec0f2b3e98538b3b87459/cassandra/query.py#L844 + # + # For prepared statements, the `statement` value is just the query_id + # which is not a statement and when trying to join with other strings + # raises an error in python3 around joining bytes to unicode, so this + # just filters out prepared statements from this tag value + q = "; ".join(q[1] for q in query._statements_and_parameters[:2] if not q[0]) + span.set_tag_str("cassandra.query", q) + span.set_metric("cassandra.batch_size", len(query._statements_and_parameters)) + elif t == "BoundStatement": + ps = getattr(query, "prepared_statement", None) + if ps: + resource = getattr(ps, "query_string", None) + elif t == "str": + resource = query + else: + resource = "unknown-query-type" # FIXME[matt] what else do to here? + + span.resource = str(resource)[:RESOURCE_MAX_LENGTH] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/celery/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/celery/__init__.py new file mode 100644 index 0000000..a17b483 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/celery/__init__.py @@ -0,0 +1,58 @@ +""" +The Celery integration will trace all tasks that are executed in the +background. Functions and class based tasks are traced only if the Celery API +is used, so calling the function directly or via the ``run()`` method will not +generate traces. However, calling ``apply()``, ``apply_async()`` and ``delay()`` +will produce tracing data. To trace your Celery application, call the patch method:: + + import celery + from ddtrace import patch + + patch(celery=True) + app = celery.Celery() + + @app.task + def my_task(): + pass + + class MyTask(app.Task): + def run(self): + pass + +Configuration +~~~~~~~~~~~~~ +.. py:data:: ddtrace.config.celery['distributed_tracing'] + + Whether or not to pass distributed tracing headers to Celery workers. + + Can also be enabled with the ``DD_CELERY_DISTRIBUTED_TRACING`` environment variable. + + Default: ``False`` + +.. py:data:: ddtrace.config.celery['producer_service_name'] + + Sets service name for producer + + Default: ``'celery-producer'`` + +.. py:data:: ddtrace.config.celery['worker_service_name'] + + Sets service name for worker + + Default: ``'celery-worker'`` + +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["celery"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .app import patch_app + from .app import unpatch_app + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "patch_app", "unpatch", "unpatch_app", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/celery/app.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/celery/app.py new file mode 100644 index 0000000..f132ee4 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/celery/app.py @@ -0,0 +1,99 @@ +import celery +from celery import signals + +from ddtrace import Pin +from ddtrace import config +from ddtrace.pin import _DD_PIN_NAME + +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from .. import trace_utils +from .signals import trace_after_publish +from .signals import trace_before_publish +from .signals import trace_failure +from .signals import trace_postrun +from .signals import trace_prerun +from .signals import trace_retry + + +def patch_app(app, pin=None): + """Attach the Pin class to the application and connect + our handlers to Celery signals. + """ + if getattr(app, "__datadog_patch", False): + return + app.__datadog_patch = True + + # attach the PIN object + pin = pin or Pin( + service=config.celery["worker_service_name"], + _config=config.celery, + ) + pin.onto(app) + + trace_utils.wrap( + "celery.beat", + "Scheduler.apply_entry", + _traced_beat_function(config.celery, "apply_entry", lambda args: args[0].name), + ) + trace_utils.wrap("celery.beat", "Scheduler.tick", _traced_beat_function(config.celery, "tick")) + pin.onto(celery.beat.Scheduler) + + # connect to the Signal framework + signals.task_prerun.connect(trace_prerun, weak=False) + signals.task_postrun.connect(trace_postrun, weak=False) + signals.before_task_publish.connect(trace_before_publish, weak=False) + signals.after_task_publish.connect(trace_after_publish, weak=False) + signals.task_failure.connect(trace_failure, weak=False) + signals.task_retry.connect(trace_retry, weak=False) + return app + + +def unpatch_app(app): + """Remove the Pin instance from the application and disconnect + our handlers from Celery signal framework. + """ + if not getattr(app, "__datadog_patch", False): + return + app.__datadog_patch = False + + pin = Pin.get_from(app) + if pin is not None: + delattr(app, _DD_PIN_NAME) + + trace_utils.unwrap(celery.beat.Scheduler, "apply_entry") + trace_utils.unwrap(celery.beat.Scheduler, "tick") + + signals.task_prerun.disconnect(trace_prerun) + signals.task_postrun.disconnect(trace_postrun) + signals.before_task_publish.disconnect(trace_before_publish) + signals.after_task_publish.disconnect(trace_after_publish) + signals.task_failure.disconnect(trace_failure) + signals.task_retry.disconnect(trace_retry) + + +def _traced_beat_function(integration_config, fn_name, resource_fn=None): + def _traced_beat_inner(func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return func(*args, **kwargs) + + with pin.tracer.trace( + "celery.beat.{}".format(fn_name), + span_type=SpanTypes.WORKER, + service=trace_utils.ext_service(pin, integration_config), + ) as span: + if resource_fn: + span.resource = resource_fn(args) + span.set_tag_str(SPAN_KIND, SpanKind.PRODUCER) + rate = config.celery.get_analytics_sample_rate() + if rate is not None: + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, rate) + span.set_tag(SPAN_MEASURED_KEY) + + return func(*args, **kwargs) + + return _traced_beat_inner diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/celery/constants.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/celery/constants.py new file mode 100644 index 0000000..39923a0 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/celery/constants.py @@ -0,0 +1,20 @@ +from ddtrace import config + + +# Celery Context key +CTX_KEY = "__dd_task_span" + +# Span names +PRODUCER_ROOT_SPAN = "celery.apply" +WORKER_ROOT_SPAN = "celery.run" + +# Task operations +TASK_TAG_KEY = "celery.action" +TASK_APPLY = "apply" +TASK_APPLY_ASYNC = "apply_async" +TASK_RUN = "run" +TASK_RETRY_REASON_KEY = "celery.retry.reason" + +# Service info +PRODUCER_SERVICE = config._get_service(default="celery-producer") +WORKER_SERVICE = config._get_service(default="celery-worker") diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/celery/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/celery/patch.py new file mode 100644 index 0000000..f604e53 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/celery/patch.py @@ -0,0 +1,41 @@ +import os + +import celery + +from ddtrace import config +from ddtrace.internal.utils.formats import asbool + +from .app import patch_app +from .app import unpatch_app +from .constants import PRODUCER_SERVICE +from .constants import WORKER_SERVICE + + +# Celery default settings +config._add( + "celery", + { + "distributed_tracing": asbool(os.getenv("DD_CELERY_DISTRIBUTED_TRACING", default=False)), + "producer_service_name": os.getenv("DD_CELERY_PRODUCER_SERVICE_NAME", default=PRODUCER_SERVICE), + "worker_service_name": os.getenv("DD_CELERY_WORKER_SERVICE_NAME", default=WORKER_SERVICE), + }, +) + + +def get_version(): + # type: () -> str + return str(celery.__version__) + + +def patch(): + """Instrument Celery base application and the `TaskRegistry` so + that any new registered task is automatically instrumented. In the + case of Django-Celery integration, also the `@shared_task` decorator + must be instrumented because Django doesn't use the Celery registry. + """ + patch_app(celery.Celery) + + +def unpatch(): + """Disconnect all signals and remove Tracing capabilities""" + unpatch_app(celery.Celery) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/celery/signals.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/celery/signals.py new file mode 100644 index 0000000..e16f5ec --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/celery/signals.py @@ -0,0 +1,225 @@ +from celery import registry +from celery.utils import nodenames + +from ddtrace import Pin +from ddtrace import config +from ddtrace.internal.constants import COMPONENT + +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from ...ext import net +from ...internal.logger import get_logger +from ...propagation.http import HTTPPropagator +from .. import trace_utils +from . import constants as c +from .utils import attach_span +from .utils import detach_span +from .utils import retrieve_span +from .utils import retrieve_task_id +from .utils import set_tags_from_context + + +log = get_logger(__name__) +propagator = HTTPPropagator + + +def trace_prerun(*args, **kwargs): + # safe-guard to avoid crashes in case the signals API + # changes in Celery + task = kwargs.get("sender") + task_id = kwargs.get("task_id") + log.debug("prerun signal start task_id=%s", task_id) + if task is None or task_id is None: + log.debug("unable to extract the Task and the task_id. This version of Celery may not be supported.") + return + + # retrieve the task Pin or fallback to the global one + pin = Pin.get_from(task) or Pin.get_from(task.app) + if pin is None: + log.debug("no pin found on task or task.app task_id=%s", task_id) + return + + request_headers = task.request.get("headers", {}) + trace_utils.activate_distributed_headers(pin.tracer, int_config=config.celery, request_headers=request_headers) + + # propagate the `Span` in the current task Context + service = config.celery["worker_service_name"] + span = pin.tracer.trace(c.WORKER_ROOT_SPAN, service=service, resource=task.name, span_type=SpanTypes.WORKER) + + # set span.kind to the type of request being performed + span.set_tag_str(SPAN_KIND, SpanKind.CONSUMER) + + # set component tag equal to name of integration + span.set_tag_str(COMPONENT, config.celery.integration_name) + + # set analytics sample rate + rate = config.celery.get_analytics_sample_rate() + if rate is not None: + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, rate) + + span.set_tag(SPAN_MEASURED_KEY) + attach_span(task, task_id, span) + + +def trace_postrun(*args, **kwargs): + # safe-guard to avoid crashes in case the signals API + # changes in Celery + task = kwargs.get("sender") + task_id = kwargs.get("task_id") + log.debug("postrun signal task_id=%s", task_id) + if task is None or task_id is None: + log.debug("unable to extract the Task and the task_id. This version of Celery may not be supported.") + return + + # retrieve and finish the Span + span = retrieve_span(task, task_id) + if span is None: + log.warning("no existing span found for task_id=%s", task_id) + return + else: + # request context tags + span.set_tag_str(c.TASK_TAG_KEY, c.TASK_RUN) + set_tags_from_context(span, kwargs) + set_tags_from_context(span, task.request.__dict__) + span.finish() + detach_span(task, task_id) + + +def trace_before_publish(*args, **kwargs): + # `before_task_publish` signal doesn't propagate the task instance so + # we need to retrieve it from the Celery Registry to access the `Pin`. The + # `Task` instance **does not** include any information about the current + # execution, so it **must not** be used to retrieve `request` data. + task_name = kwargs.get("sender") + task = registry.tasks.get(task_name) + task_id = retrieve_task_id(kwargs) + # safe-guard to avoid crashes in case the signals API + # changes in Celery + if task is None or task_id is None: + log.debug("unable to extract the Task and the task_id. This version of Celery may not be supported.") + return + + # propagate the `Span` in the current task Context + pin = Pin.get_from(task) or Pin.get_from(task.app) + if pin is None: + return + + # apply some tags here because most of the data is not available + # in the task_after_publish signal + service = config.celery["producer_service_name"] + span = pin.tracer.trace(c.PRODUCER_ROOT_SPAN, service=service, resource=task_name) + + span.set_tag_str(COMPONENT, config.celery.integration_name) + + # set span.kind to the type of request being performed + span.set_tag_str(SPAN_KIND, SpanKind.PRODUCER) + + # set analytics sample rate + rate = config.celery.get_analytics_sample_rate() + if rate is not None: + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, rate) + + span.set_tag(SPAN_MEASURED_KEY) + span.set_tag_str(c.TASK_TAG_KEY, c.TASK_APPLY_ASYNC) + span.set_tag_str("celery.id", task_id) + set_tags_from_context(span, kwargs) + if kwargs.get("headers") is not None: + # required to extract hostname from origin header on `celery>=4.0` + set_tags_from_context(span, kwargs["headers"]) + + # Note: adding tags from `traceback` or `state` calls will make an + # API call to the backend for the properties so we should rely + # only on the given `Context` + attach_span(task, task_id, span, is_publish=True) + + if config.celery["distributed_tracing"]: + trace_headers = {} + propagator.inject(span.context, trace_headers) + + # put distributed trace headers where celery will propagate them + task_headers = kwargs.get("headers") or {} + task_headers.setdefault("headers", {}) + task_headers["headers"].update(trace_headers) + kwargs["headers"] = task_headers + + +def trace_after_publish(*args, **kwargs): + task_name = kwargs.get("sender") + task = registry.tasks.get(task_name) + task_id = retrieve_task_id(kwargs) + # safe-guard to avoid crashes in case the signals API + # changes in Celery + if task is None or task_id is None: + log.debug("unable to extract the Task and the task_id. This version of Celery may not be supported.") + return + + # retrieve and finish the Span + span = retrieve_span(task, task_id, is_publish=True) + if span is None: + return + else: + nodename = span.get_tag("celery.hostname") + if nodename is not None: + _, host = nodenames.nodesplit(nodename) + span.set_tag_str(net.TARGET_HOST, host) + + span.finish() + detach_span(task, task_id, is_publish=True) + + +def trace_failure(*args, **kwargs): + # safe-guard to avoid crashes in case the signals API + # changes in Celery + task = kwargs.get("sender") + task_id = kwargs.get("task_id") + if task is None or task_id is None: + log.debug("unable to extract the Task and the task_id. This version of Celery may not be supported.") + return + + # retrieve and finish the Span + span = retrieve_span(task, task_id) + if span is None: + return + else: + # add Exception tags; post signals are still called + # so we don't need to attach other tags here + ex = kwargs.get("einfo") + if ex is None: + return + + if hasattr(task, "throws"): + original_exception = ex.exception + if hasattr(original_exception, "exc"): + # Python 3.11+ support: The original exception is wrapped in an `exc` attribute + original_exception = original_exception.exc + + if isinstance(original_exception, task.throws): + return + + span.set_exc_info(ex.type, ex.exception, ex.tb) + + +def trace_retry(*args, **kwargs): + # safe-guard to avoid crashes in case the signals API + # changes in Celery + task = kwargs.get("sender") + context = kwargs.get("request") + if task is None or context is None: + log.debug("unable to extract the Task or the Context. This version of Celery may not be supported.") + return + + reason = kwargs.get("reason") + if not reason: + log.debug("unable to extract the retry reason. This version of Celery may not be supported.") + return + + span = retrieve_span(task, context.id) + if span is None: + return + + # Add retry reason metadata to span + # DEV: Use `str(reason)` instead of `reason.message` in case we get something that isn't an `Exception` + span.set_tag_str(c.TASK_RETRY_REASON_KEY, str(reason)) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/celery/utils.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/celery/utils.py new file mode 100644 index 0000000..0893c04 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/celery/utils.py @@ -0,0 +1,136 @@ +from typing import Any +from typing import Dict +from weakref import WeakValueDictionary + +from ddtrace.contrib.trace_utils import set_flattened_tags +from ddtrace.span import Span + +from .constants import CTX_KEY + + +TAG_KEYS = frozenset( + [ + ("compression", "celery.compression"), + ("correlation_id", "celery.correlation_id"), + ("countdown", "celery.countdown"), + ("delivery_info", "celery.delivery_info"), + ("eta", "celery.eta"), + ("exchange", "celery.exchange"), + ("expires", "celery.expires"), + ("hostname", "celery.hostname"), + ("id", "celery.id"), + ("priority", "celery.priority"), + ("queue", "celery.queue"), + ("reply_to", "celery.reply_to"), + ("retries", "celery.retries"), + ("routing_key", "celery.routing_key"), + ("serializer", "celery.serializer"), + ("timelimit", "celery.timelimit"), + # Celery 4.0 uses `origin` instead of `hostname`; this change preserves + # the same name for the tag despite Celery version + ("origin", "celery.hostname"), + ("state", "celery.state"), + ] +) + + +def should_skip_context_value(key, value): + # type: (str, Any) -> bool + # Skip this key if it is not set + if value is None or value == "": + return True + + # Skip `timelimit` if it is not set (its default/unset value is a + # tuple or a list of `None` values + if key == "timelimit" and all(_ is None for _ in value): + return True + + # Skip `retries` if its value is `0` + if key == "retries" and value == 0: + return True + + return False + + +def set_tags_from_context(span: Span, context: Dict[str, Any]) -> None: + """Helper to extract meta values from a Celery Context""" + + context_tags = [] + for key, tag_name in TAG_KEYS: + value = context.get(key) + if should_skip_context_value(key, value): + continue + + context_tags.append((tag_name, value)) + + set_flattened_tags(span, context_tags) + + +def attach_span(task, task_id, span, is_publish=False): + """Helper to propagate a `Span` for the given `Task` instance. This + function uses a `WeakValueDictionary` that stores a Datadog Span using + the `(task_id, is_publish)` as a key. This is useful when information must be + propagated from one Celery signal to another. + + DEV: We use (task_id, is_publish) for the key to ensure that publishing a + task from within another task does not cause any conflicts. + + This mostly happens when either a task fails and a retry policy is in place, + or when a task is manually retried (e.g. `task.retry()`), we end up trying + to publish a task with the same id as the task currently running. + + Previously publishing the new task would overwrite the existing `celery.run` span + in the `weak_dict` causing that span to be forgotten and never finished. + + NOTE: We cannot test for this well yet, because we do not run a celery worker, + and cannot run `task.apply_async()` + """ + weak_dict = getattr(task, CTX_KEY, None) + if weak_dict is None: + weak_dict = WeakValueDictionary() + setattr(task, CTX_KEY, weak_dict) + + weak_dict[(task_id, is_publish)] = span + + +def detach_span(task, task_id, is_publish=False): + """Helper to remove a `Span` in a Celery task when it's propagated. + This function handles tasks where the `Span` is not attached. + """ + weak_dict = getattr(task, CTX_KEY, None) + if weak_dict is None: + return + + # DEV: See note in `attach_span` for key info + try: + del weak_dict[(task_id, is_publish)] + except KeyError: + pass + + +def retrieve_span(task, task_id, is_publish=False): + """Helper to retrieve an active `Span` stored in a `Task` + instance + """ + weak_dict = getattr(task, CTX_KEY, None) + if weak_dict is None: + return + else: + # DEV: See note in `attach_span` for key info + return weak_dict.get((task_id, is_publish)) + + +def retrieve_task_id(context): + """Helper to retrieve the `Task` identifier from the message `body`. + This helper supports Protocol Version 1 and 2. The Protocol is well + detailed in the official documentation: + http://docs.celeryproject.org/en/latest/internals/protocol.html + """ + headers = context.get("headers") + body = context.get("body") + if headers: + # Protocol Version 2 (default from Celery 4.0) + return headers.get("id") + else: + # Protocol Version 1 + return body.get("id") diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/cherrypy/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/cherrypy/__init__.py new file mode 100644 index 0000000..67c1e59 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/cherrypy/__init__.py @@ -0,0 +1,66 @@ +""" +The Cherrypy trace middleware will track request timings. +It uses the cherrypy hooks and creates a tool to track requests and errors + + +Usage +~~~~~ +To install the middleware, add:: + + from ddtrace import tracer + from ddtrace.contrib.cherrypy import TraceMiddleware + +and create a `TraceMiddleware` object:: + + traced_app = TraceMiddleware(cherrypy, tracer, service="my-cherrypy-app") + + +Configuration +~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.cherrypy['distributed_tracing'] + + Whether to parse distributed tracing headers from requests received by your CherryPy app. + + Can also be enabled with the ``DD_CHERRYPY_DISTRIBUTED_TRACING`` environment variable. + + Default: ``True`` + +.. py:data:: ddtrace.config.cherrypy['service'] + + The service name reported for your CherryPy app. + + Can also be configured via the ``DD_SERVICE`` environment variable. + + Default: ``'cherrypy'`` + + +Example:: +Here is the end result, in a sample app:: + + import cherrypy + + from ddtrace import tracer, Pin + from ddtrace.contrib.cherrypy import TraceMiddleware + TraceMiddleware(cherrypy, tracer, service="my-cherrypy-app") + + @cherrypy.tools.tracer() + class HelloWorld(object): + def index(self): + return "Hello World" + index.exposed = True + + cherrypy.quickstart(HelloWorld()) +""" + +from ...internal.utils.importlib import require_modules + + +required_modules = ["cherrypy"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .middleware import TraceMiddleware + from .middleware import get_version + + __all__ = ["TraceMiddleware", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/cherrypy/middleware.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/cherrypy/middleware.py new file mode 100644 index 0000000..6e64fbf --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/cherrypy/middleware.py @@ -0,0 +1,159 @@ +""" +Datadog trace code for cherrypy. +""" +import logging +import os + +import cherrypy +from cherrypy.lib.httputil import valid_status + +from ddtrace import config +from ddtrace.constants import ERROR_MSG +from ddtrace.constants import ERROR_STACK +from ddtrace.constants import ERROR_TYPE +from ddtrace.constants import SPAN_KIND +from ddtrace.internal.constants import COMPONENT + +from ...ext import SpanKind +from ...ext import SpanTypes +from ...internal import compat +from ...internal.schema import SpanDirection +from ...internal.schema import schematize_service_name +from ...internal.schema import schematize_url_operation +from ...internal.utils.formats import asbool +from .. import trace_utils + + +log = logging.getLogger(__name__) + + +# Configure default configuration +config._add( + "cherrypy", + dict( + distributed_tracing=asbool(os.getenv("DD_CHERRYPY_DISTRIBUTED_TRACING", default=True)), + ), +) + + +def get_version(): + # type: () -> str + return getattr(cherrypy, "__version__", "") + + +SPAN_NAME = schematize_url_operation("cherrypy.request", protocol="http", direction=SpanDirection.INBOUND) + + +class TraceTool(cherrypy.Tool): + def __init__(self, app, tracer, service, use_distributed_tracing=None): + self.app = app + self._tracer = tracer + self.service = service + if use_distributed_tracing is not None: + self.use_distributed_tracing = use_distributed_tracing + + # CherryPy uses priority to determine which tools act first on each event. The lower the number, the higher + # the priority. See: https://docs.cherrypy.org/en/latest/extend.html#tools-ordering + cherrypy.Tool.__init__(self, "on_start_resource", self._on_start_resource, priority=95) + + @property + def use_distributed_tracing(self): + return config.cherrypy.distributed_tracing + + @use_distributed_tracing.setter + def use_distributed_tracing(self, use_distributed_tracing): + config.cherrypy["distributed_tracing"] = asbool(use_distributed_tracing) + + @property + def service(self): + return config.cherrypy.get("service", "cherrypy") + + @service.setter + def service(self, service): + config.cherrypy["service"] = schematize_service_name(service) + + def _setup(self): + cherrypy.Tool._setup(self) + cherrypy.request.hooks.attach("on_end_request", self._on_end_request, priority=5) + cherrypy.request.hooks.attach("after_error_response", self._after_error_response, priority=5) + + def _on_start_resource(self): + trace_utils.activate_distributed_headers( + self._tracer, int_config=config.cherrypy, request_headers=cherrypy.request.headers + ) + + cherrypy.request._datadog_span = self._tracer.trace( + SPAN_NAME, + service=trace_utils.int_service(None, config.cherrypy, default="cherrypy"), + span_type=SpanTypes.WEB, + ) + + cherrypy.request._datadog_span.set_tag_str(COMPONENT, config.cherrypy.integration_name) + + # set span.kind to the type of request being performed + cherrypy.request._datadog_span.set_tag_str(SPAN_KIND, SpanKind.SERVER) + + def _after_error_response(self): + span = getattr(cherrypy.request, "_datadog_span", None) + + if not span: + log.warning("cherrypy: tracing tool after_error_response hook called, but no active span found") + return + + span.error = 1 + span.set_tag_str(ERROR_TYPE, str(cherrypy._cperror._exc_info()[0])) + span.set_tag_str(ERROR_MSG, str(cherrypy._cperror._exc_info()[1])) + span.set_tag_str(ERROR_STACK, cherrypy._cperror.format_exc()) + + self._close_span(span) + + def _on_end_request(self): + span = getattr(cherrypy.request, "_datadog_span", None) + + if not span: + log.warning("cherrypy: tracing tool on_end_request hook called, but no active span found") + return + + self._close_span(span) + + def _close_span(self, span): + # Let users specify their own resource in middleware if they so desire. + # See case https://github.com/DataDog/dd-trace-py/issues/353 + if span.resource == SPAN_NAME: + # In the future, mask virtual path components in a + # URL e.g. /dispatch/abc123 becomes /dispatch/{{test_value}}/ + # Following investigation, this should be possible using + # [find_handler](https://docs.cherrypy.org/en/latest/_modules/cherrypy/_cpdispatch.html#Dispatcher.find_handler) + # but this may not be as easy as `cherrypy.request.dispatch.find_handler(cherrypy.request.path_info)` as + # this function only ever seems to return an empty list for the virtual path components. + + # For now, default resource is method and path: + # GET / + # POST /save + resource = "{} {}".format(cherrypy.request.method, cherrypy.request.path_info) + span.resource = compat.to_unicode(resource) + + url = compat.to_unicode(cherrypy.request.base + cherrypy.request.path_info) + status_code, _, _ = valid_status(cherrypy.response.status) + + trace_utils.set_http_meta( + span, + config.cherrypy, + method=cherrypy.request.method, + url=url, + status_code=status_code, + request_headers=cherrypy.request.headers, + response_headers=cherrypy.response.headers, + ) + + span.finish() + + # Clear our span just in case. + cherrypy.request._datadog_span = None + + +class TraceMiddleware(object): + def __init__(self, app, tracer, service="cherrypy", distributed_tracing=None): + self.app = app + + self.app.tools.tracer = TraceTool(app, tracer, service, distributed_tracing) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/consul/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/consul/__init__.py new file mode 100644 index 0000000..e56a261 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/consul/__init__.py @@ -0,0 +1,33 @@ +"""Instrument Consul to trace KV queries. + +Only supports tracing for the synchronous client. + +``import ddtrace.auto`` will automatically patch your Consul client to make it work. +:: + + from ddtrace import Pin, patch + import consul + + # If not patched yet, you can patch consul specifically + patch(consul=True) + + # This will report a span with the default settings + client = consul.Consul(host="127.0.0.1", port=8500) + client.get("my-key") + + # Use a pin to specify metadata related to this client + Pin.override(client, service='consul-kv') +""" + +from ...internal.utils.importlib import require_modules + + +required_modules = ["consul"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/consul/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/consul/patch.py new file mode 100644 index 0000000..4187340 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/consul/patch.py @@ -0,0 +1,85 @@ +import consul + +from ddtrace import config +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w + +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from ...ext import consul as consulx +from ...ext import net +from ...internal.schema import schematize_service_name +from ...internal.schema import schematize_url_operation +from ...internal.utils import get_argument_value +from ...internal.utils.wrappers import unwrap as _u +from ...pin import Pin + + +_KV_FUNCS = ["put", "get", "delete"] + + +def get_version(): + # type: () -> str + return getattr(consul, "__version__", "") + + +def patch(): + if getattr(consul, "__datadog_patch", False): + return + consul.__datadog_patch = True + + pin = Pin(service=schematize_service_name(consulx.SERVICE)) + pin.onto(consul.Consul.KV) + + for f_name in _KV_FUNCS: + _w("consul", "Consul.KV.%s" % f_name, wrap_function(f_name)) + + +def unpatch(): + if not getattr(consul, "__datadog_patch", False): + return + consul.__datadog_patch = False + + for f_name in _KV_FUNCS: + _u(consul.Consul.KV, f_name) + + +def wrap_function(name): + def trace_func(wrapped, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return wrapped(*args, **kwargs) + + # Only patch the synchronous implementation + if not isinstance(instance.agent.http, consul.std.HTTPClient): + return wrapped(*args, **kwargs) + + path = get_argument_value(args, kwargs, 0, "key") + resource = name.upper() + + with pin.tracer.trace( + schematize_url_operation(consulx.CMD, protocol="http", direction=SpanDirection.OUTBOUND), + service=pin.service, + resource=resource, + span_type=SpanTypes.HTTP, + ) as span: + span.set_tag_str(COMPONENT, config.consul.integration_name) + + span.set_tag_str(net.TARGET_HOST, instance.agent.http.host) + + # set span.kind to the type of request being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + span.set_tag(SPAN_MEASURED_KEY) + rate = config.consul.get_analytics_sample_rate() + if rate is not None: + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, rate) + span.set_tag_str(consulx.KEY, path) + span.set_tag_str(consulx.CMD, resource) + return wrapped(*args, **kwargs) + + return trace_func diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/coverage/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/coverage/__init__.py new file mode 100644 index 0000000..214c471 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/coverage/__init__.py @@ -0,0 +1,32 @@ +""" +The Coverage.py integration traces test code coverage when using `pytest` or `unittest`. + + +Enabling +~~~~~~~~ + +The Coverage.py integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Alternately, use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(coverage=True) + +Note: Coverage.py instrumentation is only enabled if `pytest` or `unittest` instrumentation is enabled. +""" +from ...internal.logger import get_logger +from ...internal.utils.importlib import require_modules + + +required_modules = ["coverage"] +log = get_logger(__name__) + + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/coverage/constants.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/coverage/constants.py new file mode 100644 index 0000000..b945c4e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/coverage/constants.py @@ -0,0 +1 @@ +PCT_COVERED_KEY = "pct_coverage" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/coverage/data.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/coverage/data.py new file mode 100644 index 0000000..ebeb0e0 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/coverage/data.py @@ -0,0 +1,7 @@ +from copy import copy +import sys + + +_coverage_data = {} + +_original_sys_argv_command = copy(sys.argv) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/coverage/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/coverage/patch.py new file mode 100644 index 0000000..55471eb --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/coverage/patch.py @@ -0,0 +1,62 @@ +from ddtrace.contrib.coverage.constants import PCT_COVERED_KEY +from ddtrace.contrib.coverage.data import _coverage_data +from ddtrace.contrib.coverage.utils import is_coverage_loaded +from ddtrace.internal.logger import get_logger +from ddtrace.internal.utils.wrappers import unwrap as _u +from ddtrace.vendor import wrapt + + +try: + import coverage +except ImportError: + coverage = None # type: ignore[misc,assignment] + + +log = get_logger(__name__) + + +def get_version(): + # type: () -> str + return "" + + +def patch(): + """ + Patch the instrumented methods from Coverage.py + """ + if getattr(coverage, "_datadog_patch", False) or not is_coverage_loaded(): + return + + coverage._datadog_patch = True + + _w = wrapt.wrap_function_wrapper + + _w(coverage, "Coverage.report", report_total_pct_covered_wrapper) + + +def unpatch(): + """ + Undo patched instrumented methods from Coverage.py + """ + if not getattr(coverage, "_datadog_patch", False) or not is_coverage_loaded(): + return + + _u(coverage.Coverage, "report") + + coverage._datadog_patch = False + + +def report_total_pct_covered_wrapper(func, instance, args: tuple, kwargs: dict): + pct_covered = func(*args, **kwargs) + _coverage_data[PCT_COVERED_KEY] = pct_covered + return pct_covered + + +def run_coverage_report(): + if not is_coverage_loaded(): + return + try: + current_coverage_object = coverage.Coverage.current() + _coverage_data[PCT_COVERED_KEY] = current_coverage_object.report() + except Exception: + log.warning("An exception occurred when running a coverage report") diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/coverage/utils.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/coverage/utils.py new file mode 100644 index 0000000..018940f --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/coverage/utils.py @@ -0,0 +1,26 @@ +import os +import sys +from typing import List + +from ddtrace.contrib.coverage.data import _original_sys_argv_command + + +def is_coverage_loaded() -> bool: + return "coverage" in sys.modules + + +def _is_coverage_patched(): + if not is_coverage_loaded(): + return False + + return getattr(sys.modules["coverage"], "_datadog_patch", False) + + +def _command_invokes_coverage_run(sys_argv_command: List[str]) -> bool: + return "coverage run -m" in " ".join(sys_argv_command) + + +def _is_coverage_invoked_by_coverage_run() -> bool: + if os.environ.get("COVERAGE_RUN", False): + return True + return _command_invokes_coverage_run(_original_sys_argv_command) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/dbapi/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/dbapi/__init__.py new file mode 100644 index 0000000..92d5efe --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/dbapi/__init__.py @@ -0,0 +1,344 @@ +""" +Generic dbapi tracing code. +""" +from ddtrace import config +from ddtrace.appsec._iast._utils import _is_iast_enabled +from ddtrace.internal.constants import COMPONENT + +from ...appsec._constants import IAST_SPAN_TAGS +from ...appsec._iast._metrics import increment_iast_span_metric +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from ...ext import db +from ...ext import sql +from ...internal.logger import get_logger +from ...internal.utils import ArgumentError +from ...internal.utils import get_argument_value +from ...pin import Pin +from ...vendor import wrapt +from ..trace_utils import ext_service +from ..trace_utils import iswrapped + + +log = get_logger(__name__) + + +config._add( + "dbapi2", + dict( + _default_service="db", + _dbapi_span_name_prefix="sql", + trace_fetch_methods=None, # Part of the API. Should be implemented at the integration level. + ), +) + + +def get_version(): + # type: () -> str + return "" + + +class TracedCursor(wrapt.ObjectProxy): + """TracedCursor wraps a psql cursor and traces its queries.""" + + def __init__(self, cursor, pin, cfg): + super(TracedCursor, self).__init__(cursor) + pin.onto(self) + # Allow dbapi-based integrations to override default span name prefix + span_name_prefix = ( + cfg["_dbapi_span_name_prefix"] + if cfg and "_dbapi_span_name_prefix" in cfg + else config.dbapi2["_dbapi_span_name_prefix"] + ) + span_name = ( + cfg["_dbapi_span_operation_name"] + if cfg and "_dbapi_span_operation_name" in cfg + else "{}.query".format(span_name_prefix) + ) + self._self_datadog_name = span_name + self._self_last_execute_operation = None + self._self_config = cfg or config.dbapi2 + self._self_dbm_propagator = getattr(self._self_config, "_dbm_propagator", None) + + def __iter__(self): + return self.__wrapped__.__iter__() + + def __next__(self): + return self.__wrapped__.__next__() + + def _trace_method(self, method, name, resource, extra_tags, dbm_propagator, *args, **kwargs): + """ + Internal function to trace the call to the underlying cursor method + :param method: The callable to be wrapped + :param name: The name of the resulting span. + :param resource: The sql query. Sql queries are obfuscated on the agent side. + :param extra_tags: A dict of tags to store into the span's meta + :param dbm_propagator: _DBM_Propagator, prepends dbm comments to sql statements + :param args: The args that will be passed as positional args to the wrapped method + :param kwargs: The args that will be passed as kwargs to the wrapped method + :return: The result of the wrapped method invocation + """ + pin = Pin.get_from(self) + if not pin or not pin.enabled(): + return method(*args, **kwargs) + measured = name == self._self_datadog_name + + with pin.tracer.trace( + name, service=ext_service(pin, self._self_config), resource=resource, span_type=SpanTypes.SQL + ) as s: + if measured: + s.set_tag(SPAN_MEASURED_KEY) + # No reason to tag the query since it is set as the resource by the agent. See: + # https://github.com/DataDog/datadog-trace-agent/blob/bda1ebbf170dd8c5879be993bdd4dbae70d10fda/obfuscate/sql.go#L232 + s.set_tags(pin.tags) + s.set_tags(extra_tags) + + s.set_tag_str(COMPONENT, self._self_config.integration_name) + + # set span.kind to the type of request being performed + s.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + if _is_iast_enabled(): + try: + from ddtrace.appsec._iast._metrics import _set_metric_iast_executed_sink + from ddtrace.appsec._iast._taint_utils import check_tainted_args + from ddtrace.appsec._iast.taint_sinks.sql_injection import SqlInjection + + increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, SqlInjection.vulnerability_type) + _set_metric_iast_executed_sink(SqlInjection.vulnerability_type) + if check_tainted_args(args, kwargs, pin.tracer, self._self_config.integration_name, method): + SqlInjection.report(evidence_value=args[0]) + except Exception: + log.debug("Unexpected exception while reporting vulnerability", exc_info=True) + + # set analytics sample rate if enabled but only for non-FetchTracedCursor + if not isinstance(self, FetchTracedCursor): + s.set_tag(ANALYTICS_SAMPLE_RATE_KEY, self._self_config.get_analytics_sample_rate()) + + if dbm_propagator: + args, kwargs = dbm_propagator.inject(s, args, kwargs) + + try: + return method(*args, **kwargs) + finally: + # Try to fetch custom properties that were passed by the specific Database implementation + self._set_post_execute_tags(s) + + def executemany(self, query, *args, **kwargs): + """Wraps the cursor.executemany method""" + self._self_last_execute_operation = query + # Always return the result as-is + # DEV: Some libraries return `None`, others `int`, and others the cursor objects + # These differences should be overridden at the integration specific layer (e.g. in `sqlite3/patch.py`) + # FIXME[matt] properly handle kwargs here. arg names can be different + # with different libs. + return self._trace_method( + self.__wrapped__.executemany, + self._self_datadog_name, + query, + {"sql.executemany": "true"}, + self._self_dbm_propagator, + query, + *args, + **kwargs, + ) + + def execute(self, query, *args, **kwargs): + """Wraps the cursor.execute method""" + self._self_last_execute_operation = query + + # Always return the result as-is + # DEV: Some libraries return `None`, others `int`, and others the cursor objects + # These differences should be overridden at the integration specific layer (e.g. in `sqlite3/patch.py`) + return self._trace_method( + self.__wrapped__.execute, + self._self_datadog_name, + query, + {}, + self._self_dbm_propagator, + query, + *args, + **kwargs, + ) + + def callproc(self, proc, *args): + """Wraps the cursor.callproc method""" + self._self_last_execute_operation = proc + return self._trace_method(self.__wrapped__.callproc, self._self_datadog_name, proc, {}, None, proc, *args) + + def _set_post_execute_tags(self, span): + # rowcount is in the dbapi specification (https://peps.python.org/pep-0249/#rowcount) + # but some database drivers (cassandra-driver specifically) don't implement it. + row_count = getattr(self.__wrapped__, "rowcount", None) + if row_count is None: + return + span.set_metric(db.ROWCOUNT, row_count) + # Necessary for django integration backward compatibility. Django integration used to provide its own + # implementation of the TracedCursor, which used to store the row count into a tag instead of + # as a metric. Such custom implementation has been replaced by this generic dbapi implementation and + # this tag has been added since. + # Check row count is an integer type to avoid comparison type error + if isinstance(row_count, int) and row_count >= 0: + span.set_tag(db.ROWCOUNT, row_count) + + def __enter__(self): + # previous versions of the dbapi didn't support context managers. let's + # reference the func that would be called to ensure that errors + # messages will be the same. + self.__wrapped__.__enter__ + + # and finally, yield the traced cursor. + return self + + +class FetchTracedCursor(TracedCursor): + """ + Sub-class of :class:`TracedCursor` that also instruments `fetchone`, `fetchall`, and `fetchmany` methods. + + We do not trace these functions by default since they can get very noisy (e.g. `fetchone` with 100k rows). + """ + + def fetchone(self, *args, **kwargs): + """Wraps the cursor.fetchone method""" + span_name = "{}.{}".format(self._self_datadog_name, "fetchone") + return self._trace_method( + self.__wrapped__.fetchone, span_name, self._self_last_execute_operation, {}, None, *args, **kwargs + ) + + def fetchall(self, *args, **kwargs): + """Wraps the cursor.fetchall method""" + span_name = "{}.{}".format(self._self_datadog_name, "fetchall") + return self._trace_method( + self.__wrapped__.fetchall, span_name, self._self_last_execute_operation, {}, None, *args, **kwargs + ) + + def fetchmany(self, *args, **kwargs): + """Wraps the cursor.fetchmany method""" + span_name = "{}.{}".format(self._self_datadog_name, "fetchmany") + # We want to trace the information about how many rows were requested. Note that this number may be larger + # the number of rows actually returned if less then requested are available from the query. + size_tag_key = "db.fetch.size" + + try: + extra_tags = {size_tag_key: get_argument_value(args, kwargs, 0, "size")} + except ArgumentError: + default_array_size = getattr(self.__wrapped__, "arraysize", None) + extra_tags = {size_tag_key: default_array_size} if default_array_size else {} + + return self._trace_method( + self.__wrapped__.fetchmany, span_name, self._self_last_execute_operation, extra_tags, None, *args, **kwargs + ) + + +class TracedConnection(wrapt.ObjectProxy): + """TracedConnection wraps a Connection with tracing code.""" + + def __init__(self, conn, pin=None, cfg=None, cursor_cls=None): + if not cfg: + cfg = config.dbapi2 + # Set default cursor class if one was not provided + if not cursor_cls: + # Do not trace `fetch*` methods by default + cursor_cls = FetchTracedCursor if cfg.trace_fetch_methods else TracedCursor + + super(TracedConnection, self).__init__(conn) + name = _get_vendor(conn) + self._self_datadog_name = "{}.connection".format(name) + db_pin = pin or Pin(service=name) + db_pin.onto(self) + # wrapt requires prefix of `_self` for attributes that are only in the + # proxy (since some of our source objects will use `__slots__`) + self._self_cursor_cls = cursor_cls + self._self_config = cfg + + def __enter__(self): + """Context management is not defined by the dbapi spec. + + This means unfortunately that the database clients each define their own + implementations. + + The ones we know about are: + + - mysqlclient<2.0 which returns a cursor instance. >=2.0 returns a + connection instance. + - psycopg returns a connection. + - pyodbc returns a connection. + - pymysql doesn't implement it. + - sqlite3 returns the connection. + """ + r = self.__wrapped__.__enter__() + + if hasattr(r, "cursor"): + # r is Connection-like. + if r is self.__wrapped__: + # Return the reference to this proxy object. Returning r would + # return the untraced reference. + return self + else: + # r is a different connection object. + # This should not happen in practice but play it safe so that + # the original functionality is maintained. + return r + elif hasattr(r, "execute"): + # r is Cursor-like. + if iswrapped(r): + return r + else: + pin = Pin.get_from(self) + if not pin: + return r + return self._self_cursor_cls(r, pin, self._self_config) + else: + # Otherwise r is some other object, so maintain the functionality + # of the original. + return r + + def _trace_method(self, method, name, extra_tags, *args, **kwargs): + pin = Pin.get_from(self) + if not pin or not pin.enabled(): + return method(*args, **kwargs) + + with pin.tracer.trace(name, service=ext_service(pin, self._self_config)) as s: + s.set_tag_str(COMPONENT, self._self_config.integration_name) + + # set span.kind to the type of request being performed + s.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + s.set_tags(pin.tags) + s.set_tags(extra_tags) + + return method(*args, **kwargs) + + def cursor(self, *args, **kwargs): + cursor = self.__wrapped__.cursor(*args, **kwargs) + pin = Pin.get_from(self) + if not pin: + return cursor + return self._self_cursor_cls(cursor=cursor, pin=pin, cfg=self._self_config) + + def commit(self, *args, **kwargs): + span_name = "{}.{}".format(self._self_datadog_name, "commit") + return self._trace_method(self.__wrapped__.commit, span_name, {}, *args, **kwargs) + + def rollback(self, *args, **kwargs): + span_name = "{}.{}".format(self._self_datadog_name, "rollback") + return self._trace_method(self.__wrapped__.rollback, span_name, {}, *args, **kwargs) + + +def _get_vendor(conn): + """Return the vendor (e.g postgres, mysql) of the given + database. + """ + try: + name = _get_module_name(conn) + except Exception: + log.debug("couldn't parse module name", exc_info=True) + name = "sql" + return sql.normalize_vendor(name) + + +def _get_module_name(conn): + return conn.__class__.__module__.split(".")[0] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/dbapi_async/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/dbapi_async/__init__.py new file mode 100644 index 0000000..af20463 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/dbapi_async/__init__.py @@ -0,0 +1,258 @@ +from ddtrace import config +from ddtrace.appsec._iast._utils import _is_iast_enabled +from ddtrace.internal.constants import COMPONENT + +from ...appsec._constants import IAST_SPAN_TAGS +from ...appsec._iast._metrics import increment_iast_span_metric +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from ...internal.logger import get_logger +from ...internal.utils import ArgumentError +from ...internal.utils import get_argument_value +from ...pin import Pin +from ..dbapi import TracedConnection +from ..dbapi import TracedCursor +from ..trace_utils import ext_service +from ..trace_utils import iswrapped + + +log = get_logger(__name__) + + +def get_version(): + # type: () -> str + return "" + + +class TracedAsyncCursor(TracedCursor): + async def __aenter__(self): + # previous versions of the dbapi didn't support context managers. let's + # reference the func that would be called to ensure that error + # messages will be the same. + await self.__wrapped__.__aenter__() + + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + # previous versions of the dbapi didn't support context managers. let's + # reference the func that would be called to ensure that error + # messages will be the same. + return await self.__wrapped__.__aexit__(exc_type, exc_val, exc_tb) + + async def _trace_method(self, method, name, resource, extra_tags, dbm_propagator, *args, **kwargs): + """ + Internal function to trace the call to the underlying cursor method + :param method: The callable to be wrapped + :param name: The name of the resulting span. + :param resource: The sql query. Sql queries are obfuscated on the agent side. + :param extra_tags: A dict of tags to store into the span's meta + :param dbm_propagator: _DBM_Propagator, prepends dbm comments to sql statements + :param args: The args that will be passed as positional args to the wrapped method + :param kwargs: The args that will be passed as kwargs to the wrapped method + :return: The result of the wrapped method invocation + """ + pin = Pin.get_from(self) + if not pin or not pin.enabled(): + return await method(*args, **kwargs) + measured = name == self._self_datadog_name + + with pin.tracer.trace( + name, service=ext_service(pin, self._self_config), resource=resource, span_type=SpanTypes.SQL + ) as s: + if measured: + s.set_tag(SPAN_MEASURED_KEY) + # No reason to tag the query since it is set as the resource by the agent. See: + # https://github.com/DataDog/datadog-trace-agent/blob/bda1ebbf170dd8c5879be993bdd4dbae70d10fda/obfuscate/sql.go#L232 + s.set_tags(pin.tags) + s.set_tags(extra_tags) + + s.set_tag_str(COMPONENT, self._self_config.integration_name) + + # set span.kind to the type of request being performed + s.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + if _is_iast_enabled(): + from ddtrace.appsec._iast._metrics import _set_metric_iast_executed_sink + from ddtrace.appsec._iast._taint_utils import check_tainted_args + from ddtrace.appsec._iast.taint_sinks.sql_injection import SqlInjection + + increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, SqlInjection.vulnerability_type) + _set_metric_iast_executed_sink(SqlInjection.vulnerability_type) + if check_tainted_args(args, kwargs, pin.tracer, self._self_config.integration_name, method): + SqlInjection.report(evidence_value=args[0]) + + # set analytics sample rate if enabled but only for non-FetchTracedCursor + if not isinstance(self, FetchTracedAsyncCursor): + s.set_tag(ANALYTICS_SAMPLE_RATE_KEY, self._self_config.get_analytics_sample_rate()) + + if dbm_propagator: + args, kwargs = dbm_propagator.inject(s, args, kwargs) + + try: + return await method(*args, **kwargs) + finally: + # Try to fetch custom properties that were passed by the specific Database implementation + self._set_post_execute_tags(s) + + async def executemany(self, query, *args, **kwargs): + """Wraps the cursor.executemany method""" + self._self_last_execute_operation = query + # Always return the result as-is + # DEV: Some libraries return `None`, others `int`, and others the cursor objects + # These differences should be overridden at the integration specific layer (e.g. in `sqlite3/patch.py`) + # FIXME[matt] properly handle kwargs here. arg names can be different + # with different libs. + return await self._trace_method( + self.__wrapped__.executemany, + self._self_datadog_name, + query, + {"sql.executemany": "true"}, + self._self_dbm_propagator, + query, + *args, + **kwargs, + ) + + async def execute(self, query, *args, **kwargs): + """Wraps the cursor.execute method""" + self._self_last_execute_operation = query + + # Always return the result as-is + # DEV: Some libraries return `None`, others `int`, and others the cursor objects + # These differences should be overridden at the integration specific layer (e.g. in `sqlite3/patch.py`) + return await self._trace_method( + self.__wrapped__.execute, + self._self_datadog_name, + query, + {}, + self._self_dbm_propagator, + query, + *args, + **kwargs, + ) + + +class FetchTracedAsyncCursor(TracedAsyncCursor): + """FetchTracedAsyncCursor for psycopg""" + + async def fetchone(self, *args, **kwargs): + """Wraps the cursor.fetchone method""" + span_name = "{}.{}".format(self._self_datadog_name, "fetchone") + return await self._trace_method( + self.__wrapped__.fetchone, span_name, self._self_last_execute_operation, {}, None, *args, **kwargs + ) + + async def fetchall(self, *args, **kwargs): + """Wraps the cursor.fetchall method""" + span_name = "{}.{}".format(self._self_datadog_name, "fetchall") + return await self._trace_method( + self.__wrapped__.fetchall, span_name, self._self_last_execute_operation, {}, None, *args, **kwargs + ) + + async def fetchmany(self, *args, **kwargs): + """Wraps the cursor.fetchmany method""" + span_name = "{}.{}".format(self._self_datadog_name, "fetchmany") + # We want to trace the information about how many rows were requested. Note that this number may be larger + # the number of rows actually returned if less then requested are available from the query. + size_tag_key = "db.fetch.size" + + try: + extra_tags = {size_tag_key: get_argument_value(args, kwargs, 0, "size")} + except ArgumentError: + default_array_size = getattr(self.__wrapped__, "arraysize", None) + extra_tags = {size_tag_key: default_array_size} if default_array_size else {} + + return await self._trace_method( + self.__wrapped__.fetchmany, span_name, self._self_last_execute_operation, extra_tags, None, *args, **kwargs + ) + + +class TracedAsyncConnection(TracedConnection): + def __init__(self, conn, pin=None, cfg=config.dbapi2, cursor_cls=None): + if not cursor_cls: + # Do not trace `fetch*` methods by default + cursor_cls = FetchTracedAsyncCursor if cfg.trace_fetch_methods else TracedAsyncCursor + super(TracedAsyncConnection, self).__init__(conn, pin, cfg, cursor_cls) + + async def __aenter__(self): + """Context management is not defined by the dbapi spec. + + This means unfortunately that the database clients each define their own + implementations. + + The ones we know about are: + + - mysqlclient<2.0 which returns a cursor instance. >=2.0 returns a + connection instance. + - psycopg returns a connection. + - pyodbc returns a connection. + - pymysql doesn't implement it. + - sqlite3 returns the connection. + """ + r = await self.__wrapped__.__aenter__() + + if hasattr(r, "cursor"): + # r is Connection-like. + if r is self.__wrapped__: + # Return the reference to this proxy object. Returning r would + # return the untraced reference. + return self + else: + # r is a different connection object. + # This should not happen in practice but play it safe so that + # the original functionality is maintained. + log.warning( + "Unexpected object type returned from __wrapped__.__aenter__()." + "Expected a wrapped instance, but received a different object." + ) + return r + elif hasattr(r, "execute"): + # r is Cursor-like. + if iswrapped(r): + return r + else: + pin = Pin.get_from(self) + if not pin: + return r + return self._self_cursor_cls(r, pin, self._self_config) + else: + # Otherwise r is some other object, so maintain the functionality + # of the original. + log.warning( + "Unexpected object type returned from __wrapped__.__aenter__()." + "Expected a wrapped instance, but received a different object." + ) + return r + + async def __aexit__(self, exc_type, exc_val, exc_tb): + # previous versions of the dbapi didn't support context managers. let's + # reference the func that would be called to ensure that errors + # messages will be the same. + return await self.__wrapped__.__aexit__(exc_type, exc_val, exc_tb) + + async def _trace_method(self, method, name, extra_tags, *args, **kwargs): + pin = Pin.get_from(self) + if not pin or not pin.enabled(): + return await method(*args, **kwargs) + + with pin.tracer.trace(name, service=ext_service(pin, self._self_config)) as s: + s.set_tag_str(COMPONENT, self._self_config.integration_name) + + # set span.kind to the type of request being performed + s.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + s.set_tags(pin.tags) + s.set_tags(extra_tags) + + return await method(*args, **kwargs) + + async def commit(self, *args, **kwargs): + span_name = "{}.{}".format(self._self_datadog_name, "commit") + return await self._trace_method(self.__wrapped__.commit, span_name, {}, *args, **kwargs) + + async def rollback(self, *args, **kwargs): + span_name = "{}.{}".format(self._self_datadog_name, "rollback") + return await self._trace_method(self.__wrapped__.rollback, span_name, {}, *args, **kwargs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/django/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/django/__init__.py new file mode 100644 index 0000000..10f52a3 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/django/__init__.py @@ -0,0 +1,192 @@ +""" +The Django__ integration traces requests, views, template renderers, database +and cache calls in a Django application. + + +Enable Django tracing automatically via ``ddtrace-run``:: + + ddtrace-run python manage.py runserver + + +Django tracing can also be enabled manually:: + + import ddtrace.auto + + +To have Django capture the tracer logs, ensure the ``LOGGING`` variable in +``settings.py`` looks similar to:: + + LOGGING = { + 'loggers': { + 'ddtrace': { + 'handlers': ['console'], + 'level': 'WARNING', + }, + }, + } + + +Configuration +~~~~~~~~~~~~~ + +.. important:: + + Note that the in-code configuration must be run before Django is instrumented. This means that in-code configuration + will not work with ``ddtrace-run`` and before a call to ``patch`` or ``import ddtrace.auto``. + + +.. py:data:: ddtrace.config.django['distributed_tracing_enabled'] + + Whether or not to parse distributed tracing headers from requests received by your Django app. + + Default: ``True`` + +.. py:data:: ddtrace.config.django['service_name'] + + The service name reported for your Django app. + + Can also be configured via the ``DD_SERVICE`` environment variable. + + Default: ``'django'`` + +.. py:data:: ddtrace.config.django['cache_service_name'] + + The service name reported for your Django app cache layer. + + Can also be configured via the ``DD_DJANGO_CACHE_SERVICE_NAME`` environment variable. + + Default: ``'django'`` + +.. py:data:: ddtrace.config.django['database_service_name'] + + A string reported as the service name of the Django app database layer. + + Can also be configured via the ``DD_DJANGO_DATABASE_SERVICE_NAME`` environment variable. + + Takes precedence over database_service_name_prefix. + + Default: ``''`` + +.. py:data:: ddtrace.config.django['database_service_name_prefix'] + + A string to be prepended to the service name reported for your Django app database layer. + + Can also be configured via the ``DD_DJANGO_DATABASE_SERVICE_NAME_PREFIX`` environment variable. + + The database service name is the name of the database appended with 'db'. Has a lower precedence than database_service_name. + + Default: ``''`` + +.. py:data:: ddtrace.config.django["trace_fetch_methods"] + + Whether or not to trace fetch methods. + + Can also be configured via the ``DD_DJANGO_TRACE_FETCH_METHODS`` environment variable. + + Default: ``False`` + +.. py:data:: ddtrace.config.django['instrument_middleware'] + + Whether or not to instrument middleware. + + Can also be enabled with the ``DD_DJANGO_INSTRUMENT_MIDDLEWARE`` environment variable. + + Default: ``True`` + +.. py:data:: ddtrace.config.django['instrument_templates'] + + Whether or not to instrument template rendering. + + Can also be enabled with the ``DD_DJANGO_INSTRUMENT_TEMPLATES`` environment variable. + + Default: ``True`` + +.. py:data:: ddtrace.config.django['instrument_databases'] + + Whether or not to instrument databases. + + Can also be enabled with the ``DD_DJANGO_INSTRUMENT_DATABASES`` environment variable. + + Default: ``True`` + +.. py:data:: ddtrace.config.django['instrument_caches'] + + Whether or not to instrument caches. + + Can also be enabled with the ``DD_DJANGO_INSTRUMENT_CACHES`` environment variable. + + Default: ``True`` + +.. py:data:: ddtrace.config.django.http['trace_query_string'] + + Whether or not to include the query string as a tag. + + Default: ``False`` + +.. py:data:: ddtrace.config.django['include_user_name'] + + Whether or not to include the authenticated user's username as a tag on the root request span. + + Can also be configured via the ``DD_DJANGO_INCLUDE_USER_NAME`` environment variable. + + Default: ``True`` + +.. py:data:: ddtrace.config.django['use_handler_resource_format'] + + Whether or not to use the resource format `"{method} {handler}"`. Can also be + enabled with the ``DD_DJANGO_USE_HANDLER_RESOURCE_FORMAT`` environment + variable. + + The default resource format for Django >= 2.2.0 is otherwise `"{method} {urlpattern}"`. + + Default: ``False`` + +.. py:data:: ddtrace.config.django['use_handler_with_url_name_resource_format'] + + Whether or not to use the resource format `"{method} {handler}.{url_name}"`. Can also be + enabled with the ``DD_DJANGO_USE_HANDLER_WITH_URL_NAME_RESOURCE_FORMAT`` environment + variable. + + This configuration applies only for Django <= 2.2.0. + + Default: ``False`` + +.. py:data:: ddtrace.config.django['use_legacy_resource_format'] + + Whether or not to use the legacy resource format `"{handler}"`. Can also be + enabled with the ``DD_DJANGO_USE_LEGACY_RESOURCE_FORMAT`` environment + variable. + + The default resource format for Django >= 2.2.0 is otherwise `"{method} {urlpattern}"`. + + Default: ``False`` + +Example:: + + from ddtrace import config + + # Enable distributed tracing + config.django['distributed_tracing_enabled'] = True + + # Override service name + config.django['service_name'] = 'custom-service-name' + + +:ref:`Headers tracing ` is supported for this integration. + +.. __: https://www.djangoproject.com/ +""" # noqa: E501 +from ...internal.utils.importlib import require_modules + + +required_modules = ["django"] + + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from . import patch as _patch + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "_patch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/django/_asgi.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/django/_asgi.py new file mode 100644 index 0000000..74f563d --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/django/_asgi.py @@ -0,0 +1,36 @@ +""" +Module providing async hooks. Do not import this module unless using Python >= 3.6. +""" +from ddtrace.contrib.asgi import span_from_scope + +from ...internal.utils import get_argument_value +from .. import trace_utils +from .utils import REQUEST_DEFAULT_RESOURCE +from .utils import _after_request_tags +from .utils import _before_request_tags + + +@trace_utils.with_traced_module +async def traced_get_response_async(django, pin, func, instance, args, kwargs): + """Trace django.core.handlers.base.BaseHandler.get_response() (or other implementations). + + This is the main entry point for requests. + + Django requests are handled by a Handler.get_response method (inherited from base.BaseHandler). + This method invokes the middleware chain and returns the response generated by the chain. + """ + request = get_argument_value(args, kwargs, 0, "request") + span = span_from_scope(request.scope) + if span is None: + return await func(*args, **kwargs) + + # Reset the span resource so we can know if it was modified during the request or not + span.resource = REQUEST_DEFAULT_RESOURCE + _before_request_tags(pin, span, request) + response = None + try: + response = await func(*args, **kwargs) + finally: + # DEV: Always set these tags, this is where `span.resource` is set + _after_request_tags(pin, span, request, response) + return response diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/django/compat.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/django/compat.py new file mode 100644 index 0000000..20f0a52 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/django/compat.py @@ -0,0 +1,31 @@ +import django + + +if django.VERSION >= (1, 10, 1): + from django.urls import get_resolver + + def user_is_authenticated(user): + # Explicit comparison due to the following bug + # https://code.djangoproject.com/ticket/26988 + return user.is_authenticated == True # noqa E712 + +else: + from django.conf import settings + from django.core import urlresolvers + + def user_is_authenticated(user): + return user.is_authenticated() + + if django.VERSION >= (1, 9, 0): + + def get_resolver(urlconf=None): + urlconf = urlconf or settings.ROOT_URLCONF + urlresolvers.set_urlconf(urlconf) + return urlresolvers.get_resolver(urlconf) + + else: + + def get_resolver(urlconf=None): + urlconf = urlconf or settings.ROOT_URLCONF + urlresolvers.set_urlconf(urlconf) + return urlresolvers.RegexURLResolver(r"^/", urlconf) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/django/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/django/patch.py new file mode 100644 index 0000000..b6b76f9 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/django/patch.py @@ -0,0 +1,875 @@ +""" +The Django patching works as follows: + +Django internals are instrumented via normal `patch()`. + +`django.apps.registry.Apps.populate` is patched to add instrumentation for any +specific Django apps like Django Rest Framework (DRF). +""" +import functools +from inspect import getmro +from inspect import isclass +from inspect import isfunction +import os + +from ddtrace import Pin +from ddtrace import config +from ddtrace.constants import SPAN_KIND +from ddtrace.contrib import dbapi +from ddtrace.contrib import func_name +from ddtrace.ext import SpanKind +from ddtrace.ext import SpanTypes +from ddtrace.ext import http +from ddtrace.ext import sql as sqlx +from ddtrace.internal import core +from ddtrace.internal.compat import Iterable +from ddtrace.internal.compat import maybe_stringify +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.constants import HTTP_REQUEST_BLOCKED +from ddtrace.internal.constants import STATUS_403_TYPE_AUTO +from ddtrace.internal.core.event_hub import ResultType +from ddtrace.internal.logger import get_logger +from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.schema import schematize_url_operation +from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.internal.utils import http as http_utils +from ddtrace.internal.utils.formats import asbool +from ddtrace.settings.asm import config as asm_config +from ddtrace.settings.integration import IntegrationConfig +from ddtrace.vendor import wrapt +from ddtrace.vendor.wrapt.importer import when_imported + +from ...appsec._utils import _UserInfoRetriever +from ...internal.utils import get_argument_value +from .. import trace_utils +from ..trace_utils import _get_request_header_user_agent + + +log = get_logger(__name__) + +config._add( + "django", + dict( + _default_service=schematize_service_name("django"), + cache_service_name=os.getenv("DD_DJANGO_CACHE_SERVICE_NAME", default="django"), + database_service_name_prefix=os.getenv("DD_DJANGO_DATABASE_SERVICE_NAME_PREFIX", default=""), + database_service_name=os.getenv("DD_DJANGO_DATABASE_SERVICE_NAME", default=""), + trace_fetch_methods=asbool(os.getenv("DD_DJANGO_TRACE_FETCH_METHODS", default=False)), + distributed_tracing_enabled=True, + instrument_middleware=asbool(os.getenv("DD_DJANGO_INSTRUMENT_MIDDLEWARE", default=True)), + instrument_templates=asbool(os.getenv("DD_DJANGO_INSTRUMENT_TEMPLATES", default=True)), + instrument_databases=asbool(os.getenv("DD_DJANGO_INSTRUMENT_DATABASES", default=True)), + instrument_caches=asbool(os.getenv("DD_DJANGO_INSTRUMENT_CACHES", default=True)), + analytics_enabled=None, # None allows the value to be overridden by the global config + analytics_sample_rate=None, + trace_query_string=None, # Default to global config + include_user_name=asbool(os.getenv("DD_DJANGO_INCLUDE_USER_NAME", default=True)), + use_handler_with_url_name_resource_format=asbool( + os.getenv("DD_DJANGO_USE_HANDLER_WITH_URL_NAME_RESOURCE_FORMAT", default=False) + ), + use_handler_resource_format=asbool(os.getenv("DD_DJANGO_USE_HANDLER_RESOURCE_FORMAT", default=False)), + use_legacy_resource_format=asbool(os.getenv("DD_DJANGO_USE_LEGACY_RESOURCE_FORMAT", default=False)), + ), +) + +_NotSet = object() +psycopg_cursor_cls = Psycopg2TracedCursor = Psycopg3TracedCursor = _NotSet + + +def get_version(): + # type: () -> str + import django + + return django.__version__ + + +def patch_conn(django, conn): + global psycopg_cursor_cls, Psycopg2TracedCursor, Psycopg3TracedCursor + + if psycopg_cursor_cls is _NotSet: + try: + from psycopg.cursor import Cursor as psycopg_cursor_cls + + from ddtrace.contrib.psycopg.cursor import Psycopg3TracedCursor + except ImportError: + Psycopg3TracedCursor = None + try: + from psycopg2._psycopg import cursor as psycopg_cursor_cls + + from ddtrace.contrib.psycopg.cursor import Psycopg2TracedCursor + except ImportError: + psycopg_cursor_cls = None + Psycopg2TracedCursor = None + + def cursor(django, pin, func, instance, args, kwargs): + alias = getattr(conn, "alias", "default") + + if config.django.database_service_name: + service = config.django.database_service_name + else: + database_prefix = config.django.database_service_name_prefix + service = "{}{}{}".format(database_prefix, alias, "db") + service = schematize_service_name(service) + + vendor = getattr(conn, "vendor", "db") + prefix = sqlx.normalize_vendor(vendor) + tags = { + "django.db.vendor": vendor, + "django.db.alias": alias, + } + pin = Pin(service, tags=tags, tracer=pin.tracer) + cursor = func(*args, **kwargs) + traced_cursor_cls = dbapi.TracedCursor + try: + if cursor.cursor.__class__.__module__.startswith("psycopg2."): + # Import lazily to avoid importing psycopg2 if not already imported. + from ddtrace.contrib.psycopg.cursor import Psycopg2TracedCursor + + traced_cursor_cls = Psycopg2TracedCursor + elif type(cursor.cursor).__name__ == "Psycopg3TracedCursor": + # Import lazily to avoid importing psycopg if not already imported. + from ddtrace.contrib.psycopg.cursor import Psycopg3TracedCursor + + traced_cursor_cls = Psycopg3TracedCursor + except AttributeError: + pass + + # Each db alias will need its own config for dbapi + cfg = IntegrationConfig( + config.django.global_config, # global_config needed for analytics sample rate + "{}-{}".format("django", alias), # name not used but set anyway + _default_service=config.django._default_service, + _dbapi_span_name_prefix=prefix, + trace_fetch_methods=config.django.trace_fetch_methods, + analytics_enabled=config.django.analytics_enabled, + analytics_sample_rate=config.django.analytics_sample_rate, + ) + return traced_cursor_cls(cursor, pin, cfg) + + if not isinstance(conn.cursor, wrapt.ObjectProxy): + conn.cursor = wrapt.FunctionWrapper(conn.cursor, trace_utils.with_traced_module(cursor)(django)) + + +def instrument_dbs(django): + def get_connection(wrapped, instance, args, kwargs): + conn = wrapped(*args, **kwargs) + try: + patch_conn(django, conn) + except Exception: + log.debug("Error instrumenting database connection %r", conn, exc_info=True) + return conn + + if not isinstance(django.db.utils.ConnectionHandler.__getitem__, wrapt.ObjectProxy): + django.db.utils.ConnectionHandler.__getitem__ = wrapt.FunctionWrapper( + django.db.utils.ConnectionHandler.__getitem__, get_connection + ) + + +@trace_utils.with_traced_module +def traced_cache(django, pin, func, instance, args, kwargs): + from . import utils + + if not config.django.instrument_caches: + return func(*args, **kwargs) + + cache_backend = "{}.{}".format(instance.__module__, instance.__class__.__name__) + tags = {COMPONENT: config.django.integration_name, "django.cache.backend": cache_backend} + if args: + keys = utils.quantize_key_values(args[0]) + tags["django.cache.key"] = keys + + with core.context_with_data( + "django.cache", + span_name="django.cache", + span_type=SpanTypes.CACHE, + service=config.django.cache_service_name, + resource=utils.resource_from_cache_prefix(func_name(func), instance), + tags=tags, + pin=pin, + ) as ctx, ctx["call"]: + result = func(*args, **kwargs) + rowcount = 0 + if func.__name__ == "get_many": + rowcount = sum(1 for doc in result if doc) if result and isinstance(result, Iterable) else 0 + elif func.__name__ == "get": + try: + # check also for special case for Django~3.2 that returns an empty Sentinel + # object for empty results + # also check if result is Iterable first since some iterables return ambiguous + # truth results with ``==`` + if result is None or ( + not isinstance(result, Iterable) and result == getattr(instance, "_missing_key", None) + ): + rowcount = 0 + else: + rowcount = 1 + except (AttributeError, NotImplementedError, ValueError): + pass + core.dispatch("django.cache", (ctx, rowcount)) + return result + + +def instrument_caches(django): + cache_backends = set([cache["BACKEND"] for cache in django.conf.settings.CACHES.values()]) + for cache_path in cache_backends: + split = cache_path.split(".") + cache_module = ".".join(split[:-1]) + cache_cls = split[-1] + for method in ["get", "set", "add", "delete", "incr", "decr", "get_many", "set_many", "delete_many"]: + try: + cls = django.utils.module_loading.import_string(cache_path) + # DEV: this can be removed when we add an idempotent `wrap` + if not trace_utils.iswrapped(cls, method): + trace_utils.wrap(cache_module, "{0}.{1}".format(cache_cls, method), traced_cache(django)) + except Exception: + log.debug("Error instrumenting cache %r", cache_path, exc_info=True) + + +@trace_utils.with_traced_module +def traced_populate(django, pin, func, instance, args, kwargs): + """django.apps.registry.Apps.populate is the method used to populate all the apps. + + It is used as a hook to install instrumentation for 3rd party apps (like DRF). + + `populate()` works in 3 phases: + + - Phase 1: Initializes the app configs and imports the app modules. + - Phase 2: Imports models modules for each app. + - Phase 3: runs ready() of each app config. + + If all 3 phases successfully run then `instance.ready` will be `True`. + """ + + # populate() can be called multiple times, we don't want to instrument more than once + if instance.ready: + log.debug("Django instrumentation already installed, skipping.") + return func(*args, **kwargs) + + ret = func(*args, **kwargs) + + if not instance.ready: + log.debug("populate() failed skipping instrumentation.") + return ret + + settings = django.conf.settings + + # Instrument databases + if config.django.instrument_databases: + try: + instrument_dbs(django) + except Exception: + log.debug("Error instrumenting Django database connections", exc_info=True) + + # Instrument caches + if config.django.instrument_caches: + try: + instrument_caches(django) + except Exception: + log.debug("Error instrumenting Django caches", exc_info=True) + + # Instrument Django Rest Framework if it's installed + INSTALLED_APPS = getattr(settings, "INSTALLED_APPS", []) + + if "rest_framework" in INSTALLED_APPS: + try: + from .restframework import patch_restframework + + patch_restframework(django) + except Exception: + log.debug("Error patching rest_framework", exc_info=True) + + return ret + + +def traced_func(django, name, resource=None, ignored_excs=None): + def wrapped(django, pin, func, instance, args, kwargs): + tags = {COMPONENT: config.django.integration_name} + with core.context_with_data( + "django.func.wrapped", span_name=name, resource=resource, tags=tags, pin=pin + ) as ctx, ctx["call"]: + core.dispatch( + "django.func.wrapped", + ( + args, + kwargs, + django.core.handlers.wsgi.WSGIRequest if hasattr(django.core.handlers, "wsgi") else object, + ctx, + ignored_excs, + ), + ) + return func(*args, **kwargs) + + return trace_utils.with_traced_module(wrapped)(django) + + +def traced_process_exception(django, name, resource=None): + def wrapped(django, pin, func, instance, args, kwargs): + tags = {COMPONENT: config.django.integration_name} + with core.context_with_data( + "django.process_exception", span_name=name, resource=resource, tags=tags, pin=pin + ) as ctx, ctx["call"]: + resp = func(*args, **kwargs) + core.dispatch( + "django.process_exception", (ctx, hasattr(resp, "status_code") and 500 <= resp.status_code < 600) + ) + return resp + + return trace_utils.with_traced_module(wrapped)(django) + + +@trace_utils.with_traced_module +def traced_load_middleware(django, pin, func, instance, args, kwargs): + """ + Patches django.core.handlers.base.BaseHandler.load_middleware to instrument all + middlewares. + """ + settings_middleware = [] + # Gather all the middleware + if getattr(django.conf.settings, "MIDDLEWARE", None): + settings_middleware += django.conf.settings.MIDDLEWARE + if getattr(django.conf.settings, "MIDDLEWARE_CLASSES", None): + settings_middleware += django.conf.settings.MIDDLEWARE_CLASSES + + # Iterate over each middleware provided in settings.py + # Each middleware can either be a function or a class + for mw_path in settings_middleware: + mw = django.utils.module_loading.import_string(mw_path) + + # Instrument function-based middleware + if isfunction(mw) and not trace_utils.iswrapped(mw): + split = mw_path.split(".") + if len(split) < 2: + continue + base = ".".join(split[:-1]) + attr = split[-1] + + # DEV: We need to have a closure over `mw_path` for the resource name or else + # all function based middleware will share the same resource name + def _wrapper(resource): + # Function-based middleware is a factory which returns a handler function for + # requests. + # So instead of tracing the factory, we want to trace its returned value. + # We wrap the factory to return a traced version of the handler function. + def wrapped_factory(func, instance, args, kwargs): + # r is the middleware handler function returned from the factory + r = func(*args, **kwargs) + if r: + return wrapt.FunctionWrapper( + r, + traced_func(django, "django.middleware", resource=resource), + ) + # If r is an empty middleware function (i.e. returns None), don't wrap since + # NoneType cannot be called + else: + return r + + return wrapped_factory + + trace_utils.wrap(base, attr, _wrapper(resource=mw_path)) + + # Instrument class-based middleware + elif isclass(mw): + for hook in [ + "process_request", + "process_response", + "process_view", + "process_template_response", + "__call__", + ]: + if hasattr(mw, hook) and not trace_utils.iswrapped(mw, hook): + trace_utils.wrap( + mw, hook, traced_func(django, "django.middleware", resource=mw_path + ".{0}".format(hook)) + ) + # Do a little extra for `process_exception` + if hasattr(mw, "process_exception") and not trace_utils.iswrapped(mw, "process_exception"): + res = mw_path + ".{0}".format("process_exception") + trace_utils.wrap( + mw, "process_exception", traced_process_exception(django, "django.middleware", resource=res) + ) + + return func(*args, **kwargs) + + +def _gather_block_metadata(request, request_headers, ctx: core.ExecutionContext): + from . import utils + + try: + metadata = {http.STATUS_CODE: "403", http.METHOD: request.method} + url = utils.get_request_uri(request) + query = request.META.get("QUERY_STRING", "") + if query and config.django.trace_query_string: + metadata[http.QUERY_STRING] = query + user_agent = _get_request_header_user_agent(request_headers) + if user_agent: + metadata[http.USER_AGENT] = user_agent + except Exception as e: + log.warning("Could not gather some metadata on blocked request: %s", str(e)) # noqa: G200 + core.dispatch("django.block_request_callback", (ctx, metadata, config.django, url, query)) + + +def _block_request_callable(request, request_headers, ctx: core.ExecutionContext): + # This is used by user-id blocking to block responses. It could be called + # at any point so it's a callable stored in the ASM context. + from django.core.exceptions import PermissionDenied + + core.root.set_item(HTTP_REQUEST_BLOCKED, STATUS_403_TYPE_AUTO) + _gather_block_metadata(request, request_headers, ctx) + raise PermissionDenied() + + +@trace_utils.with_traced_module +def traced_get_response(django, pin, func, instance, args, kwargs): + """Trace django.core.handlers.base.BaseHandler.get_response() (or other implementations). + + This is the main entry point for requests. + + Django requests are handled by a Handler.get_response method (inherited from base.BaseHandler). + This method invokes the middleware chain and returns the response generated by the chain. + """ + from ddtrace.contrib.django.compat import get_resolver + + from . import utils + + request = get_argument_value(args, kwargs, 0, "request") + if request is None: + return func(*args, **kwargs) + + request_headers = utils._get_request_headers(request) + + with core.context_with_data( + "django.traced_get_response", + remote_addr=request.META.get("REMOTE_ADDR"), + headers=request_headers, + headers_case_sensitive=django.VERSION < (2, 2), + span_name=schematize_url_operation("django.request", protocol="http", direction=SpanDirection.INBOUND), + resource=utils.REQUEST_DEFAULT_RESOURCE, + service=trace_utils.int_service(pin, config.django), + span_type=SpanTypes.WEB, + tags={COMPONENT: config.django.integration_name, SPAN_KIND: SpanKind.SERVER}, + distributed_headers_config=config.django, + distributed_headers=request_headers, + pin=pin, + ) as ctx, ctx.get_item("call"): + core.dispatch( + "django.traced_get_response.pre", + ( + functools.partial(_block_request_callable, request, request_headers, ctx), + ctx, + request, + utils._before_request_tags, + ), + ) + + response = None + + def blocked_response(): + from django.http import HttpResponse + + block_config = core.get_item(HTTP_REQUEST_BLOCKED) + desired_type = block_config.get("type", "auto") + status = block_config.get("status_code", 403) + if desired_type == "none": + response = HttpResponse("", status=status) + location = block_config.get("location", "") + if location: + response["location"] = location + else: + if desired_type == "auto": + ctype = "text/html" if "text/html" in request_headers.get("Accept", "").lower() else "text/json" + else: + ctype = "text/" + desired_type + content = http_utils._get_blocked_template(ctype) + response = HttpResponse(content, content_type=ctype, status=status) + response.content = content + utils._after_request_tags(pin, ctx["call"], request, response) + return response + + try: + if core.get_item(HTTP_REQUEST_BLOCKED): + response = blocked_response() + return response + + query = request.META.get("QUERY_STRING", "") + uri = utils.get_request_uri(request) + if uri is not None and query: + uri += "?" + query + resolver = get_resolver(getattr(request, "urlconf", None)) + if resolver: + try: + path = resolver.resolve(request.path_info).kwargs + log.debug("resolver.pattern %s", path) + except Exception: + path = None + + core.dispatch("django.start_response", (ctx, request, utils._extract_body, query, uri, path)) + core.dispatch("django.start_response.post", ("Django",)) + + if core.get_item(HTTP_REQUEST_BLOCKED): + response = blocked_response() + return response + + response = func(*args, **kwargs) + + if core.get_item(HTTP_REQUEST_BLOCKED): + response = blocked_response() + return response + + return response + finally: + core.dispatch("django.finalize_response.pre", (ctx, utils._after_request_tags, request, response)) + if not core.get_item(HTTP_REQUEST_BLOCKED): + core.dispatch("django.finalize_response", ("Django",)) + if core.get_item(HTTP_REQUEST_BLOCKED): + response = blocked_response() + return response # noqa: B012 + + +@trace_utils.with_traced_module +def traced_template_render(django, pin, wrapped, instance, args, kwargs): + # DEV: Check here in case this setting is configured after a template has been instrumented + if not config.django.instrument_templates: + return wrapped(*args, **kwargs) + + template_name = maybe_stringify(getattr(instance, "name", None)) + if template_name: + resource = template_name + else: + resource = "{0}.{1}".format(func_name(instance), wrapped.__name__) + + tags = {COMPONENT: config.django.integration_name} + if template_name: + tags["django.template.name"] = template_name + engine = getattr(instance, "engine", None) + if engine: + tags["django.template.engine.class"] = func_name(engine) + + with core.context_with_data( + "django.template.render", + span_name="django.template.render", + resource=resource, + span_type=http.TEMPLATE, + tags=tags, + pin=pin, + ) as ctx, ctx["call"]: + return wrapped(*args, **kwargs) + + +def instrument_view(django, view): + """ + Helper to wrap Django views. + + We want to wrap all lifecycle/http method functions for every class in the MRO for this view + """ + if hasattr(view, "__mro__"): + for cls in reversed(getmro(view)): + _instrument_view(django, cls) + + return _instrument_view(django, view) + + +def _instrument_view(django, view): + """Helper to wrap Django views.""" + from . import utils + + # All views should be callable, double check before doing anything + if not callable(view): + return view + + # Patch view HTTP methods and lifecycle methods + http_method_names = getattr(view, "http_method_names", ("get", "delete", "post", "options", "head")) + lifecycle_methods = ("setup", "dispatch", "http_method_not_allowed") + for name in list(http_method_names) + list(lifecycle_methods): + try: + func = getattr(view, name, None) + if not func or isinstance(func, wrapt.ObjectProxy): + continue + + resource = "{0}.{1}".format(func_name(view), name) + op_name = "django.view.{0}".format(name) + trace_utils.wrap(view, name, traced_func(django, name=op_name, resource=resource)) + except Exception: + log.debug("Failed to instrument Django view %r function %s", view, name, exc_info=True) + + # Patch response methods + response_cls = getattr(view, "response_class", None) + if response_cls: + methods = ("render",) + for name in methods: + try: + func = getattr(response_cls, name, None) + # Do not wrap if the method does not exist or is already wrapped + if not func or isinstance(func, wrapt.ObjectProxy): + continue + + resource = "{0}.{1}".format(func_name(response_cls), name) + op_name = "django.response.{0}".format(name) + trace_utils.wrap(response_cls, name, traced_func(django, name=op_name, resource=resource)) + except Exception: + log.debug("Failed to instrument Django response %r function %s", response_cls, name, exc_info=True) + + # If the view itself is not wrapped, wrap it + if not isinstance(view, wrapt.ObjectProxy): + view = utils.DjangoViewProxy( + view, traced_func(django, "django.view", resource=func_name(view), ignored_excs=[django.http.Http404]) + ) + return view + + +@trace_utils.with_traced_module +def traced_urls_path(django, pin, wrapped, instance, args, kwargs): + """Wrapper for url path helpers to ensure all views registered as urls are traced.""" + try: + if "view" in kwargs: + kwargs["view"] = instrument_view(django, kwargs["view"]) + elif len(args) >= 2: + args = list(args) + args[1] = instrument_view(django, args[1]) + args = tuple(args) + except Exception: + log.debug("Failed to instrument Django url path %r %r", args, kwargs, exc_info=True) + return wrapped(*args, **kwargs) + + +@trace_utils.with_traced_module +def traced_as_view(django, pin, func, instance, args, kwargs): + """ + Wrapper for django's View.as_view class method + """ + try: + instrument_view(django, instance) + except Exception: + log.debug("Failed to instrument Django view %r", instance, exc_info=True) + view = func(*args, **kwargs) + return wrapt.FunctionWrapper(view, traced_func(django, "django.view", resource=func_name(instance))) + + +@trace_utils.with_traced_module +def traced_get_asgi_application(django, pin, func, instance, args, kwargs): + from ddtrace.contrib.asgi import TraceMiddleware + + def django_asgi_modifier(span, scope): + span.name = schematize_url_operation("django.request", protocol="http", direction=SpanDirection.INBOUND) + + return TraceMiddleware(func(*args, **kwargs), integration_config=config.django, span_modifier=django_asgi_modifier) + + +class _DjangoUserInfoRetriever(_UserInfoRetriever): + def get_username(self): + if hasattr(self.user, "USERNAME_FIELD") and not asm_config._user_model_name_field: + user_type = type(self.user) + return getattr(self.user, user_type.USERNAME_FIELD, None) + + return super(_DjangoUserInfoRetriever, self).get_username() + + def get_name(self): + if not asm_config._user_model_name_field: + if hasattr(self.user, "get_full_name"): + try: + return self.user.get_full_name() + except Exception: + log.debug("User model get_full_name member produced an exception: ", exc_info=True) + + if hasattr(self.user, "first_name") and hasattr(self.user, "last_name"): + return "%s %s" % (self.user.first_name, self.user.last_name) + + return super(_DjangoUserInfoRetriever, self).get_name() + + def get_user_email(self): + if hasattr(self.user, "EMAIL_FIELD") and not asm_config._user_model_name_field: + user_type = type(self.user) + return getattr(self.user, user_type.EMAIL_FIELD, None) + + return super(_DjangoUserInfoRetriever, self).get_user_email() + + +@trace_utils.with_traced_module +def traced_login(django, pin, func, instance, args, kwargs): + func(*args, **kwargs) + + try: + mode = asm_config._automatic_login_events_mode + request = get_argument_value(args, kwargs, 0, "request") + user = get_argument_value(args, kwargs, 1, "user") + + if mode == "disabled": + return + + core.dispatch( + "django.login", + ( + pin, + request, + user, + mode, + _DjangoUserInfoRetriever(user), + ), + ) + except Exception: + log.debug("Error while trying to trace Django login", exc_info=True) + + +@trace_utils.with_traced_module +def traced_authenticate(django, pin, func, instance, args, kwargs): + result_user = func(*args, **kwargs) + try: + mode = asm_config._automatic_login_events_mode + if mode == "disabled": + return result_user + + result = core.dispatch_with_results( + "django.auth", + ( + result_user, + mode, + kwargs, + pin, + _DjangoUserInfoRetriever(result_user), + ), + ).user + if result and result.value[0]: + return result.value[1] + + except Exception: + log.debug("Error while trying to trace Django authenticate", exc_info=True) + + return result_user + + +def unwrap_views(func, instance, args, kwargs): + """ + Django channels uses path() and re_path() to route asgi applications. This broke our initial + assumption that + django path/re_path/url functions only accept views. Here we unwrap ddtrace view + instrumentation from asgi + applications. + + Ex. ``channels.routing.URLRouter([path('', get_asgi_application())])`` + On startup ddtrace.contrib.django.path.instrument_view() will wrap get_asgi_application in a + DjangoViewProxy. + Since get_asgi_application is not a django view callback this function will unwrap it. + """ + from . import utils + + routes = get_argument_value(args, kwargs, 0, "routes") + for route in routes: + if isinstance(route.callback, utils.DjangoViewProxy): + route.callback = route.callback.__wrapped__ + + return func(*args, **kwargs) + + +def _patch(django): + Pin().onto(django) + + when_imported("django.apps.registry")(lambda m: trace_utils.wrap(m, "Apps.populate", traced_populate(django))) + + if config.django.instrument_middleware: + when_imported("django.core.handlers.base")( + lambda m: trace_utils.wrap(m, "BaseHandler.load_middleware", traced_load_middleware(django)) + ) + + when_imported("django.core.handlers.wsgi")(lambda m: trace_utils.wrap(m, "WSGIRequest.__init__", wrap_wsgi_environ)) + core.dispatch("django.patch", ()) + + @when_imported("django.core.handlers.base") + def _(m): + import django + + trace_utils.wrap(m, "BaseHandler.get_response", traced_get_response(django)) + if django.VERSION >= (3, 1): + # Have to inline this import as the module contains syntax incompatible with Python 3.5 and below + from ._asgi import traced_get_response_async + + trace_utils.wrap(m, "BaseHandler.get_response_async", traced_get_response_async(django)) + + @when_imported("django.contrib.auth") + def _(m): + trace_utils.wrap(m, "login", traced_login(django)) + trace_utils.wrap(m, "authenticate", traced_authenticate(django)) + + # Only wrap get_asgi_application if get_response_async exists. Otherwise we will effectively double-patch + # because get_response and get_asgi_application will be used. We must rely on the version instead of coalescing + # with the previous patching hook because of circular imports within `django.core.asgi`. + if django.VERSION >= (3, 1): + when_imported("django.core.asgi")( + lambda m: trace_utils.wrap(m, "get_asgi_application", traced_get_asgi_application(django)) + ) + + if config.django.instrument_templates: + when_imported("django.template.base")( + lambda m: trace_utils.wrap(m, "Template.render", traced_template_render(django)) + ) + + if django.VERSION < (4, 0, 0): + when_imported("django.conf.urls")(lambda m: trace_utils.wrap(m, "url", traced_urls_path(django))) + + if django.VERSION >= (2, 0, 0): + + @when_imported("django.urls") + def _(m): + trace_utils.wrap(m, "path", traced_urls_path(django)) + trace_utils.wrap(m, "re_path", traced_urls_path(django)) + + when_imported("django.views.generic.base")(lambda m: trace_utils.wrap(m, "View.as_view", traced_as_view(django))) + + @when_imported("channels.routing") + def _(m): + import channels + + channels_version = tuple(int(x) for x in channels.__version__.split(".")) + if channels_version >= (3, 0): + # ASGI3 is only supported in channels v3.0+ + trace_utils.wrap(m, "URLRouter.__init__", unwrap_views) + + +def wrap_wsgi_environ(wrapped, _instance, args, kwargs): + result = core.dispatch_with_results("django.wsgi_environ", (wrapped, _instance, args, kwargs)).wrapped_result + # if the callback is registered and runs, return the result + if result: + return result.value + # if the callback is not registered, return the original result + elif result.response_type == ResultType.RESULT_UNDEFINED: + return wrapped(*args, **kwargs) + # if an exception occurs, raise it. It should never happen. + elif result.exception: + raise result.exception + + +def patch(): + import django + + if getattr(django, "_datadog_patch", False): + return + _patch(django) + + django._datadog_patch = True + + +def _unpatch(django): + trace_utils.unwrap(django.apps.registry.Apps, "populate") + trace_utils.unwrap(django.core.handlers.base.BaseHandler, "load_middleware") + trace_utils.unwrap(django.core.handlers.base.BaseHandler, "get_response") + trace_utils.unwrap(django.core.handlers.base.BaseHandler, "get_response_async") + trace_utils.unwrap(django.template.base.Template, "render") + trace_utils.unwrap(django.conf.urls.static, "static") + trace_utils.unwrap(django.conf.urls, "url") + trace_utils.unwrap(django.contrib.auth.login, "login") + trace_utils.unwrap(django.contrib.auth.authenticate, "authenticate") + if django.VERSION >= (2, 0, 0): + trace_utils.unwrap(django.urls, "path") + trace_utils.unwrap(django.urls, "re_path") + trace_utils.unwrap(django.views.generic.base.View, "as_view") + for conn in django.db.connections.all(): + trace_utils.unwrap(conn, "cursor") + trace_utils.unwrap(django.db.utils.ConnectionHandler, "__getitem__") + + +def unpatch(): + import django + + if not getattr(django, "_datadog_patch", False): + return + + _unpatch(django) + + django._datadog_patch = False diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/django/restframework.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/django/restframework.py new file mode 100644 index 0000000..ed0c789 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/django/restframework.py @@ -0,0 +1,33 @@ +import rest_framework.views + +from ddtrace.vendor.wrapt import wrap_function_wrapper as wrap + +from ..trace_utils import iswrapped +from ..trace_utils import with_traced_module + + +@with_traced_module +def _traced_handle_exception(django, pin, wrapped, instance, args, kwargs): + """Sets the error message, error type and exception stack trace to the current span + before calling the original exception handler. + """ + span = pin.tracer.current_span() + + if span is not None: + span.set_traceback() + + return wrapped(*args, **kwargs) + + +def patch_restframework(django): + """Patches rest_framework app. + + To trace exceptions occurring during view processing we currently use a TraceExceptionMiddleware. + However the rest_framework handles exceptions before they come to our middleware. + So we need to manually patch the rest_framework exception handler + to set the exception stack trace in the current span. + """ + + # trace the handle_exception method + if not iswrapped(rest_framework.views.APIView, "handle_exception"): + wrap("rest_framework.views", "APIView.handle_exception", _traced_handle_exception(django)) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/django/utils.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/django/utils.py new file mode 100644 index 0000000..71571ea --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/django/utils.py @@ -0,0 +1,421 @@ +import json +from typing import Any # noqa:F401 +from typing import Dict # noqa:F401 +from typing import List # noqa:F401 +from typing import Mapping # noqa:F401 +from typing import Text # noqa:F401 +from typing import Union # noqa:F401 + +import django +from django.utils.functional import SimpleLazyObject +import xmltodict + +from ddtrace import Span +from ddtrace import config +from ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import SPAN_MEASURED_KEY +from ddtrace.contrib import func_name +from ddtrace.ext import SpanTypes +from ddtrace.ext import user as _user +from ddtrace.internal import compat +from ddtrace.internal.utils.http import parse_form_multipart +from ddtrace.internal.utils.http import parse_form_params +from ddtrace.propagation._utils import from_wsgi_header + +from ...internal import core +from ...internal.logger import get_logger +from ...internal.utils.formats import stringify_cache_args +from ...vendor.wrapt import FunctionWrapper +from .. import trace_utils +from .compat import get_resolver +from .compat import user_is_authenticated + + +try: + from json import JSONDecodeError +except ImportError: + # handling python 2.X import error + JSONDecodeError = ValueError # type: ignore + + +log = get_logger(__name__) + +if django.VERSION < (1, 10, 0): + Resolver404 = django.core.urlresolvers.Resolver404 +else: + Resolver404 = django.urls.exceptions.Resolver404 + + +DJANGO22 = django.VERSION >= (2, 2, 0) + +REQUEST_DEFAULT_RESOURCE = "__django_request" +_BODY_METHODS = {"POST", "PUT", "DELETE", "PATCH"} + +_quantize_text = Union[Text, bytes] +_quantize_param = Union[_quantize_text, List[_quantize_text], Dict[_quantize_text, Any], Any] + + +def resource_from_cache_prefix(resource, cache): + """ + Combine the resource name with the cache prefix (if any) + """ + if getattr(cache, "key_prefix", None): + name = " ".join((resource, cache.key_prefix)) + else: + name = resource + + # enforce lowercase to make the output nicer to read + return name.lower() + + +def quantize_key_values(keys): + # type: (_quantize_param) -> Text + """ + Used for Django cache key normalization. + + If a dict is provided we return a list of keys as text. + + If a list or tuple is provided we convert each element to text. + + If text is provided we convert to text. + """ + args = [] # type: List[Union[Text, bytes, Any]] + + # Normalize input values into a List[Text, bytes] + if isinstance(keys, dict): + args = list(keys.keys()) + elif isinstance(keys, (list, tuple)): + args = keys + else: + args = [keys] + + return stringify_cache_args(args) + + +def get_django_2_route(request, resolver_match): + # Try to use `resolver_match.route` if available + # Otherwise, look for `resolver.pattern.regex.pattern` + route = resolver_match.route + if route: + return route + + resolver = get_resolver(getattr(request, "urlconf", None)) + if resolver: + try: + return resolver.pattern.regex.pattern + except AttributeError: + pass + + return None + + +def set_tag_array(span, prefix, value): + """Helper to set a span tag as a single value or an array""" + if not value: + return + + if len(value) == 1: + if value[0]: + span.set_tag_str(prefix, value[0]) + else: + for i, v in enumerate(value, start=0): + if v: + span.set_tag_str("".join((prefix, ".", str(i))), v) + + +def get_request_uri(request): + """ + Helper to rebuild the original request url + + query string or fragments are not included. + """ + # DEV: Use django.http.request.HttpRequest._get_raw_host() when available + # otherwise back-off to PEP 333 as done in django 1.8.x + if hasattr(request, "_get_raw_host"): + host = request._get_raw_host() + else: + try: + # Try to build host how Django would have + # https://github.com/django/django/blob/e8d0d2a5efc8012dcc8bf1809dec065ebde64c81/django/http/request.py#L85-L102 + if "HTTP_HOST" in request.META: + host = request.META["HTTP_HOST"] + else: + host = request.META["SERVER_NAME"] + port = str(request.META["SERVER_PORT"]) + if port != ("443" if request.is_secure() else "80"): + host = "".join((host, ":", port)) + except Exception: + # This really shouldn't ever happen, but lets guard here just in case + log.debug("Failed to build Django request host", exc_info=True) + host = "unknown" + + # If request scheme is missing, possible in case where wsgi.url_scheme + # environ has not been set, return None and skip providing a uri + if request.scheme is None: + return + + # Build request url from the information available + # DEV: We are explicitly omitting query strings since they may contain sensitive information + urlparts = {"scheme": request.scheme, "netloc": host, "path": request.path} + + # If any url part is a SimpleLazyObject, use its __class__ property to cast + # str/bytes and allow for _setup() to execute + for k, v in urlparts.items(): + if isinstance(v, SimpleLazyObject): + if issubclass(v.__class__, str): + v = str(v) + elif issubclass(v.__class__, bytes): + v = bytes(v) + else: + # lazy object that is not str or bytes should not happen here + # but if it does skip providing a uri + log.debug( + "Skipped building Django request uri, %s is SimpleLazyObject wrapping a %s class", + k, + v.__class__.__name__, + ) + return None + urlparts[k] = compat.ensure_text(v) + + return "".join((urlparts["scheme"], "://", urlparts["netloc"], urlparts["path"])) + + +def _set_resolver_tags(pin, span, request): + # Default to just the HTTP method when we cannot determine a reasonable resource + resource = request.method + + try: + # Get resolver match result and build resource name pieces + resolver_match = request.resolver_match + if not resolver_match: + # The request quite likely failed (e.g. 404) so we do the resolution anyway. + resolver = get_resolver(getattr(request, "urlconf", None)) + resolver_match = resolver.resolve(request.path_info) + + if hasattr(resolver_match[0], "view_class"): + # In django==4.0, view.__name__ defaults to .views.view + # Accessing view.view_class is equired for django>4.0 to get the name of underlying view + handler = func_name(resolver_match[0].view_class) + else: + handler = func_name(resolver_match[0]) + + route = None + # In Django >= 2.2.0 we can access the original route or regex pattern + # TODO: Validate if `resolver.pattern.regex.pattern` is available on django<2.2 + if DJANGO22: + # Determine the resolver and resource name for this request + route = get_django_2_route(request, resolver_match) + if route: + span.set_tag_str("http.route", route) + + if config.django.use_handler_resource_format: + resource = " ".join((request.method, handler)) + elif config.django.use_legacy_resource_format: + resource = handler + else: + if route: + resource = " ".join((request.method, route)) + else: + if config.django.use_handler_with_url_name_resource_format: + # Append url name in order to distinguish different routes of the same ViewSet + url_name = resolver_match.url_name + if url_name: + handler = ".".join([handler, url_name]) + + resource = " ".join((request.method, handler)) + + span.set_tag_str("django.view", resolver_match.view_name) + set_tag_array(span, "django.namespace", resolver_match.namespaces) + + # Django >= 2.0.0 + if hasattr(resolver_match, "app_names"): + set_tag_array(span, "django.app", resolver_match.app_names) + + except Resolver404: + # Normalize all 404 requests into a single resource name + # DEV: This is for potential cardinality issues + resource = " ".join((request.method, "404")) + except Exception: + log.debug( + "Failed to resolve request path %r with path info %r", + request, + getattr(request, "path_info", "not-set"), + exc_info=True, + ) + finally: + # Only update the resource name if it was not explicitly set + # by anyone during the request lifetime + if span.resource == REQUEST_DEFAULT_RESOURCE: + span.resource = resource + + +def _before_request_tags(pin, span, request): + # DEV: Do not set `span.resource` here, leave it as `None` + # until `_set_resolver_tags` so we can know if the user + # has explicitly set it during the request lifetime + span.service = trace_utils.int_service(pin, config.django) + span.span_type = SpanTypes.WEB + span._metrics[SPAN_MEASURED_KEY] = 1 + + analytics_sr = config.django.get_analytics_sample_rate(use_global_config=True) + if analytics_sr is not None: + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, analytics_sr) + + span.set_tag_str("django.request.class", func_name(request)) + + +def _extract_body(request): + # DEV: Do not use request.POST or request.data, this could prevent custom parser to be used after + if request.method in _BODY_METHODS: + req_body = None + content_type = request.content_type if hasattr(request, "content_type") else request.META.get("CONTENT_TYPE") + headers = core.dispatch_with_results("django.extract_body").headers.value + try: + if content_type == "application/x-www-form-urlencoded": + req_body = parse_form_params(request.body.decode("UTF-8", errors="ignore")) + elif content_type == "multipart/form-data": + req_body = parse_form_multipart(request.body.decode("UTF-8", errors="ignore"), headers) + elif content_type in ("application/json", "text/json"): + req_body = json.loads(request.body.decode("UTF-8", errors="ignore")) + elif content_type in ("application/xml", "text/xml"): + req_body = xmltodict.parse(request.body.decode("UTF-8", errors="ignore")) + else: # text/plain, others: don't use them + req_body = None + except BaseException: + log.debug("Failed to parse request body", exc_info=True) + return req_body + + +def _get_request_headers(request): + # type: (Any) -> Mapping[str, str] + if DJANGO22: + request_headers = request.headers # type: Mapping[str, str] + else: + request_headers = {} # type: Mapping[str, str] + for header, value in request.META.items(): + name = from_wsgi_header(header) + if name: + request_headers[name] = value + + return request_headers + + +def _after_request_tags(pin, span: Span, request, response): + # Response can be None in the event that the request failed + # We still want to set additional request tags that are resolved + # during the request. + + try: + user = getattr(request, "user", None) + if user is not None: + # Note: getattr calls to user / user_is_authenticated may result in ImproperlyConfigured exceptions from + # Django's get_user_model(): + # https://github.com/django/django/blob/a464ead29db8bf6a27a5291cad9eb3f0f3f0472b/django/contrib/auth/__init__.py + # + # FIXME: getattr calls to user fail in async contexts. + # Sample Error: django.core.exceptions.SynchronousOnlyOperation: You cannot call this from an async context + # - use a thread or sync_to_async. + try: + if hasattr(user, "is_authenticated"): + span.set_tag_str("django.user.is_authenticated", str(user_is_authenticated(user))) + + uid = getattr(user, "pk", None) + if uid: + span.set_tag_str("django.user.id", str(uid)) + span.set_tag_str(_user.ID, str(uid)) + if config.django.include_user_name: + username = getattr(user, "username", None) + if username: + span.set_tag_str("django.user.name", username) + except Exception: + log.debug("Error retrieving authentication information for user", exc_info=True) + + # DEV: Resolve the view and resource name at the end of the request in case + # urlconf changes at any point during the request + _set_resolver_tags(pin, span, request) + if response: + status = response.status_code + span.set_tag_str("django.response.class", func_name(response)) + if hasattr(response, "template_name"): + # template_name is a bit of a misnomer, as it could be any of: + # a list of strings, a tuple of strings, a single string, or an instance of Template + # for more detail, see: + # https://docs.djangoproject.com/en/3.0/ref/template-response/#django.template.response.SimpleTemplateResponse.template_name + template = response.template_name + + if isinstance(template, str): + template_names = [template] + elif isinstance( + template, + ( + list, + tuple, + ), + ): + template_names = template + elif hasattr(template, "template"): + # ^ checking by attribute here because + # django backend implementations don't have a common base + # `.template` is also the most consistent across django versions + template_names = [template.template.name] + else: + template_names = None + + set_tag_array(span, "django.response.template", template_names) + + url = get_request_uri(request) + + request_headers = core.dispatch_with_results("django.after_request_headers").headers.value + if not request_headers: + request_headers = _get_request_headers(request) + + response_headers = dict(response.items()) if response else {} + + response_cookies = {} + if response.cookies: + for k, v in response.cookies.items(): + response_cookies[k] = v.OutputString() + + raw_uri = url + if raw_uri and request.META.get("QUERY_STRING"): + raw_uri += "?" + request.META["QUERY_STRING"] + + core.dispatch( + "django.after_request_headers.post", + ( + request_headers, + response_headers, + span, + config.django, + request, + url, + raw_uri, + status, + response_cookies, + ), + ) + content = getattr(response, "content", None) + if content is None: + content = getattr(response, "streaming_content", None) + core.dispatch("django.after_request_headers.finalize", (content, None)) + finally: + if span.resource == REQUEST_DEFAULT_RESOURCE: + span.resource = request.method + + +class DjangoViewProxy(FunctionWrapper): + """ + This custom function wrapper is used to wrap the callback passed to django views handlers (path/re_path/url). + This allows us to distinguish between wrapped django views and wrapped asgi applications in django channels. + """ + + @property + def __module__(self): + """ + DjangoViewProxy.__module__ defaults to ddtrace.contrib.django when a wrapped function does not have + a __module__ attribute. This method ensures that DjangoViewProxy.__module__ always returns the module + attribute of the wrapped function or an empty string if this attribute is not available. + The function Django.urls.path() does not have a __module__ attribute and would require this override + to resolve the correct module name. + """ + return self.__wrapped__.__module__ diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/dogpile_cache/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/dogpile_cache/__init__.py new file mode 100644 index 0000000..8666443 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/dogpile_cache/__init__.py @@ -0,0 +1,50 @@ +""" +Instrument dogpile.cache__ to report all cached lookups. + +This will add spans around the calls to your cache backend (e.g. redis, memory, +etc). The spans will also include the following tags: + +- key/keys: The key(s) dogpile passed to your backend. Note that this will be + the output of the region's ``function_key_generator``, but before any key + mangling is applied (i.e. the region's ``key_mangler``). +- region: Name of the region. +- backend: Name of the backend class. +- hit: If the key was found in the cache. +- expired: If the key is expired. This is only relevant if the key was found. + +While cache tracing will generally already have keys in tags, some caching +setups will not have useful tag values - such as when you're using consistent +hashing with memcached - the key(s) will appear as a mangled hash. +:: + + # Patch before importing dogpile.cache + from ddtrace import patch + patch(dogpile_cache=True) + + from dogpile.cache import make_region + + region = make_region().configure( + "dogpile.cache.pylibmc", + expiration_time=3600, + arguments={"url": ["127.0.0.1"]}, + ) + + @region.cache_on_arguments() + def hello(name): + # Some complicated, slow calculation + return "Hello, {}".format(name) + +.. __: https://dogpilecache.sqlalchemy.org/ +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["dogpile.cache"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/dogpile_cache/lock.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/dogpile_cache/lock.py new file mode 100644 index 0000000..8bf1c0e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/dogpile_cache/lock.py @@ -0,0 +1,39 @@ +import dogpile + +from ...internal.utils.formats import asbool +from ...pin import Pin + + +def _wrap_lock_ctor(func, instance, args, kwargs): + """ + This seems rather odd. But to track hits, we need to patch the wrapped function that + dogpile passes to the region and locks. Unfortunately it's a closure defined inside + the get_or_create* methods themselves, so we can't easily patch those. + """ + func(*args, **kwargs) + ori_backend_fetcher = instance.value_and_created_fn + + def wrapped_backend_fetcher(): + pin = Pin.get_from(dogpile.cache) + if not pin or not pin.enabled(): + return ori_backend_fetcher() + + hit = False + expired = True + try: + value, createdtime = ori_backend_fetcher() + hit = value is not dogpile.cache.api.NO_VALUE + # dogpile sometimes returns None, but only checks for truthiness. Coalesce + # to minimize APM users' confusion. + expired = instance._is_expired(createdtime) or False + return value, createdtime + finally: + # Keys are checked in random order so the 'final' answer for partial hits + # should really be false (ie. if any are 'negative', then the tag value + # should be). This means ANDing all hit values and ORing all expired values. + span = pin.tracer.current_span() + if span: + span.set_tag("hit", asbool(span.get_tag("hit") or "True") and hit) + span.set_tag("expired", asbool(span.get_tag("expired") or "False") or expired) + + instance.value_and_created_fn = wrapped_backend_fetcher diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/dogpile_cache/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/dogpile_cache/patch.py new file mode 100644 index 0000000..1621512 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/dogpile_cache/patch.py @@ -0,0 +1,52 @@ +try: + import dogpile.cache as dogpile_cache + import dogpile.lock as dogpile_lock +except AttributeError: + from dogpile import cache as dogpile_cache + from dogpile import lock as dogpile_lock + +from ddtrace.internal.schema import schematize_service_name +from ddtrace.pin import _DD_PIN_NAME +from ddtrace.pin import _DD_PIN_PROXY_NAME +from ddtrace.pin import Pin +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w + +from .lock import _wrap_lock_ctor +from .region import _wrap_get_create +from .region import _wrap_get_create_multi + + +_get_or_create = dogpile_cache.region.CacheRegion.get_or_create +_get_or_create_multi = dogpile_cache.region.CacheRegion.get_or_create_multi +_lock_ctor = dogpile_lock.Lock.__init__ + + +def get_version(): + # type: () -> str + return getattr(dogpile_cache, "__version__", "") + + +def patch(): + if getattr(dogpile_cache, "_datadog_patch", False): + return + dogpile_cache._datadog_patch = True + + _w("dogpile.cache.region", "CacheRegion.get_or_create", _wrap_get_create) + _w("dogpile.cache.region", "CacheRegion.get_or_create_multi", _wrap_get_create_multi) + _w("dogpile.lock", "Lock.__init__", _wrap_lock_ctor) + + Pin(service=schematize_service_name("dogpile.cache")).onto(dogpile_cache) + + +def unpatch(): + if not getattr(dogpile_cache, "_datadog_patch", False): + return + dogpile_cache._datadog_patch = False + # This looks silly but the unwrap util doesn't support class instance methods, even + # though wrapt does. This was causing the patches to stack on top of each other + # during testing. + dogpile_cache.region.CacheRegion.get_or_create = _get_or_create + dogpile_cache.region.CacheRegion.get_or_create_multi = _get_or_create_multi + dogpile_lock.Lock.__init__ = _lock_ctor + setattr(dogpile_cache, _DD_PIN_NAME, None) + setattr(dogpile_cache, _DD_PIN_PROXY_NAME, None) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/dogpile_cache/region.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/dogpile_cache/region.py new file mode 100644 index 0000000..28e8dbb --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/dogpile_cache/region.py @@ -0,0 +1,55 @@ +import dogpile + +from ddtrace.ext import SpanTypes +from ddtrace.internal.constants import COMPONENT + +from ...constants import SPAN_MEASURED_KEY +from ...ext import db +from ...internal.schema import schematize_cache_operation +from ...internal.schema import schematize_service_name +from ...internal.utils import get_argument_value +from ...pin import Pin + + +def _wrap_get_create(func, instance, args, kwargs): + pin = Pin.get_from(dogpile.cache) + if not pin or not pin.enabled(): + return func(*args, **kwargs) + + key = get_argument_value(args, kwargs, 0, "key") + with pin.tracer.trace( + schematize_cache_operation("dogpile.cache", cache_provider="dogpile"), + service=schematize_service_name(None), + resource="get_or_create", + span_type=SpanTypes.CACHE, + ) as span: + span.set_tag_str(COMPONENT, "dogpile_cache") + span.set_tag(SPAN_MEASURED_KEY) + span.set_tag("key", key) + span.set_tag("region", instance.name) + span.set_tag("backend", instance.actual_backend.__class__.__name__) + response = func(*args, **kwargs) + span.set_metric(db.ROWCOUNT, 1) + return response + + +def _wrap_get_create_multi(func, instance, args, kwargs): + pin = Pin.get_from(dogpile.cache) + if not pin or not pin.enabled(): + return func(*args, **kwargs) + + keys = get_argument_value(args, kwargs, 0, "keys") + with pin.tracer.trace( + schematize_cache_operation("dogpile.cache", cache_provider="dogpile"), + service=schematize_service_name(None), + resource="get_or_create_multi", + span_type="cache", + ) as span: + span.set_tag_str(COMPONENT, "dogpile_cache") + span.set_tag(SPAN_MEASURED_KEY) + span.set_tag("keys", keys) + span.set_tag("region", instance.name) + span.set_tag("backend", instance.actual_backend.__class__.__name__) + response = func(*args, **kwargs) + span.set_metric(db.ROWCOUNT, len(response)) + return response diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/elasticsearch/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/elasticsearch/__init__.py new file mode 100644 index 0000000..bd0deb9 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/elasticsearch/__init__.py @@ -0,0 +1,57 @@ +""" +The Elasticsearch integration will trace Elasticsearch queries. + +Enabling +~~~~~~~~ + +The elasticsearch integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + from elasticsearch import Elasticsearch + + patch(elasticsearch=True) + # This will report spans with the default instrumentation + es = Elasticsearch(port=ELASTICSEARCH_CONFIG['port']) + # Example of instrumented query + es.indices.create(index='books', ignore=400) + + # Use a pin to specify metadata related to this client + es = Elasticsearch(port=ELASTICSEARCH_CONFIG['port']) + Pin.override(es.transport, service='elasticsearch-videos') + es.indices.create(index='videos', ignore=400) + +OpenSearch is also supported (`opensearch-py`):: + + from ddtrace import patch + from opensearchpy import OpenSearch + + patch(elasticsearch=True) + os = OpenSearch() + # Example of instrumented query + os.indices.create(index='books', ignore=400) + + +Configuration +~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.elasticsearch['service'] + + The service name reported for your elasticsearch app. + + +Example:: + + from ddtrace import config + + # Override service name + config.elasticsearch['service'] = 'custom-service-name' +""" +from .patch import get_version +from .patch import get_versions +from .patch import patch + + +__all__ = ["patch", "get_version", "get_versions"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/elasticsearch/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/elasticsearch/patch.py new file mode 100644 index 0000000..f23a997 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/elasticsearch/patch.py @@ -0,0 +1,269 @@ +from importlib import import_module +from typing import List # noqa:F401 + +from ddtrace import config +from ddtrace._trace import _limits +from ddtrace.contrib.trace_utils import ext_service +from ddtrace.contrib.trace_utils import extract_netloc_and_query_info_from_url +from ddtrace.ext import net +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.logger import get_logger +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w + +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from ...ext import elasticsearch as metadata +from ...ext import http +from ...internal.compat import parse +from ...internal.schema import schematize_service_name +from ...internal.utils.wrappers import unwrap as _u +from ...pin import Pin +from .quantize import quantize + + +log = get_logger(__name__) + +config._add( + "elasticsearch", + { + "_default_service": schematize_service_name("elasticsearch"), + }, +) + + +def _es_modules(): + module_names = ( + "elasticsearch", + "elasticsearch1", + "elasticsearch2", + "elasticsearch5", + "elasticsearch6", + "elasticsearch7", + # Starting with version 8, the default transport which is what we + # actually patch is found in the separate elastic_transport package + "elastic_transport", + "opensearchpy", + ) + for module_name in module_names: + try: + module = import_module(module_name) + versions[module_name] = getattr(module, "__versionstr__", "") + yield module + except ImportError: + pass + + +versions = {} + + +def get_version_tuple(elasticsearch): + return getattr(elasticsearch, "__version__", "") + + +def get_version(): + # type: () -> str + return "" + + +def get_versions(): + # type: () -> List[str] + return versions + + +def _get_transport_module(elasticsearch): + try: + # elasticsearch7/opensearch async + return elasticsearch._async.transport + except AttributeError: + try: + # elasticsearch<8/opensearch sync + return elasticsearch.transport + except AttributeError: + # elastic_transport (elasticsearch8) + return elasticsearch + + +# NB: We are patching the default elasticsearch transport module +def patch(): + for elasticsearch in _es_modules(): + _patch(_get_transport_module(elasticsearch)) + + +def _patch(transport): + if getattr(transport, "_datadog_patch", False): + return + if hasattr(transport, "Transport"): + transport._datadog_patch = True + _w(transport.Transport, "perform_request", _get_perform_request(transport)) + Pin().onto(transport.Transport) + if hasattr(transport, "AsyncTransport"): + transport._datadog_patch = True + _w(transport.AsyncTransport, "perform_request", _get_perform_request_async(transport)) + Pin().onto(transport.AsyncTransport) + + +def unpatch(): + for elasticsearch in _es_modules(): + _unpatch(_get_transport_module(elasticsearch)) + + +def _unpatch(transport): + if not getattr(transport, "_datadog_patch", False): + return + for classname in ("Transport", "AsyncTransport"): + try: + cls = getattr(transport, classname) + except AttributeError: + continue + transport._datadog_patch = False + _u(cls, "perform_request") + + +def _get_perform_request_coro(transport): + def _perform_request(func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + yield func(*args, **kwargs) + return + + with pin.tracer.trace( + "elasticsearch.query", service=ext_service(pin, config.elasticsearch), span_type=SpanTypes.ELASTICSEARCH + ) as span: + if pin.tags: + span.set_tags(pin.tags) + + span.set_tag_str(COMPONENT, config.elasticsearch.integration_name) + + # set span.kind to the type of request being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + span.set_tag(SPAN_MEASURED_KEY) + + # Don't instrument if the trace is not sampled + if not span.sampled: + yield func(*args, **kwargs) + return + + method, target = args + params = kwargs.get("params") + body = kwargs.get("body") + + # elastic_transport gets target url with query params already appended + parsed = parse.urlparse(target) + url = parsed.path + if params: + encoded_params = parse.urlencode(params) + else: + encoded_params = parsed.query + + span.set_tag_str(metadata.METHOD, method) + span.set_tag_str(metadata.URL, url) + span.set_tag_str(metadata.PARAMS, encoded_params) + try: + # elasticsearch<8 + connections = instance.connection_pool.connections + except AttributeError: + # elastic_transport + connections = instance.node_pool.all() + for connection in connections: + hostname, _ = extract_netloc_and_query_info_from_url(connection.host) + if hostname: + span.set_tag_str(net.TARGET_HOST, hostname) + break + + if config.elasticsearch.trace_query_string: + span.set_tag_str(http.QUERY_STRING, encoded_params) + + if method in ["GET", "POST"]: + try: + # elasticsearch<8 + ser_body = instance.serializer.dumps(body) + except AttributeError: + # elastic_transport + ser_body = instance.serializers.dumps(body) + # Elasticsearch request bodies can be very large resulting in traces being too large + # to send. + # When this occurs, drop the value. + # Ideally the body should be truncated, however we cannot truncate as the obfuscation + # logic for the body lives in the agent and truncating would make the body undecodable. + if len(ser_body) <= _limits.MAX_SPAN_META_VALUE_LEN: + span.set_tag_str(metadata.BODY, ser_body) + else: + span.set_tag_str( + metadata.BODY, + "" % (len(ser_body), _limits.MAX_SPAN_META_VALUE_LEN), + ) + status = None + + # set analytics sample rate + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.elasticsearch.get_analytics_sample_rate()) + + span = quantize(span) + + try: + result = yield func(*args, **kwargs) + except transport.TransportError as e: + span.set_tag(http.STATUS_CODE, getattr(e, "status_code", 500)) + span.error = 1 + raise + + try: + # Optional metadata extraction with soft fail. + if isinstance(result, tuple): + try: + # elastic_transport returns a named tuple + meta, data = result.meta, result.body + status = meta.status + except AttributeError: + # elasticsearch<2.4; it returns both the status and the body + status, data = result + else: + # elasticsearch>=2.4,<8; internal change for ``Transport.perform_request`` + # that just returns the body + data = result + + took = data.get("took") + if took: + span.set_metric(metadata.TOOK, int(took)) + except Exception: + log.debug("Unexpected exception", exc_info=True) + + if status: + span.set_tag(http.STATUS_CODE, status) + + return + + return _perform_request + + +def _get_perform_request(transport): + _perform_request_coro = _get_perform_request_coro(transport) + + def _perform_request(func, instance, args, kwargs): + coro = _perform_request_coro(func, instance, args, kwargs) + result = next(coro) + try: + coro.send(result) + except StopIteration: + pass + return result + + return _perform_request + + +def _get_perform_request_async(transport): + _perform_request_coro = _get_perform_request_coro(transport) + + async def _perform_request(func, instance, args, kwargs): + coro = _perform_request_coro(func, instance, args, kwargs) + result = await next(coro) + try: + coro.send(result) + except StopIteration: + pass + return result + + return _perform_request diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/elasticsearch/quantize.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/elasticsearch/quantize.py new file mode 100644 index 0000000..5b526a3 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/elasticsearch/quantize.py @@ -0,0 +1,35 @@ +import re + +from ...ext import elasticsearch as metadata + + +# Replace any ID +ID_REGEXP = re.compile(r"/([0-9]+)([/\?]|$)") +ID_PLACEHOLDER = r"/?\2" + +# Remove digits from potential timestamped indexes (should be an option). +# For now, let's say 2+ digits +INDEX_REGEXP = re.compile(r"[0-9]{2,}") +INDEX_PLACEHOLDER = r"?" + + +def quantize(span): + """Quantize an elasticsearch span + + We want to extract a meaningful `resource` from the request. + We do it based on the method + url, with some cleanup applied to the URL. + + The URL might a ID, but also it is common to have timestamped indexes. + While the first is easy to catch, the second should probably be configurable. + + All of this should probably be done in the Agent. Later. + """ + url = span.get_tag(metadata.URL) + method = span.get_tag(metadata.METHOD) + + quantized_url = ID_REGEXP.sub(ID_PLACEHOLDER, url) + quantized_url = INDEX_REGEXP.sub(INDEX_PLACEHOLDER, quantized_url) + + span.resource = "{method} {url}".format(method=method, url=quantized_url) + + return span diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/falcon/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/falcon/__init__.py new file mode 100644 index 0000000..41fdf55 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/falcon/__init__.py @@ -0,0 +1,58 @@ +""" +To trace the falcon web framework, install the trace middleware:: + + import falcon + from ddtrace import tracer + from ddtrace.contrib.falcon import TraceMiddleware + + mw = TraceMiddleware(tracer, 'my-falcon-app') + falcon.API(middleware=[mw]) + +You can also use the autopatching functionality:: + + import falcon + from ddtrace import tracer, patch + + patch(falcon=True) + + app = falcon.API() + +To disable distributed tracing when using autopatching, set the +``DD_FALCON_DISTRIBUTED_TRACING`` environment variable to ``False``. + +**Supported span hooks** + +The following is a list of available tracer hooks that can be used to intercept +and modify spans created by this integration. + +- ``request`` + - Called before the response has been finished + - ``def on_falcon_request(span, request, response)`` + + +Example:: + + import ddtrace.auto + import falcon + from ddtrace import config + + app = falcon.API() + + @config.falcon.hooks.on('request') + def on_falcon_request(span, request, response): + span.set_tag('my.custom', 'tag') + +:ref:`Headers tracing ` is supported for this integration. +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["falcon"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .middleware import TraceMiddleware + from .patch import get_version + from .patch import patch + + __all__ = ["TraceMiddleware", "patch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/falcon/middleware.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/falcon/middleware.py new file mode 100644 index 0000000..ba398f0 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/falcon/middleware.py @@ -0,0 +1,123 @@ +import sys + +from ddtrace import config +from ddtrace.ext import SpanKind +from ddtrace.ext import SpanTypes +from ddtrace.ext import http as httpx +from ddtrace.internal.constants import COMPONENT + +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...internal.schema import SpanDirection +from ...internal.schema import schematize_service_name +from ...internal.schema import schematize_url_operation +from .. import trace_utils + + +class TraceMiddleware(object): + def __init__(self, tracer, service=None, distributed_tracing=None): + if service is None: + service = schematize_service_name("falcon") + # store tracing references + self.tracer = tracer + self.service = service + if distributed_tracing is not None: + config.falcon["distributed_tracing"] = distributed_tracing + + def process_request(self, req, resp): + # Falcon uppercases all header names. + headers = dict((k.lower(), v) for k, v in req.headers.items()) + trace_utils.activate_distributed_headers(self.tracer, int_config=config.falcon, request_headers=headers) + + span = self.tracer.trace( + schematize_url_operation("falcon.request", protocol="http", direction=SpanDirection.INBOUND), + service=self.service, + span_type=SpanTypes.WEB, + ) + span.set_tag_str(COMPONENT, config.falcon.integration_name) + + # set span.kind to the type of operation being performed + span.set_tag_str(SPAN_KIND, SpanKind.SERVER) + + span.set_tag(SPAN_MEASURED_KEY) + + # set analytics sample rate with global config enabled + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.falcon.get_analytics_sample_rate(use_global_config=True)) + + trace_utils.set_http_meta( + span, config.falcon, method=req.method, url=req.url, query=req.query_string, request_headers=req.headers + ) + + def process_resource(self, req, resp, resource, params): + span = self.tracer.current_span() + if not span: + return # unexpected + span.resource = "%s %s" % (req.method, _name(resource)) + + def process_response(self, req, resp, resource, req_succeeded=None): + # req_succeded is not a kwarg in the API, but we need that to support + # Falcon 1.0 that doesn't provide this argument + span = self.tracer.current_span() + if not span: + return # unexpected + + status = resp.status.partition(" ")[0] + + # falcon does not map errors or unmatched routes + # to proper status codes, so we have to try to infer them + # here. + if resource is None: + status = "404" + span.resource = "%s 404" % req.method + span.set_tag(httpx.STATUS_CODE, status) + span.finish() + return + + err_type = sys.exc_info()[0] + if err_type is not None: + if req_succeeded is None: + # backward-compatibility with Falcon 1.0; any version + # greater than 1.0 has req_succeded in [True, False] + # TODO[manu]: drop the support at some point + status = _detect_and_set_status_error(err_type, span) + elif req_succeeded is False: + # Falcon 1.1+ provides that argument that is set to False + # if get an Exception (404 is still an exception) + status = _detect_and_set_status_error(err_type, span) + + route = req.root_path or "" + req.uri_template + + trace_utils.set_http_meta( + span, + config.falcon, + status_code=status, + response_headers=resp._headers, + route=route, + ) + + # Emit span hook for this response + # DEV: Emit before closing so they can overwrite `span.resource` if they want + config.falcon.hooks.emit("request", span, req, resp) + + # Close the span + span.finish() + + +def _is_404(err_type): + return "HTTPNotFound" in err_type.__name__ + + +def _detect_and_set_status_error(err_type, span): + """Detect the HTTP status code from the current stacktrace and + set the traceback to the given Span + """ + if not _is_404(err_type): + span.set_traceback() + return "500" + elif _is_404(err_type): + return "404" + + +def _name(r): + return "%s.%s" % (r.__module__, r.__class__.__name__) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/falcon/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/falcon/patch.py new file mode 100644 index 0000000..d8deeb6 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/falcon/patch.py @@ -0,0 +1,52 @@ +import os + +import falcon + +from ddtrace import config +from ddtrace import tracer +from ddtrace.vendor import wrapt + +from ...internal.utils.formats import asbool +from ...internal.utils.version import parse_version +from .middleware import TraceMiddleware + + +FALCON_VERSION = parse_version(falcon.__version__) + + +config._add( + "falcon", + dict( + distributed_tracing=asbool(os.getenv("DD_FALCON_DISTRIBUTED_TRACING", default=True)), + ), +) + + +def get_version(): + # type: () -> str + return getattr(falcon, "__version__", "") + + +def patch(): + """ + Patch falcon.API to include contrib.falcon.TraceMiddleware + by default + """ + if getattr(falcon, "_datadog_patch", False): + return + + falcon._datadog_patch = True + if FALCON_VERSION >= (3, 0, 0): + wrapt.wrap_function_wrapper("falcon", "App.__init__", traced_init) + if FALCON_VERSION < (4, 0, 0): + wrapt.wrap_function_wrapper("falcon", "API.__init__", traced_init) + + +def traced_init(wrapped, instance, args, kwargs): + mw = kwargs.pop("middleware", []) + service = config._get_service(default="falcon") + + mw.insert(0, TraceMiddleware(tracer, service)) + kwargs["middleware"] = mw + + wrapped(*args, **kwargs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/fastapi/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/fastapi/__init__.py new file mode 100644 index 0000000..015d2cb --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/fastapi/__init__.py @@ -0,0 +1,72 @@ +""" +The fastapi integration will trace requests to and from FastAPI. + +Enabling +~~~~~~~~ + +The fastapi integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + from fastapi import FastAPI + + patch(fastapi=True) + app = FastAPI() + + +On Python 3.6 and below, you must enable the legacy ``AsyncioContextProvider`` before using the middleware:: + + from ddtrace.contrib.asyncio.provider import AsyncioContextProvider + from ddtrace import tracer # Or whichever tracer instance you plan to use + tracer.configure(context_provider=AsyncioContextProvider()) + + +When registering your own ASGI middleware using FastAPI's ``add_middleware()`` function, +keep in mind that Datadog spans close after your middleware's call to ``await self.app()`` returns. +This means that accesses of span data from within the middleware should be performed +prior to this call. + + +Configuration +~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.fastapi['service_name'] + + The service name reported for your fastapi app. + + Can also be configured via the ``DD_SERVICE`` environment variable. + + Default: ``'fastapi'`` + +.. py:data:: ddtrace.config.fastapi['request_span_name'] + + The span name for a fastapi request. + + Default: ``'fastapi.request'`` + + +Example:: + + from ddtrace import config + + # Override service name + config.fastapi['service_name'] = 'custom-service-name' + + # Override request span name + config.fastapi['request_span_name'] = 'custom-request-span-name' + +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["fastapi"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/fastapi/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/fastapi/patch.py new file mode 100644 index 0000000..60d67e4 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/fastapi/patch.py @@ -0,0 +1,100 @@ +import fastapi +import fastapi.routing + +from ddtrace import Pin +from ddtrace import config +from ddtrace.contrib.asgi.middleware import TraceMiddleware +from ddtrace.contrib.starlette.patch import _trace_background_tasks +from ddtrace.contrib.starlette.patch import traced_handler +from ddtrace.internal.logger import get_logger +from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.utils.wrappers import unwrap as _u +from ddtrace.vendor.wrapt import ObjectProxy +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w + + +log = get_logger(__name__) + +config._add( + "fastapi", + dict( + _default_service=schematize_service_name("fastapi"), + request_span_name="fastapi.request", + distributed_tracing=True, + trace_query_string=None, # Default to global config + ), +) + + +def get_version(): + # type: () -> str + return getattr(fastapi, "__version__", "") + + +def wrap_middleware_stack(wrapped, instance, args, kwargs): + return TraceMiddleware(app=wrapped(*args, **kwargs), integration_config=config.fastapi) + + +async def traced_serialize_response(wrapped, instance, args, kwargs): + """Wrapper for fastapi.routing.serialize_response function. + + This function is called on all non-Response objects to + convert them to a serializable form. + + This is the wrapper which calls ``jsonable_encoder``. + + This function does not do the actual encoding from + obj -> json string (e.g. json.dumps()). That is handled + by the Response.render function. + + DEV: We do not wrap ``jsonable_encoder`` because it calls + itself recursively, so there is a chance the overhead + added by creating spans will be higher than desired for + the result. + """ + pin = Pin.get_from(fastapi) + if not pin or not pin.enabled(): + return await wrapped(*args, **kwargs) + + with pin.tracer.trace("fastapi.serialize_response"): + return await wrapped(*args, **kwargs) + + +def patch(): + if getattr(fastapi, "_datadog_patch", False): + return + + fastapi._datadog_patch = True + Pin().onto(fastapi) + _w("fastapi.applications", "FastAPI.build_middleware_stack", wrap_middleware_stack) + _w("fastapi.routing", "serialize_response", traced_serialize_response) + + if not isinstance(fastapi.BackgroundTasks.add_task, ObjectProxy): + _w("fastapi", "BackgroundTasks.add_task", _trace_background_tasks(fastapi)) + + # We need to check that Starlette instrumentation hasn't already patched these + if not isinstance(fastapi.routing.APIRoute.handle, ObjectProxy): + _w("fastapi.routing", "APIRoute.handle", traced_handler) + + if not isinstance(fastapi.routing.Mount.handle, ObjectProxy): + _w("starlette.routing", "Mount.handle", traced_handler) + + +def unpatch(): + if not getattr(fastapi, "_datadog_patch", False): + return + + fastapi._datadog_patch = False + + _u(fastapi.applications.FastAPI, "build_middleware_stack") + _u(fastapi.routing, "serialize_response") + + # We need to check that Starlette instrumentation hasn't already unpatched these + if isinstance(fastapi.routing.APIRoute.handle, ObjectProxy): + _u(fastapi.routing.APIRoute, "handle") + + if isinstance(fastapi.routing.Mount.handle, ObjectProxy): + _u(fastapi.routing.Mount, "handle") + + if isinstance(fastapi.BackgroundTasks.add_task, ObjectProxy): + _u(fastapi.BackgroundTasks, "add_task") diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask/__init__.py new file mode 100644 index 0000000..c3a619a --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask/__init__.py @@ -0,0 +1,113 @@ +""" +The Flask__ integration will add tracing to all requests to your Flask application. + +This integration will track the entire Flask lifecycle including user-defined endpoints, hooks, +signals, and template rendering. + +To configure tracing manually:: + + import ddtrace.auto + + from flask import Flask + + app = Flask(__name__) + + + @app.route('/') + def index(): + return 'hello world' + + + if __name__ == '__main__': + app.run() + + +You may also enable Flask tracing automatically via ddtrace-run:: + + ddtrace-run python app.py + +Note that if you are using IAST/Custom Code to detect vulnerabilities (`DD_IAST_ENABLED=1`) +and your main `app.py` file contains code outside the `app.run()` call (e.g. routes or +utility functions) you will need to import and call `ddtrace_iast_flask_patch()` before +the `app.run()` to ensure the code inside the main module is patched to propagation works: + + from flask import Flask + from ddtrace.appsec._iast import ddtrace_iast_flask_patch + + app = Flask(__name__) + + if __name__ == '__main__': + ddtrace_iast_flask_patch() + app.run() + + +Configuration +~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.flask['distributed_tracing_enabled'] + + Whether to parse distributed tracing headers from requests received by your Flask app. + + Default: ``True`` + +.. py:data:: ddtrace.config.flask['service_name'] + + The service name reported for your Flask app. + + Can also be configured via the ``DD_SERVICE`` environment variable. + + Default: ``'flask'`` + +.. py:data:: ddtrace.config.flask['collect_view_args'] + + Whether to add request tags for view function argument values. + + Default: ``True`` + +.. py:data:: ddtrace.config.flask['template_default_name'] + + The default template name to use when one does not exist. + + Default: ```` + +.. py:data:: ddtrace.config.flask['trace_signals'] + + Whether to trace Flask signals (``before_request``, ``after_request``, etc). + + Default: ``True`` + + +Example:: + + from ddtrace import config + + # Enable distributed tracing + config.flask['distributed_tracing_enabled'] = True + + # Override service name + config.flask['service_name'] = 'custom-service-name' + + # Report 401, and 403 responses as errors + config.http_server.error_statuses = '401,403' + +.. __: http://flask.pocoo.org/ + +:ref:`All HTTP tags ` are supported for this integration. + +""" + +from ...internal.utils.importlib import require_modules + + +required_modules = ["flask"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + # DEV: We do this so we can `@mock.patch('ddtrace.contrib.flask._patch.')` in tests + from . import patch as _patch + + patch = _patch.patch + unpatch = _patch.unpatch + get_version = _patch.get_version + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask/patch.py new file mode 100644 index 0000000..c062674 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask/patch.py @@ -0,0 +1,559 @@ +import flask +import werkzeug +from werkzeug.exceptions import BadRequest +from werkzeug.exceptions import NotFound +from werkzeug.exceptions import abort + +from ddtrace.contrib import trace_utils +from ddtrace.ext import SpanTypes +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.constants import HTTP_REQUEST_BLOCKED +from ddtrace.internal.constants import STATUS_403_TYPE_AUTO +from ddtrace.internal.schema.span_attribute_schema import SpanDirection + +from ...internal import core +from ...internal.schema import schematize_service_name +from ...internal.schema import schematize_url_operation +from ...internal.utils import http as http_utils + + +# Not all versions of flask/werkzeug have this mixin +try: + from werkzeug.wrappers.json import JSONMixin + + _HAS_JSON_MIXIN = True +except ImportError: + _HAS_JSON_MIXIN = False + +from ddtrace import Pin +from ddtrace import config +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w + +from ...contrib.wsgi.wsgi import _DDWSGIMiddlewareBase +from ...internal.logger import get_logger +from ...internal.utils import get_argument_value +from ...internal.utils.importlib import func_name +from ...internal.utils.version import parse_version +from ..trace_utils import unwrap as _u +from .wrappers import _wrap_call_with_pin_check +from .wrappers import get_current_app +from .wrappers import simple_call_wrapper +from .wrappers import with_instance_pin +from .wrappers import wrap_function +from .wrappers import wrap_view + + +try: + from json import JSONDecodeError +except ImportError: + # handling python 2.X import error + JSONDecodeError = ValueError # type: ignore + + +log = get_logger(__name__) + +FLASK_VERSION = "flask.version" +_BODY_METHODS = {"POST", "PUT", "DELETE", "PATCH"} + +# Configure default configuration +config._add( + "flask", + dict( + # Flask service configuration + _default_service=schematize_service_name("flask"), + collect_view_args=True, + distributed_tracing_enabled=True, + template_default_name="", + trace_signals=True, + ), +) + + +def get_version(): + # type: () -> str + return getattr(flask, "__version__", "") + + +if _HAS_JSON_MIXIN: + + class RequestWithJson(werkzeug.Request, JSONMixin): + pass + + _RequestType = RequestWithJson +else: + _RequestType = werkzeug.Request + +# Extract flask version into a tuple e.g. (0, 12, 1) or (1, 0, 2) +# DEV: This makes it so we can do `if flask_version >= (0, 12, 0):` +# DEV: Example tests: +# (0, 10, 0) > (0, 10) +# (0, 10, 0) >= (0, 10, 0) +# (0, 10, 1) >= (0, 10) +# (0, 11, 1) >= (0, 10) +# (0, 11, 1) >= (0, 10, 2) +# (1, 0, 0) >= (0, 10) +# (0, 9) == (0, 9) +# (0, 9, 0) != (0, 9) +# (0, 8, 5) <= (0, 9) +flask_version_str = getattr(flask, "__version__", "") +flask_version = parse_version(flask_version_str) + + +class _FlaskWSGIMiddleware(_DDWSGIMiddlewareBase): + _request_call_name = schematize_url_operation("flask.request", protocol="http", direction=SpanDirection.INBOUND) + _application_call_name = "flask.application" + _response_call_name = "flask.response" + + def _wrapped_start_response(self, start_response, ctx, status_code, headers, exc_info=None): + core.dispatch("flask.start_response.pre", (flask.request, ctx, config.flask, status_code, headers)) + if not core.get_item(HTTP_REQUEST_BLOCKED): + headers_from_context = "" + result = core.dispatch_with_results("flask.start_response", ("Flask",)).waf + if result: + headers_from_context = result.value + if core.get_item(HTTP_REQUEST_BLOCKED): + # response code must be set here, or it will be too late + block_config = core.get_item(HTTP_REQUEST_BLOCKED) + desired_type = block_config.get("type", "auto") + status = block_config.get("status_code", 403) + if desired_type == "none": + response_headers = [] + else: + if block_config.get("type", "auto") == "auto": + ctype = "text/html" if "text/html" in headers_from_context else "text/json" + else: + ctype = "text/" + block_config["type"] + response_headers = [("content-type", ctype)] + result = start_response(str(status), response_headers) + core.dispatch("flask.start_response.blocked", (ctx, config.flask, response_headers, status)) + else: + result = start_response(status_code, headers) + else: + result = start_response(status_code, headers) + return result + + def _request_call_modifier(self, ctx, parsed_headers=None): + environ = ctx.get_item("environ") + # Create a werkzeug request from the `environ` to make interacting with it easier + # DEV: This executes before a request context is created + request = _RequestType(environ) + + req_body = None + result = core.dispatch_with_results( + "flask.request_call_modifier", + ( + ctx, + config.flask, + request, + environ, + _HAS_JSON_MIXIN, + FLASK_VERSION, + flask_version_str, + BadRequest, + ), + ).request_body + if result: + req_body = result.value + core.dispatch("flask.request_call_modifier.post", (ctx, config.flask, request, req_body)) + + +def patch(): + """ + Patch `flask` module for tracing + """ + # Check to see if we have patched Flask yet or not + if getattr(flask, "_datadog_patch", False): + return + flask._datadog_patch = True + + Pin().onto(flask.Flask) + core.dispatch("flask.patch", (flask_version,)) + # flask.app.Flask methods that have custom tracing (add metadata, wrap functions, etc) + _w("flask", "Flask.wsgi_app", patched_wsgi_app) + _w("flask", "Flask.dispatch_request", request_patcher("dispatch_request")) + _w("flask", "Flask.preprocess_request", request_patcher("preprocess_request")) + _w("flask", "Flask.add_url_rule", patched_add_url_rule) + _w("flask", "Flask.endpoint", patched_endpoint) + + _w("flask", "Flask.finalize_request", patched_finalize_request) + + if flask_version >= (2, 0, 0): + _w("flask", "Flask.register_error_handler", patched_register_error_handler) + else: + _w("flask", "Flask._register_error_handler", patched__register_error_handler) + + # flask.blueprints.Blueprint methods that have custom tracing (add metadata, wrap functions, etc) + _w("flask", "Blueprint.register", patched_blueprint_register) + _w("flask", "Blueprint.add_url_rule", patched_blueprint_add_url_rule) + + flask_hooks = [ + "before_request", + "after_request", + "teardown_request", + "teardown_appcontext", + ] + if flask_version < (2, 3, 0): + flask_hooks.append("before_first_request") + + for hook in flask_hooks: + _w("flask", "Flask.{}".format(hook), patched_flask_hook) + _w("flask", "after_this_request", patched_flask_hook) + + flask_app_traces = [ + "process_response", + "handle_exception", + "handle_http_exception", + "handle_user_exception", + "do_teardown_request", + "do_teardown_appcontext", + "send_static_file", + ] + if flask_version < (2, 2, 0): + flask_app_traces.append("try_trigger_before_first_request_functions") + + for name in flask_app_traces: + _w("flask", "Flask.{}".format(name), simple_call_wrapper("flask.{}".format(name))) + # flask static file helpers + _w("flask", "send_file", simple_call_wrapper("flask.send_file")) + + # flask.json.jsonify + _w("flask", "jsonify", patched_jsonify) + + _w("flask.templating", "_render", patched_render) + _w("flask", "render_template", _build_render_template_wrapper("render_template")) + _w("flask", "render_template_string", _build_render_template_wrapper("render_template_string")) + + bp_hooks = [ + "after_app_request", + "after_request", + "before_app_request", + "before_request", + "teardown_request", + "teardown_app_request", + ] + if flask_version < (2, 3, 0): + bp_hooks.append("before_app_first_request") + + for hook in bp_hooks: + _w("flask", "Blueprint.{}".format(hook), patched_flask_hook) + + if config.flask["trace_signals"]: + signals = [ + "template_rendered", + "request_started", + "request_finished", + "request_tearing_down", + "got_request_exception", + "appcontext_tearing_down", + ] + # These were added in 0.11.0 + if flask_version >= (0, 11): + signals.append("before_render_template") + + # These were added in 0.10.0 + if flask_version >= (0, 10): + signals.append("appcontext_pushed") + signals.append("appcontext_popped") + signals.append("message_flashed") + + for signal in signals: + module = "flask" + + # v0.9 missed importing `appcontext_tearing_down` in `flask/__init__.py` + # https://github.com/pallets/flask/blob/0.9/flask/__init__.py#L35-L37 + # https://github.com/pallets/flask/blob/0.9/flask/signals.py#L52 + # DEV: Version 0.9 doesn't have a patch version + if flask_version <= (0, 9) and signal == "appcontext_tearing_down": + module = "flask.signals" + + # DEV: Patch `receivers_for` instead of `connect` to ensure we don't mess with `disconnect` + _w(module, "{}.receivers_for".format(signal), patched_signal_receivers_for(signal)) + + +def unpatch(): + if not getattr(flask, "_datadog_patch", False): + return + flask._datadog_patch = False + + props = [ + # Flask + "Flask.wsgi_app", + "Flask.dispatch_request", + "Flask.add_url_rule", + "Flask.endpoint", + "Flask.preprocess_request", + "Flask.process_response", + "Flask.handle_exception", + "Flask.handle_http_exception", + "Flask.handle_user_exception", + "Flask.do_teardown_request", + "Flask.do_teardown_appcontext", + "Flask.send_static_file", + # Flask Hooks + "Flask.before_request", + "Flask.after_request", + "Flask.teardown_request", + "Flask.teardown_appcontext", + # Blueprint + "Blueprint.register", + "Blueprint.add_url_rule", + # Blueprint Hooks + "Blueprint.after_app_request", + "Blueprint.after_request", + "Blueprint.before_app_request", + "Blueprint.before_request", + "Blueprint.teardown_request", + "Blueprint.teardown_app_request", + # Signals + "template_rendered.receivers_for", + "request_started.receivers_for", + "request_finished.receivers_for", + "request_tearing_down.receivers_for", + "got_request_exception.receivers_for", + "appcontext_tearing_down.receivers_for", + # Top level props + "after_this_request", + "send_file", + "jsonify", + "render_template", + "render_template_string", + "templating._render", + ] + + props.append("Flask.finalize_request") + + if flask_version >= (2, 0, 0): + props.append("Flask.register_error_handler") + else: + props.append("Flask._register_error_handler") + + # These were added in 0.11.0 + if flask_version >= (0, 11): + props.append("before_render_template.receivers_for") + + # These were added in 0.10.0 + if flask_version >= (0, 10): + props.append("appcontext_pushed.receivers_for") + props.append("appcontext_popped.receivers_for") + props.append("message_flashed.receivers_for") + + # These were removed in 2.2.0 + if flask_version < (2, 2, 0): + props.append("Flask.try_trigger_before_first_request_functions") + + # These were removed in 2.3.0 + if flask_version < (2, 3, 0): + props.append("Flask.before_first_request") + props.append("Blueprint.before_app_first_request") + + for prop in props: + # Handle 'flask.request_started.receivers_for' + obj = flask + + # v0.9.0 missed importing `appcontext_tearing_down` in `flask/__init__.py` + # https://github.com/pallets/flask/blob/0.9/flask/__init__.py#L35-L37 + # https://github.com/pallets/flask/blob/0.9/flask/signals.py#L52 + # DEV: Version 0.9 doesn't have a patch version + if flask_version <= (0, 9) and prop == "appcontext_tearing_down.receivers_for": + obj = flask.signals + + if "." in prop: + attr, _, prop = prop.partition(".") + obj = getattr(obj, attr, object()) + _u(obj, prop) + + +@with_instance_pin +def patched_wsgi_app(pin, wrapped, instance, args, kwargs): + # This wrapper is the starting point for all requests. + # DEV: This is safe before this is the args for a WSGI handler + # https://www.python.org/dev/peps/pep-3333/ + environ, start_response = args + middleware = _FlaskWSGIMiddleware(wrapped, pin.tracer, config.flask, pin) + return middleware(environ, start_response) + + +def patched_finalize_request(wrapped, instance, args, kwargs): + """ + Wrapper for flask.app.Flask.finalize_request + """ + rv = wrapped(*args, **kwargs) + response = None + headers = None + if getattr(rv, "is_sequence", False): + response = rv.response + headers = rv.headers + core.dispatch("flask.finalize_request.post", (response, headers)) + return rv + + +def patched_blueprint_register(wrapped, instance, args, kwargs): + """ + Wrapper for flask.blueprints.Blueprint.register + + This wrapper just ensures the blueprint has a pin, either set manually on + itself from the user or inherited from the application + """ + app = get_argument_value(args, kwargs, 0, "app") + # Check if this Blueprint has a pin, otherwise clone the one from the app onto it + pin = Pin.get_from(instance) + if not pin: + pin = Pin.get_from(app) + if pin: + pin.clone().onto(instance) + return wrapped(*args, **kwargs) + + +def patched_blueprint_add_url_rule(wrapped, instance, args, kwargs): + pin = Pin._find(wrapped, instance) + if not pin: + return wrapped(*args, **kwargs) + + def _wrap(rule, endpoint=None, view_func=None, **kwargs): + if view_func: + pin.clone().onto(view_func) + return wrapped(rule, endpoint=endpoint, view_func=view_func, **kwargs) + + return _wrap(*args, **kwargs) + + +def patched_add_url_rule(wrapped, instance, args, kwargs): + """Wrapper for flask.app.Flask.add_url_rule to wrap all views attached to this app""" + + def _wrap(rule, endpoint=None, view_func=None, **kwargs): + if view_func: + # TODO: `if hasattr(view_func, 'view_class')` then this was generated from a `flask.views.View` + # should we do something special with these views? Change the name/resource? Add tags? + view_func = wrap_view(instance, view_func, name=endpoint, resource=rule) + + return wrapped(rule, endpoint=endpoint, view_func=view_func, **kwargs) + + return _wrap(*args, **kwargs) + + +def patched_endpoint(wrapped, instance, args, kwargs): + """Wrapper for flask.app.Flask.endpoint to ensure all endpoints are wrapped""" + endpoint = kwargs.get("endpoint", args[0]) + + def _wrapper(func): + return wrapped(endpoint)(wrap_function(instance, func, resource=endpoint)) + + return _wrapper + + +def patched_flask_hook(wrapped, instance, args, kwargs): + func = get_argument_value(args, kwargs, 0, "f") + return wrapped(wrap_function(instance, func)) + + +def traced_render_template(wrapped, instance, args, kwargs): + return _build_render_template_wrapper("render_template")(wrapped, instance, args, kwargs) + + +def traced_render_template_string(wrapped, instance, args, kwargs): + return _build_render_template_wrapper("render_template_string")(wrapped, instance, args, kwargs) + + +def _build_render_template_wrapper(name): + name = "flask.%s" % name + + def traced_render(wrapped, instance, args, kwargs): + pin = Pin._find(wrapped, instance, get_current_app()) + if not pin or not pin.enabled(): + return wrapped(*args, **kwargs) + with core.context_with_data( + "flask.render_template", + span_name=name, + pin=pin, + flask_config=config.flask, + tags={COMPONENT: config.flask.integration_name}, + span_type=SpanTypes.TEMPLATE, + call_key=[name + ".call", "current_span"], + ) as ctx, ctx.get_item(name + ".call"): + return wrapped(*args, **kwargs) + + return traced_render + + +def patched_render(wrapped, instance, args, kwargs): + pin = Pin._find(wrapped, instance, get_current_app()) + + if not pin.enabled: + return wrapped(*args, **kwargs) + + def _wrap(template, context, app): + core.dispatch("flask.render", (template, config.flask)) + return wrapped(*args, **kwargs) + + return _wrap(*args, **kwargs) + + +def patched__register_error_handler(wrapped, instance, args, kwargs): + def _wrap(key, code_or_exception, f): + return wrapped(key, code_or_exception, wrap_function(instance, f)) + + return _wrap(*args, **kwargs) + + +def patched_register_error_handler(wrapped, instance, args, kwargs): + def _wrap(code_or_exception, f): + return wrapped(code_or_exception, wrap_function(instance, f)) + + return _wrap(*args, **kwargs) + + +def _block_request_callable(call): + core.set_item(HTTP_REQUEST_BLOCKED, STATUS_403_TYPE_AUTO) + core.dispatch("flask.blocked_request_callable", (call,)) + ctype = "text/html" if "text/html" in flask.request.headers.get("Accept", "").lower() else "text/json" + abort(flask.Response(http_utils._get_blocked_template(ctype), content_type=ctype, status=403)) + + +def request_patcher(name): + @with_instance_pin + def _patched_request(pin, wrapped, instance, args, kwargs): + with core.context_with_data( + "flask._patched_request", + span_name=".".join(("flask", name)), + pin=pin, + service=trace_utils.int_service(pin, config.flask, pin), + flask_config=config.flask, + flask_request=flask.request, + block_request_callable=_block_request_callable, + ignored_exception_type=NotFound, + call_key="flask_request_call", + tags={COMPONENT: config.flask.integration_name}, + ) as ctx, ctx.get_item("flask_request_call"): + core.dispatch("flask._patched_request", (ctx,)) + return wrapped(*args, **kwargs) + + return _patched_request + + +def patched_signal_receivers_for(signal): + def outer(wrapped, instance, args, kwargs): + sender = get_argument_value(args, kwargs, 0, "sender") + # See if they gave us the flask.app.Flask as the sender + app = None + if isinstance(sender, flask.Flask): + app = sender + for receiver in wrapped(*args, **kwargs): + yield _wrap_call_with_pin_check(receiver, app, func_name(receiver), signal=signal) + + return outer + + +def patched_jsonify(wrapped, instance, args, kwargs): + pin = Pin._find(wrapped, instance, get_current_app()) + if not pin or not pin.enabled(): + return wrapped(*args, **kwargs) + + with core.context_with_data( + "flask.jsonify", + span_name="flask.jsonify", + flask_config=config.flask, + tags={COMPONENT: config.flask.integration_name}, + pin=pin, + call_key="flask_jsonify_call", + ) as ctx, ctx.get_item("flask_jsonify_call"): + return wrapped(*args, **kwargs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask/wrappers.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask/wrappers.py new file mode 100644 index 0000000..e1a7e47 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask/wrappers.py @@ -0,0 +1,96 @@ +import flask + +from ddtrace import config +from ddtrace.contrib import trace_utils +from ddtrace.internal import core +from ddtrace.internal.constants import COMPONENT +from ddtrace.vendor.wrapt import function_wrapper + +from ...internal.logger import get_logger +from ...internal.utils.importlib import func_name +from ...pin import Pin + + +log = get_logger(__name__) + + +def wrap_view(instance, func, name=None, resource=None): + return _wrap_call_with_pin_check(func, instance, name or func_name(func), resource=resource, do_dispatch=True) + + +def get_current_app(): + """Helper to get the flask.app.Flask from the current app context""" + try: + return flask.current_app + except RuntimeError: + # raised if current_app is None: https://github.com/pallets/flask/blob/2.1.3/src/flask/globals.py#L40 + pass + return None + + +def _wrap_call( + wrapped, pin, name, resource=None, signal=None, span_type=None, do_dispatch=False, args=None, kwargs=None +): + args = args or [] + kwargs = kwargs or {} + tags = {COMPONENT: config.flask.integration_name} + if signal: + tags["flask.signal"] = signal + with core.context_with_data( + "flask.call", + span_name=name, + pin=pin, + resource=resource, + service=trace_utils.int_service(pin, config.flask), + span_type=span_type, + tags=tags, + call_key="flask_call", + ) as ctx, ctx.get_item("flask_call"): + if do_dispatch: + result = core.dispatch_with_results("flask.wrapped_view", (kwargs,)).callback_and_args + if result: + callback_block, _kwargs = result.value + if callback_block: + return callback_block() + if _kwargs: + for k in kwargs: + kwargs[k] = _kwargs[k] + return wrapped(*args, **kwargs) + + +def _wrap_call_with_pin_check(func, instance, name, resource=None, signal=None, do_dispatch=False): + @function_wrapper + def patch_func(wrapped, _instance, args, kwargs): + pin = Pin._find(wrapped, _instance, instance, get_current_app()) + if not pin or not pin.enabled(): + return wrapped(*args, **kwargs) + return _wrap_call( + wrapped, pin, name, resource=resource, signal=signal, do_dispatch=do_dispatch, args=args, kwargs=kwargs + ) + + return patch_func(func) + + +def wrap_function(instance, func, name=None, resource=None): + return _wrap_call_with_pin_check(func, instance, name or func_name(func), resource=resource) + + +def simple_call_wrapper(name, span_type=None): + @with_instance_pin + def wrapper(pin, wrapped, instance, args, kwargs): + return _wrap_call(wrapped, pin, name, span_type=span_type, args=args, kwargs=kwargs) + + return wrapper + + +def with_instance_pin(func): + """Helper to wrap a function wrapper and ensure an enabled pin is available for the `instance`""" + + def wrapper(wrapped, instance, args, kwargs): + pin = Pin._find(wrapped, instance, get_current_app()) + if not pin or not pin.enabled(): + return wrapped(*args, **kwargs) + + return func(pin, wrapped, instance, args, kwargs) + + return wrapper diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask_cache/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask_cache/__init__.py new file mode 100644 index 0000000..a859b53 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask_cache/__init__.py @@ -0,0 +1,57 @@ +""" +The flask cache tracer will track any access to a cache backend. +You can use this tracer together with the Flask tracer middleware. + +The tracer supports both `Flask-Cache `_ +and `Flask-Caching `_. + +To install the tracer, ``from ddtrace import tracer`` needs to be added:: + + from ddtrace import tracer + from ddtrace.contrib.flask_cache import get_traced_cache + +and the tracer needs to be initialized:: + + Cache = get_traced_cache(tracer, service='my-flask-cache-app') + +Here is the end result, in a sample app:: + + from flask import Flask + + from ddtrace import tracer + from ddtrace.contrib.flask_cache import get_traced_cache + + app = Flask(__name__) + + # get the traced Cache class + Cache = get_traced_cache(tracer, service='my-flask-cache-app') + + # use the Cache as usual with your preferred CACHE_TYPE + cache = Cache(app, config={'CACHE_TYPE': 'simple'}) + + def counter(): + # this access is traced + conn_counter = cache.get("conn_counter") + +Use a specific ``Cache`` implementation with:: + + from ddtrace import tracer + from ddtrace.contrib.flask_cache import get_traced_cache + + from flask_caching import Cache + + Cache = get_traced_cache(tracer, service='my-flask-cache-app', cache_cls=Cache) + +""" + +from ...internal.utils.importlib import require_modules + + +required_modules = ["flask_cache", "flask_caching"] + +with require_modules(required_modules) as missing_modules: + if len(missing_modules) < len(required_modules): + from .tracers import get_traced_cache + from .tracers import get_version + + __all__ = ["get_traced_cache", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask_cache/tracers.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask_cache/tracers.py new file mode 100644 index 0000000..aba1b95 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask_cache/tracers.py @@ -0,0 +1,188 @@ +""" +Datadog trace code for flask_cache +""" + +import logging +import typing + +from ddtrace import config +from ddtrace.internal.constants import COMPONENT + +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanTypes +from ...ext import db +from ...internal.schema import schematize_cache_operation +from ...internal.schema import schematize_service_name +from .utils import _extract_client +from .utils import _extract_conn_tags +from .utils import _resource_from_cache_prefix + + +if typing.TYPE_CHECKING: # pragma: no cover + from ddtrace import Span # noqa:F401 + + +log = logging.Logger(__name__) + +DEFAULT_SERVICE = config.service or schematize_service_name("flask-cache") + +# standard tags +COMMAND_KEY = "flask_cache.key" +CACHE_BACKEND = "flask_cache.backend" +CONTACT_POINTS = "flask_cache.contact_points" + + +def get_version(): + # type: () -> str + try: + import flask_caching + + return getattr(flask_caching, "__version__", "") + except ImportError: + return "" + + +def get_traced_cache(ddtracer, service=DEFAULT_SERVICE, meta=None, cache_cls=None): + """ + Return a traced Cache object that behaves exactly as ``cache_cls``. + + ``cache_cls`` defaults to ``flask.ext.cache.Cache`` if Flask-Cache is installed + or ``flask_caching.Cache`` if flask-caching is installed. + """ + + if cache_cls is None: + # for compatibility reason, first check if flask_cache is present + try: + from flask.ext.cache import Cache + + cache_cls = Cache + except ImportError: + # use flask_caching if flask_cache is not present + from flask_caching import Cache + + cache_cls = Cache + + class TracedCache(cache_cls): + """ + Traced cache backend that monitors any operations done by flask_cache. Observed actions are: + * get, set, add, delete, clear + * all ``many_`` operations + """ + + _datadog_tracer = ddtracer + _datadog_service = service + _datadog_meta = meta + + def __trace(self, cmd): + # type: (str, bool) -> Span + """ + Start a tracing with default attributes and tags + """ + # create a new span + s = self._datadog_tracer.trace( + schematize_cache_operation(cmd, cache_provider="flask_cache"), + span_type=SpanTypes.CACHE, + service=self._datadog_service, + ) + + s.set_tag_str(COMPONENT, config.flask_cache.integration_name) + + s.set_tag(SPAN_MEASURED_KEY) + # set span tags + s.set_tag_str(CACHE_BACKEND, self.config.get("CACHE_TYPE")) + s.set_tags(self._datadog_meta) + # set analytics sample rate + s.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.flask_cache.get_analytics_sample_rate()) + # add connection meta if there is one + client = _extract_client(self.cache) + if client is not None: + try: + s.set_tags(_extract_conn_tags(client)) + except Exception: + log.debug("error parsing connection tags", exc_info=True) + + return s + + def get(self, *args, **kwargs): + """ + Track ``get`` operation + """ + with self.__trace("flask_cache.cmd") as span: + span.resource = _resource_from_cache_prefix("GET", self.config) + if len(args) > 0: + span.set_tag_str(COMMAND_KEY, args[0]) + result = super(TracedCache, self).get(*args, **kwargs) + span.set_metric(db.ROWCOUNT, 1 if result else 0) + return result + + def set(self, *args, **kwargs): + """ + Track ``set`` operation + """ + with self.__trace("flask_cache.cmd") as span: + span.resource = _resource_from_cache_prefix("SET", self.config) + if len(args) > 0: + span.set_tag_str(COMMAND_KEY, args[0]) + return super(TracedCache, self).set(*args, **kwargs) + + def add(self, *args, **kwargs): + """ + Track ``add`` operation + """ + with self.__trace("flask_cache.cmd") as span: + span.resource = _resource_from_cache_prefix("ADD", self.config) + if len(args) > 0: + span.set_tag_str(COMMAND_KEY, args[0]) + return super(TracedCache, self).add(*args, **kwargs) + + def delete(self, *args, **kwargs): + """ + Track ``delete`` operation + """ + with self.__trace("flask_cache.cmd") as span: + span.resource = _resource_from_cache_prefix("DELETE", self.config) + if len(args) > 0: + span.set_tag_str(COMMAND_KEY, args[0]) + return super(TracedCache, self).delete(*args, **kwargs) + + def delete_many(self, *args, **kwargs): + """ + Track ``delete_many`` operation + """ + with self.__trace("flask_cache.cmd") as span: + span.resource = _resource_from_cache_prefix("DELETE_MANY", self.config) + span.set_tag(COMMAND_KEY, list(args)) + return super(TracedCache, self).delete_many(*args, **kwargs) + + def clear(self, *args, **kwargs): + """ + Track ``clear`` operation + """ + with self.__trace("flask_cache.cmd") as span: + span.resource = _resource_from_cache_prefix("CLEAR", self.config) + return super(TracedCache, self).clear(*args, **kwargs) + + def get_many(self, *args, **kwargs): + """ + Track ``get_many`` operation + """ + with self.__trace("flask_cache.cmd") as span: + span.resource = _resource_from_cache_prefix("GET_MANY", self.config) + span.set_tag(COMMAND_KEY, list(args)) + result = super(TracedCache, self).get_many(*args, **kwargs) + # get many returns a list, with either the key value or None if it doesn't exist + span.set_metric(db.ROWCOUNT, sum(1 for val in result if val)) + return result + + def set_many(self, *args, **kwargs): + """ + Track ``set_many`` operation + """ + with self.__trace("flask_cache.cmd") as span: + span.resource = _resource_from_cache_prefix("SET_MANY", self.config) + if len(args) > 0: + span.set_tag(COMMAND_KEY, list(args[0].keys())) + return super(TracedCache, self).set_many(*args, **kwargs) + + return TracedCache diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask_cache/utils.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask_cache/utils.py new file mode 100644 index 0000000..d7f33ec --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask_cache/utils.py @@ -0,0 +1,62 @@ +# project +from ...ext import net +from ..pylibmc.addrs import parse_addresses +from ..trace_utils_redis import _extract_conn_tags as extract_redis_tags + + +def _resource_from_cache_prefix(resource, cache): + """ + Combine the resource name with the cache prefix (if any) + """ + if getattr(cache, "key_prefix", None): + name = "{} {}".format(resource, cache.key_prefix) + else: + name = resource + + # enforce lowercase to make the output nicer to read + return name.lower() + + +def _extract_client(cache): + """ + Get the client from the cache instance according to the current operation + """ + client = getattr(cache, "_client", None) + if client is None: + # flask-caching has _read_clients & _write_client for the redis backend + # These use the same connection so just try to get a reference to one of them. + # flask-caching < 2.0.0 uses _read_clients so look for that one too. + for attr in ("_write_client", "_read_client", "_read_clients"): + client = getattr(cache, attr, None) + if client is not None: + break + return client + + +def _extract_conn_tags(client): + """ + For the given client extracts connection tags + """ + tags = {} + + if hasattr(client, "servers"): + # Memcached backend supports an address pool + if isinstance(client.servers, list) and len(client.servers) > 0: + # use the first address of the pool as a host because + # the code doesn't expose more information + contact_point = client.servers[0].address + tags[net.TARGET_HOST] = contact_point[0] + tags[net.TARGET_PORT] = contact_point[1] + elif hasattr(client, "connection_pool"): + # Redis main connection + redis_tags = extract_redis_tags(client.connection_pool.connection_kwargs) + tags.update(**redis_tags) + elif hasattr(client, "addresses"): + # pylibmc + # FIXME[matt] should we memoize this? + addrs = parse_addresses(client.addresses) + if addrs: + _, host, port, _ = addrs[0] + tags[net.TARGET_PORT] = port + tags[net.TARGET_HOST] = host + return tags diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask_login/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask_login/__init__.py new file mode 100644 index 0000000..84e92e1 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask_login/__init__.py @@ -0,0 +1,44 @@ +""" +The ``flask_login`` integration implements appsec automatic user login events +when ``DD_APPSEC_ENABLED=1``. This will automatically fill the following tags +when a user tries to log in using ``flask_login`` as an authentication plugin: + +- ``appsec.events.users.login.success.track`` +- ``appsec.events.users.login.failure.track`` +- ``appsec.events.users.login.success.[email|login|username]`` + +Note that, by default, this will be enabled if ``DD_APPSEC_ENABLED=1`` with +``DD_APPSEC_AUTOMATIC_USER_EVENTS_TRACKING`` set to ``safe`` which will store the user's +``id`` but not the username or email. Check the configuration docs to see how to disable this feature entirely, +or set it to extended mode which would also store the username and email or customize the id, email and name +fields to adapt them to your custom ``User`` model. + +Also, since ``flask_login`` is a "roll your own" kind of authentication system, in your main login function, where you +check the user password (usually with ``check_password_hash``) you must manually call +``track_user_login_failure_event(tracer, user_id, exists)`` to store the correct tags for authentication failure. As +a helper, you can call ``flask_login.login_user`` with a user object with a ``get_id()`` returning ``-1`` to +automatically set the tags for a login failure where the user doesn't exist. + + +Enabling +~~~~~~~~ + +This integration is enabled automatically when using ``DD_APPSEC_ENABLED=1`. Use +``DD_APPSEC_AUTOMATIC_USER_EVENTS_TRACKING=disabled`` to explicitly disable it. +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["flask_login"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = [ + "get_version", + "patch", + "unpatch", + ] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask_login/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask_login/patch.py new file mode 100644 index 0000000..4eaffb2 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/flask_login/patch.py @@ -0,0 +1,108 @@ +import flask +import flask_login + +from ddtrace import Pin +from ddtrace.appsec.trace_utils import track_user_login_failure_event +from ddtrace.appsec.trace_utils import track_user_login_success_event +from ddtrace.internal.logger import get_logger +from ddtrace.settings.asm import config as asm_config +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w + +from ...appsec._utils import _UserInfoRetriever +from ...ext import SpanTypes +from ...internal.utils import get_argument_value +from .. import trace_utils +from ..flask.wrappers import get_current_app + + +log = get_logger(__name__) + + +def get_version(): + # type: () -> str + return flask_login.__version__ + + +class _FlaskLoginUserInfoRetriever(_UserInfoRetriever): + def get_userid(self): + if hasattr(self.user, "get_id") and not asm_config._user_model_login_field: + return self.user.get_id() + + return super(_FlaskLoginUserInfoRetriever, self).get_userid() + + +def traced_login_user(func, instance, args, kwargs): + pin = Pin._find(func, instance, get_current_app()) + ret = func(*args, **kwargs) + + try: + mode = asm_config._automatic_login_events_mode + if not asm_config._asm_enabled or mode == "disabled": + return ret + + user = get_argument_value(args, kwargs, 0, "user") + if not user: + track_user_login_failure_event(pin.tracer, user_id=None, exists=False, login_events_mode=mode) + return ret + + if hasattr(user, "is_anonymous") and user.is_anonymous: + return ret + + if not isinstance(user, flask_login.UserMixin): + log.debug( + "Automatic Login Events Tracking: flask_login User models not inheriting from UserMixin not supported", + ) + return ret + + info_retriever = _FlaskLoginUserInfoRetriever(user) + user_id, user_extra = info_retriever.get_user_info() + if user_id == -1: + with pin.tracer.trace("flask_login.login_user", span_type=SpanTypes.AUTH): + track_user_login_failure_event(pin.tracer, user_id="missing", exists=False, login_events_mode=mode) + return ret + if not user_id: + track_user_login_failure_event(pin.tracer, user_id=None, exists=False, login_events_mode=mode) + log.debug( + "Automatic Login Events Tracking: Could not determine user id field user for the %s user Model; " + "set DD_USER_MODEL_LOGIN_FIELD to the name of the field used for the user id or implement the " + "get_id method for your model", + type(user), + ) + return ret + + with pin.tracer.trace("flask_login.login_user", span_type=SpanTypes.AUTH): + session_key = flask.session.get("_id", None) + track_user_login_success_event( + pin.tracer, + user_id=user_id, + session_id=session_key, + propagate=True, + login_events_mode=mode, + **user_extra, + ) + except Exception: + log.debug("Error while trying to trace flask_login.login_user", exc_info=True) + + return ret + + +def patch(): + if not asm_config._asm_enabled: + return + + if getattr(flask_login, "_datadog_patch", False): + return + + Pin().onto(flask_login) + _w("flask_login", "login_user", traced_login_user) + flask_login._datadog_patch = True + + +def unpatch(): + import flask_login + + if not getattr(flask_login, "_datadog_patch", False): + return + + trace_utils.unwrap(flask_login, "login_user") + flask_login._datadog_patch = False diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/futures/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/futures/__init__.py new file mode 100644 index 0000000..c11b61b --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/futures/__init__.py @@ -0,0 +1,34 @@ +""" +The ``futures`` integration propagates the current active tracing context +to tasks spawned using a :class:`~concurrent.futures.ThreadPoolExecutor`. +The integration ensures that when operations are executed in another thread, +those operations can continue the previously generated trace. + + +Enabling +~~~~~~~~ + +The futures integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(futures=True) +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["concurrent.futures"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = [ + "get_version", + "patch", + "unpatch", + ] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/futures/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/futures/patch.py new file mode 100644 index 0000000..86687d7 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/futures/patch.py @@ -0,0 +1,43 @@ +import sys + +from ddtrace.internal.wrapping import unwrap as _u +from ddtrace.internal.wrapping import wrap as _w + +from .threading import _wrap_submit + + +def get_version(): + # type: () -> str + return "" + + +def patch(): + """Enables Context Propagation between threads""" + try: + # Ensure that we get hold of the reloaded module if module cleanup was + # performed. + thread = sys.modules["concurrent.futures.thread"] + except KeyError: + import concurrent.futures.thread as thread + + if getattr(thread, "__datadog_patch", False): + return + thread.__datadog_patch = True + + _w(thread.ThreadPoolExecutor.submit, _wrap_submit) + + +def unpatch(): + """Disables Context Propagation between threads""" + try: + # Ensure that we get hold of the reloaded module if module cleanup was + # performed. + thread = sys.modules["concurrent.futures.thread"] + except KeyError: + return + + if not getattr(thread, "__datadog_patch", False): + return + thread.__datadog_patch = False + + _u(thread.ThreadPoolExecutor.submit, _wrap_submit) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/futures/threading.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/futures/threading.py new file mode 100644 index 0000000..ab67e21 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/futures/threading.py @@ -0,0 +1,44 @@ +import ddtrace + + +def _wrap_submit(func, args, kwargs): + """ + Wrap `Executor` method used to submit a work executed in another + thread. This wrapper ensures that a new `Context` is created and + properly propagated using an intermediate function. + """ + # If there isn't a currently active context, then do not create one + # DEV: Calling `.active()` when there isn't an active context will create a new context + # DEV: We need to do this in case they are either: + # - Starting nested futures + # - Starting futures from outside of an existing context + # + # In either of these cases we essentially will propagate the wrong context between futures + # + # The resolution is to not create/propagate a new context if one does not exist, but let the + # future's thread create the context instead. + current_ctx = None + if ddtrace.tracer.context_provider._has_active_context(): + current_ctx = ddtrace.tracer.context_provider.active() + + # The target function can be provided as a kwarg argument "fn" or the first positional argument + self = args[0] + if "fn" in kwargs: + fn = kwargs.pop("fn") + fn_args = args[1:] + else: + fn, fn_args = args[1], args[2:] + return func(self, _wrap_execution, current_ctx, fn, fn_args, kwargs) + + +def _wrap_execution(ctx, fn, args, kwargs): + """ + Intermediate target function that is executed in a new thread; + it receives the original function with arguments and keyword + arguments, including our tracing `Context`. The current context + provider sets the Active context in a thread local storage + variable because it's outside the asynchronous loop. + """ + if ctx is not None: + ddtrace.tracer.context_provider.activate(ctx) + return fn(*args, **kwargs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/gevent/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/gevent/__init__.py new file mode 100644 index 0000000..495a1db --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/gevent/__init__.py @@ -0,0 +1,72 @@ +""" +The gevent integration adds support for tracing across greenlets. + + +The integration patches the gevent internals to add context management logic. + +.. note:: + If ``ddtrace-run`` is not being used then be sure to ``import ddtrace.auto`` + before calling ``gevent.monkey.patch_all``. + If ``ddtrace-run`` is being used then no additional configuration is required. + + +The integration also configures the global tracer instance to use a gevent +context provider to utilize the context management logic. + +If custom tracer instances are being used in a gevent application, then +configure it with:: + + from ddtrace.contrib.gevent import context_provider + + tracer.configure(context_provider=context_provider) + + +Enabling +~~~~~~~~ + +The integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(gevent=True) + + +Example of the context propagation:: + + def my_parent_function(): + with tracer.trace("web.request") as span: + span.service = "web" + gevent.spawn(worker_function) + + + def worker_function(): + # then trace its child + with tracer.trace("greenlet.call") as span: + span.service = "greenlet" + ... + + with tracer.trace("greenlet.child_call") as child: + ... +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["gevent"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + from .provider import GeventContextProvider + + context_provider = GeventContextProvider() + + __all__ = [ + "patch", + "unpatch", + "context_provider", + "get_version" + ] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/gevent/greenlet.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/gevent/greenlet.py new file mode 100644 index 0000000..6140964 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/gevent/greenlet.py @@ -0,0 +1,60 @@ +import gevent +import gevent.pool as gpool + +from .provider import GeventContextProvider + + +GEVENT_VERSION = gevent.version_info[0:3] + + +class TracingMixin(object): + def __init__(self, *args, **kwargs): + # get the active context/span if available + current_g = gevent.getcurrent() + ctx = getattr(current_g, GeventContextProvider._CONTEXT_ATTR, None) + + # create the Greenlet as usual + super(TracingMixin, self).__init__(*args, **kwargs) + + # copy the active span/context into the new greenlet + if ctx: + setattr(self, GeventContextProvider._CONTEXT_ATTR, ctx) + + +class TracedGreenlet(TracingMixin, gevent.Greenlet): + """ + ``Greenlet`` class that is used to replace the original ``gevent`` + class. This class is supposed to do ``Context`` replacing operation, so + that any greenlet inherits the context from the parent Greenlet. + When a new greenlet is spawned from the main greenlet, a new instance + of ``Context`` is created. The main greenlet is not affected by this behavior. + + There is no need to inherit this class to create or optimize greenlets + instances, because this class replaces ``gevent.greenlet.Greenlet`` + through the ``patch()`` method. After the patch, extending the gevent + ``Greenlet`` class means extending automatically ``TracedGreenlet``. + """ + + def __init__(self, *args, **kwargs): + super(TracedGreenlet, self).__init__(*args, **kwargs) + + +class TracedIMapUnordered(TracingMixin, gpool.IMapUnordered): + def __init__(self, *args, **kwargs): + super(TracedIMapUnordered, self).__init__(*args, **kwargs) + + +if GEVENT_VERSION >= (1, 3) or GEVENT_VERSION < (1, 1): + # For gevent <1.1 and >=1.3, IMap is its own class, so we derive + # from TracingMixin + class TracedIMap(TracingMixin, gpool.IMap): + def __init__(self, *args, **kwargs): + super(TracedIMap, self).__init__(*args, **kwargs) + + +else: + # For gevent >=1.1 and <1.3, IMap derives from IMapUnordered, so we derive + # from TracedIMapUnordered and get tracing that way + class TracedIMap(gpool.IMap, TracedIMapUnordered): + def __init__(self, *args, **kwargs): + super(TracedIMap, self).__init__(*args, **kwargs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/gevent/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/gevent/patch.py new file mode 100644 index 0000000..5aa7035 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/gevent/patch.py @@ -0,0 +1,80 @@ +import gevent +import gevent.pool + +import ddtrace + +from ...provider import DefaultContextProvider +from .greenlet import GEVENT_VERSION +from .greenlet import TracedGreenlet +from .greenlet import TracedIMap +from .greenlet import TracedIMapUnordered +from .provider import GeventContextProvider + + +__Greenlet = gevent.Greenlet +__IMap = gevent.pool.IMap +__IMapUnordered = gevent.pool.IMapUnordered + + +def get_version(): + # type: () -> str + return getattr(gevent, "__version__", "") + + +def patch(): + """ + Patch the gevent module so that all references to the + internal ``Greenlet`` class points to the ``DatadogGreenlet`` + class. + + This action ensures that if a user extends the ``Greenlet`` + class, the ``TracedGreenlet`` is used as a parent class. + """ + if getattr(gevent, "__datadog_patch", False): + return + gevent.__datadog_patch = True + + _replace(TracedGreenlet, TracedIMap, TracedIMapUnordered) + ddtrace.tracer.configure(context_provider=GeventContextProvider()) + + +def unpatch(): + """ + Restore the original ``Greenlet``. This function must be invoked + before executing application code, otherwise the ``DatadogGreenlet`` + class may be used during initialization. + """ + if not getattr(gevent, "__datadog_patch", False): + return + gevent.__datadog_patch = False + + _replace(__Greenlet, __IMap, __IMapUnordered) + ddtrace.tracer.configure(context_provider=DefaultContextProvider()) + + +def _replace(g_class, imap_class, imap_unordered_class): + """ + Utility function that replace the gevent Greenlet class with the given one. + """ + # replace the original Greenlet classes with the new one + gevent.greenlet.Greenlet = g_class + + if GEVENT_VERSION >= (1, 3): + # For gevent >= 1.3.0, IMap and IMapUnordered were pulled out of + # gevent.pool and into gevent._imap + gevent._imap.IMap = imap_class + gevent._imap.IMapUnordered = imap_unordered_class + gevent.pool.IMap = gevent._imap.IMap + gevent.pool.IMapUnordered = gevent._imap.IMapUnordered + gevent.pool.Greenlet = gevent.greenlet.Greenlet + else: + # For gevent < 1.3, only patching of gevent.pool classes necessary + gevent.pool.IMap = imap_class + gevent.pool.IMapUnordered = imap_unordered_class + + gevent.pool.Group.greenlet_class = g_class + + # replace gevent shortcuts + gevent.Greenlet = gevent.greenlet.Greenlet + gevent.spawn = gevent.greenlet.Greenlet.spawn + gevent.spawn_later = gevent.greenlet.Greenlet.spawn_later diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/gevent/provider.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/gevent/provider.py new file mode 100644 index 0000000..13b05d3 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/gevent/provider.py @@ -0,0 +1,42 @@ +import gevent + +from ...provider import BaseContextProvider +from ...provider import DatadogContextMixin +from ...span import Span + + +class GeventContextProvider(BaseContextProvider, DatadogContextMixin): + """Manages the active context for gevent execution. + + This provider depends on corresponding monkey patches to copy the active + context from one greenlet to another. + """ + + # Greenlet attribute used to set/get the context + _CONTEXT_ATTR = "__datadog_context" + + def _get_current_context(self): + """Helper to get the active context from the current greenlet.""" + current_g = gevent.getcurrent() + if current_g is not None: + return getattr(current_g, self._CONTEXT_ATTR, None) + return None + + def _has_active_context(self): + """Helper to determine if there is an active context.""" + return self._get_current_context() is not None + + def activate(self, context): + """Sets the active context for the current running ``Greenlet``.""" + current_g = gevent.getcurrent() + if current_g is not None: + setattr(current_g, self._CONTEXT_ATTR, context) + super(GeventContextProvider, self).activate(context) + return context + + def active(self): + """Returns the active context for this execution flow.""" + ctx = self._get_current_context() + if isinstance(ctx, Span): + return self._update_active(ctx) + return ctx diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/graphql/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/graphql/__init__.py new file mode 100644 index 0000000..9e3b49d --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/graphql/__init__.py @@ -0,0 +1,58 @@ +""" +This integration instruments ``graphql-core`` queries. + +Enabling +~~~~~~~~ + +The graphql integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch() ` to manually enable the integration:: + + from ddtrace import patch + patch(graphql=True) + import graphql + ... + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.graphql["service"] + + The service name reported by default for graphql instances. + + This option can also be set with the ``DD_SERVICE`` environment + variable. + + Default: ``"graphql"`` + +.. py:data:: ddtrace.config.graphql["resolvers_enabled"] + + To enable ``graphql.resolve`` spans set ``DD_TRACE_GRAPHQL_RESOLVERS_ENABLED`` to True + + Default: ``False`` + + Enabling instrumentation for resolvers will produce a ``graphql.resolve`` span for every graphql field. + For complex graphql queries this could produce large traces. + + +To configure the graphql integration using the +``Pin`` API:: + + from ddtrace import Pin + import graphql + + Pin.override(graphql, service="mygraphql") +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["graphql"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/graphql/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/graphql/patch.py new file mode 100644 index 0000000..115c83a --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/graphql/patch.py @@ -0,0 +1,321 @@ +import os +import re +import sys +from typing import TYPE_CHECKING +from typing import List + +from ddtrace import Span +from ddtrace.internal.schema.span_attribute_schema import SpanDirection + + +if TYPE_CHECKING: # pragma: no cover + from typing import Callable # noqa:F401 + from typing import Dict # noqa:F401 + from typing import Iterable # noqa:F401 + from typing import Tuple # noqa:F401 + from typing import Union # noqa:F401 + + +import graphql +from graphql import MiddlewareManager +from graphql.error import GraphQLError +from graphql.execution import ExecutionResult +from graphql.language.source import Source + +from ddtrace import config +from ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import ERROR_MSG +from ddtrace.constants import ERROR_TYPE +from ddtrace.constants import SPAN_MEASURED_KEY +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.schema import schematize_url_operation +from ddtrace.internal.utils import ArgumentError +from ddtrace.internal.utils import get_argument_value +from ddtrace.internal.utils import set_argument_value +from ddtrace.internal.utils.formats import asbool +from ddtrace.internal.utils.version import parse_version +from ddtrace.internal.wrapping import unwrap +from ddtrace.internal.wrapping import wrap +from ddtrace.pin import Pin + +from ...ext import SpanTypes +from .. import trace_utils + + +_graphql_version_str = graphql.__version__ +_graphql_version = parse_version(_graphql_version_str) + +if _graphql_version < (3, 0): + from graphql.language.ast import Document +else: + from graphql.language.ast import DocumentNode as Document + + +def get_version(): + # type: () -> str + return _graphql_version_str + + +config._add( + "graphql", + dict( + _default_service=schematize_service_name("graphql"), + resolvers_enabled=asbool(os.getenv("DD_TRACE_GRAPHQL_RESOLVERS_ENABLED", default=False)), + ), +) + +_GRAPHQL_SOURCE = "graphql.source" +_GRAPHQL_OPERATION_TYPE = "graphql.operation.type" +_GRAPHQL_OPERATION_NAME = "graphql.operation.name" + + +def patch(): + if getattr(graphql, "_datadog_patch", False): + return + graphql._datadog_patch = True + Pin().onto(graphql) + + for module_str, func_name, wrapper in _get_patching_candidates(): + _update_patching(wrap, module_str, func_name, wrapper) + + +def unpatch(): + if not getattr(graphql, "_datadog_patch", False) or _graphql_version < (2, 0): + return + + for module_str, func_name, wrapper in _get_patching_candidates(): + _update_patching(unwrap, module_str, func_name, wrapper) + + graphql._datadog_patch = False + + +def _get_patching_candidates(): + if _graphql_version < (3, 0): + return [ + ("graphql.graphql", "execute_graphql", _traced_query), + ("graphql.language.parser", "parse", _traced_parse), + ("graphql.validation.validation", "validate", _traced_validate), + ("graphql.execution.executor", "execute", _traced_execute), + ] + return [ + ("graphql.graphql", "graphql_impl", _traced_query), + ("graphql.language.parser", "parse", _traced_parse), + ("graphql.validation.validate", "validate", _traced_validate), + ("graphql.execution.execute", "execute", _traced_execute), + ] + + +def _update_patching(operation, module_str, func_name, wrapper): + module = sys.modules[module_str] + func = getattr(module, func_name) + operation(func, wrapper) + + +def _traced_parse(func, args, kwargs): + pin = Pin.get_from(graphql) + if not pin or not pin.enabled(): + return func(*args, **kwargs) + + source = get_argument_value(args, kwargs, 0, "source") + source_str = _get_source_str(source) + # If graphql.parse() is called outside graphql.graphql(), graphql.parse will + # be a top level span. Therefore we must explicitly set the service name. + with pin.tracer.trace( + name="graphql.parse", + service=trace_utils.int_service(pin, config.graphql), + span_type=SpanTypes.GRAPHQL, + ) as span: + span.set_tag_str(COMPONENT, config.graphql.integration_name) + + span.set_tag_str(_GRAPHQL_SOURCE, source_str) + return func(*args, **kwargs) + + +def _traced_validate(func, args, kwargs): + pin = Pin.get_from(graphql) + if not pin or not pin.enabled(): + return func(*args, **kwargs) + + document = get_argument_value(args, kwargs, 1, "ast") + source_str = _get_source_str(document) + # If graphql.validate() is called outside graphql.graphql(), graphql.validate will + # be a top level span. Therefore we must explicitly set the service name. + with pin.tracer.trace( + name="graphql.validate", + service=trace_utils.int_service(pin, config.graphql), + span_type=SpanTypes.GRAPHQL, + ) as span: + span.set_tag_str(COMPONENT, config.graphql.integration_name) + + span.set_tag_str(_GRAPHQL_SOURCE, source_str) + errors = func(*args, **kwargs) + _set_span_errors(errors, span) + return errors + + +def _traced_execute(func, args, kwargs): + pin = Pin.get_from(graphql) + if not pin or not pin.enabled(): + return func(*args, **kwargs) + + if config.graphql.resolvers_enabled: + # patch resolvers + args, kwargs = _inject_trace_middleware_to_args(_resolver_middleware, args, kwargs) + + # set resource name + if _graphql_version < (3, 0): + document = get_argument_value(args, kwargs, 1, "document_ast") + else: + document = get_argument_value(args, kwargs, 1, "document") + source_str = _get_source_str(document) + + with pin.tracer.trace( + name="graphql.execute", + resource=source_str, + service=trace_utils.int_service(pin, config.graphql), + span_type=SpanTypes.GRAPHQL, + ) as span: + span.set_tag_str(COMPONENT, config.graphql.integration_name) + + span.set_tag(SPAN_MEASURED_KEY) + + _set_span_operation_tags(span, document) + span.set_tag_str(_GRAPHQL_SOURCE, source_str) + + result = func(*args, **kwargs) + if isinstance(result, ExecutionResult): + # set error tags if the result contains a list of GraphqlErrors, skip if it's a promise + _set_span_errors(result.errors, span) + return result + + +def _traced_query(func, args, kwargs): + pin = Pin.get_from(graphql) + if not pin or not pin.enabled(): + return func(*args, **kwargs) + + # set resource name + source = get_argument_value(args, kwargs, 1, "source") + resource = _get_source_str(source) + + with pin.tracer.trace( + name=schematize_url_operation("graphql.request", protocol="graphql", direction=SpanDirection.INBOUND), + resource=resource, + service=trace_utils.int_service(pin, config.graphql), + span_type=SpanTypes.GRAPHQL, + ) as span: + span.set_tag_str(COMPONENT, config.graphql.integration_name) + + # mark span as measured and set sample rate + span.set_tag(SPAN_MEASURED_KEY) + sample_rate = config.graphql.get_analytics_sample_rate() + if sample_rate is not None: + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, sample_rate) + + result = func(*args, **kwargs) + if isinstance(result, ExecutionResult): + # set error tags if the result contains a list of GraphqlErrors, skip if it's a promise + # If the wrapped validate and execute functions return a list of errors we will duplicate + # the span errors here. + _set_span_errors(result.errors, span) + return result + + +def _resolver_middleware(next_middleware, root, info, **args): + """ + trace middleware which wraps the resolvers of graphql fields. + Note - graphql middlewares can not be a partial. It must be a class or a function. + """ + pin = Pin.get_from(graphql) + if not pin or not pin.enabled(): + return next_middleware(root, info, **args) + + with pin.tracer.trace( + name="graphql.resolve", + resource=info.field_name, + span_type=SpanTypes.GRAPHQL, + ) as span: + span.set_tag_str(COMPONENT, config.graphql.integration_name) + + return next_middleware(root, info, **args) + + +def _inject_trace_middleware_to_args(trace_middleware, args, kwargs): + # type: (Callable, Tuple, Dict) -> Tuple[Tuple, Dict] + """ + Adds a trace middleware to graphql.execute(..., middleware, ...) + """ + middlewares_arg = 8 + if _graphql_version >= (3, 2): + # middleware is the 10th argument graphql.execute(..) version 3.2+ + middlewares_arg = 9 + + # get middlewares from args or kwargs + try: + middlewares = get_argument_value(args, kwargs, middlewares_arg, "middleware") or [] + if isinstance(middlewares, MiddlewareManager): + # First we must get the middlewares iterable from the MiddlewareManager then append + # trace_middleware. For the trace_middleware to be called a new MiddlewareManager will + # need to initialized. This is handled in graphql.execute(): + # https://github.com/graphql-python/graphql-core/blob/v3.2.1/src/graphql/execution/execute.py#L254 + middlewares = middlewares.middlewares # type: Iterable + except ArgumentError: + middlewares = [] + + # Note - graphql middlewares are called in reverse order + # add trace_middleware to the end of the list to wrap the execution of resolver and all middlewares + middlewares = list(middlewares) + [trace_middleware] + + # update args and kwargs to contain trace_middleware + args, kwargs = set_argument_value(args, kwargs, middlewares_arg, "middleware", middlewares) + return args, kwargs + + +def _get_source_str(obj): + # type: (Union[str, Source, Document]) -> str + """ + Parses graphql Documents and Source objects to retrieve + the graphql source input for a request. + """ + if isinstance(obj, str): + source_str = obj + elif isinstance(obj, Source): + source_str = obj.body + elif isinstance(obj, Document) and obj.loc is not None: + source_str = obj.loc.source.body + else: + source_str = "" + # remove new lines, tabs and extra whitespace from source_str + return re.sub(r"\s+", " ", source_str).strip() + + +def _set_span_errors(errors: List[GraphQLError], span: Span) -> None: + if not errors: + # do nothing if the list of graphql errors is empty + return + + span.error = 1 + exc_type_str = "%s.%s" % (GraphQLError.__module__, GraphQLError.__name__) + span.set_tag_str(ERROR_TYPE, exc_type_str) + error_msgs = "\n".join([str(error) for error in errors]) + # Since we do not support adding and visualizing multiple tracebacks to one span + # we will not set the error.stack tag on graphql spans. Setting only one traceback + # could be misleading and might obfuscate errors. + span.set_tag_str(ERROR_MSG, error_msgs) + + +def _set_span_operation_tags(span, document): + operation_def = graphql.get_operation_ast(document) + if not operation_def: + return + + # operation_def.operation should never be None + if _graphql_version < (3, 0): + span.set_tag_str(_GRAPHQL_OPERATION_TYPE, operation_def.operation) + else: + # OperationDefinition.operation is an Enum in graphql-core>=3 + span.set_tag_str(_GRAPHQL_OPERATION_TYPE, operation_def.operation.value) + + if operation_def.name: + span.set_tag_str(_GRAPHQL_OPERATION_NAME, operation_def.name.value) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/__init__.py new file mode 100644 index 0000000..6fbc970 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/__init__.py @@ -0,0 +1,89 @@ +""" +The gRPC integration traces the client and server using the interceptor pattern. + + +Enabling +~~~~~~~~ + +The gRPC integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(grpc=True) + + # use grpc like usual + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.grpc["service"] + + The service name reported by default for gRPC client instances. + + This option can also be set with the ``DD_GRPC_SERVICE`` environment + variable. + + Default: ``"grpc-client"`` + +.. py:data:: ddtrace.config.grpc_server["service"] + + The service name reported by default for gRPC server instances. + + This option can also be set with the ``DD_SERVICE`` or + ``DD_GRPC_SERVER_SERVICE`` environment variables. + + Default: ``"grpc-server"`` + + +Instance Configuration +~~~~~~~~~~~~~~~~~~~~~~ + +To configure the gRPC integration on an per-channel basis use the +``Pin`` API:: + + import grpc + from ddtrace import Pin, patch, Tracer + + patch(grpc=True) + custom_tracer = Tracer() + + # override the pin on the client + Pin.override(grpc.Channel, service='mygrpc', tracer=custom_tracer) + with grpc.insecure_channel('localhost:50051') as channel: + # create stubs and send requests + pass + +To configure the gRPC integration on the server use the ``Pin`` API:: + + import grpc + from grpc.framework.foundation import logging_pool + + from ddtrace import Pin, patch, Tracer + + patch(grpc=True) + custom_tracer = Tracer() + + # override the pin on the server + Pin.override(grpc.Server, service='mygrpc', tracer=custom_tracer) + server = grpc.server(logging_pool.pool(2)) + server.add_insecure_port('localhost:50051') + add_MyServicer_to_server(MyServicer(), server) + server.start() +""" + + +from ...internal.utils.importlib import require_modules + + +required_modules = ["grpc"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/aio_client_interceptor.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/aio_client_interceptor.py new file mode 100644 index 0000000..2e08c64 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/aio_client_interceptor.py @@ -0,0 +1,252 @@ +import asyncio +import functools +from typing import Callable # noqa:F401 +from typing import Tuple # noqa:F401 +from typing import Union # noqa:F401 + +import grpc +from grpc import aio +from grpc.aio._typing import RequestIterableType +from grpc.aio._typing import RequestType +from grpc.aio._typing import ResponseIterableType +from grpc.aio._typing import ResponseType + +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema import schematize_url_operation +from ddtrace.internal.schema.span_attribute_schema import SpanDirection + +from ... import Pin +from ... import Span +from ... import config +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import ERROR_MSG +from ...constants import ERROR_TYPE +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from ...internal.compat import to_unicode +from ...propagation.http import HTTPPropagator +from .. import trace_utils +from ..grpc import constants +from ..grpc import utils + + +def create_aio_client_interceptors(pin, host, port): + # type: (Pin, str, int) -> Tuple[aio.ClientInterceptor, ...] + return ( + _UnaryUnaryClientInterceptor(pin, host, port), + _UnaryStreamClientInterceptor(pin, host, port), + _StreamUnaryClientInterceptor(pin, host, port), + _StreamStreamClientInterceptor(pin, host, port), + ) + + +def _done_callback(span, code, details): + # type: (Span, grpc.StatusCode, str) -> Callable[[aio.Call], None] + def func(call): + # type: (aio.Call) -> None + try: + span.set_tag_str(constants.GRPC_STATUS_CODE_KEY, to_unicode(code)) + + # Handle server-side error in unary response RPCs + if code != grpc.StatusCode.OK: + _handle_error(span, call, code, details) + finally: + span.finish() + + return func + + +def _handle_error(span, call, code, details): + # type: (Span, aio.Call, grpc.StatusCode, str) -> None + span.error = 1 + span.set_tag_str(ERROR_MSG, details) + span.set_tag_str(ERROR_TYPE, to_unicode(code)) + + +def _handle_rpc_error(span, rpc_error): + # type: (Span, aio.AioRpcError) -> None + code = to_unicode(rpc_error.code()) + span.error = 1 + span.set_tag_str(constants.GRPC_STATUS_CODE_KEY, code) + span.set_tag_str(ERROR_MSG, rpc_error.details()) + span.set_tag_str(ERROR_TYPE, code) + span.finish() + + +async def _handle_cancelled_error(call, span): + # type: (aio.Call, Span) -> None + code = to_unicode(await call.code()) + span.error = 1 + span.set_tag_str(constants.GRPC_STATUS_CODE_KEY, code) + span.set_tag_str(ERROR_MSG, await call.details()) + span.set_tag_str(ERROR_TYPE, code) + span.finish() + + +class _ClientInterceptor: + def __init__(self, pin: Pin, host: str, port: int) -> None: + self._pin = pin + self._host = host + self._port = port + + def _intercept_client_call(self, method_kind, client_call_details): + # type: (str, aio.ClientCallDetails) -> Tuple[Span, aio.ClientCallDetails] + tracer = self._pin.tracer + + method_as_str = client_call_details.method.decode() + span = tracer.trace( + schematize_url_operation("grpc", protocol="grpc", direction=SpanDirection.OUTBOUND), + span_type=SpanTypes.GRPC, + service=trace_utils.ext_service(self._pin, config.grpc_aio_client), + resource=method_as_str, + ) + + span.set_tag_str(COMPONENT, config.grpc_aio_client.integration_name) + + # set span.kind to the type of operation being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + span.set_tag(SPAN_MEASURED_KEY) + + utils.set_grpc_method_meta(span, method_as_str, method_kind) + utils.set_grpc_client_meta(span, self._host, self._port) + span.set_tag_str(constants.GRPC_SPAN_KIND_KEY, constants.GRPC_SPAN_KIND_VALUE_CLIENT) + + sample_rate = config.grpc_aio_client.get_analytics_sample_rate() + if sample_rate is not None: + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, sample_rate) + + # inject tags from pin + if self._pin.tags: + span.set_tags(self._pin.tags) + + # propagate distributed tracing headers if available + headers = {} + if config.grpc_aio_client.distributed_tracing_enabled: + HTTPPropagator.inject(span.context, headers) + + metadata = [] + if client_call_details.metadata is not None: + metadata = list(client_call_details.metadata) + metadata.extend(headers.items()) + + client_call_details = aio.ClientCallDetails( + client_call_details.method, + client_call_details.timeout, + metadata, + client_call_details.credentials, + client_call_details.wait_for_ready, + ) + + return span, client_call_details + + # NOTE: Since this function is executed as an async generator when the RPC is called, + # `continuation` must be called before the RPC. + async def _wrap_stream_response( + self, + call: Union[aio.StreamStreamCall, aio.UnaryStreamCall], + span: Span, + ) -> ResponseIterableType: + try: + async for response in call: + yield response + code = await call.code() + details = await call.details() + # NOTE: The callback is registered after the iteration is done, + # otherwise `call.code()` and `call.details()` block indefinitely. + call.add_done_callback(_done_callback(span, code, details)) + except aio.AioRpcError as rpc_error: + # NOTE: We can also handle the error in done callbacks, + # but reuse this error handling function used in unary response RPCs. + _handle_rpc_error(span, rpc_error) + raise + except asyncio.CancelledError: + # NOTE: We can't handle the cancelled error in done callbacks + # because they cannot handle awaitable functions. + await _handle_cancelled_error(call, span) + raise + + # NOTE: `continuation` must be called inside of this function to catch exceptions. + async def _wrap_unary_response( + self, + continuation: Callable[[], Union[aio.StreamUnaryCall, aio.UnaryUnaryCall]], + span: Span, + ): + # type: (...) -> Union[aio.StreamUnaryCall, aio.UnaryUnaryCall] + try: + call = await continuation() + code = await call.code() + details = await call.details() + # NOTE: As both `code` and `details` are available after the RPC is done (= we get `call` object), + # and we can't call awaitable functions inside the non-async callback, + # there is no other way but to register the callback here. + call.add_done_callback(_done_callback(span, code, details)) + return call + except aio.AioRpcError as rpc_error: + # NOTE: `AioRpcError` is raised in `await continuation(...)` + # and `call` object is not assigned yet in that case. + # So we can't handle the error in done callbacks. + _handle_rpc_error(span, rpc_error) + raise + + +class _UnaryUnaryClientInterceptor(aio.UnaryUnaryClientInterceptor, _ClientInterceptor): + async def intercept_unary_unary( + self, + continuation: Callable[[aio.ClientCallDetails, RequestType], aio.UnaryUnaryCall], + client_call_details: aio.ClientCallDetails, + request: RequestType, + ) -> Union[aio.UnaryUnaryCall, ResponseType]: + span, client_call_details = self._intercept_client_call( + constants.GRPC_METHOD_KIND_UNARY, + client_call_details, + ) + continuation_with_args = functools.partial(continuation, client_call_details, request) + return await self._wrap_unary_response(continuation_with_args, span) + + +class _UnaryStreamClientInterceptor(aio.UnaryStreamClientInterceptor, _ClientInterceptor): + async def intercept_unary_stream( + self, + continuation: Callable[[aio.ClientCallDetails, RequestType], aio.UnaryStreamCall], + client_call_details: aio.ClientCallDetails, + request: RequestType, + ) -> Union[aio.UnaryStreamCall, ResponseIterableType]: + span, client_call_details = self._intercept_client_call( + constants.GRPC_METHOD_KIND_SERVER_STREAMING, + client_call_details, + ) + call = await continuation(client_call_details, request) + return self._wrap_stream_response(call, span) + + +class _StreamUnaryClientInterceptor(aio.StreamUnaryClientInterceptor, _ClientInterceptor): + async def intercept_stream_unary( + self, + continuation: Callable[[aio.ClientCallDetails, RequestType], aio.StreamUnaryCall], + client_call_details: aio.ClientCallDetails, + request_iterator: RequestIterableType, + ) -> aio.StreamUnaryCall: + span, client_call_details = self._intercept_client_call( + constants.GRPC_METHOD_KIND_CLIENT_STREAMING, + client_call_details, + ) + continuation_with_args = functools.partial(continuation, client_call_details, request_iterator) + return await self._wrap_unary_response(continuation_with_args, span) + + +class _StreamStreamClientInterceptor(aio.StreamStreamClientInterceptor, _ClientInterceptor): + async def intercept_stream_stream( + self, + continuation: Callable[[aio.ClientCallDetails, RequestType], aio.StreamStreamCall], + client_call_details: aio.ClientCallDetails, + request_iterator: RequestIterableType, + ) -> Union[aio.StreamStreamCall, ResponseIterableType]: + span, client_call_details = self._intercept_client_call( + constants.GRPC_METHOD_KIND_BIDI_STREAMING, + client_call_details, + ) + call = await continuation(client_call_details, request_iterator) + return self._wrap_stream_response(call, span) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/aio_server_interceptor.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/aio_server_interceptor.py new file mode 100644 index 0000000..11e0a27 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/aio_server_interceptor.py @@ -0,0 +1,301 @@ +import inspect +from typing import Any # noqa:F401 +from typing import Awaitable # noqa:F401 +from typing import Callable # noqa:F401 +from typing import Iterable # noqa:F401 +from typing import Union # noqa:F401 + +import grpc +from grpc import aio +from grpc.aio._typing import RequestIterableType +from grpc.aio._typing import RequestType +from grpc.aio._typing import ResponseIterableType +from grpc.aio._typing import ResponseType + +from ddtrace import Pin # noqa:F401 +from ddtrace import Span # noqa:F401 +from ddtrace import config +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema import schematize_url_operation +from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.vendor import wrapt + +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import ERROR_MSG +from ...constants import ERROR_TYPE +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from ...internal.compat import to_unicode +from .. import trace_utils +from ..grpc import constants +from ..grpc.utils import set_grpc_method_meta + + +Continuation = Callable[[grpc.HandlerCallDetails], Awaitable[grpc.RpcMethodHandler]] + + +# Used to get a status code from integer +# as `grpc._cython.cygrpc._ServicerContext.code()` returns an integer. +_INT2CODE = {s.value[0]: s for s in grpc.StatusCode} + + +def _is_coroutine_handler(handler): + # type: (grpc.RpcMethodHandler) -> bool + if not handler.request_streaming and not handler.response_streaming: + return inspect.iscoroutinefunction(handler.unary_unary) + elif not handler.request_streaming and handler.response_streaming: + return inspect.iscoroutinefunction(handler.unary_stream) + elif handler.request_streaming and not handler.response_streaming: + return inspect.iscoroutinefunction(handler.stream_unary) + else: + return inspect.iscoroutinefunction(handler.stream_stream) + + +def _is_async_gen_handler(handler): + # type: (grpc.RpcMethodHandler) -> bool + if not handler.response_streaming: + return False + if handler.request_streaming: + return inspect.isasyncgenfunction(handler.stream_stream) + else: + return inspect.isasyncgenfunction(handler.unary_stream) + + +def create_aio_server_interceptor(pin): + # type: (Pin) -> _ServerInterceptor + async def interceptor_function( + continuation, # type: Continuation + handler_call_details, # type: grpc.HandlerCallDetails + ): + # type: (...) -> Union[TracedRpcMethodHandlerType, None] + rpc_method_handler = await continuation(handler_call_details) + + # continuation returns an RpcMethodHandler instance if the RPC is + # considered serviced, or None otherwise + # https://grpc.github.io/grpc/python/grpc.html#grpc.ServerInterceptor.intercept_service + + if rpc_method_handler is None: + return None + + # Since streaming response RPC can be either a coroutine or an async generator, we're checking either here. + if _is_coroutine_handler(rpc_method_handler): + return _TracedCoroRpcMethodHandler(pin, handler_call_details, rpc_method_handler) + elif _is_async_gen_handler(rpc_method_handler): + return _TracedAsyncGenRpcMethodHandler(pin, handler_call_details, rpc_method_handler) + else: + return _TracedRpcMethodHandler(pin, handler_call_details, rpc_method_handler) + + return _ServerInterceptor(interceptor_function) + + +def _handle_server_exception( + servicer_context, # type: Union[None, grpc.ServicerContext] + span, # type: Span +): + # type: (...) -> None + span.error = 1 + if servicer_context is None: + return + if hasattr(servicer_context, "details"): + span.set_tag_str(ERROR_MSG, to_unicode(servicer_context.details())) + if hasattr(servicer_context, "code") and servicer_context.code() != 0 and servicer_context.code() in _INT2CODE: + span.set_tag_str(ERROR_TYPE, to_unicode(_INT2CODE[servicer_context.code()])) + + +async def _wrap_aio_stream_response( + behavior: Callable[[Union[RequestIterableType, RequestType], aio.ServicerContext], ResponseIterableType], + request_or_iterator: Union[RequestIterableType, RequestType], + servicer_context: aio.ServicerContext, + span: Span, +) -> ResponseIterableType: + try: + call = behavior(request_or_iterator, servicer_context) + async for response in call: + yield response + except Exception: + span.set_traceback() + _handle_server_exception(servicer_context, span) + raise + finally: + span.finish() + + +async def _wrap_aio_unary_response( + behavior: Callable[[Union[RequestIterableType, RequestType], aio.ServicerContext], Awaitable[ResponseType]], + request_or_iterator: Union[RequestIterableType, RequestType], + servicer_context: aio.ServicerContext, + span: Span, +) -> ResponseType: + try: + return await behavior(request_or_iterator, servicer_context) + except Exception: + span.set_traceback() + _handle_server_exception(servicer_context, span) + raise + finally: + span.finish() + + +def _wrap_stream_response( + behavior, # type: Callable[[Any, grpc.ServicerContext], Iterable[Any]] + request_or_iterator, # type: Any + servicer_context, # type: grpc.ServicerContext + span, # type: Span +): + # type: (...) -> Iterable[Any] + try: + for response in behavior(request_or_iterator, servicer_context): + yield response + except Exception: + span.set_traceback() + _handle_server_exception(servicer_context, span) + raise + finally: + span.finish() + + +def _wrap_unary_response( + behavior, # type: Callable[[Any, grpc.ServicerContext], Any] + request_or_iterator, # type: Any + servicer_context, # type: grpc.ServicerContext + span, # type: Span +): + # type: (...) -> Any + try: + return behavior(request_or_iterator, servicer_context) + except Exception: + span.set_traceback() + _handle_server_exception(servicer_context, span) + raise + finally: + span.finish() + + +def _create_span(pin, handler_call_details, method_kind): + # type: (Pin, grpc.HandlerCallDetails, str) -> Span + tracer = pin.tracer + headers = dict(handler_call_details.invocation_metadata) + + trace_utils.activate_distributed_headers(tracer, int_config=config.grpc_aio_server, request_headers=headers) + + span = tracer.trace( + schematize_url_operation("grpc", protocol="grpc", direction=SpanDirection.INBOUND), + span_type=SpanTypes.GRPC, + service=trace_utils.int_service(pin, config.grpc_aio_server), + resource=handler_call_details.method, + ) + + span.set_tag_str(COMPONENT, config.grpc_aio_server.integration_name) + + # set span.kind to the type of operation being performed + span.set_tag_str(SPAN_KIND, SpanKind.SERVER) + + span.set_tag(SPAN_MEASURED_KEY) + + set_grpc_method_meta(span, handler_call_details.method, method_kind) + span.set_tag_str(constants.GRPC_SPAN_KIND_KEY, constants.GRPC_SPAN_KIND_VALUE_SERVER) + + sample_rate = config.grpc_aio_server.get_analytics_sample_rate() + if sample_rate is not None: + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, sample_rate) + + if pin.tags: + span.set_tags(pin.tags) + + return span + + +class _TracedCoroRpcMethodHandler(wrapt.ObjectProxy): + def __init__(self, pin, handler_call_details, wrapped): + # type: (Pin, grpc.HandlerCallDetails, grpc.RpcMethodHandler) -> None + super(_TracedCoroRpcMethodHandler, self).__init__(wrapped) + self._pin = pin + self._handler_call_details = handler_call_details + + async def unary_unary(self, request: RequestType, context: aio.ServicerContext) -> ResponseType: + span = _create_span(self._pin, self._handler_call_details, constants.GRPC_METHOD_KIND_UNARY) + return await _wrap_aio_unary_response(self.__wrapped__.unary_unary, request, context, span) + + async def unary_stream(self, request: RequestType, context: aio.ServicerContext) -> ResponseType: + span = _create_span(self._pin, self._handler_call_details, constants.GRPC_METHOD_KIND_SERVER_STREAMING) + return await _wrap_aio_unary_response(self.__wrapped__.unary_stream, request, context, span) + + async def stream_unary(self, request_iterator: RequestIterableType, context: aio.ServicerContext) -> ResponseType: + span = _create_span(self._pin, self._handler_call_details, constants.GRPC_METHOD_KIND_CLIENT_STREAMING) + return await _wrap_aio_unary_response(self.__wrapped__.stream_unary, request_iterator, context, span) + + async def stream_stream(self, request_iterator: RequestIterableType, context: aio.ServicerContext) -> ResponseType: + span = _create_span(self._pin, self._handler_call_details, constants.GRPC_METHOD_KIND_BIDI_STREAMING) + return await _wrap_aio_unary_response(self.__wrapped__.stream_stream, request_iterator, context, span) + + +class _TracedAsyncGenRpcMethodHandler(wrapt.ObjectProxy): + def __init__(self, pin, handler_call_details, wrapped): + # type: (Pin, grpc.HandlerCallDetails, grpc.RpcMethodHandler) -> None + super(_TracedAsyncGenRpcMethodHandler, self).__init__(wrapped) + self._pin = pin + self._handler_call_details = handler_call_details + + async def unary_stream(self, request: RequestType, context: aio.ServicerContext) -> ResponseIterableType: + span = _create_span(self._pin, self._handler_call_details, constants.GRPC_METHOD_KIND_SERVER_STREAMING) + async for response in _wrap_aio_stream_response(self.__wrapped__.unary_stream, request, context, span): + yield response + + async def stream_stream( + self, request_iterator: RequestIterableType, context: aio.ServicerContext + ) -> ResponseIterableType: + span = _create_span(self._pin, self._handler_call_details, constants.GRPC_METHOD_KIND_BIDI_STREAMING) + async for response in _wrap_aio_stream_response( + self.__wrapped__.stream_stream, request_iterator, context, span + ): + yield response + + +class _TracedRpcMethodHandler(wrapt.ObjectProxy): + def __init__(self, pin, handler_call_details, wrapped): + # type: (Pin, grpc.HandlerCallDetails, grpc.RpcMethodHandler) -> None + super(_TracedRpcMethodHandler, self).__init__(wrapped) + self._pin = pin + self._handler_call_details = handler_call_details + + def unary_unary(self, request, context): + # type: (Any, grpc.ServicerContext) -> Any + span = _create_span(self._pin, self._handler_call_details, constants.GRPC_METHOD_KIND_UNARY) + return _wrap_unary_response(self.__wrapped__.unary_unary, request, context, span) + + def unary_stream(self, request, context): + # type: (Any, grpc.ServicerContext) -> Iterable[Any] + span = _create_span(self._pin, self._handler_call_details, constants.GRPC_METHOD_KIND_SERVER_STREAMING) + for response in _wrap_stream_response(self.__wrapped__.unary_stream, request, context, span): + yield response + + def stream_unary(self, request_iterator, context): + # type: (Iterable[Any], grpc.ServicerContext) -> Any + span = _create_span(self._pin, self._handler_call_details, constants.GRPC_METHOD_KIND_CLIENT_STREAMING) + return _wrap_unary_response(self.__wrapped__.stream_unary, request_iterator, context, span) + + def stream_stream(self, request_iterator, context): + # type: (Iterable[Any], grpc.ServicerContext) -> Iterable[Any] + span = _create_span(self._pin, self._handler_call_details, constants.GRPC_METHOD_KIND_BIDI_STREAMING) + for response in _wrap_stream_response(self.__wrapped__.stream_stream, request_iterator, context, span): + yield response + + +TracedRpcMethodHandlerType = Union[ + _TracedAsyncGenRpcMethodHandler, _TracedCoroRpcMethodHandler, _TracedRpcMethodHandler +] + + +class _ServerInterceptor(aio.ServerInterceptor): + def __init__(self, interceptor_function): + self._fn = interceptor_function + + async def intercept_service( + self, + continuation, # type: Continuation + handler_call_details, # type: grpc.HandlerCallDetails + ): + # type: (...) -> Union[TracedRpcMethodHandlerType, None] + return await self._fn(continuation, handler_call_details) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/client_interceptor.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/client_interceptor.py new file mode 100644 index 0000000..59dc4ab --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/client_interceptor.py @@ -0,0 +1,275 @@ +import collections + +import grpc + +from ddtrace import config +from ddtrace.ext import SpanKind +from ddtrace.ext import SpanTypes +from ddtrace.internal.compat import to_unicode +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.vendor import wrapt + +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import ERROR_MSG +from ...constants import ERROR_STACK +from ...constants import ERROR_TYPE +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...internal.logger import get_logger +from ...internal.schema import schematize_url_operation +from ...propagation.http import HTTPPropagator +from .. import trace_utils +from . import constants +from . import utils + + +log = get_logger(__name__) + +# DEV: Follows Python interceptors RFC laid out in +# https://github.com/grpc/proposal/blob/master/L13-python-interceptors.md + +# DEV: __version__ added in v1.21.4 +# https://github.com/grpc/grpc/commit/dd4830eae80143f5b0a9a3a1a024af4cf60e7d02 + + +def create_client_interceptor(pin, host, port): + return _ClientInterceptor(pin, host, port) + + +def intercept_channel(wrapped, instance, args, kwargs): + channel = args[0] + interceptors = args[1:] + if isinstance(getattr(channel, "_interceptor", None), _ClientInterceptor): + dd_interceptor = channel._interceptor + base_channel = getattr(channel, "_channel", None) + if base_channel: + new_channel = wrapped(channel._channel, *interceptors) + return grpc.intercept_channel(new_channel, dd_interceptor) + + return wrapped(*args, **kwargs) + + +class _ClientCallDetails( + collections.namedtuple("_ClientCallDetails", ("method", "timeout", "metadata", "credentials")), + grpc.ClientCallDetails, +): + pass + + +def _future_done_callback(span): + def func(response): + try: + # pull out response code from gRPC response to use both for `grpc.status.code` + # tag and the error type tag if the response is an exception + response_code = response.code() + # cast code to unicode for tags + status_code = to_unicode(response_code) + span.set_tag_str(constants.GRPC_STATUS_CODE_KEY, status_code) + + if response_code != grpc.StatusCode.OK: + _handle_error(span, response, status_code) + finally: + span.finish() + + return func + + +def _handle_response(span, response): + # use duck-typing to support future-like response as in the case of + # google-api-core which has its own future base class + # https://github.com/googleapis/python-api-core/blob/49c6755a21215bbb457b60db91bab098185b77da/google/api_core/future/base.py#L23 + if hasattr(response, "add_done_callback"): + response.add_done_callback(_future_done_callback(span)) + + +def _handle_error(span, response_error, status_code): + # response_error should be a grpc.Future and so we expect to have cancelled(), + # exception() and traceback() methods if a computation has resulted in an + # exception being raised + if ( + not callable(getattr(response_error, "cancelled", None)) + and not callable(getattr(response_error, "exception", None)) + and not callable(getattr(response_error, "traceback", None)) + ): + return + + if response_error.cancelled(): + # handle cancelled futures separately to avoid raising grpc.FutureCancelledError + span.error = 1 + exc_val = to_unicode(response_error.details()) + span.set_tag_str(ERROR_MSG, exc_val) + span.set_tag_str(ERROR_TYPE, status_code) + return + + exception = response_error.exception() + traceback = response_error.traceback() + + if exception is not None and traceback is not None: + span.error = 1 + if isinstance(exception, grpc.RpcError): + # handle internal gRPC exceptions separately to get status code and + # details as tags properly + exc_val = to_unicode(response_error.details()) + span.set_tag_str(ERROR_MSG, exc_val) + span.set_tag_str(ERROR_TYPE, status_code) + span.set_tag_str(ERROR_STACK, str(traceback)) + else: + exc_type = type(exception) + span.set_exc_info(exc_type, exception, traceback) + status_code = to_unicode(response_error.code()) + + +class _WrappedResponseCallFuture(wrapt.ObjectProxy): + def __init__(self, wrapped, span): + super(_WrappedResponseCallFuture, self).__init__(wrapped) + self._span = span + # Registers callback on the _MultiThreadedRendezvous future to finish + # span in case StopIteration is never raised but RPC is terminated + _handle_response(self._span, self.__wrapped__) + + def __iter__(self): + return self + + def _next(self): + # While an iterator ObjectProxy requires only __iter__ and __next__, we + # make sure to also proxy the grpc._channel._Rendezvous._next method in + # case it is being called directly as in google.api_core.grpc_helpers + # and grpc_gcp._channel. + # https://github.com/grpc/grpc/blob/5195a06ddea8da6603c6672e0ed09fec9b5c16ac/src/python/grpcio/grpc/_channel.py#L418-L419 + # https://github.com/googleapis/python-api-core/blob/35e87e0aca52167029784379ca84e979098e1d6c/google/api_core/grpc_helpers.py#L84 + # https://github.com/GoogleCloudPlatform/grpc-gcp-python/blob/5a2cd9807bbaf1b85402a2a364775e5b65853df6/src/grpc_gcp/_channel.py#L102 + try: + return next(self.__wrapped__) + except StopIteration: + # Callback will handle span finishing + raise + except grpc.RpcError as rpc_error: + # DEV: grpcio<1.18.0 grpc.RpcError is raised rather than returned as response + # https://github.com/grpc/grpc/commit/8199aff7a66460fbc4e9a82ade2e95ef076fd8f9 + # handle as a response + _handle_response(self._span, rpc_error) + raise + except Exception: + # DEV: added for safety though should not be reached since wrapped response + log.debug("unexpected non-grpc exception raised, closing open span", exc_info=True) + self._span.set_traceback() + self._span.finish() + raise + + def __next__(self): + return self._next() + + next = __next__ + + +class _ClientInterceptor( + grpc.UnaryUnaryClientInterceptor, + grpc.UnaryStreamClientInterceptor, + grpc.StreamUnaryClientInterceptor, + grpc.StreamStreamClientInterceptor, +): + def __init__(self, pin, host, port): + self._pin = pin + self._host = host + self._port = port + + def _intercept_client_call(self, method_kind, client_call_details): + tracer = self._pin.tracer + + span = tracer.trace( + schematize_url_operation("grpc", protocol="grpc", direction=SpanDirection.OUTBOUND), + span_type=SpanTypes.GRPC, + service=trace_utils.ext_service(self._pin, config.grpc), + resource=client_call_details.method, + ) + + span.set_tag_str(COMPONENT, config.grpc.integration_name) + + # set span.kind to the type of operation being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + span.set_tag(SPAN_MEASURED_KEY) + + utils.set_grpc_method_meta(span, client_call_details.method, method_kind) + utils.set_grpc_client_meta(span, self._host, self._port) + span.set_tag_str(constants.GRPC_SPAN_KIND_KEY, constants.GRPC_SPAN_KIND_VALUE_CLIENT) + + sample_rate = config.grpc.get_analytics_sample_rate() + if sample_rate is not None: + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, sample_rate) + + # inject tags from pin + if self._pin.tags: + span.set_tags(self._pin.tags) + + # propagate distributed tracing headers if available + headers = {} + if config.grpc.distributed_tracing_enabled: + HTTPPropagator.inject(span.context, headers) + + metadata = [] + if client_call_details.metadata is not None: + metadata = list(client_call_details.metadata) + metadata.extend(headers.items()) + + client_call_details = _ClientCallDetails( + client_call_details.method, + client_call_details.timeout, + metadata, + client_call_details.credentials, + ) + + return span, client_call_details + + def intercept_unary_unary(self, continuation, client_call_details, request): + span, client_call_details = self._intercept_client_call( + constants.GRPC_METHOD_KIND_UNARY, + client_call_details, + ) + try: + response = continuation(client_call_details, request) + _handle_response(span, response) + except grpc.RpcError as rpc_error: + # DEV: grpcio<1.18.0 grpc.RpcError is raised rather than returned as response + # https://github.com/grpc/grpc/commit/8199aff7a66460fbc4e9a82ade2e95ef076fd8f9 + # handle as a response + _handle_response(span, rpc_error) + raise + + return response + + def intercept_unary_stream(self, continuation, client_call_details, request): + span, client_call_details = self._intercept_client_call( + constants.GRPC_METHOD_KIND_SERVER_STREAMING, + client_call_details, + ) + response_iterator = continuation(client_call_details, request) + response_iterator = _WrappedResponseCallFuture(response_iterator, span) + return response_iterator + + def intercept_stream_unary(self, continuation, client_call_details, request_iterator): + span, client_call_details = self._intercept_client_call( + constants.GRPC_METHOD_KIND_CLIENT_STREAMING, + client_call_details, + ) + try: + response = continuation(client_call_details, request_iterator) + _handle_response(span, response) + except grpc.RpcError as rpc_error: + # DEV: grpcio<1.18.0 grpc.RpcError is raised rather than returned as response + # https://github.com/grpc/grpc/commit/8199aff7a66460fbc4e9a82ade2e95ef076fd8f9 + # handle as a response + _handle_response(span, rpc_error) + raise + + return response + + def intercept_stream_stream(self, continuation, client_call_details, request_iterator): + span, client_call_details = self._intercept_client_call( + constants.GRPC_METHOD_KIND_BIDI_STREAMING, + client_call_details, + ) + response_iterator = continuation(client_call_details, request_iterator) + response_iterator = _WrappedResponseCallFuture(response_iterator, span) + return response_iterator diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/constants.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/constants.py new file mode 100644 index 0000000..232c709 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/constants.py @@ -0,0 +1,26 @@ +import grpc + + +GRPC_PIN_MODULE_SERVER = grpc.Server +GRPC_PIN_MODULE_CLIENT = grpc.Channel +GRPC_METHOD_PATH_KEY = "grpc.method.path" +GRPC_METHOD_PACKAGE_SERVICE_KEY = "rpc.service" +GRPC_METHOD_PACKAGE_KEY = "grpc.method.package" +GRPC_METHOD_SERVICE_KEY = "grpc.method.service" +GRPC_METHOD_NAME_KEY = "grpc.method.name" +GRPC_METHOD_KIND_KEY = "grpc.method.kind" +GRPC_STATUS_CODE_KEY = "grpc.status.code" +GRPC_REQUEST_METADATA_PREFIX_KEY = "grpc.request.metadata." +GRPC_RESPONSE_METADATA_PREFIX_KEY = "grpc.response.metadata." +GRPC_HOST_KEY = "grpc.host" +GRPC_SPAN_KIND_KEY = "span.kind" +GRPC_SPAN_KIND_VALUE_CLIENT = "client" +GRPC_SPAN_KIND_VALUE_SERVER = "server" +GRPC_METHOD_KIND_UNARY = "unary" +GRPC_METHOD_KIND_CLIENT_STREAMING = "client_streaming" +GRPC_METHOD_KIND_SERVER_STREAMING = "server_streaming" +GRPC_METHOD_KIND_BIDI_STREAMING = "bidi_streaming" +GRPC_SERVICE_SERVER = "grpc-server" +GRPC_AIO_SERVICE_SERVER = "grpc-aio-server" +GRPC_SERVICE_CLIENT = "grpc-client" +GRPC_AIO_SERVICE_CLIENT = "grpc-aio-client" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/patch.py new file mode 100644 index 0000000..25bf4df --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/patch.py @@ -0,0 +1,260 @@ +import grpc + +from ddtrace import Pin +from ddtrace import config +from ddtrace.internal.schema import schematize_service_name +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w + +from ..trace_utils import unwrap as _u +from . import constants +from . import utils +from .client_interceptor import create_client_interceptor +from .client_interceptor import intercept_channel +from .server_interceptor import create_server_interceptor + + +def get_version(): + # type: () -> str + return getattr(grpc, "__version__", "") + + +try: + # `grpc.aio` is only available with `grpcio>=1.32`. + import grpc.aio + + from .aio_client_interceptor import create_aio_client_interceptors + from .aio_server_interceptor import create_aio_server_interceptor + + HAS_GRPC_AIO = True + # NOTE: These are not defined in constants.py because we would end up having + # try-except in both files. + GRPC_AIO_PIN_MODULE_SERVER = grpc.aio.Server + GRPC_AIO_PIN_MODULE_CLIENT = grpc.aio.Channel +except ImportError: + HAS_GRPC_AIO = False + # NOTE: These are defined just to prevent a 'not defined' error. + # Be sure not to use them when `HAS_GRPC_AIO` is False. + GRPC_AIO_PIN_MODULE_SERVER = None + GRPC_AIO_PIN_MODULE_CLIENT = None + +config._add( + "grpc_server", + dict( + _default_service=schematize_service_name(constants.GRPC_SERVICE_SERVER), + distributed_tracing_enabled=True, + ), +) + +config._add( + "grpc_client", + dict( + _default_service=schematize_service_name(constants.GRPC_SERVICE_CLIENT), + distributed_tracing_enabled=True, + ), +) + + +# TODO[tbutt]: keeping name for client config unchanged to maintain backwards +# compatibility but should change in future +config._add( + "grpc", + dict( + _default_service=schematize_service_name(constants.GRPC_SERVICE_CLIENT), + distributed_tracing_enabled=True, + ), +) + + +if HAS_GRPC_AIO: + config._add( + "grpc_aio_server", + dict( + _default_service=schematize_service_name(constants.GRPC_AIO_SERVICE_SERVER), + distributed_tracing_enabled=True, + ), + ) + + config._add( + "grpc_aio_client", + dict( + _default_service=schematize_service_name(constants.GRPC_AIO_SERVICE_CLIENT), + distributed_tracing_enabled=True, + ), + ) + + +def patch(): + _patch_client() + _patch_server() + if HAS_GRPC_AIO: + _patch_aio_client() + _patch_aio_server() + + +def unpatch(): + _unpatch_client() + _unpatch_server() + if HAS_GRPC_AIO: + _unpatch_aio_client() + _unpatch_aio_server() + + +def _patch_client(): + if getattr(constants.GRPC_PIN_MODULE_CLIENT, "__datadog_patch", False): + return + constants.GRPC_PIN_MODULE_CLIENT.__datadog_patch = True + + Pin().onto(constants.GRPC_PIN_MODULE_CLIENT) + + _w("grpc", "insecure_channel", _client_channel_interceptor) + _w("grpc", "secure_channel", _client_channel_interceptor) + _w("grpc", "intercept_channel", intercept_channel) + + +def _patch_aio_client(): + if getattr(GRPC_AIO_PIN_MODULE_CLIENT, "__datadog_patch", False): + return + GRPC_AIO_PIN_MODULE_CLIENT.__datadog_patch = True + + Pin().onto(GRPC_AIO_PIN_MODULE_CLIENT) + + _w("grpc.aio", "insecure_channel", _aio_client_channel_interceptor) + _w("grpc.aio", "secure_channel", _aio_client_channel_interceptor) + + +def _unpatch_client(): + if not getattr(constants.GRPC_PIN_MODULE_CLIENT, "__datadog_patch", False): + return + constants.GRPC_PIN_MODULE_CLIENT.__datadog_patch = False + + pin = Pin.get_from(constants.GRPC_PIN_MODULE_CLIENT) + if pin: + pin.remove_from(constants.GRPC_PIN_MODULE_CLIENT) + + _u(grpc, "secure_channel") + _u(grpc, "insecure_channel") + _u(grpc, "intercept_channel") + + +def _unpatch_aio_client(): + if not getattr(GRPC_AIO_PIN_MODULE_CLIENT, "__datadog_patch", False): + return + GRPC_AIO_PIN_MODULE_CLIENT.__datadog_patch = False + + pin = Pin.get_from(GRPC_AIO_PIN_MODULE_CLIENT) + if pin: + pin.remove_from(GRPC_AIO_PIN_MODULE_CLIENT) + + _u(grpc.aio, "insecure_channel") + _u(grpc.aio, "secure_channel") + + +def _patch_server(): + if getattr(constants.GRPC_PIN_MODULE_SERVER, "__datadog_patch", False): + return + constants.GRPC_PIN_MODULE_SERVER.__datadog_patch = True + + Pin().onto(constants.GRPC_PIN_MODULE_SERVER) + + _w("grpc", "server", _server_constructor_interceptor) + + +def _patch_aio_server(): + if getattr(GRPC_AIO_PIN_MODULE_SERVER, "__datadog_patch", False): + return + GRPC_AIO_PIN_MODULE_SERVER.__datadog_patch = True + + Pin().onto(GRPC_AIO_PIN_MODULE_SERVER) + + _w("grpc.aio", "server", _aio_server_constructor_interceptor) + + +def _unpatch_server(): + if not getattr(constants.GRPC_PIN_MODULE_SERVER, "__datadog_patch", False): + return + constants.GRPC_PIN_MODULE_SERVER.__datadog_patch = False + + pin = Pin.get_from(constants.GRPC_PIN_MODULE_SERVER) + if pin: + pin.remove_from(constants.GRPC_PIN_MODULE_SERVER) + + _u(grpc, "server") + + +def _unpatch_aio_server(): + if not getattr(GRPC_AIO_PIN_MODULE_SERVER, "__datadog_patch", False): + return + GRPC_AIO_PIN_MODULE_SERVER.__datadog_patch = False + + pin = Pin.get_from(GRPC_AIO_PIN_MODULE_SERVER) + if pin: + pin.remove_from(GRPC_AIO_PIN_MODULE_SERVER) + + _u(grpc.aio, "server") + + +def _client_channel_interceptor(wrapped, instance, args, kwargs): + channel = wrapped(*args, **kwargs) + + pin = Pin.get_from(channel) + if not pin or not pin.enabled(): + return channel + + (host, port) = utils._parse_target_from_args(args, kwargs) + + interceptor_function = create_client_interceptor(pin, host, port) + return grpc.intercept_channel(channel, interceptor_function) + + +def _aio_client_channel_interceptor(wrapped, instance, args, kwargs): + channel = wrapped(*args, **kwargs) + + pin = Pin.get_from(channel) + if not pin or not pin.enabled(): + return channel + + (host, port) = utils._parse_target_from_args(args, kwargs) + + interceptors = create_aio_client_interceptors(pin, host, port) + # DEV: Inject our tracing interceptor first in the list of interceptors + if "interceptors" in kwargs: + kwargs["interceptors"] = interceptors + tuple(kwargs["interceptors"]) + else: + kwargs["interceptors"] = interceptors + + return wrapped(*args, **kwargs) + + +def _server_constructor_interceptor(wrapped, instance, args, kwargs): + # DEV: we clone the pin on the grpc module and configure it for the server + # interceptor + + pin = Pin.get_from(constants.GRPC_PIN_MODULE_SERVER) + if not pin or not pin.enabled(): + return wrapped(*args, **kwargs) + + interceptor = create_server_interceptor(pin) + + # DEV: Inject our tracing interceptor first in the list of interceptors + if "interceptors" in kwargs: + kwargs["interceptors"] = (interceptor,) + tuple(kwargs["interceptors"]) + else: + kwargs["interceptors"] = (interceptor,) + + return wrapped(*args, **kwargs) + + +def _aio_server_constructor_interceptor(wrapped, instance, args, kwargs): + pin = Pin.get_from(GRPC_AIO_PIN_MODULE_SERVER) + + if not pin or not pin.enabled(): + return wrapped(*args, **kwargs) + + interceptor = create_aio_server_interceptor(pin) + # DEV: Inject our tracing interceptor first in the list of interceptors + if "interceptors" in kwargs: + kwargs["interceptors"] = (interceptor,) + tuple(kwargs["interceptors"]) + else: + kwargs["interceptors"] = (interceptor,) + + return wrapped(*args, **kwargs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/server_interceptor.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/server_interceptor.py new file mode 100644 index 0000000..0691cd9 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/server_interceptor.py @@ -0,0 +1,135 @@ +import grpc + +from ddtrace import config +from ddtrace.internal.compat import to_unicode +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema import schematize_url_operation +from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.vendor import wrapt + +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import ERROR_MSG +from ...constants import ERROR_TYPE +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from .. import trace_utils +from . import constants +from .utils import set_grpc_method_meta + + +def create_server_interceptor(pin): + def interceptor_function(continuation, handler_call_details): + if not pin.enabled: + return continuation(handler_call_details) + + rpc_method_handler = continuation(handler_call_details) + + # continuation returns an RpcMethodHandler instance if the RPC is + # considered serviced, or None otherwise + # https://grpc.github.io/grpc/python/grpc.html#grpc.ServerInterceptor.intercept_service + + if rpc_method_handler: + return _TracedRpcMethodHandler(pin, handler_call_details, rpc_method_handler) + + return rpc_method_handler + + return _ServerInterceptor(interceptor_function) + + +def _handle_server_exception(server_context, span): + if server_context is not None and hasattr(server_context, "_state") and server_context._state is not None: + code = to_unicode(server_context._state.code) + details = to_unicode(server_context._state.details) + span.error = 1 + span.set_tag_str(ERROR_MSG, details) + span.set_tag_str(ERROR_TYPE, code) + + +def _wrap_response_iterator(response_iterator, server_context, span): + try: + for response in response_iterator: + yield response + except Exception: + span.set_traceback() + _handle_server_exception(server_context, span) + raise + finally: + span.finish() + + +class _TracedRpcMethodHandler(wrapt.ObjectProxy): + def __init__(self, pin, handler_call_details, wrapped): + super(_TracedRpcMethodHandler, self).__init__(wrapped) + self._pin = pin + self._handler_call_details = handler_call_details + + def _fn(self, method_kind, behavior, args, kwargs): + tracer = self._pin.tracer + headers = dict(self._handler_call_details.invocation_metadata) + + trace_utils.activate_distributed_headers(tracer, int_config=config.grpc_server, request_headers=headers) + + span = tracer.trace( + schematize_url_operation("grpc", protocol="grpc", direction=SpanDirection.INBOUND), + span_type=SpanTypes.GRPC, + service=trace_utils.int_service(self._pin, config.grpc_server), + resource=self._handler_call_details.method, + ) + + span.set_tag_str(COMPONENT, config.grpc_server.integration_name) + + # set span.kind tag equal to type of span + span.set_tag_str(SPAN_KIND, SpanKind.SERVER) + + span.set_tag(SPAN_MEASURED_KEY) + + set_grpc_method_meta(span, self._handler_call_details.method, method_kind) + span.set_tag_str(constants.GRPC_SPAN_KIND_KEY, constants.GRPC_SPAN_KIND_VALUE_SERVER) + + sample_rate = config.grpc_server.get_analytics_sample_rate() + if sample_rate is not None: + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, sample_rate) + + # access server context by taking second argument as server context + # if not found, skip using context to tag span with server state information + server_context = args[1] if isinstance(args[1], grpc.ServicerContext) else None + + if self._pin.tags: + span.set_tags(self._pin.tags) + + try: + response_or_iterator = behavior(*args, **kwargs) + + if self.__wrapped__.response_streaming: + response_or_iterator = _wrap_response_iterator(response_or_iterator, server_context, span) + except Exception: + span.set_traceback() + _handle_server_exception(server_context, span) + raise + finally: + if not self.__wrapped__.response_streaming: + span.finish() + + return response_or_iterator + + def unary_unary(self, *args, **kwargs): + return self._fn(constants.GRPC_METHOD_KIND_UNARY, self.__wrapped__.unary_unary, args, kwargs) + + def unary_stream(self, *args, **kwargs): + return self._fn(constants.GRPC_METHOD_KIND_SERVER_STREAMING, self.__wrapped__.unary_stream, args, kwargs) + + def stream_unary(self, *args, **kwargs): + return self._fn(constants.GRPC_METHOD_KIND_CLIENT_STREAMING, self.__wrapped__.stream_unary, args, kwargs) + + def stream_stream(self, *args, **kwargs): + return self._fn(constants.GRPC_METHOD_KIND_BIDI_STREAMING, self.__wrapped__.stream_stream, args, kwargs) + + +class _ServerInterceptor(grpc.ServerInterceptor): + def __init__(self, interceptor_function): + self._fn = interceptor_function + + def intercept_service(self, continuation, handler_call_details): + return self._fn(continuation, handler_call_details) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/utils.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/utils.py new file mode 100644 index 0000000..4589dcf --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/grpc/utils.py @@ -0,0 +1,83 @@ +import ipaddress +import logging + +from ddtrace.internal.compat import parse + +from ...ext import net +from . import constants + + +log = logging.getLogger(__name__) + + +def parse_method_path(method_path): + """Returns (package_service, package, service, method) tuple from parsing method path""" + # unpack method path based on "/{package}.{service}/{method}" + # first remove leading "/" as unnecessary + package_service, method_name = method_path.lstrip("/").rsplit("/", 1) + + package_service_split = package_service.rsplit(".", 1) + # {package} is optional + if len(package_service_split) == 2: + return package_service, package_service_split[0], package_service_split[1], method_name + + return package_service, None, package_service_split[0], method_name + + +def set_grpc_method_meta(span, method, method_kind): + method_path = method + method_package_service, method_package, method_service, method_name = parse_method_path(method_path) + if method_package_service is not None: + span.set_tag_str(constants.GRPC_METHOD_PACKAGE_SERVICE_KEY, method_package_service) + if method_path is not None: + span.set_tag_str(constants.GRPC_METHOD_PATH_KEY, method_path) + if method_package is not None: + span.set_tag_str(constants.GRPC_METHOD_PACKAGE_KEY, method_package) + if method_service is not None: + span.set_tag_str(constants.GRPC_METHOD_SERVICE_KEY, method_service) + if method_name is not None: + span.set_tag_str(constants.GRPC_METHOD_NAME_KEY, method_name) + if method_kind is not None: + span.set_tag_str(constants.GRPC_METHOD_KIND_KEY, method_kind) + + +def set_grpc_client_meta(span, host, port): + if host: + span.set_tag_str(constants.GRPC_HOST_KEY, host) + try: + ipaddress.ip_address(host) + except ValueError: + span.set_tag_str(net.PEER_HOSTNAME, host) + else: + span.set_tag_str(net.TARGET_IP, host) + if port: + span.set_tag_str(net.TARGET_PORT, str(port)) + span.set_tag_str(constants.GRPC_SPAN_KIND_KEY, constants.GRPC_SPAN_KIND_VALUE_CLIENT) + + +def _parse_target_from_args(args, kwargs): + if "target" in kwargs: + target = kwargs["target"] + else: + target = args[0] + + try: + if target is None: + return + + # ensure URI follows RFC 3986 and is preceded by double slash + # https://tools.ietf.org/html/rfc3986#section-3.2 + parsed = parse.urlsplit("//" + target if not target.startswith("//") else target) + port = None + try: + port = parsed.port + except ValueError: + log.warning("Non-integer port in target '%s'", target) + + # an empty hostname in Python 2.7 will be an empty string rather than + # None + hostname = parsed.hostname if parsed.hostname is not None and len(parsed.hostname) > 0 else None + + return hostname, port + except ValueError: + log.warning("Malformed target '%s'.", target) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/gunicorn/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/gunicorn/__init__.py new file mode 100644 index 0000000..d098a3f --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/gunicorn/__init__.py @@ -0,0 +1,22 @@ +""" +ddtrace works with Gunicorn. + +.. note:: + If you cannot wrap your Gunicorn server with the ``ddtrace-run`` command and + it uses ``gevent`` workers be sure to ``import ddtrace.auto`` as early as + possible in your application's lifecycle. + Do not use ``ddtrace-run`` with ``import ddtrace.auto``. +""" + + +def get_version(): + # type: () -> str + return "" + + +def patch(): + pass + + +def unpatch(): + pass diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/httplib/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/httplib/__init__.py new file mode 100644 index 0000000..b3cdaf9 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/httplib/__init__.py @@ -0,0 +1,66 @@ +""" +Trace the standard library ``httplib``/``http.client`` libraries to trace +HTTP requests. + + +Enabling +~~~~~~~~ + +The httplib integration is disabled by default. It can be enabled when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto` +using the ``DD_TRACE_HTTPLIB_ENABLED`` environment variable:: + + DD_TRACE_HTTPLIB_ENABLED=true ddtrace-run .... + +The integration can also be enabled manually in code with +:func:`patch_all()`:: + + from ddtrace import patch_all + patch_all(httplib=True) + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.httplib['distributed_tracing'] + + Include distributed tracing headers in requests sent from httplib. + + This option can also be set with the ``DD_HTTPLIB_DISTRIBUTED_TRACING`` + environment variable. + + Default: ``True`` + + +Instance Configuration +~~~~~~~~~~~~~~~~~~~~~~ + + +The integration can be configured per instance:: + + from ddtrace import config + + # Disable distributed tracing globally. + config.httplib['distributed_tracing'] = False + + # Change the service distributed tracing option only for this HTTP + # connection. + + # Python 2 + connection = urllib.HTTPConnection('www.datadog.com') + + # Python 3 + connection = http.client.HTTPConnection('www.datadog.com') + + cfg = config.get_from(connection) + cfg['distributed_tracing'] = True + + +:ref:`Headers tracing ` is supported for this integration. +""" +from .patch import get_version +from .patch import patch +from .patch import unpatch + + +__all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/httplib/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/httplib/patch.py new file mode 100644 index 0000000..47aa60b --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/httplib/patch.py @@ -0,0 +1,229 @@ +import os +import sys + +from ddtrace import config +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.vendor import wrapt + +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import SPAN_KIND +from ...ext import SpanKind +from ...ext import SpanTypes +from ...internal.compat import httplib +from ...internal.compat import parse +from ...internal.constants import _HTTPLIB_NO_TRACE_REQUEST +from ...internal.logger import get_logger +from ...internal.schema import schematize_url_operation +from ...internal.utils.formats import asbool +from ...pin import Pin +from ...propagation.http import HTTPPropagator +from .. import trace_utils +from ..trace_utils import unwrap as _u + + +span_name = "http.client.request" +span_name = schematize_url_operation(span_name, protocol="http", direction=SpanDirection.OUTBOUND) + +log = get_logger(__name__) + + +config._add( + "httplib", + { + "distributed_tracing": asbool(os.getenv("DD_HTTPLIB_DISTRIBUTED_TRACING", default=True)), + "default_http_tag_query_string": os.getenv("DD_HTTP_CLIENT_TAG_QUERY_STRING", "true"), + }, +) + + +def get_version(): + # type: () -> str + return "" + + +def _wrap_init(func, instance, args, kwargs): + Pin(service=None, _config=config.httplib).onto(instance) + return func(*args, **kwargs) + + +def _wrap_getresponse(func, instance, args, kwargs): + # Use any attached tracer if available, otherwise use the global tracer + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return func(*args, **kwargs) + + resp = None + try: + resp = func(*args, **kwargs) + return resp + finally: + try: + # Get the span attached to this instance, if available + span = getattr(instance, "_datadog_span", None) + if span: + if resp: + trace_utils.set_http_meta( + span, config.httplib, status_code=resp.status, response_headers=resp.getheaders() + ) + + span.finish() + delattr(instance, "_datadog_span") + except Exception: + log.debug("error applying request tags", exc_info=True) + + +def _wrap_request(func, instance, args, kwargs): + # Use any attached tracer if available, otherwise use the global tracer + pin = Pin.get_from(instance) + if should_skip_request(pin, instance): + return func(*args, **kwargs) + + cfg = config.get_from(instance) + + try: + # Create a new span and attach to this instance (so we can retrieve/update/close later on the response) + span = pin.tracer.trace(span_name, span_type=SpanTypes.HTTP) + + span.set_tag_str(COMPONENT, config.httplib.integration_name) + + # set span.kind to the type of operation being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + instance._datadog_span = span + + # propagate distributed tracing headers + if cfg.get("distributed_tracing"): + if len(args) > 3: + headers = args[3] + else: + headers = kwargs.setdefault("headers", {}) + HTTPPropagator.inject(span.context, headers) + except Exception: + log.debug("error configuring request", exc_info=True) + span = getattr(instance, "_datadog_span", None) + if span: + span.finish() + + try: + return func(*args, **kwargs) + except Exception: + span = getattr(instance, "_datadog_span", None) + exc_info = sys.exc_info() + if span: + span.set_exc_info(*exc_info) + span.finish() + raise + + +def _wrap_putrequest(func, instance, args, kwargs): + # Use any attached tracer if available, otherwise use the global tracer + pin = Pin.get_from(instance) + if should_skip_request(pin, instance): + return func(*args, **kwargs) + + try: + if hasattr(instance, "_datadog_span"): + # Reuse an existing span set in _wrap_request + span = instance._datadog_span + else: + # Create a new span and attach to this instance (so we can retrieve/update/close later on the response) + span = pin.tracer.trace(span_name, span_type=SpanTypes.HTTP) + + span.set_tag_str(COMPONENT, config.httplib.integration_name) + + # set span.kind to the type of operation being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + instance._datadog_span = span + + method, path = args[:2] + scheme = "https" if isinstance(instance, httplib.HTTPSConnection) else "http" + port = ":{port}".format(port=instance.port) + + if (scheme == "http" and instance.port == 80) or (scheme == "https" and instance.port == 443): + port = "" + url = "{scheme}://{host}{port}{path}".format(scheme=scheme, host=instance.host, port=port, path=path) + + # sanitize url + parsed = parse.urlparse(url) + sanitized_url = parse.urlunparse( + (parsed.scheme, parsed.netloc, parsed.path, parsed.params, None, parsed.fragment) # drop query + ) + trace_utils.set_http_meta( + span, config.httplib, method=method, url=sanitized_url, target_host=instance.host, query=parsed.query + ) + + # set analytics sample rate + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.httplib.get_analytics_sample_rate()) + except Exception: + log.debug("error applying request tags", exc_info=True) + + # Close the span to prevent memory leaks. + span = getattr(instance, "_datadog_span", None) + if span: + span.finish() + + try: + return func(*args, **kwargs) + except Exception: + span = getattr(instance, "_datadog_span", None) + exc_info = sys.exc_info() + if span: + span.set_exc_info(*exc_info) + span.finish() + raise + + +def _wrap_putheader(func, instance, args, kwargs): + span = getattr(instance, "_datadog_span", None) + if span: + request_headers = {args[0]: args[1]} + trace_utils.set_http_meta(span, config.httplib, request_headers=request_headers) + + return func(*args, **kwargs) + + +def should_skip_request(pin, request): + """Helper to determine if the provided request should be traced""" + if getattr(request, _HTTPLIB_NO_TRACE_REQUEST, False): + return True + + if not pin or not pin.enabled(): + return True + + # httplib is used to send apm events (profiling,di, tracing, etc.) to the datadog agent + # Tracing these requests introduces a significant noise and instability in ddtrace tests. + # TO DO: Avoid tracing requests to APM internal services (ie: extend this functionality to agentless products). + agent_url = pin.tracer.agent_trace_url + if agent_url: + parsed = parse.urlparse(agent_url) + return request.host == parsed.hostname and request.port == parsed.port + return False + + +def patch(): + """patch the built-in urllib/httplib/httplib.client methods for tracing""" + if getattr(httplib, "__datadog_patch", False): + return + httplib.__datadog_patch = True + + # Patch the desired methods + httplib.HTTPConnection.__init__ = wrapt.FunctionWrapper(httplib.HTTPConnection.__init__, _wrap_init) + httplib.HTTPConnection.getresponse = wrapt.FunctionWrapper(httplib.HTTPConnection.getresponse, _wrap_getresponse) + httplib.HTTPConnection.request = wrapt.FunctionWrapper(httplib.HTTPConnection.request, _wrap_request) + httplib.HTTPConnection.putrequest = wrapt.FunctionWrapper(httplib.HTTPConnection.putrequest, _wrap_putrequest) + httplib.HTTPConnection.putheader = wrapt.FunctionWrapper(httplib.HTTPConnection.putheader, _wrap_putheader) + + +def unpatch(): + """unpatch any previously patched modules""" + if not getattr(httplib, "__datadog_patch", False): + return + httplib.__datadog_patch = False + + _u(httplib.HTTPConnection, "__init__") + _u(httplib.HTTPConnection, "getresponse") + _u(httplib.HTTPConnection, "request") + _u(httplib.HTTPConnection, "putrequest") + _u(httplib.HTTPConnection, "putheader") diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/httpx/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/httpx/__init__.py new file mode 100644 index 0000000..509847c --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/httpx/__init__.py @@ -0,0 +1,91 @@ +""" +The httpx__ integration traces all HTTP requests made with the ``httpx`` +library. + +Enabling +~~~~~~~~ + +The ``httpx`` integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Alternatively, use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(httpx=True) + + # use httpx like usual + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.httpx['service'] + + The default service name for ``httpx`` requests. + By default the ``httpx`` integration will not define a service name and inherit + its service name from its parent span. + + If you are making calls to uninstrumented third party applications you can + set this setting, use the ``ddtrace.config.httpx['split_by_domain']`` setting, + or use a ``Pin`` to override an individual connection's settings (see example + below for ``Pin`` usage). + + This option can also be set with the ``DD_HTTPX_SERVICE`` environment + variable. + + Default: ``None`` + + +.. py:data:: ddtrace.config.httpx['distributed_tracing'] + + Whether or not to inject distributed tracing headers into requests. + + Default: ``True`` + + +.. py:data:: ddtrace.config.httpx['split_by_domain'] + + Whether or not to use the domain name of requests as the service name. This + setting can be overridden with session overrides (described in the Instance + Configuration section). + + This setting takes precedence over ``ddtrace.config.httpx['service']`` + + Default: ``False`` + + +Instance Configuration +~~~~~~~~~~~~~~~~~~~~~~ + +To configure particular ``httpx`` client instances use the :class:`Pin ` API:: + + import httpx + from ddtrace import Pin + + client = httpx.Client() + # Override service name for this instance + Pin.override(client, service="custom-http-service") + + async_client = httpx.AsyncClient( + # Override service name for this instance + Pin.override(async_client, service="custom-async-http-service") + + +:ref:`Headers tracing ` is supported for this integration. + +:ref:`HTTP Tagging ` is supported for this integration. + +.. __: https://www.python-httpx.org/ +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["httpx"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/httpx/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/httpx/patch.py new file mode 100644 index 0000000..11647da --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/httpx/patch.py @@ -0,0 +1,204 @@ +import os + +import httpx + +from ddtrace import config +from ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import SPAN_KIND +from ddtrace.constants import SPAN_MEASURED_KEY +from ddtrace.contrib.trace_utils import distributed_tracing_enabled +from ddtrace.contrib.trace_utils import ext_service +from ddtrace.contrib.trace_utils import set_http_meta +from ddtrace.ext import SpanKind +from ddtrace.ext import SpanTypes +from ddtrace.internal.compat import ensure_binary +from ddtrace.internal.compat import ensure_text +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema import schematize_url_operation +from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.internal.utils import get_argument_value +from ddtrace.internal.utils.formats import asbool +from ddtrace.internal.utils.version import parse_version +from ddtrace.internal.utils.wrappers import unwrap as _u +from ddtrace.pin import Pin +from ddtrace.propagation.http import HTTPPropagator +from ddtrace.vendor.wrapt import BoundFunctionWrapper +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w + + +HTTPX_VERSION = parse_version(httpx.__version__) + + +def get_version(): + # type: () -> str + return getattr(httpx, "__version__", "") + + +config._add( + "httpx", + { + "distributed_tracing": asbool(os.getenv("DD_HTTPX_DISTRIBUTED_TRACING", default=True)), + "split_by_domain": asbool(os.getenv("DD_HTTPX_SPLIT_BY_DOMAIN", default=False)), + "default_http_tag_query_string": os.getenv("DD_HTTP_CLIENT_TAG_QUERY_STRING", "true"), + }, +) + + +def _url_to_str(url): + # type: (httpx.URL) -> str + """ + Helper to convert the httpx.URL parts from bytes to a str + """ + # httpx==0.13.0 added URL.raw, removed in httpx==0.23.1. Otherwise, must construct manually + if HTTPX_VERSION < (0, 13, 0): + # Manually construct the same way httpx==0.13 does it: + # https://github.com/encode/httpx/blob/2c2c6a71a9ff520d237f8283a586df2753f01f5e/httpx/_models.py#L161 + scheme = url.scheme.encode("ascii") + host = url.host.encode("ascii") + port = url.port + raw_path = url.full_path.encode("ascii") + elif HTTPX_VERSION < (0, 23, 1): + scheme, host, port, raw_path = url.raw + else: + scheme = url.raw_scheme + host = url.raw_host + port = url.port + raw_path = url.raw_path + url = scheme + b"://" + host + if port is not None: + url += b":" + ensure_binary(str(port)) + url += raw_path + return ensure_text(url) + + +def _get_service_name(pin, request): + # type: (Pin, httpx.Request) -> typing.Text + if config.httpx.split_by_domain: + if hasattr(request.url, "netloc"): + return ensure_text(request.url.netloc, errors="backslashreplace") + else: + service = ensure_binary(request.url.host) + if request.url.port: + service += b":" + ensure_binary(str(request.url.port)) + return ensure_text(service, errors="backslashreplace") + return ext_service(pin, config.httpx) + + +def _init_span(span, request): + # type: (Span, httpx.Request) -> None + span.set_tag(SPAN_MEASURED_KEY) + + if distributed_tracing_enabled(config.httpx): + HTTPPropagator.inject(span.context, request.headers) + + sample_rate = config.httpx.get_analytics_sample_rate(use_global_config=True) + if sample_rate is not None: + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, sample_rate) + + +def _set_span_meta(span, request, response): + # type: (Span, httpx.Request, httpx.Response) -> None + set_http_meta( + span, + config.httpx, + method=request.method, + url=_url_to_str(request.url), + target_host=request.url.host, + status_code=response.status_code if response else None, + query=request.url.query, + request_headers=request.headers, + response_headers=response.headers if response else None, + ) + + +async def _wrapped_async_send( + wrapped: BoundFunctionWrapper, + instance, # type: httpx.AsyncClient + args, # type: typing.Tuple[httpx.Request] + kwargs, # type: typing.Dict[typing.Str, typing.Any] +): + # type: (...) -> typing.Coroutine[None, None, httpx.Response] + req = get_argument_value(args, kwargs, 0, "request") + + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return await wrapped(*args, **kwargs) + + operation_name = schematize_url_operation("http.request", protocol="http", direction=SpanDirection.OUTBOUND) + with pin.tracer.trace(operation_name, service=_get_service_name(pin, req), span_type=SpanTypes.HTTP) as span: + span.set_tag_str(COMPONENT, config.httpx.integration_name) + + # set span.kind to the operation type being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + _init_span(span, req) + resp = None + try: + resp = await wrapped(*args, **kwargs) + return resp + finally: + _set_span_meta(span, req, resp) + + +def _wrapped_sync_send( + wrapped: BoundFunctionWrapper, + instance, # type: httpx.AsyncClient + args, # type: typing.Tuple[httpx.Request] + kwargs, # type: typing.Dict[typing.Str, typing.Any] +): + # type: (...) -> httpx.Response + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return wrapped(*args, **kwargs) + + req = get_argument_value(args, kwargs, 0, "request") + + operation_name = schematize_url_operation("http.request", protocol="http", direction=SpanDirection.OUTBOUND) + with pin.tracer.trace(operation_name, service=_get_service_name(pin, req), span_type=SpanTypes.HTTP) as span: + span.set_tag_str(COMPONENT, config.httpx.integration_name) + + # set span.kind to the operation type being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + _init_span(span, req) + resp = None + try: + resp = wrapped(*args, **kwargs) + return resp + finally: + _set_span_meta(span, req, resp) + + +def patch(): + # type: () -> None + if getattr(httpx, "_datadog_patch", False): + return + + httpx._datadog_patch = True + + pin = Pin() + + if HTTPX_VERSION >= (0, 11): + # httpx==0.11 created synchronous Client class separate from AsyncClient + _w(httpx.Client, "send", _wrapped_sync_send) + _w(httpx.AsyncClient, "send", _wrapped_async_send) + pin.onto(httpx.AsyncClient) + else: + # httpx==0.9 Client class was asynchronous, httpx==0.10 made Client synonymous with AsyncClient + _w(httpx.Client, "send", _wrapped_async_send) + + pin.onto(httpx.Client) + + +def unpatch(): + # type: () -> None + if not getattr(httpx, "_datadog_patch", False): + return + + httpx._datadog_patch = False + + if HTTPX_VERSION >= (0, 11): + # See above patching code for when this patching occurred + _u(httpx.AsyncClient, "send") + + _u(httpx.Client, "send") diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/jinja2/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/jinja2/__init__.py new file mode 100644 index 0000000..5e2214f --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/jinja2/__init__.py @@ -0,0 +1,41 @@ +""" +The ``jinja2`` integration traces templates loading, compilation and rendering. +Auto instrumentation is available using the ``patch``. The following is an example:: + + from ddtrace import patch + from jinja2 import Environment, FileSystemLoader + + patch(jinja2=True) + + env = Environment( + loader=FileSystemLoader("templates") + ) + template = env.get_template('mytemplate.html') + + +The library can be configured globally and per instance, using the Configuration API:: + + from ddtrace import config + + # Change service name globally + config.jinja2['service_name'] = 'jinja-templates' + + # change the service name only for this environment + cfg = config.get_from(env) + cfg['service_name'] = 'jinja-templates' + +By default, the service name is set to None, so it is inherited from the parent span. +If there is no parent span and the service name is not overridden the agent will drop the traces. +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["jinja2"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/jinja2/constants.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/jinja2/constants.py new file mode 100644 index 0000000..1bda6e1 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/jinja2/constants.py @@ -0,0 +1 @@ +DEFAULT_TEMPLATE_NAME = "" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/jinja2/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/jinja2/patch.py new file mode 100644 index 0000000..fede2c6 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/jinja2/patch.py @@ -0,0 +1,110 @@ +import os + +import jinja2 + +from ddtrace import config +from ddtrace.internal.constants import COMPONENT +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w + +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanTypes +from ...internal.utils import ArgumentError +from ...internal.utils import get_argument_value +from ...pin import Pin +from ..trace_utils import unwrap as _u +from .constants import DEFAULT_TEMPLATE_NAME + + +# default settings +config._add( + "jinja2", + { + "service_name": os.getenv("DD_JINJA2_SERVICE_NAME"), + }, +) + + +def get_version(): + # type: () -> str + return getattr(jinja2, "__version__", "") + + +def patch(): + if getattr(jinja2, "__datadog_patch", False): + # already patched + return + jinja2.__datadog_patch = True + Pin( + service=config.jinja2["service_name"], + _config=config.jinja2, + ).onto(jinja2.environment.Environment) + _w(jinja2, "environment.Template.render", _wrap_render) + _w(jinja2, "environment.Template.generate", _wrap_render) + _w(jinja2, "environment.Environment.compile", _wrap_compile) + _w(jinja2, "environment.Environment._load_template", _wrap_load_template) + + +def unpatch(): + if not getattr(jinja2, "__datadog_patch", False): + return + jinja2.__datadog_patch = False + _u(jinja2.Template, "render") + _u(jinja2.Template, "generate") + _u(jinja2.Environment, "compile") + _u(jinja2.Environment, "_load_template") + + +def _wrap_render(wrapped, instance, args, kwargs): + """Wrap `Template.render()` or `Template.generate()`""" + pin = Pin.get_from(instance.environment) + if not pin or not pin.enabled(): + return wrapped(*args, **kwargs) + + template_name = str(instance.name or DEFAULT_TEMPLATE_NAME) + with pin.tracer.trace("jinja2.render", pin.service, span_type=SpanTypes.TEMPLATE) as span: + span.set_tag_str(COMPONENT, config.jinja2.integration_name) + + span.set_tag(SPAN_MEASURED_KEY) + try: + return wrapped(*args, **kwargs) + finally: + span.resource = template_name + span.set_tag_str("jinja2.template_name", template_name) + + +def _wrap_compile(wrapped, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return wrapped(*args, **kwargs) + + try: + template_name = get_argument_value(args, kwargs, 1, "name") + except ArgumentError: + template_name = DEFAULT_TEMPLATE_NAME + + with pin.tracer.trace("jinja2.compile", pin.service, span_type=SpanTypes.TEMPLATE) as span: + try: + return wrapped(*args, **kwargs) + finally: + span.set_tag_str(COMPONENT, config.jinja2.integration_name) + + span.resource = template_name + span.set_tag_str("jinja2.template_name", template_name) + + +def _wrap_load_template(wrapped, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return wrapped(*args, **kwargs) + + template_name = get_argument_value(args, kwargs, 0, "name") + with pin.tracer.trace("jinja2.load", pin.service, span_type=SpanTypes.TEMPLATE) as span: + template = None + try: + template = wrapped(*args, **kwargs) + return template + finally: + span.resource = template_name + span.set_tag_str("jinja2.template_name", template_name) + if template: + span.set_tag_str("jinja2.template_path", template.filename) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/kafka/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/kafka/__init__.py new file mode 100644 index 0000000..f49a5cc --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/kafka/__init__.py @@ -0,0 +1,55 @@ +""" +This integration instruments the ``confluent-kafka`` +library to trace event streaming. + +Enabling +~~~~~~~~ + +The kafka integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch() ` to manually enable the integration:: + + from ddtrace import patch + patch(kafka=True) + import confluent_kafka + ... + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.kafka["service"] + + The service name reported by default for your kafka spans. + + This option can also be set with the ``DD_KAFKA_SERVICE`` environment + variable. + + Default: ``"kafka"`` + + +To configure the kafka integration using the +``Pin`` API:: + + from ddtrace import Pin + from ddtrace import patch + + # Make sure to patch before importing confluent_kafka + patch(kafka=True) + + import confluent_kafka + + Pin.override(confluent_kafka, service="custom-service-name") +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["confluent_kafka"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/kafka/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/kafka/patch.py new file mode 100644 index 0000000..4218b11 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/kafka/patch.py @@ -0,0 +1,287 @@ +import os + +import confluent_kafka + +from ddtrace import config +from ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import SPAN_KIND +from ddtrace.constants import SPAN_MEASURED_KEY +from ddtrace.contrib import trace_utils +from ddtrace.ext import SpanKind +from ddtrace.ext import SpanTypes +from ddtrace.ext import kafka as kafkax +from ddtrace.internal import core +from ddtrace.internal.compat import time_ns +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.constants import MESSAGING_SYSTEM +from ddtrace.internal.logger import get_logger +from ddtrace.internal.schema import schematize_messaging_operation +from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.internal.utils import ArgumentError +from ddtrace.internal.utils import get_argument_value +from ddtrace.internal.utils import set_argument_value +from ddtrace.internal.utils.formats import asbool +from ddtrace.internal.utils.version import parse_version +from ddtrace.pin import Pin +from ddtrace.propagation.http import HTTPPropagator as Propagator + + +_Producer = confluent_kafka.Producer +_Consumer = confluent_kafka.Consumer +_SerializingProducer = confluent_kafka.SerializingProducer if hasattr(confluent_kafka, "SerializingProducer") else None +_DeserializingConsumer = ( + confluent_kafka.DeserializingConsumer if hasattr(confluent_kafka, "DeserializingConsumer") else None +) + + +log = get_logger(__name__) + + +config._add( + "kafka", + dict( + _default_service=schematize_service_name("kafka"), + distributed_tracing_enabled=asbool(os.getenv("DD_KAFKA_PROPAGATION_ENABLED", default=False)), + trace_empty_poll_enabled=asbool(os.getenv("DD_KAFKA_EMPTY_POLL_ENABLED", default=True)), + ), +) + + +def get_version(): + # type: () -> str + return getattr(confluent_kafka, "__version__", "") + + +KAFKA_VERSION_TUPLE = parse_version(get_version()) + + +_SerializationContext = confluent_kafka.serialization.SerializationContext if KAFKA_VERSION_TUPLE >= (1, 4, 0) else None +_MessageField = confluent_kafka.serialization.MessageField if KAFKA_VERSION_TUPLE >= (1, 4, 0) else None + + +class TracedProducerMixin: + def __init__(self, config, *args, **kwargs): + super(TracedProducerMixin, self).__init__(config, *args, **kwargs) + self._dd_bootstrap_servers = ( + config.get("bootstrap.servers") + if config.get("bootstrap.servers") is not None + else config.get("metadata.broker.list") + ) + + # in older versions of confluent_kafka, bool(Producer()) evaluates to False, + # which makes the Pin functionality ignore it. + def __bool__(self): + return True + + __nonzero__ = __bool__ + + +class TracedConsumerMixin: + def __init__(self, config, *args, **kwargs): + super(TracedConsumerMixin, self).__init__(config, *args, **kwargs) + self._group_id = config.get("group.id", "") + self._auto_commit = asbool(config.get("enable.auto.commit", True)) + + +class TracedConsumer(TracedConsumerMixin, confluent_kafka.Consumer): + pass + + +class TracedProducer(TracedProducerMixin, confluent_kafka.Producer): + pass + + +class TracedDeserializingConsumer(TracedConsumerMixin, confluent_kafka.DeserializingConsumer): + pass + + +class TracedSerializingProducer(TracedProducerMixin, confluent_kafka.SerializingProducer): + pass + + +def patch(): + if getattr(confluent_kafka, "_datadog_patch", False): + return + confluent_kafka._datadog_patch = True + + confluent_kafka.Producer = TracedProducer + confluent_kafka.Consumer = TracedConsumer + if _SerializingProducer is not None: + confluent_kafka.SerializingProducer = TracedSerializingProducer + if _DeserializingConsumer is not None: + confluent_kafka.DeserializingConsumer = TracedDeserializingConsumer + + for producer in (TracedProducer, TracedSerializingProducer): + trace_utils.wrap(producer, "produce", traced_produce) + for consumer in (TracedConsumer, TracedDeserializingConsumer): + trace_utils.wrap(consumer, "poll", traced_poll) + trace_utils.wrap(consumer, "commit", traced_commit) + Pin().onto(confluent_kafka.Producer) + Pin().onto(confluent_kafka.Consumer) + Pin().onto(confluent_kafka.SerializingProducer) + Pin().onto(confluent_kafka.DeserializingConsumer) + + +def unpatch(): + if getattr(confluent_kafka, "_datadog_patch", False): + confluent_kafka._datadog_patch = False + + for producer in (TracedProducer, TracedSerializingProducer): + if trace_utils.iswrapped(producer.produce): + trace_utils.unwrap(producer, "produce") + for consumer in (TracedConsumer, TracedDeserializingConsumer): + if trace_utils.iswrapped(consumer.poll): + trace_utils.unwrap(consumer, "poll") + if trace_utils.iswrapped(consumer.commit): + trace_utils.unwrap(consumer, "commit") + + confluent_kafka.Producer = _Producer + confluent_kafka.Consumer = _Consumer + if _SerializingProducer is not None: + confluent_kafka.SerializingProducer = _SerializingProducer + if _DeserializingConsumer is not None: + confluent_kafka.DeserializingConsumer = _DeserializingConsumer + + +def traced_produce(func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return func(*args, **kwargs) + + topic = get_argument_value(args, kwargs, 0, "topic") or "" + core.set_item("kafka_topic", topic) + try: + value = get_argument_value(args, kwargs, 1, "value") + except ArgumentError: + value = None + message_key = kwargs.get("key", "") or "" + partition = kwargs.get("partition", -1) + headers = get_argument_value(args, kwargs, 6, "headers", optional=True) or {} + with pin.tracer.trace( + schematize_messaging_operation(kafkax.PRODUCE, provider="kafka", direction=SpanDirection.OUTBOUND), + service=trace_utils.ext_service(pin, config.kafka), + span_type=SpanTypes.WORKER, + ) as span: + core.dispatch("kafka.produce.start", (instance, args, kwargs, isinstance(instance, _SerializingProducer), span)) + span.set_tag_str(MESSAGING_SYSTEM, kafkax.SERVICE) + span.set_tag_str(COMPONENT, config.kafka.integration_name) + span.set_tag_str(SPAN_KIND, SpanKind.PRODUCER) + span.set_tag_str(kafkax.TOPIC, topic) + + if _SerializingProducer is not None and isinstance(instance, _SerializingProducer): + serialized_key = serialize_key(instance, topic, message_key, headers) + if serialized_key is not None: + span.set_tag_str(kafkax.MESSAGE_KEY, serialized_key) + else: + span.set_tag_str(kafkax.MESSAGE_KEY, message_key) + + span.set_tag(kafkax.PARTITION, partition) + span.set_tag_str(kafkax.TOMBSTONE, str(value is None)) + span.set_tag(SPAN_MEASURED_KEY) + if instance._dd_bootstrap_servers is not None: + span.set_tag_str(kafkax.HOST_LIST, instance._dd_bootstrap_servers) + rate = config.kafka.get_analytics_sample_rate() + if rate is not None: + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, rate) + + # inject headers with Datadog tags if trace propagation is enabled + if config.kafka.distributed_tracing_enabled: + # inject headers with Datadog tags: + headers = get_argument_value(args, kwargs, 6, "headers", True) or {} + Propagator.inject(span.context, headers) + args, kwargs = set_argument_value(args, kwargs, 6, "headers", headers) + return func(*args, **kwargs) + + +def traced_poll(func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return func(*args, **kwargs) + + # we must get start time now since execute before starting a span in order to get distributed context + # if it exists + start_ns = time_ns() + # wrap in a try catch and raise exception after span is started + err = None + try: + message = func(*args, **kwargs) + except Exception as e: + err = e + ctx = None + if message and config.kafka.distributed_tracing_enabled and message.headers(): + ctx = Propagator.extract(dict(message.headers())) + if message or config.kafka.trace_empty_poll_enabled: + with pin.tracer.start_span( + name=schematize_messaging_operation(kafkax.CONSUME, provider="kafka", direction=SpanDirection.PROCESSING), + service=trace_utils.ext_service(pin, config.kafka), + span_type=SpanTypes.WORKER, + child_of=ctx if ctx is not None else pin.tracer.context_provider.active(), + activate=True, + ) as span: + # reset span start time to before function call + span.start_ns = start_ns + + span.set_tag_str(MESSAGING_SYSTEM, kafkax.SERVICE) + span.set_tag_str(COMPONENT, config.kafka.integration_name) + span.set_tag_str(SPAN_KIND, SpanKind.CONSUMER) + span.set_tag_str(kafkax.RECEIVED_MESSAGE, str(message is not None)) + span.set_tag_str(kafkax.GROUP_ID, instance._group_id) + if message is not None: + core.set_item("kafka_topic", message.topic()) + core.dispatch("kafka.consume.start", (instance, message, span)) + + message_key = message.key() or "" + message_offset = message.offset() or -1 + span.set_tag_str(kafkax.TOPIC, message.topic()) + + # If this is a deserializing consumer, do not set the key as a tag since we + # do not have the serialization function + if ( + (_DeserializingConsumer is not None and not isinstance(instance, _DeserializingConsumer)) + or isinstance(message_key, str) + or isinstance(message_key, bytes) + ): + span.set_tag_str(kafkax.MESSAGE_KEY, message_key) + span.set_tag(kafkax.PARTITION, message.partition()) + span.set_tag_str(kafkax.TOMBSTONE, str(len(message) == 0)) + span.set_tag(kafkax.MESSAGE_OFFSET, message_offset) + span.set_tag(SPAN_MEASURED_KEY) + rate = config.kafka.get_analytics_sample_rate() + if rate is not None: + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, rate) + + # raise exception if one was encountered + if err is not None: + raise err + return message + else: + if err is not None: + raise err + else: + return message + + +def traced_commit(func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return func(*args, **kwargs) + + core.dispatch("kafka.commit.start", (instance, args, kwargs)) + + return func(*args, **kwargs) + + +def serialize_key(instance, topic, key, headers): + if _SerializationContext is not None and _MessageField is not None: + ctx = _SerializationContext(topic, _MessageField.KEY, headers) + if hasattr(instance, "_key_serializer") and instance._key_serializer is not None: + try: + key = instance._key_serializer(key, ctx) + return key + except Exception: + log.debug("Failed to set Kafka Consumer key tag: %s", str(key)) + return None + else: + log.warning("Failed to set Kafka Consumer key tag, no method available to serialize key: %s", str(key)) + return None diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/kombu/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/kombu/__init__.py new file mode 100644 index 0000000..009a4fe --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/kombu/__init__.py @@ -0,0 +1,45 @@ +"""Instrument kombu to report AMQP messaging. + +``patch_all`` will not automatically patch your Kombu client to make it work, as this would conflict with the +Celery integration. You must specifically request kombu be patched, as in the example below. + +Note: To permit distributed tracing for the kombu integration you must enable the tracer with priority +sampling. Refer to the documentation here: +https://ddtrace.readthedocs.io/en/stable/advanced_usage.html#priority-sampling + +Without enabling distributed tracing, spans within a trace generated by the kombu integration might be dropped +without the whole trace being dropped. +:: + + from ddtrace import Pin, patch + import kombu + + # If not patched yet, you can patch kombu specifically + patch(kombu=True) + + # This will report a span with the default settings + conn = kombu.Connection("amqp://guest:guest@127.0.0.1:5672//") + conn.connect() + task_queue = kombu.Queue('tasks', kombu.Exchange('tasks'), routing_key='tasks') + to_publish = {'hello': 'world'} + producer = conn.Producer() + producer.publish(to_publish, + exchange=task_queue.exchange, + routing_key=task_queue.routing_key, + declare=[task_queue]) + + # Use a pin to specify metadata related to this client + Pin.override(producer, service='kombu-consumer') +""" + +from ...internal.utils.importlib import require_modules + + +required_modules = ["kombu", "kombu.messaging"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + + __all__ = ["patch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/kombu/constants.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/kombu/constants.py new file mode 100644 index 0000000..bcada46 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/kombu/constants.py @@ -0,0 +1 @@ +DEFAULT_SERVICE = "kombu" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/kombu/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/kombu/patch.py new file mode 100644 index 0000000..e1297c8 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/kombu/patch.py @@ -0,0 +1,167 @@ +import os + +# 3p +import kombu + +from ddtrace import config +from ddtrace.internal import core +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema import schematize_messaging_operation +from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.internal.utils.formats import asbool +from ddtrace.vendor import wrapt + +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from ...ext import kombu as kombux +from ...internal.utils import get_argument_value +from ...internal.utils.wrappers import unwrap +from ...pin import Pin +from ...propagation.http import HTTPPropagator + +# project +from .. import trace_utils +from .constants import DEFAULT_SERVICE +from .utils import HEADER_POS +from .utils import extract_conn_tags +from .utils import get_body_length_from_args +from .utils import get_exchange_from_args +from .utils import get_routing_key_from_args + + +def get_version(): + # type: () -> str + return str(kombu.__version__) + + +# kombu default settings + +config._add( + "kombu", + { + "distributed_tracing_enabled": asbool(os.getenv("DD_KOMBU_DISTRIBUTED_TRACING", default=True)), + "service_name": config.service or os.getenv("DD_KOMBU_SERVICE_NAME", default=DEFAULT_SERVICE), + }, +) + +propagator = HTTPPropagator + + +def patch(): + """Patch the instrumented methods + + This duplicated doesn't look nice. The nicer alternative is to use an ObjectProxy on top + of Kombu. However, it means that any "import kombu.Connection" won't be instrumented. + """ + if getattr(kombu, "_datadog_patch", False): + return + kombu._datadog_patch = True + + _w = wrapt.wrap_function_wrapper + # We wrap the _publish method because the publish method: + # * defines defaults in its kwargs + # * potentially overrides kwargs with values from self + # * extracts/normalizes things like exchange + _w("kombu", "Producer._publish", traced_publish) + _w("kombu", "Consumer.receive", traced_receive) + + # We do not provide a service for producer spans since they represent + # external calls to another service. + # Instead the service should be inherited from the parent. + if config.service: + prod_service = None + # DEV: backwards-compatibility for users who set a kombu service + else: + prod_service = os.getenv("DD_KOMBU_SERVICE_NAME", default=DEFAULT_SERVICE) + + Pin( + service=schematize_service_name(prod_service), + ).onto(kombu.messaging.Producer) + + Pin(service=schematize_service_name(config.kombu["service_name"])).onto(kombu.messaging.Consumer) + + +def unpatch(): + if getattr(kombu, "_datadog_patch", False): + kombu._datadog_patch = False + unwrap(kombu.Producer, "_publish") + unwrap(kombu.Consumer, "receive") + + +# +# tracing functions +# + + +def traced_receive(func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return func(*args, **kwargs) + + # Signature only takes 2 args: (body, message) + message = get_argument_value(args, kwargs, 1, "message") + + trace_utils.activate_distributed_headers(pin.tracer, request_headers=message.headers, int_config=config.kombu) + + with pin.tracer.trace( + schematize_messaging_operation(kombux.RECEIVE_NAME, provider="kombu", direction=SpanDirection.PROCESSING), + service=pin.service, + span_type=SpanTypes.WORKER, + ) as s: + s.set_tag_str(COMPONENT, config.kombu.integration_name) + + # set span.kind to the type of operation being performed + s.set_tag_str(SPAN_KIND, SpanKind.CONSUMER) + + s.set_tag(SPAN_MEASURED_KEY) + # run the command + exchange = message.delivery_info["exchange"] + s.resource = exchange + s.set_tag_str(kombux.EXCHANGE, exchange) + + s.set_tags(extract_conn_tags(message.channel.connection)) + s.set_tag_str(kombux.ROUTING_KEY, message.delivery_info["routing_key"]) + # set analytics sample rate + s.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.kombu.get_analytics_sample_rate()) + result = func(*args, **kwargs) + core.dispatch("kombu.amqp.receive.post", [instance, message, s]) + return result + + +def traced_publish(func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return func(*args, **kwargs) + + with pin.tracer.trace( + schematize_messaging_operation(kombux.PUBLISH_NAME, provider="kombu", direction=SpanDirection.OUTBOUND), + service=pin.service, + span_type=SpanTypes.WORKER, + ) as s: + s.set_tag_str(COMPONENT, config.kombu.integration_name) + + # set span.kind to the type of operation being performed + s.set_tag_str(SPAN_KIND, SpanKind.PRODUCER) + + s.set_tag(SPAN_MEASURED_KEY) + exchange_name = get_exchange_from_args(args) + s.resource = exchange_name + s.set_tag_str(kombux.EXCHANGE, exchange_name) + if pin.tags: + s.set_tags(pin.tags) + s.set_tag_str(kombux.ROUTING_KEY, get_routing_key_from_args(args)) + s.set_tags(extract_conn_tags(instance.channel.connection)) + s.set_metric(kombux.BODY_LEN, get_body_length_from_args(args)) + # set analytics sample rate + s.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.kombu.get_analytics_sample_rate()) + # run the command + if config.kombu.distributed_tracing_enabled: + propagator.inject(s.context, args[HEADER_POS]) + core.dispatch( + "kombu.amqp.publish.pre", [args, kwargs, s] + ) # Has to happen after trace injection for actual payload size + return func(*args, **kwargs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/kombu/utils.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/kombu/utils.py new file mode 100644 index 0000000..fcce88a --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/kombu/utils.py @@ -0,0 +1,49 @@ +""" +Some utils used by the dogtrace kombu integration +""" +from ...ext import kombu as kombux +from ...ext import net + + +PUBLISH_BODY_IDX = 0 +PUBLISH_ROUTING_KEY = 6 +PUBLISH_EXCHANGE_IDX = 9 + +HEADER_POS = 4 + + +def extract_conn_tags(connection): + """Transform kombu conn info into dogtrace metas""" + try: + host, port = connection.host.split(":") + return { + net.TARGET_HOST: host, + net.TARGET_PORT: port, + kombux.VHOST: connection.virtual_host, + } + except AttributeError: + # Unlikely that we don't have .host or .virtual_host but let's not die over it + return {} + + +def get_exchange_from_args(args): + """Extract the exchange + + The publish method extracts the name and hands that off to _publish (what we patch) + """ + + return args[PUBLISH_EXCHANGE_IDX] + + +def get_routing_key_from_args(args): + """Extract the routing key""" + + name = args[PUBLISH_ROUTING_KEY] + return name + + +def get_body_length_from_args(args): + """Extract the length of the body""" + + length = len(args[PUBLISH_BODY_IDX]) + return length diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/langchain/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/langchain/__init__.py new file mode 100644 index 0000000..4557e9d --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/langchain/__init__.py @@ -0,0 +1,207 @@ +""" +The LangChain integration instruments the LangChain Python library to emit metrics, +traces, and logs (logs are disabled by default) for requests made to the LLMs, +chat models, embeddings, chains, and vector store interfaces. + +All metrics, logs, and traces submitted from the LangChain integration are tagged by: + +- ``service``, ``env``, ``version``: see the `Unified Service Tagging docs `_. +- ``langchain.request.provider``: LLM provider used in the request. +- ``langchain.request.model``: LLM/Chat/Embeddings model used in the request. +- ``langchain.request.api_key``: LLM provider API key used to make the request (obfuscated into the format ``...XXXX`` where ``XXXX`` is the last 4 digits of the key). + +Metrics +~~~~~~~ + +The following metrics are collected by default by the LangChain integration. + +.. important:: + If the Agent is configured to use a non-default Statsd hostname or port, use ``DD_DOGSTATSD_URL`` to configure + ``ddtrace`` to use it. + + +.. py:data:: langchain.request.duration + + The duration of the LangChain request in seconds. + + Type: ``distribution`` + + +.. py:data:: langchain.request.error + + The number of errors from requests made with LangChain. + + Type: ``count`` + + +.. py:data:: langchain.tokens.prompt + + The number of tokens used in the prompt of a LangChain request. + + Type: ``distribution`` + + +.. py:data:: langchain.tokens.completion + + The number of tokens used in the completion of a LangChain response. + + Type: ``distribution`` + + +.. py:data:: langchain.tokens.total + + The total number of tokens used in the prompt and completion of a LangChain request/response. + + Type: ``distribution`` + + +.. py:data:: langchain.tokens.total_cost + + The estimated cost in USD based on token usage. + + Type: ``count`` + + +(beta) Prompt and Completion Sampling +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following data is collected in span tags with a default sampling rate of ``1.0``: + +- Prompt inputs and completions for the ``LLM`` interface. +- Message inputs and completions for the ``ChatModel`` interface. +- Embedding inputs for the ``Embeddings`` interface. +- Prompt inputs, chain inputs, and outputs for the ``Chain`` interface. +- Query inputs and document outputs for the ``VectorStore`` interface. + +Prompt and message inputs and completions can also be emitted as log data. +Logs are **not** emitted by default. When logs are enabled they are sampled at ``0.1``. + +Read the **Global Configuration** section for information about enabling logs and configuring sampling +rates. + +.. important:: + + To submit logs, you must set the ``DD_API_KEY`` environment variable. + + Set ``DD_SITE`` to send logs to a Datadog site such as ``datadoghq.eu``. The default is ``datadoghq.com``. + + +Enabling +~~~~~~~~ + +The LangChain integration is enabled automatically when you use +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Note that these commands also enable the ``requests`` and ``aiohttp`` +integrations which trace HTTP requests to LLM providers, as well as the +``openai`` integration which traces requests to the OpenAI library. + +Alternatively, use :func:`patch() ` to manually enable the LangChain integration:: + + from ddtrace import config, patch + + # Note: be sure to configure the integration before calling ``patch()``! + # eg. config.langchain["logs_enabled"] = True + + patch(langchain=True) + + # to trace synchronous HTTP requests + # patch(langchain=True, requests=True) + + # to trace asynchronous HTTP requests (to the OpenAI library) + # patch(langchain=True, aiohttp=True) + + # to include underlying OpenAI spans from the OpenAI integration + # patch(langchain=True, openai=True) + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.langchain["service"] + + The service name reported by default for LangChain requests. + + Alternatively, you can set this option with the ``DD_SERVICE`` or ``DD_LANGCHAIN_SERVICE`` environment + variables. + + Default: ``DD_SERVICE`` + + +.. py:data:: ddtrace.config.langchain["logs_enabled"] + + Enable collection of prompts and completions as logs. You can adjust the rate of prompts and completions collected + using the sample rate configuration described below. + + Alternatively, you can set this option with the ``DD_LANGCHAIN_LOGS_ENABLED`` environment + variable. + + Note that you must set the ``DD_API_KEY`` environment variable to enable sending logs. + + Default: ``False`` + + +.. py:data:: ddtrace.config.langchain["metrics_enabled"] + + Enable collection of LangChain metrics. + + If the Datadog Agent is configured to use a non-default Statsd hostname + or port, use ``DD_DOGSTATSD_URL`` to configure ``ddtrace`` to use it. + + Alternatively, you can set this option with the ``DD_LANGCHAIN_METRICS_ENABLED`` environment + variable. + + Default: ``True`` + + +.. py:data:: (beta) ddtrace.config.langchain["span_char_limit"] + + Configure the maximum number of characters for the following data within span tags: + + - Prompt inputs and completions + - Message inputs and completions + - Embedding inputs + + Text exceeding the maximum number of characters is truncated to the character limit + and has ``...`` appended to the end. + + Alternatively, you can set this option with the ``DD_LANGCHAIN_SPAN_CHAR_LIMIT`` environment + variable. + + Default: ``128`` + + +.. py:data:: (beta) ddtrace.config.langchain["span_prompt_completion_sample_rate"] + + Configure the sample rate for the collection of prompts and completions as span tags. + + Alternatively, you can set this option with the ``DD_LANGCHAIN_SPAN_PROMPT_COMPLETION_SAMPLE_RATE`` environment + variable. + + Default: ``1.0`` + + +.. py:data:: (beta) ddtrace.config.langchain["log_prompt_completion_sample_rate"] + + Configure the sample rate for the collection of prompts and completions as logs. + + Alternatively, you can set this option with the ``DD_LANGCHAIN_LOG_PROMPT_COMPLETION_SAMPLE_RATE`` environment + variable. + + Default: ``0.1`` + +""" # noqa: E501 +from ...internal.utils.importlib import require_modules + + +required_modules = ["langchain"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from . import patch as _patch + + patch = _patch.patch + unpatch = _patch.unpatch + get_version = _patch.get_version + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/langchain/constants.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/langchain/constants.py new file mode 100644 index 0000000..e160453 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/langchain/constants.py @@ -0,0 +1,87 @@ +text_embedding_models = ( + "OpenAIEmbeddings", + "HuggingFaceEmbeddings", + "CohereEmbeddings", + "ElasticsearchEmbeddings", + "JinaEmbeddings", + "LlamaCppEmbeddings", + "HuggingFaceHubEmbeddings", + "ModelScopeEmbeddings", + "TensorflowHubEmbeddings", + "SagemakerEndpointEmbeddings", + "HuggingFaceInstructEmbeddings", + "MosaicMLInstructorEmbeddings", + "SelfHostedEmbeddings", + "SelfHostedHuggingFaceEmbeddings", + "SelfHostedHuggingFaceInstructEmbeddings", + "FakeEmbeddings", + "AlephAlphaAsymmetricSemanticEmbedding", + "AlephAlphaSymmetricSemanticEmbedding", + "SentenceTransformerEmbeddings", + "GooglePalmEmbeddings", + "MiniMaxEmbeddings", + "VertexAIEmbeddings", + "BedrockEmbeddings", + "DeepInfraEmbeddings", + "DashScopeEmbeddings", + "EmbaasEmbeddings", +) + +vectorstore_classes = ( + "AzureSearch", + "Redis", + "ElasticVectorSearch", + "FAISS", + "VectorStore", + "Pinecone", + "Weaviate", + "Qdrant", + "Milvus", + "Zilliz", + "SingleStoreDB", + "Chroma", + "OpenSearchVectorSearch", + "AtlasDB", + "DeepLake", + "Annoy", + "MongoDBAtlasVectorSearch", + "MyScale", + "SKLearnVectorStore", + "SupabaseVectorStore", + "AnalyticDB", + "Vectara", + "Tair", + "LanceDB", + "DocArrayHnswSearch", + "DocArrayInMemorySearch", + "Typesense", + "Hologres", + "Clickhouse", + "Tigris", + "MatchingEngine", + "AwaDB", +) + +agent_output_parser_classes = { + "chat": {"output_parser": "ChatOutputParser"}, + "conversational": {"output_parser": "ConvoOutputParser"}, + "conversational_chat": {"output_parser": "ConvoOutputParser"}, + "mrkl": {"output_parser": "MRKLOutputParser"}, + "output_parsers": { + "json": "JSONAgentOutputParser", + "openai_functions": "OpenAIFunctionsAgentOutputParser", + "react_json_single_input": "ReActJsonSingleInputOutputParser", + "react_single_input": "ReActSingleInputOutputParser", + "self_ask": "SelfAskOutputParser", + "xml": "XMLAgentOutputParser", + }, + "react": {"output_parser": "ReActOutputParser"}, + "self_ask_with_search": {"output_parser": "SelfAskOutputParser"}, + "structured_chat": {"output_parser": "StructuredChatOutputParser"}, +} + +API_KEY = "langchain.request.api_key" +MODEL = "langchain.request.model" +COMPLETION_TOKENS = "langchain.tokens.completion_tokens" +PROMPT_TOKENS = "langchain.tokens.prompt_tokens" +TOTAL_COST = "langchain.tokens.total_cost" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/langchain/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/langchain/patch.py new file mode 100644 index 0000000..a49d8a0 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/langchain/patch.py @@ -0,0 +1,825 @@ +import os +import sys +from typing import TYPE_CHECKING # noqa:F401 +from typing import Any # noqa:F401 +from typing import Dict # noqa:F401 +from typing import Optional # noqa:F401 +from typing import Union + +import langchain + +from ddtrace.appsec._iast import _is_iast_enabled + + +try: + from langchain.callbacks.openai_info import get_openai_token_cost_for_model +except ImportError: + from langchain_community.callbacks.openai_info import get_openai_token_cost_for_model +from pydantic import SecretStr + +from ddtrace import config +from ddtrace.contrib.langchain.constants import API_KEY +from ddtrace.contrib.langchain.constants import COMPLETION_TOKENS +from ddtrace.contrib.langchain.constants import MODEL +from ddtrace.contrib.langchain.constants import PROMPT_TOKENS +from ddtrace.contrib.langchain.constants import TOTAL_COST +from ddtrace.contrib.langchain.constants import agent_output_parser_classes +from ddtrace.contrib.langchain.constants import text_embedding_models +from ddtrace.contrib.langchain.constants import vectorstore_classes +from ddtrace.contrib.trace_utils import unwrap +from ddtrace.contrib.trace_utils import with_traced_module +from ddtrace.contrib.trace_utils import wrap +from ddtrace.internal.llmobs.integrations import LangChainIntegration +from ddtrace.internal.logger import get_logger +from ddtrace.internal.utils import ArgumentError +from ddtrace.internal.utils import get_argument_value +from ddtrace.internal.utils.formats import asbool +from ddtrace.internal.utils.formats import deep_getattr +from ddtrace.pin import Pin +from ddtrace.vendor import wrapt + + +if TYPE_CHECKING: + from ddtrace import Span # noqa:F401 + + +log = get_logger(__name__) + + +def get_version(): + # type: () -> str + return getattr(langchain, "__version__", "") + + +config._add( + "langchain", + { + "logs_enabled": asbool(os.getenv("DD_LANGCHAIN_LOGS_ENABLED", False)), + "metrics_enabled": asbool(os.getenv("DD_LANGCHAIN_METRICS_ENABLED", True)), + "span_prompt_completion_sample_rate": float(os.getenv("DD_LANGCHAIN_SPAN_PROMPT_COMPLETION_SAMPLE_RATE", 1.0)), + "log_prompt_completion_sample_rate": float(os.getenv("DD_LANGCHAIN_LOG_PROMPT_COMPLETION_SAMPLE_RATE", 0.1)), + "span_char_limit": int(os.getenv("DD_LANGCHAIN_SPAN_CHAR_LIMIT", 128)), + }, +) + + +def _extract_model_name(instance): + # type: (langchain.llm.BaseLLM) -> Optional[str] + """Extract model name or ID from llm instance.""" + for attr in ("model", "model_name", "model_id", "model_key", "repo_id"): + if hasattr(instance, attr): + return getattr(instance, attr) + return None + + +def _format_api_key(api_key: Union[str, SecretStr]) -> str: + """Obfuscate a given LLM provider API key by returning the last four characters.""" + if hasattr(api_key, "get_secret_value"): + api_key = api_key.get_secret_value() + + if not api_key or len(api_key) < 4: + return "" + return "...%s" % api_key[-4:] + + +def _extract_api_key(instance): + # type: (Any) -> str + """ + Extract and format LLM-provider API key from instance. + Note that langchain's LLM/ChatModel/Embeddings interfaces do not have a + standard attribute name for storing the provider-specific API key, so make a + best effort here by checking for attributes that end with `api_key/api_token`. + """ + api_key_attrs = [a for a in dir(instance) if a.endswith(("api_token", "api_key"))] + if api_key_attrs and hasattr(instance, str(api_key_attrs[0])): + api_key = getattr(instance, api_key_attrs[0], None) + if api_key: + return _format_api_key(api_key) + return "" + + +def _tag_openai_token_usage(span, llm_output, propagated_cost=0, propagate=False): + # type: (Span, Dict[str, Any], int, bool) -> None + """ + Extract token usage from llm_output, tag on span. + Calculate the total cost for each LLM/chat_model, then propagate those values up the trace so that + the root span will store the total token_usage/cost of all of its descendants. + """ + for token_type in ("prompt", "completion", "total"): + current_metric_value = span.get_metric("langchain.tokens.%s_tokens" % token_type) or 0 + metric_value = llm_output["token_usage"].get("%s_tokens" % token_type, 0) + span.set_metric("langchain.tokens.%s_tokens" % token_type, current_metric_value + metric_value) + total_cost = span.get_metric(TOTAL_COST) or 0 + if not propagate: + try: + completion_cost = get_openai_token_cost_for_model( + span.get_tag(MODEL), + span.get_metric(COMPLETION_TOKENS), + is_completion=True, + ) + prompt_cost = get_openai_token_cost_for_model(span.get_tag(MODEL), span.get_metric(PROMPT_TOKENS)) + total_cost = completion_cost + prompt_cost + except ValueError: + # If not in langchain's openai model catalog, the above helpers will raise a ValueError. + log.debug("Cannot calculate token/cost as the model is not in LangChain's OpenAI model catalog.") + span.set_metric(TOTAL_COST, propagated_cost + total_cost) + if span._parent is not None: + _tag_openai_token_usage(span._parent, llm_output, propagated_cost=propagated_cost + total_cost, propagate=True) + + +@with_traced_module +def traced_llm_generate(langchain, pin, func, instance, args, kwargs): + llm_provider = instance._llm_type + prompts = get_argument_value(args, kwargs, 0, "prompts") + integration = langchain._datadog_integration + model = _extract_model_name(instance) + span = integration.trace( + pin, + "%s.%s" % (instance.__module__, instance.__class__.__name__), + interface_type="llm", + provider=llm_provider, + model=model, + api_key=_extract_api_key(instance), + ) + completions = None + try: + if integration.is_pc_sampled_span(span): + for idx, prompt in enumerate(prompts): + span.set_tag_str("langchain.request.prompts.%d" % idx, integration.trunc(str(prompt))) + for param, val in getattr(instance, "_identifying_params", {}).items(): + if isinstance(val, dict): + for k, v in val.items(): + span.set_tag_str("langchain.request.%s.parameters.%s.%s" % (llm_provider, param, k), str(v)) + else: + span.set_tag_str("langchain.request.%s.parameters.%s" % (llm_provider, param), str(val)) + + completions = func(*args, **kwargs) + if isinstance(instance, langchain.llms.OpenAI): + _tag_openai_token_usage(span, completions.llm_output) + integration.record_usage(span, completions.llm_output) + + for idx, completion in enumerate(completions.generations): + if integration.is_pc_sampled_span(span): + span.set_tag_str("langchain.response.completions.%d.text" % idx, integration.trunc(completion[0].text)) + if completion and completion[0].generation_info is not None: + span.set_tag_str( + "langchain.response.completions.%d.finish_reason" % idx, + str(completion[0].generation_info.get("finish_reason")), + ) + span.set_tag_str( + "langchain.response.completions.%d.logprobs" % idx, + str(completion[0].generation_info.get("logprobs")), + ) + except Exception: + span.set_exc_info(*sys.exc_info()) + integration.metric(span, "incr", "request.error", 1) + raise + finally: + span.finish() + integration.metric(span, "dist", "request.duration", span.duration_ns) + if integration.is_pc_sampled_log(span): + if completions is None: + log_completions = [] + else: + log_completions = [ + [{"text": completion.text} for completion in completions] for completions in completions.generations + ] + integration.log( + span, + "info" if span.error == 0 else "error", + "sampled %s.%s" % (instance.__module__, instance.__class__.__name__), + attrs={ + "prompts": prompts, + "choices": log_completions, + }, + ) + return completions + + +@with_traced_module +async def traced_llm_agenerate(langchain, pin, func, instance, args, kwargs): + llm_provider = instance._llm_type + prompts = get_argument_value(args, kwargs, 0, "prompts") + integration = langchain._datadog_integration + model = _extract_model_name(instance) + span = integration.trace( + pin, + "%s.%s" % (instance.__module__, instance.__class__.__name__), + interface_type="llm", + provider=llm_provider, + model=model, + api_key=_extract_api_key(instance), + ) + completions = None + try: + if integration.is_pc_sampled_span(span): + for idx, prompt in enumerate(prompts): + span.set_tag_str("langchain.request.prompts.%d" % idx, integration.trunc(str(prompt))) + for param, val in getattr(instance, "_identifying_params", {}).items(): + if isinstance(val, dict): + for k, v in val.items(): + span.set_tag_str("langchain.request.%s.parameters.%s.%s" % (llm_provider, param, k), str(v)) + else: + span.set_tag_str("langchain.request.%s.parameters.%s" % (llm_provider, param), str(val)) + + completions = await func(*args, **kwargs) + if isinstance(instance, langchain.llms.OpenAI): + _tag_openai_token_usage(span, completions.llm_output) + integration.record_usage(span, completions.llm_output) + + for idx, completion in enumerate(completions.generations): + if integration.is_pc_sampled_span(span): + span.set_tag_str("langchain.response.completions.%d.text" % idx, integration.trunc(completion[0].text)) + if completion and completion[0].generation_info is not None: + span.set_tag_str( + "langchain.response.completions.%d.finish_reason" % idx, + str(completion[0].generation_info.get("finish_reason")), + ) + span.set_tag_str( + "langchain.response.completions.%d.logprobs" % idx, + str(completion[0].generation_info.get("logprobs")), + ) + except Exception: + span.set_exc_info(*sys.exc_info()) + integration.metric(span, "incr", "request.error", 1) + raise + finally: + span.finish() + integration.metric(span, "dist", "request.duration", span.duration_ns) + if integration.is_pc_sampled_log(span): + if completions is None: + log_completions = [] + else: + log_completions = [ + [{"text": completion.text} for completion in completions] for completions in completions.generations + ] + integration.log( + span, + "info" if span.error == 0 else "error", + "sampled %s.%s" % (instance.__module__, instance.__class__.__name__), + attrs={ + "prompts": prompts, + "choices": log_completions, + }, + ) + return completions + + +@with_traced_module +def traced_chat_model_generate(langchain, pin, func, instance, args, kwargs): + llm_provider = instance._llm_type.split("-")[0] + chat_messages = get_argument_value(args, kwargs, 0, "messages") + integration = langchain._datadog_integration + span = integration.trace( + pin, + "%s.%s" % (instance.__module__, instance.__class__.__name__), + interface_type="chat_model", + provider=llm_provider, + model=_extract_model_name(instance), + api_key=_extract_api_key(instance), + ) + chat_completions = None + try: + for message_set_idx, message_set in enumerate(chat_messages): + for message_idx, message in enumerate(message_set): + if integration.is_pc_sampled_span(span): + span.set_tag_str( + "langchain.request.messages.%d.%d.content" % (message_set_idx, message_idx), + integration.trunc(message.content), + ) + span.set_tag_str( + "langchain.request.messages.%d.%d.message_type" % (message_set_idx, message_idx), + message.__class__.__name__, + ) + for param, val in getattr(instance, "_identifying_params", {}).items(): + if isinstance(val, dict): + for k, v in val.items(): + span.set_tag_str("langchain.request.%s.parameters.%s.%s" % (llm_provider, param, k), str(v)) + else: + span.set_tag_str("langchain.request.%s.parameters.%s" % (llm_provider, param), str(val)) + + chat_completions = func(*args, **kwargs) + if isinstance(instance, langchain.chat_models.ChatOpenAI): + _tag_openai_token_usage(span, chat_completions.llm_output) + integration.record_usage(span, chat_completions.llm_output) + + for message_set_idx, message_set in enumerate(chat_completions.generations): + for idx, chat_completion in enumerate(message_set): + if integration.is_pc_sampled_span(span): + span.set_tag_str( + "langchain.response.completions.%d.%d.content" % (message_set_idx, idx), + integration.trunc(chat_completion.text), + ) + span.set_tag_str( + "langchain.response.completions.%d.%d.message_type" % (message_set_idx, idx), + chat_completion.message.__class__.__name__, + ) + except Exception: + span.set_exc_info(*sys.exc_info()) + integration.metric(span, "incr", "request.error", 1) + raise + finally: + span.finish() + integration.metric(span, "dist", "request.duration", span.duration_ns) + if integration.is_pc_sampled_log(span): + if chat_completions is None: + log_chat_completions = [] + else: + log_chat_completions = [ + [ + { + "content": message.text, + "message_type": message.message.__class__.__name__, + } + for message in messages + ] + for messages in chat_completions.generations + ] + integration.log( + span, + "info" if span.error == 0 else "error", + "sampled %s.%s" % (instance.__module__, instance.__class__.__name__), + attrs={ + "messages": [ + [ + { + "content": message.content, + "message_type": message.__class__.__name__, + } + for message in messages + ] + for messages in chat_messages + ], + "choices": log_chat_completions, + }, + ) + return chat_completions + + +@with_traced_module +async def traced_chat_model_agenerate(langchain, pin, func, instance, args, kwargs): + llm_provider = instance._llm_type.split("-")[0] + chat_messages = get_argument_value(args, kwargs, 0, "messages") + integration = langchain._datadog_integration + span = integration.trace( + pin, + "%s.%s" % (instance.__module__, instance.__class__.__name__), + interface_type="chat_model", + provider=llm_provider, + model=_extract_model_name(instance), + api_key=_extract_api_key(instance), + ) + chat_completions = None + try: + for message_set_idx, message_set in enumerate(chat_messages): + for message_idx, message in enumerate(message_set): + if integration.is_pc_sampled_span(span): + span.set_tag_str( + "langchain.request.messages.%d.%d.content" % (message_set_idx, message_idx), + integration.trunc(message.content), + ) + span.set_tag_str( + "langchain.request.messages.%d.%d.message_type" % (message_set_idx, message_idx), + message.__class__.__name__, + ) + for param, val in getattr(instance, "_identifying_params", {}).items(): + if isinstance(val, dict): + for k, v in val.items(): + span.set_tag_str("langchain.request.%s.parameters.%s.%s" % (llm_provider, param, k), str(v)) + else: + span.set_tag_str("langchain.request.%s.parameters.%s" % (llm_provider, param), str(val)) + + chat_completions = await func(*args, **kwargs) + if isinstance(instance, langchain.chat_models.ChatOpenAI): + _tag_openai_token_usage(span, chat_completions.llm_output) + integration.record_usage(span, chat_completions.llm_output) + + for message_set_idx, message_set in enumerate(chat_completions.generations): + for idx, chat_completion in enumerate(message_set): + if integration.is_pc_sampled_span(span): + span.set_tag_str( + "langchain.response.completions.%d.%d.content" % (message_set_idx, idx), + integration.trunc(chat_completion.text), + ) + span.set_tag_str( + "langchain.response.completions.%d.%d.message_type" % (message_set_idx, idx), + chat_completion.message.__class__.__name__, + ) + except Exception: + span.set_exc_info(*sys.exc_info()) + integration.metric(span, "incr", "request.error", 1) + raise + finally: + span.finish() + integration.metric(span, "dist", "request.duration", span.duration_ns) + if integration.is_pc_sampled_log(span): + if chat_completions is None: + log_chat_completions = [] + else: + log_chat_completions = [ + [ + { + "content": message.text, + "message_type": message.message.__class__.__name__, + } + for message in messages + ] + for messages in chat_completions.generations + ] + integration.log( + span, + "info" if span.error == 0 else "error", + "sampled %s.%s" % (instance.__module__, instance.__class__.__name__), + attrs={ + "messages": [ + [ + { + "content": message.content, + "message_type": message.__class__.__name__, + } + for message in messages + ] + for messages in chat_messages + ], + "choices": log_chat_completions, + }, + ) + return chat_completions + + +@with_traced_module +def traced_embedding(langchain, pin, func, instance, args, kwargs): + """ + This traces both embed_query(text) and embed_documents(texts), so we need to make sure + we get the right arg/kwarg. + """ + try: + input_texts = get_argument_value(args, kwargs, 0, "texts") + except ArgumentError: + input_texts = get_argument_value(args, kwargs, 0, "text") + + provider = instance.__class__.__name__.split("Embeddings")[0].lower() + integration = langchain._datadog_integration + span = integration.trace( + pin, + "%s.%s" % (instance.__module__, instance.__class__.__name__), + interface_type="embedding", + provider=provider, + model=_extract_model_name(instance), + api_key=_extract_api_key(instance), + ) + try: + if isinstance(input_texts, str): + if integration.is_pc_sampled_span(span): + span.set_tag_str("langchain.request.inputs.0.text", integration.trunc(input_texts)) + span.set_metric("langchain.request.input_count", 1) + else: + if integration.is_pc_sampled_span(span): + for idx, text in enumerate(input_texts): + span.set_tag_str("langchain.request.inputs.%d.text" % idx, integration.trunc(text)) + span.set_metric("langchain.request.input_count", len(input_texts)) + # langchain currently does not support token tracking for OpenAI embeddings: + # https://github.com/hwchase17/langchain/issues/945 + embeddings = func(*args, **kwargs) + if isinstance(embeddings, list) and embeddings and isinstance(embeddings[0], list): + for idx, embedding in enumerate(embeddings): + span.set_metric("langchain.response.outputs.%d.embedding_length" % idx, len(embedding)) + else: + span.set_metric("langchain.response.outputs.embedding_length", len(embeddings)) + except Exception: + span.set_exc_info(*sys.exc_info()) + integration.metric(span, "incr", "request.error", 1) + raise + finally: + span.finish() + integration.metric(span, "dist", "request.duration", span.duration_ns) + if integration.is_pc_sampled_log(span): + integration.log( + span, + "info" if span.error == 0 else "error", + "sampled %s.%s" % (instance.__module__, instance.__class__.__name__), + attrs={"inputs": [input_texts] if isinstance(input_texts, str) else input_texts}, + ) + return embeddings + + +@with_traced_module +def traced_chain_call(langchain, pin, func, instance, args, kwargs): + integration = langchain._datadog_integration + span = integration.trace(pin, "%s.%s" % (instance.__module__, instance.__class__.__name__), interface_type="chain") + final_outputs = {} + try: + inputs = get_argument_value(args, kwargs, 0, "inputs") + if not isinstance(inputs, dict): + inputs = {instance.input_keys[0]: inputs} + if integration.is_pc_sampled_span(span): + for k, v in inputs.items(): + span.set_tag_str("langchain.request.inputs.%s" % k, integration.trunc(str(v))) + template = deep_getattr(instance, "prompt.template", default="") + if template: + span.set_tag_str("langchain.request.prompt", integration.trunc(str(template))) + final_outputs = func(*args, **kwargs) + if integration.is_pc_sampled_span(span): + for k, v in final_outputs.items(): + span.set_tag_str("langchain.response.outputs.%s" % k, integration.trunc(str(v))) + if _is_iast_enabled(): + taint_outputs(instance, inputs, final_outputs) + except Exception: + span.set_exc_info(*sys.exc_info()) + integration.metric(span, "incr", "request.error", 1) + raise + finally: + span.finish() + integration.metric(span, "dist", "request.duration", span.duration_ns) + if integration.is_pc_sampled_log(span): + log_inputs = {} + log_outputs = {} + for k, v in inputs.items(): + log_inputs[k] = str(v) + for k, v in final_outputs.items(): + log_outputs[k] = str(v) + integration.log( + span, + "info" if span.error == 0 else "error", + "sampled %s.%s" % (instance.__module__, instance.__class__.__name__), + attrs={ + "inputs": log_inputs, + "prompt": str(deep_getattr(instance, "prompt.template", default="")), + "outputs": log_outputs, + }, + ) + return final_outputs + + +@with_traced_module +async def traced_chain_acall(langchain, pin, func, instance, args, kwargs): + integration = langchain._datadog_integration + span = integration.trace(pin, "%s.%s" % (instance.__module__, instance.__class__.__name__), interface_type="chain") + final_outputs = {} + try: + inputs = get_argument_value(args, kwargs, 0, "inputs") + if not isinstance(inputs, dict): + inputs = {instance.input_keys[0]: inputs} + if integration.is_pc_sampled_span(span): + for k, v in inputs.items(): + span.set_tag_str("langchain.request.inputs.%s" % k, integration.trunc(str(v))) + template = deep_getattr(instance, "prompt.template", default="") + if template: + span.set_tag_str("langchain.request.prompt", integration.trunc(str(template))) + final_outputs = await func(*args, **kwargs) + if integration.is_pc_sampled_span(span): + for k, v in final_outputs.items(): + span.set_tag_str("langchain.response.outputs.%s" % k, integration.trunc(str(v))) + except Exception: + span.set_exc_info(*sys.exc_info()) + integration.metric(span, "incr", "request.error", 1) + raise + finally: + span.finish() + integration.metric(span, "dist", "request.duration", span.duration_ns) + if integration.is_pc_sampled_log(span): + log_inputs = {} + log_outputs = {} + for k, v in inputs.items(): + log_inputs[k] = str(v) + for k, v in final_outputs.items(): + log_outputs[k] = str(v) + integration.log( + span, + "info" if span.error == 0 else "error", + "sampled %s.%s" % (instance.__module__, instance.__class__.__name__), + attrs={ + "inputs": log_inputs, + "prompt": str(deep_getattr(instance, "prompt.template", default="")), + "outputs": log_outputs, + }, + ) + return final_outputs + + +@with_traced_module +def traced_similarity_search(langchain, pin, func, instance, args, kwargs): + integration = langchain._datadog_integration + query = get_argument_value(args, kwargs, 0, "query") + k = kwargs.get("k", args[1] if len(args) >= 2 else None) + provider = instance.__class__.__name__.lower() + span = integration.trace( + pin, + "%s.%s" % (instance.__module__, instance.__class__.__name__), + interface_type="similarity_search", + provider=provider, + api_key=_extract_api_key(instance), + ) + documents = [] + try: + if integration.is_pc_sampled_span(span): + span.set_tag_str("langchain.request.query", integration.trunc(query)) + if k is not None: + span.set_tag_str("langchain.request.k", str(k)) + for kwarg_key, v in kwargs.items(): + span.set_tag_str("langchain.request.%s" % kwarg_key, str(v)) + if isinstance(instance, langchain.vectorstores.Pinecone): + span.set_tag_str( + "langchain.request.pinecone.environment", + instance._index.configuration.server_variables.get("environment", ""), + ) + span.set_tag_str( + "langchain.request.pinecone.index_name", + instance._index.configuration.server_variables.get("index_name", ""), + ) + span.set_tag_str( + "langchain.request.pinecone.project_name", + instance._index.configuration.server_variables.get("project_name", ""), + ) + api_key = instance._index.configuration.api_key.get("ApiKeyAuth", "") + span.set_tag_str(API_KEY, _format_api_key(api_key)) # override api_key for Pinecone + documents = func(*args, **kwargs) + span.set_metric("langchain.response.document_count", len(documents)) + for idx, document in enumerate(documents): + span.set_tag_str( + "langchain.response.document.%d.page_content" % idx, integration.trunc(str(document.page_content)) + ) + for kwarg_key, v in document.metadata.items(): + span.set_tag_str( + "langchain.response.document.%d.metadata.%s" % (idx, kwarg_key), integration.trunc(str(v)) + ) + except Exception: + span.set_exc_info(*sys.exc_info()) + integration.metric(span, "incr", "request.error", 1) + raise + finally: + span.finish() + integration.metric(span, "dist", "request.duration", span.duration_ns) + if integration.is_pc_sampled_log(span): + integration.log( + span, + "info" if span.error == 0 else "error", + "sampled %s.%s" % (instance.__module__, instance.__class__.__name__), + attrs={ + "query": query, + "k": k or "", + "documents": [ + {"page_content": document.page_content, "metadata": document.metadata} for document in documents + ], + }, + ) + return documents + + +def patch(): + if getattr(langchain, "_datadog_patch", False): + return + langchain._datadog_patch = True + + Pin().onto(langchain) + integration = LangChainIntegration(integration_config=config.langchain) + langchain._datadog_integration = integration + + # Langchain doesn't allow wrapping directly from root, so we have to import the base classes first before wrapping. + # ref: https://github.com/DataDog/dd-trace-py/issues/7123 + from langchain import embeddings # noqa:F401 + from langchain import vectorstores # noqa:F401 + from langchain.chains.base import Chain # noqa:F401 + from langchain.chat_models.base import BaseChatModel # noqa:F401 + from langchain.llms.base import BaseLLM # noqa:F401 + + wrap("langchain", "llms.base.BaseLLM.generate", traced_llm_generate(langchain)) + wrap("langchain", "llms.base.BaseLLM.agenerate", traced_llm_agenerate(langchain)) + wrap("langchain", "chat_models.base.BaseChatModel.generate", traced_chat_model_generate(langchain)) + wrap("langchain", "chat_models.base.BaseChatModel.agenerate", traced_chat_model_agenerate(langchain)) + wrap("langchain", "chains.base.Chain.__call__", traced_chain_call(langchain)) + wrap("langchain", "chains.base.Chain.acall", traced_chain_acall(langchain)) + # Text embedding models override two abstract base methods instead of super calls, so we need to + # wrap each langchain-provided text embedding model. + for text_embedding_model in text_embedding_models: + if hasattr(langchain.embeddings, text_embedding_model): + # Ensure not double patched, as some Embeddings interfaces are pointers to other Embeddings. + if not isinstance( + deep_getattr(langchain.embeddings, "%s.embed_query" % text_embedding_model), wrapt.ObjectProxy + ): + wrap("langchain", "embeddings.%s.embed_query" % text_embedding_model, traced_embedding(langchain)) + if not isinstance( + deep_getattr(langchain.embeddings, "%s.embed_documents" % text_embedding_model), wrapt.ObjectProxy + ): + wrap("langchain", "embeddings.%s.embed_documents" % text_embedding_model, traced_embedding(langchain)) + # TODO: langchain >= 0.0.209 includes async embedding implementation (only for OpenAI) + # We need to do the same with Vectorstores. + for vectorstore in vectorstore_classes: + if hasattr(langchain.vectorstores, vectorstore): + # Ensure not double patched, as some Embeddings interfaces are pointers to other Embeddings. + if not isinstance( + deep_getattr(langchain.vectorstores, "%s.similarity_search" % vectorstore), wrapt.ObjectProxy + ): + wrap( + "langchain", "vectorstores.%s.similarity_search" % vectorstore, traced_similarity_search(langchain) + ) + + if _is_iast_enabled(): + from ddtrace.appsec._iast._metrics import _set_iast_error_metric + + def wrap_output_parser(module, parser): + # Ensure not double patched + if not isinstance(deep_getattr(module, "%s.parse" % parser), wrapt.ObjectProxy): + wrap(module, "%s.parse" % parser, taint_parser_output) + + try: + with_agent_output_parser(wrap_output_parser) + except Exception as e: + _set_iast_error_metric("IAST propagation error. langchain wrap_output_parser. {}".format(e)) + + +def unpatch(): + if not getattr(langchain, "_datadog_patch", False): + return + langchain._datadog_patch = False + + unwrap(langchain.llms.base.BaseLLM, "generate") + unwrap(langchain.llms.base.BaseLLM, "agenerate") + unwrap(langchain.chat_models.base.BaseChatModel, "generate") + unwrap(langchain.chat_models.base.BaseChatModel, "agenerate") + unwrap(langchain.chains.base.Chain, "__call__") + unwrap(langchain.chains.base.Chain, "acall") + for text_embedding_model in text_embedding_models: + if hasattr(langchain.embeddings, text_embedding_model): + if isinstance( + deep_getattr(langchain.embeddings, "%s.embed_query" % text_embedding_model), wrapt.ObjectProxy + ): + unwrap(getattr(langchain.embeddings, text_embedding_model), "embed_query") + if isinstance( + deep_getattr(langchain.embeddings, "%s.embed_documents" % text_embedding_model), wrapt.ObjectProxy + ): + unwrap(getattr(langchain.embeddings, text_embedding_model), "embed_documents") + for vectorstore in vectorstore_classes: + if hasattr(langchain.vectorstores, vectorstore): + if isinstance( + deep_getattr(langchain.vectorstores, "%s.similarity_search" % vectorstore), wrapt.ObjectProxy + ): + unwrap(getattr(langchain.vectorstores, vectorstore), "similarity_search") + + delattr(langchain, "_datadog_integration") + + +def taint_outputs(instance, inputs, outputs): + from ddtrace.appsec._iast._metrics import _set_iast_error_metric + from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges + from ddtrace.appsec._iast._taint_tracking import taint_pyobject + + try: + ranges = None + for key in filter(lambda x: x in inputs, instance.input_keys): + input_val = inputs.get(key) + if input_val: + ranges = get_tainted_ranges(input_val) + if ranges: + break + + if ranges: + source = ranges[0].source + for key in filter(lambda x: x in outputs, instance.output_keys): + output_value = outputs[key] + outputs[key] = taint_pyobject(output_value, source.name, source.value, source.origin) + except Exception as e: + _set_iast_error_metric("IAST propagation error. langchain taint_outputs. {}".format(e)) + + +def taint_parser_output(func, instance, args, kwargs): + from ddtrace.appsec._iast._metrics import _set_iast_error_metric + from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges + from ddtrace.appsec._iast._taint_tracking import taint_pyobject + + result = func(*args, **kwargs) + try: + try: + from langchain_core.agents import AgentAction + from langchain_core.agents import AgentFinish + except ImportError: + from langchain.agents import AgentAction + from langchain.agents import AgentFinish + ranges = get_tainted_ranges(args[0]) + if ranges: + source = ranges[0].source + if isinstance(result, AgentAction): + result.tool_input = taint_pyobject(result.tool_input, source.name, source.value, source.origin) + elif isinstance(result, AgentFinish) and "output" in result.return_values: + values = result.return_values + values["output"] = taint_pyobject(values["output"], source.name, source.value, source.origin) + except Exception as e: + _set_iast_error_metric("IAST propagation error. langchain taint_parser_output. {}".format(e)) + + return result + + +def with_agent_output_parser(f): + import langchain.agents + + queue = [(langchain.agents, agent_output_parser_classes)] + + while len(queue) > 0: + module, current = queue.pop(0) + if isinstance(current, str): + if hasattr(module, current): + f(module, current) + elif isinstance(current, dict): + for name, value in current.items(): + if hasattr(module, name): + queue.append((getattr(module, name), value)) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/logbook/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/logbook/__init__.py new file mode 100644 index 0000000..72dab43 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/logbook/__init__.py @@ -0,0 +1,62 @@ +r""" +Datadog APM traces can be integrated with the logs produced by ```logbook`` by: + +1. Having ``ddtrace`` patch the ``logbook`` module. This will configure a +patcher which appends trace related values to the log. + +2. Ensuring the logger has a format which emits new values from the log record + +3. For log correlation between APM and logs, the easiest format is via JSON +so that no further configuration needs to be done in the Datadog UI assuming +that the Datadog trace values are at the top level of the JSON + +Enabling +-------- + +Patch ``logbook`` +~~~~~~~~~~~~~~~~~~~ + +If using :ref:`ddtrace-run` then set the environment variable ``DD_LOGS_INJECTION=true``. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(logbook=True) + +Proper Formatting +~~~~~~~~~~~~~~~~~ + +The trace values are patched to every log at the top level of the record. In order to correlate +logs, it is highly recommended to use JSON logs which can be achieved by using a handler with +a proper formatting:: + + handler = FileHandler('output.log', format_string='{{\"message\": "{record.message}",' + '\"dd.trace_id\": "{record.extra[dd.trace_id]}",' + '\"dd.span_id\": "{record.extra[dd.span_id]}",' + '\"dd.env\": "{record.extra[dd.env]}",' + '\"dd.service\": "{record.extra[dd.service]}",' + '\"dd.version\": "{record.extra[dd.version]}"}}') + handler.push_application() + +Note that the ``extra`` field does not have a ``dd`` object but rather only a ``dd.trace_id``, ``dd.span_id``, etc. +To access the trace values inside extra, please use the ``[]`` operator. + +This will create a handler for the application that formats the logs in a way that is JSON with all the +Datadog trace values in a JSON format that can be automatically parsed by the Datadog backend. + +For more information, please see the attached guide for the Datadog Logging Product: +https://docs.datadoghq.com/logs/log_collection/python/ +""" + +from ...internal.utils.importlib import require_modules + + +required_modules = ["logbook"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/logbook/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/logbook/patch.py new file mode 100644 index 0000000..2fc37e1 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/logbook/patch.py @@ -0,0 +1,74 @@ +import logbook + +import ddtrace +from ddtrace import config + +from ...internal.utils import get_argument_value +from ...vendor.wrapt import wrap_function_wrapper as _w +from ..logging.constants import RECORD_ATTR_ENV +from ..logging.constants import RECORD_ATTR_SERVICE +from ..logging.constants import RECORD_ATTR_SPAN_ID +from ..logging.constants import RECORD_ATTR_TRACE_ID +from ..logging.constants import RECORD_ATTR_VALUE_EMPTY +from ..logging.constants import RECORD_ATTR_VALUE_ZERO +from ..logging.constants import RECORD_ATTR_VERSION +from ..trace_utils import unwrap as _u + + +config._add( + "logbook", + dict(), +) + + +def get_version(): + # type: () -> str + return getattr(logbook, "__version__", "") + + +def _tracer_injection(event_dict): + span = ddtrace.tracer.current_span() + + trace_id = None + span_id = None + if span: + span_id = span.span_id + trace_id = span.trace_id + if config._128_bit_trace_id_enabled and not config._128_bit_trace_id_logging_enabled: + trace_id = span._trace_id_64bits + + # add ids to logbook event dictionary + event_dict[RECORD_ATTR_TRACE_ID] = str(trace_id or RECORD_ATTR_VALUE_ZERO) + event_dict[RECORD_ATTR_SPAN_ID] = str(span_id or RECORD_ATTR_VALUE_ZERO) + # add the env, service, and version configured for the tracer + event_dict[RECORD_ATTR_ENV] = config.env or RECORD_ATTR_VALUE_EMPTY + event_dict[RECORD_ATTR_SERVICE] = config.service or RECORD_ATTR_VALUE_EMPTY + event_dict[RECORD_ATTR_VERSION] = config.version or RECORD_ATTR_VALUE_EMPTY + + return event_dict + + +def _w_process_record(func, instance, args, kwargs): + # patch logger to include datadog info before logging + record = get_argument_value(args, kwargs, 0, "record") + _tracer_injection(record.extra) + return func(*args, **kwargs) + + +def patch(): + """ + Patch ``logbook`` module for injection of tracer information + by editing a log record created via ``logbook.base.RecordDispatcher.process_record`` + """ + if getattr(logbook, "_datadog_patch", False): + return + logbook._datadog_patch = True + + _w(logbook.base.RecordDispatcher, "process_record", _w_process_record) + + +def unpatch(): + if getattr(logbook, "_datadog_patch", False): + logbook._datadog_patch = False + + _u(logbook.base.RecordDispatcher, "process_record") diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/logging/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/logging/__init__.py new file mode 100644 index 0000000..3dedbaf --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/logging/__init__.py @@ -0,0 +1,75 @@ +""" +Datadog APM traces can be integrated with the logs product by: + +1. Having ``ddtrace`` patch the ``logging`` module. This will add trace +attributes to the log record. + +2. Updating the log formatter used by the application. In order to inject +tracing information into a log the formatter must be updated to include the +tracing attributes from the log record. + + +Enabling +-------- + +Patch ``logging`` +~~~~~~~~~~~~~~~~~ + +There are a few ways to tell ddtrace to patch the ``logging`` module: + +1. If using :ref:`ddtrace-run`, you can set the environment variable ``DD_LOGS_INJECTION=true``. + +2. Use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(logging=True) + +3. (beta) Set ``log_injection_enabled`` at runtime via the Datadog UI. + + +Update Log Format +~~~~~~~~~~~~~~~~~ + +Make sure that your log format exactly matches the following:: + + import logging + from ddtrace import tracer + + FORMAT = ('%(asctime)s %(levelname)s [%(name)s] [%(filename)s:%(lineno)d] ' + '[dd.service=%(dd.service)s dd.env=%(dd.env)s ' + 'dd.version=%(dd.version)s ' + 'dd.trace_id=%(dd.trace_id)s dd.span_id=%(dd.span_id)s] ' + '- %(message)s') + logging.basicConfig(format=FORMAT) + log = logging.getLogger() + log.level = logging.INFO + + + @tracer.wrap() + def hello(): + log.info('Hello, World!') + + hello() + +Note that most host based setups log by default to UTC time. +If the log timestamps aren't automatically in UTC, the formatter can be updated to use UTC:: + + import time + logging.Formatter.converter = time.gmtime + +For more information, please see the attached guide on common timestamp issues: +https://docs.datadoghq.com/logs/guide/logs-not-showing-expected-timestamp/ +""" + +from ...internal.utils.importlib import require_modules + + +required_modules = ["logging"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/logging/constants.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/logging/constants.py new file mode 100644 index 0000000..1e47ddf --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/logging/constants.py @@ -0,0 +1,7 @@ +RECORD_ATTR_TRACE_ID = "dd.trace_id" +RECORD_ATTR_SPAN_ID = "dd.span_id" +RECORD_ATTR_ENV = "dd.env" +RECORD_ATTR_VERSION = "dd.version" +RECORD_ATTR_SERVICE = "dd.service" +RECORD_ATTR_VALUE_ZERO = "0" +RECORD_ATTR_VALUE_EMPTY = "" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/logging/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/logging/patch.py new file mode 100644 index 0000000..c511496 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/logging/patch.py @@ -0,0 +1,145 @@ +import logging + +import attr + +import ddtrace +from ddtrace import config + +from ...internal.utils import get_argument_value +from ...vendor.wrapt import wrap_function_wrapper as _w +from ..trace_utils import unwrap as _u +from .constants import RECORD_ATTR_ENV +from .constants import RECORD_ATTR_SERVICE +from .constants import RECORD_ATTR_SPAN_ID +from .constants import RECORD_ATTR_TRACE_ID +from .constants import RECORD_ATTR_VALUE_EMPTY +from .constants import RECORD_ATTR_VALUE_ZERO +from .constants import RECORD_ATTR_VERSION + + +_LOG_SPAN_KEY = "__datadog_log_span" + +config._add( + "logging", + dict( + tracer=None, + ), +) # by default, override here for custom tracer + + +def get_version(): + # type: () -> str + return getattr(logging, "__version__", "") + + +@attr.s(slots=True) +class DDLogRecord(object): + trace_id = attr.ib(type=int) + span_id = attr.ib(type=int) + service = attr.ib(type=str) + version = attr.ib(type=str) + env = attr.ib(type=str) + + +def _get_current_span(tracer=None): + """Helper to get the currently active span""" + + if not tracer: + # With the addition of a custom ddtrace logger in _logger.py, logs that happen on startup + # don't have access to `ddtrace.tracer`. Checking that this exists prevents an error + # if log injection is enabled. + if not getattr(ddtrace, "tracer", False): + return None + + tracer = ddtrace.tracer + + # We might be calling this during library initialization, in which case `ddtrace.tracer` might + # be the `tracer` module and not the global tracer instance. + if not getattr(tracer, "enabled", False): + return None + + return tracer.current_span() + + +def _w_makeRecord(func, instance, args, kwargs): + # Get the LogRecord instance for this log + record = func(*args, **kwargs) + + setattr(record, RECORD_ATTR_VERSION, config.version or RECORD_ATTR_VALUE_EMPTY) + setattr(record, RECORD_ATTR_ENV, config.env or RECORD_ATTR_VALUE_EMPTY) + setattr(record, RECORD_ATTR_SERVICE, config.service or RECORD_ATTR_VALUE_EMPTY) + + # logs from internal logger may explicitly pass the current span to + # avoid deadlocks in getting the current span while already in locked code. + span_from_log = getattr(record, _LOG_SPAN_KEY, None) + if isinstance(span_from_log, ddtrace.Span): + span = span_from_log + else: + span = _get_current_span(tracer=config.logging.tracer) + + if span: + trace_id = span.trace_id + if config._128_bit_trace_id_enabled and not config._128_bit_trace_id_logging_enabled: + trace_id = span._trace_id_64bits + setattr(record, RECORD_ATTR_TRACE_ID, str(trace_id)) + setattr(record, RECORD_ATTR_SPAN_ID, str(span.span_id)) + else: + setattr(record, RECORD_ATTR_TRACE_ID, RECORD_ATTR_VALUE_ZERO) + setattr(record, RECORD_ATTR_SPAN_ID, RECORD_ATTR_VALUE_ZERO) + + return record + + +def _w_StrFormatStyle_format(func, instance, args, kwargs): + # The format string "dd.service={dd.service}" expects + # the record to have a "dd" property which is an object that + # has a "service" property + # PercentStyle, and StringTemplateStyle both look for + # a "dd.service" property on the record + record = get_argument_value(args, kwargs, 0, "record") + + record.dd = DDLogRecord( + trace_id=getattr(record, RECORD_ATTR_TRACE_ID, RECORD_ATTR_VALUE_ZERO), + span_id=getattr(record, RECORD_ATTR_SPAN_ID, RECORD_ATTR_VALUE_ZERO), + service=getattr(record, RECORD_ATTR_SERVICE, RECORD_ATTR_VALUE_EMPTY), + version=getattr(record, RECORD_ATTR_VERSION, RECORD_ATTR_VALUE_EMPTY), + env=getattr(record, RECORD_ATTR_ENV, RECORD_ATTR_VALUE_EMPTY), + ) + + try: + return func(*args, **kwargs) + finally: + # We need to remove this extra attribute so it does not pollute other formatters + # For example: if we format with StrFormatStyle and then a JSON logger + # then the JSON logger will have `dd.{service,version,env,trace_id,span_id}` as + # well as the `record.dd` `DDLogRecord` instance + del record.dd + + +def patch(): + """ + Patch ``logging`` module in the Python Standard Library for injection of + tracer information by wrapping the base factory method ``Logger.makeRecord`` + """ + if getattr(logging, "_datadog_patch", False): + return + logging._datadog_patch = True + + _w(logging.Logger, "makeRecord", _w_makeRecord) + if hasattr(logging, "StrFormatStyle"): + if hasattr(logging.StrFormatStyle, "_format"): + _w(logging.StrFormatStyle, "_format", _w_StrFormatStyle_format) + else: + _w(logging.StrFormatStyle, "format", _w_StrFormatStyle_format) + + +def unpatch(): + if getattr(logging, "_datadog_patch", False): + logging._datadog_patch = False + + _u(logging.Logger, "makeRecord") + if hasattr(logging, "StrFormatStyle"): + if hasattr(logging.StrFormatStyle, "_format"): + _u(logging.StrFormatStyle, "_format") + else: + _u(logging.StrFormatStyle, "format") diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/loguru/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/loguru/__init__.py new file mode 100644 index 0000000..2727b0a --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/loguru/__init__.py @@ -0,0 +1,77 @@ +r""" +Datadog APM traces can be integrated with the logs produced by ```loguru`` by: + +1. Having ``ddtrace`` patch the ``loguru`` module. This will configure a +patcher which appends trace related values to the log. + +2. Ensuring the logger has a format which emits new values from the log record + +3. For log correlation between APM and logs, the easiest format is via JSON +so that no further configuration needs to be done in the Datadog UI assuming +that the Datadog trace values are at the top level of the JSON + +Enabling +-------- + +Patch ``loguru`` +~~~~~~~~~~~~~~~~~~~ + +If using :ref:`ddtrace-run` then set the environment variable ``DD_LOGS_INJECTION=true``. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(loguru=True) + +Proper Formatting +~~~~~~~~~~~~~~~~~ + +The trace values are patched to every log at the top level of the record. In order to correlate +logs, it is highly recommended to use JSON logs. Here are two ways to do this: + +1. Use the built-in serialize function within the library that emits the entire log record into a JSON log:: + + from loguru import logger + + logger.add("app.log", serialize=True) + +This will emit the entire log record with the trace values into a file "app.log" + +2. Create a custom format that includes the trace values in JSON format:: + + def serialize(record): + subset = { + "message": record["message"], + "dd.trace_id": record["dd.trace_id"], + "dd.span_id": record["dd.span_id"], + "dd.env": record["dd.env"], + "dd.version": record["dd.version"], + "dd.service": record["dd.service"], + } + return json.dumps(subset) + + def log_format(record): + record["extra"]["serialized"] = serialize(record) + return "{extra[serialized]}\n" + logger.add("app.log", format=log_format) + +This will emit the log in a format where the output contains the trace values of the log at the top level of a JSON +along with the message. The log will not include all the possible information in the record, but rather only the values +included in the subset object within the ``serialize`` method + +For more information, please see the attached guide for the Datadog Logging Product: +https://docs.datadoghq.com/logs/log_collection/python/ +""" + +from ...internal.utils.importlib import require_modules + + +required_modules = ["loguru"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/loguru/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/loguru/patch.py new file mode 100644 index 0000000..a6062cb --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/loguru/patch.py @@ -0,0 +1,85 @@ +import loguru + +import ddtrace +from ddtrace import config + +from ...vendor.wrapt import wrap_function_wrapper as _w +from ..logging.constants import RECORD_ATTR_ENV +from ..logging.constants import RECORD_ATTR_SERVICE +from ..logging.constants import RECORD_ATTR_SPAN_ID +from ..logging.constants import RECORD_ATTR_TRACE_ID +from ..logging.constants import RECORD_ATTR_VALUE_EMPTY +from ..logging.constants import RECORD_ATTR_VALUE_ZERO +from ..logging.constants import RECORD_ATTR_VERSION +from ..trace_utils import unwrap as _u + + +config._add( + "loguru", + dict(), +) + + +def get_version(): + # type: () -> str + return getattr(loguru, "__version__", "") + + +def _tracer_injection(event_dict): + span = ddtrace.tracer.current_span() + + trace_id = None + span_id = None + if span: + span_id = span.span_id + trace_id = span.trace_id + if config._128_bit_trace_id_enabled and not config._128_bit_trace_id_logging_enabled: + trace_id = span._trace_id_64bits + + # add ids to loguru event dictionary + event_dict[RECORD_ATTR_TRACE_ID] = str(trace_id or RECORD_ATTR_VALUE_ZERO) + event_dict[RECORD_ATTR_SPAN_ID] = str(span_id or RECORD_ATTR_VALUE_ZERO) + # add the env, service, and version configured for the tracer + event_dict[RECORD_ATTR_ENV] = config.env or RECORD_ATTR_VALUE_EMPTY + event_dict[RECORD_ATTR_SERVICE] = config.service or RECORD_ATTR_VALUE_EMPTY + event_dict[RECORD_ATTR_VERSION] = config.version or RECORD_ATTR_VALUE_EMPTY + + return event_dict + + +def _w_configure(func, instance, args, kwargs): + original_patcher = kwargs.get("patcher", None) + instance._dd_original_patcher = original_patcher + if not original_patcher: + # no patcher, we do not need to worry about ddtrace fields being overridden + return func(*args, **kwargs) + + def _wrapped_patcher(record): + original_patcher(record) + record.update(_tracer_injection(record["extra"])) + + kwargs["patcher"] = _wrapped_patcher + return func(*args, **kwargs) + + +def patch(): + """ + Patch ``loguru`` module for injection of tracer information + by appending a patcher before the add function ``loguru.add`` + """ + if getattr(loguru, "_datadog_patch", False): + return + loguru._datadog_patch = True + # Adds ddtrace fields to loguru logger + loguru.logger.configure(patcher=lambda record: record.update(_tracer_injection(record["extra"]))) + # Ensures that calling loguru.logger.configure(..) does not overwrite ddtrace fields + _w(loguru.logger, "configure", _w_configure) + + +def unpatch(): + if getattr(loguru, "_datadog_patch", False): + loguru._datadog_patch = False + + _u(loguru.logger, "configure") + if hasattr(loguru.logger, "_dd_original_patcher"): + loguru.logger.configure(patcher=loguru.logger._dd_original_patcher) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mako/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mako/__init__.py new file mode 100644 index 0000000..1ca29a3 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mako/__init__.py @@ -0,0 +1,24 @@ +""" +The ``mako`` integration traces templates rendering. +Auto instrumentation is available using the ``patch``. The following is an example:: + + from ddtrace import patch + from mako.template import Template + + patch(mako=True) + + t = Template(filename="index.html") + +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["mako"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mako/constants.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mako/constants.py new file mode 100644 index 0000000..1bda6e1 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mako/constants.py @@ -0,0 +1 @@ +DEFAULT_TEMPLATE_NAME = "" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mako/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mako/patch.py new file mode 100644 index 0000000..e683309 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mako/patch.py @@ -0,0 +1,68 @@ +import mako +from mako.template import DefTemplate +from mako.template import Template + +from ddtrace import config +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema import schematize_service_name + +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanTypes +from ...internal.utils.importlib import func_name +from ...pin import Pin +from ..trace_utils import unwrap as _u +from ..trace_utils import wrap as _w +from .constants import DEFAULT_TEMPLATE_NAME + + +def get_version(): + # type: () -> str + return getattr(mako, "__version__", "") + + +def patch(): + if getattr(mako, "__datadog_patch", False): + # already patched + return + mako.__datadog_patch = True + + Pin(service=config.service or schematize_service_name("mako")).onto(Template) + + _w(mako, "template.Template.render", _wrap_render) + _w(mako, "template.Template.render_unicode", _wrap_render) + _w(mako, "template.Template.render_context", _wrap_render) + + +def unpatch(): + if not getattr(mako, "__datadog_patch", False): + return + mako.__datadog_patch = False + + _u(mako.template.Template, "render") + _u(mako.template.Template, "render_unicode") + _u(mako.template.Template, "render_context") + + +def _wrap_render(wrapped, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return wrapped(*args, **kwargs) + + # Determine the resource and `mako.template_name` tag value + # DefTemplate is a wrapper around a callable from another template, it does not have a filename + # https://github.com/sqlalchemy/mako/blob/c2c690ac9add584f2216dc655cdf8215b24ef03c/mako/template.py#L603-L622 + if isinstance(instance, DefTemplate) and hasattr(instance, "callable_"): + template_name = func_name(instance.callable_) + else: + template_name = getattr(instance, "filename", None) + template_name = template_name or DEFAULT_TEMPLATE_NAME + + with pin.tracer.trace(func_name(wrapped), pin.service, span_type=SpanTypes.TEMPLATE) as span: + span.set_tag_str(COMPONENT, "mako") + + span.set_tag(SPAN_MEASURED_KEY) + try: + return wrapped(*args, **kwargs) + finally: + span.resource = template_name + span.set_tag("mako.template_name", template_name) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mariadb/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mariadb/__init__.py new file mode 100644 index 0000000..77d9278 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mariadb/__init__.py @@ -0,0 +1,66 @@ +""" +The MariaDB integration instruments the +`MariaDB library `_ to trace queries. + + +Enabling +~~~~~~~~ + +The MariaDB integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(mariadb=True) + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.mariadb["service"] + + The service name reported by default for MariaDB spans. + + This option can also be set with the ``DD_MARIADB_SERVICE`` environment + variable. + + Default: ``"mariadb"`` + + +Instance Configuration +~~~~~~~~~~~~~~~~~~~~~~ + +To configure the mariadb integration on an per-connection basis use the +``Pin`` API:: + + from ddtrace import Pin + from ddtrace import patch + + # Make sure to patch before importing mariadb + patch(mariadb=True) + + import mariadb.connector + + # This will report a span with the default settings + conn = mariadb.connector.connect(user="alice", password="b0b", host="localhost", port=3306, database="test") + + # Use a pin to override the service name for this connection. + Pin.override(conn, service="mariadb-users") + + cursor = conn.cursor() + cursor.execute("SELECT 6*7 AS the_answer;") + +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["mariadb"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mariadb/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mariadb/patch.py new file mode 100644 index 0000000..771bb05 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mariadb/patch.py @@ -0,0 +1,58 @@ +import os + +import mariadb + +from ddtrace import Pin +from ddtrace import config +from ddtrace.contrib.dbapi import TracedConnection +from ddtrace.ext import db +from ddtrace.ext import net +from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.utils.formats import asbool +from ddtrace.internal.utils.wrappers import unwrap +from ddtrace.vendor import wrapt + + +config._add( + "mariadb", + dict( + trace_fetch_methods=asbool(os.getenv("DD_MARIADB_TRACE_FETCH_METHODS", default=False)), + _default_service=schematize_service_name("mariadb"), + _dbapi_span_name_prefix="mariadb", + ), +) + + +def get_version(): + # type: () -> str + return getattr(mariadb, "__version__", "") + + +def patch(): + if getattr(mariadb, "_datadog_patch", False): + return + mariadb._datadog_patch = True + wrapt.wrap_function_wrapper("mariadb", "connect", _connect) + + +def unpatch(): + if getattr(mariadb, "_datadog_patch", False): + mariadb._datadog_patch = False + unwrap(mariadb, "connect") + + +def _connect(func, instance, args, kwargs): + conn = func(*args, **kwargs) + tags = { + net.TARGET_HOST: kwargs.get("host", "127.0.0.1"), + net.TARGET_PORT: kwargs.get("port", 3306), + db.USER: kwargs.get("user", "test"), + db.NAME: kwargs.get("database", "test"), + db.SYSTEM: "mariadb", + } + + pin = Pin(tags=tags) + + wrapped = TracedConnection(conn, pin=pin, cfg=config.mariadb) + pin.onto(wrapped) + return wrapped diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/molten/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/molten/__init__.py new file mode 100644 index 0000000..b8eb519 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/molten/__init__.py @@ -0,0 +1,50 @@ +""" +The molten web framework is automatically traced by ``ddtrace``:: + + import ddtrace.auto + from molten import App, Route + + def hello(name: str, age: int) -> str: + return f'Hello {age} year old named {name}!' + app = App(routes=[Route('/hello/{name}/{age}', hello)]) + + +You may also enable molten tracing automatically via ``ddtrace-run``:: + + ddtrace-run python app.py + + +Configuration +~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.molten['distributed_tracing'] + + Whether to parse distributed tracing headers from requests received by your Molten app. + + Default: ``True`` + +.. py:data:: ddtrace.config.molten['service_name'] + + The service name reported for your Molten app. + + Can also be configured via the ``DD_SERVICE`` or ``DD_MOLTEN_SERVICE`` environment variables. + + Default: ``'molten'`` + +:ref:`All HTTP tags ` are supported for this integration. + +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["molten"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from . import patch as _patch + + patch = _patch.patch + unpatch = _patch.unpatch + get_version = _patch.get_version + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/molten/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/molten/patch.py new file mode 100644 index 0000000..cdab5a2 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/molten/patch.py @@ -0,0 +1,180 @@ +import os + +import molten + +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.vendor import wrapt +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w + +from ... import Pin +from ... import config +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from ...internal.compat import urlencode +from ...internal.schema import schematize_service_name +from ...internal.schema import schematize_url_operation +from ...internal.utils.formats import asbool +from ...internal.utils.importlib import func_name +from ...internal.utils.version import parse_version +from .. import trace_utils +from ..trace_utils import unwrap as _u +from .wrappers import MOLTEN_ROUTE +from .wrappers import WrapperComponent +from .wrappers import WrapperMiddleware +from .wrappers import WrapperRenderer +from .wrappers import WrapperRouter + + +MOLTEN_VERSION = parse_version(molten.__version__) + +# Configure default configuration +config._add( + "molten", + dict( + _default_service=schematize_service_name("molten"), + distributed_tracing=asbool(os.getenv("DD_MOLTEN_DISTRIBUTED_TRACING", default=True)), + ), +) + + +def get_version(): + # type: () -> str + return getattr(molten, "__version__", "") + + +def patch(): + """Patch the instrumented methods""" + if getattr(molten, "_datadog_patch", False): + return + molten._datadog_patch = True + + pin = Pin() + + # add pin to module since many classes use __slots__ + pin.onto(molten) + + _w(molten.BaseApp, "__init__", patch_app_init) + _w(molten.App, "__call__", patch_app_call) + + +def unpatch(): + """Remove instrumentation""" + if getattr(molten, "_datadog_patch", False): + molten._datadog_patch = False + + # remove pin + pin = Pin.get_from(molten) + if pin: + pin.remove_from(molten) + + _u(molten.BaseApp, "__init__") + _u(molten.App, "__call__") + + +def patch_app_call(wrapped, instance, args, kwargs): + """Patch wsgi interface for app""" + pin = Pin.get_from(molten) + + if not pin or not pin.enabled(): + return wrapped(*args, **kwargs) + + # DEV: This is safe because this is the args for a WSGI handler + # https://www.python.org/dev/peps/pep-3333/ + environ, start_response = args + + request = molten.http.Request.from_environ(environ) + resource = func_name(wrapped) + + # request.headers is type Iterable[Tuple[str, str]] + trace_utils.activate_distributed_headers( + pin.tracer, int_config=config.molten, request_headers=dict(request.headers) + ) + + with pin.tracer.trace( + schematize_url_operation("molten.request", protocol="http", direction=SpanDirection.INBOUND), + service=trace_utils.int_service(pin, config.molten), + resource=resource, + span_type=SpanTypes.WEB, + ) as span: + span.set_tag_str(COMPONENT, config.molten.integration_name) + + # set span.kind tag equal to type of operation being performed + span.set_tag_str(SPAN_KIND, SpanKind.SERVER) + + span.set_tag(SPAN_MEASURED_KEY) + # set analytics sample rate with global config enabled + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.molten.get_analytics_sample_rate(use_global_config=True)) + + @wrapt.function_wrapper + def _w_start_response(wrapped, instance, args, kwargs): + """Patch respond handling to set metadata""" + + pin = Pin.get_from(molten) + if not pin or not pin.enabled(): + return wrapped(*args, **kwargs) + + status, headers, exc_info = args + code, _, _ = status.partition(" ") + + try: + code = int(code) + except ValueError: + pass + + if not span.get_tag(MOLTEN_ROUTE): + # if route never resolve, update root resource + span.resource = "{} {}".format(request.method, code) + + trace_utils.set_http_meta(span, config.molten, status_code=code) + + return wrapped(*args, **kwargs) + + # patching for extracting response code + start_response = _w_start_response(start_response) + + url = "%s://%s:%s%s" % ( + request.scheme, + request.host, + request.port, + request.path, + ) + query = urlencode(dict(request.params)) + trace_utils.set_http_meta( + span, config.molten, method=request.method, url=url, query=query, request_headers=request.headers + ) + + span.set_tag_str("molten.version", molten.__version__) + return wrapped(environ, start_response, **kwargs) + + +def patch_app_init(wrapped, instance, args, kwargs): + """Patch app initialization of middleware, components and renderers""" + # allow instance to be initialized before wrapping them + wrapped(*args, **kwargs) + + # add Pin to instance + pin = Pin.get_from(molten) + + if not pin or not pin.enabled(): + return + + # Wrappers here allow us to trace objects without altering class or instance + # attributes, which presents a problem when classes in molten use + # ``__slots__`` + + instance.router = WrapperRouter(instance.router) + + # wrap middleware functions/callables + instance.middleware = [WrapperMiddleware(mw) for mw in instance.middleware] + + # wrap components objects within injector + # NOTE: the app instance also contains a list of components but it does not + # appear to be used for anything passing along to the dependency injector + instance.injector.components = [WrapperComponent(c) for c in instance.injector.components] + + # but renderers objects + instance.renderers = [WrapperRenderer(r) for r in instance.renderers] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/molten/wrappers.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/molten/wrappers.py new file mode 100644 index 0000000..851fccc --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/molten/wrappers.py @@ -0,0 +1,124 @@ +import molten + +from ddtrace import config +from ddtrace.constants import SPAN_KIND +from ddtrace.internal.constants import COMPONENT +from ddtrace.vendor import wrapt + +from ... import Pin +from ...ext import SpanKind +from ...ext import http +from ...internal.utils.importlib import func_name +from .. import trace_utils + + +MOLTEN_ROUTE = "molten.route" + + +def trace_wrapped(resource, wrapped, *args, **kwargs): + pin = Pin.get_from(molten) + if not pin or not pin.enabled(): + return wrapped(*args, **kwargs) + + with pin.tracer.trace( + func_name(wrapped), service=trace_utils.int_service(pin, config.molten), resource=resource + ) as span: + span.set_tag_str(COMPONENT, config.molten.integration_name) + + # set span.kind to the operation type being performed + span.set_tag_str(SPAN_KIND, SpanKind.SERVER) + + return wrapped(*args, **kwargs) + + +def trace_func(resource): + """Trace calls to function using provided resource name""" + + @wrapt.function_wrapper + def _trace_func(wrapped, instance, args, kwargs): + pin = Pin.get_from(molten) + + if not pin or not pin.enabled(): + return wrapped(*args, **kwargs) + + with pin.tracer.trace( + func_name(wrapped), service=trace_utils.int_service(pin, config.molten, pin), resource=resource + ) as span: + span.set_tag_str(COMPONENT, config.molten.integration_name) + + # set span.kind to the operation type being performed + span.set_tag_str(SPAN_KIND, SpanKind.SERVER) + + return wrapped(*args, **kwargs) + + return _trace_func + + +class WrapperComponent(wrapt.ObjectProxy): + """Tracing of components""" + + def can_handle_parameter(self, *args, **kwargs): + func = self.__wrapped__.can_handle_parameter + cname = func_name(self.__wrapped__) + resource = "{}.{}".format(cname, func.__name__) + return trace_wrapped(resource, func, *args, **kwargs) + + # TODO[tahir]: the signature of a wrapped resolve method causes DIError to + # be thrown since parameter types cannot be determined + + +class WrapperRenderer(wrapt.ObjectProxy): + """Tracing of renderers""" + + def render(self, *args, **kwargs): + func = self.__wrapped__.render + cname = func_name(self.__wrapped__) + resource = "{}.{}".format(cname, func.__name__) + return trace_wrapped(resource, func, *args, **kwargs) + + +class WrapperMiddleware(wrapt.ObjectProxy): + """Tracing of callable functional-middleware""" + + def __call__(self, *args, **kwargs): + func = self.__wrapped__.__call__ + resource = func_name(self.__wrapped__) + return trace_wrapped(resource, func, *args, **kwargs) + + +class WrapperRouter(wrapt.ObjectProxy): + """Tracing of router on the way back from a matched route""" + + def match(self, *args, **kwargs): + # catch matched route and wrap tracer around its handler and set root span resource + func = self.__wrapped__.match + route_and_params = func(*args, **kwargs) + + pin = Pin.get_from(molten) + if not pin or not pin.enabled(): + return route_and_params + + if route_and_params is not None: + route, params = route_and_params + + route.handler = trace_func(func_name(route.handler))(route.handler) + + # update root span resource while we know the matched route + resource = "{} {}".format( + route.method, + route.template, + ) + root_span = pin.tracer.current_root_span() + root_span.resource = resource + + # if no root route set make sure we record it based on this resolved + # route + if root_span: + if not root_span.get_tag(MOLTEN_ROUTE): + root_span.set_tag(MOLTEN_ROUTE, route.name) + if not root_span.get_tag(http.ROUTE): + root_span.set_tag_str(http.ROUTE, route.template) + + return route, params + + return route_and_params diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mongoengine/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mongoengine/__init__.py new file mode 100644 index 0000000..9bfb560 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mongoengine/__init__.py @@ -0,0 +1,30 @@ +"""Instrument mongoengine to report MongoDB queries. + +``import ddtrace.auto`` will automatically patch your mongoengine connect method to make it work. +:: + + from ddtrace import Pin, patch + import mongoengine + + # If not patched yet, you can patch mongoengine specifically + patch(mongoengine=True) + + # At that point, mongoengine is instrumented with the default settings + mongoengine.connect('db', alias='default') + + # Use a pin to specify metadata related to this client + client = mongoengine.connect('db', alias='master') + Pin.override(client, service="mongo-master") +""" + +from ...internal.utils.importlib import require_modules + + +required_modules = ["mongoengine"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + + __all__ = ["patch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mongoengine/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mongoengine/patch.py new file mode 100644 index 0000000..fa7ef8c --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mongoengine/patch.py @@ -0,0 +1,20 @@ +import mongoengine + +from .trace import WrappedConnect + + +# Original connect function +_connect = mongoengine.connect + + +def get_version(): + # type: () -> str + return getattr(mongoengine, "__version__", "") + + +def patch(): + mongoengine.connect = WrappedConnect(_connect) + + +def unpatch(): + mongoengine.connect = _connect diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mongoengine/trace.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mongoengine/trace.py new file mode 100644 index 0000000..17add79 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mongoengine/trace.py @@ -0,0 +1,34 @@ +# 3p +# project +import ddtrace +from ddtrace.contrib.pymongo.client import TracedMongoClient +from ddtrace.ext import mongo as mongox +from ddtrace.internal.schema import schematize_service_name +from ddtrace.vendor import wrapt + + +# TODO(Benjamin): we should instrument register_connection instead, because more generic +# We should also extract the "alias" attribute and set it as a meta +_SERVICE = schematize_service_name(mongox.SERVICE) + + +class WrappedConnect(wrapt.ObjectProxy): + """WrappedConnect wraps mongoengines 'connect' function to ensure + that all returned connections are wrapped for tracing. + """ + + def __init__(self, connect): + super(WrappedConnect, self).__init__(connect) + ddtrace.Pin(_SERVICE, tracer=ddtrace.tracer).onto(self) + + def __call__(self, *args, **kwargs): + client = self.__wrapped__(*args, **kwargs) + pin = ddtrace.Pin.get_from(self) + if pin: + # mongoengine uses pymongo internally, so we can just piggyback on the + # existing pymongo integration and make sure that the connections it + # uses internally are traced. + client = TracedMongoClient(client) + ddtrace.Pin(service=pin.service, tracer=pin.tracer).onto(client) + + return client diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mysql/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mysql/__init__.py new file mode 100644 index 0000000..60d9d7b --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mysql/__init__.py @@ -0,0 +1,76 @@ +""" +The mysql integration instruments the mysql library to trace MySQL queries. + + +Enabling +~~~~~~~~ + +The mysql integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(mysql=True) + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.mysql["service"] + + The service name reported by default for mysql spans. + + This option can also be set with the ``DD_MYSQL_SERVICE`` environment + variable. + + Default: ``"mysql"`` + +.. py:data:: ddtrace.config.mysql["trace_fetch_methods"] + + Whether or not to trace fetch methods. + + Can also configured via the ``DD_MYSQL_TRACE_FETCH_METHODS`` environment variable. + + Default: ``False`` + + +Instance Configuration +~~~~~~~~~~~~~~~~~~~~~~ + +To configure the mysql integration on an per-connection basis use the +``Pin`` API:: + + from ddtrace import Pin + # Make sure to import mysql.connector and not the 'connect' function, + # otherwise you won't have access to the patched version + import mysql.connector + + # This will report a span with the default settings + conn = mysql.connector.connect(user="alice", password="b0b", host="localhost", port=3306, database="test") + + # Use a pin to override the service name for this connection. + Pin.override(conn, service='mysql-users') + + cursor = conn.cursor() + cursor.execute("SELECT 6*7 AS the_answer;") + + +Only the default full-Python integration works. The binary C connector, +provided by _mysql_connector, is not supported. + +Help on mysql.connector can be found on: +https://dev.mysql.com/doc/connector-python/en/ +""" +from ...internal.utils.importlib import require_modules + + +# check `mysql-connector` availability +required_modules = ["mysql.connector"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + + __all__ = ["patch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mysql/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mysql/patch.py new file mode 100644 index 0000000..03a4f92 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mysql/patch.py @@ -0,0 +1,68 @@ +import os + +import mysql.connector + +from ddtrace import Pin +from ddtrace import config +from ddtrace.contrib.dbapi import TracedConnection +from ddtrace.vendor import wrapt + +from ...ext import db +from ...ext import net +from ...internal.schema import schematize_database_operation +from ...internal.schema import schematize_service_name +from ...internal.utils.formats import asbool + + +config._add( + "mysql", + dict( + _default_service=schematize_service_name("mysql"), + _dbapi_span_name_prefix="mysql", + _dbapi_span_operation_name=schematize_database_operation("mysql.query", database_provider="mysql"), + trace_fetch_methods=asbool(os.getenv("DD_MYSQL_TRACE_FETCH_METHODS", default=False)), + ), +) + + +def get_version(): + # type: () -> str + return mysql.connector.version.VERSION_TEXT + + +CONN_ATTR_BY_TAG = { + net.TARGET_HOST: "server_host", + net.TARGET_PORT: "server_port", + db.USER: "user", + db.NAME: "database", +} + + +def patch(): + wrapt.wrap_function_wrapper("mysql.connector", "connect", _connect) + # `Connect` is an alias for `connect`, patch it too + if hasattr(mysql.connector, "Connect"): + mysql.connector.Connect = mysql.connector.connect + + +def unpatch(): + if isinstance(mysql.connector.connect, wrapt.ObjectProxy): + mysql.connector.connect = mysql.connector.connect.__wrapped__ + if hasattr(mysql.connector, "Connect"): + mysql.connector.Connect = mysql.connector.connect + + +def _connect(func, instance, args, kwargs): + conn = func(*args, **kwargs) + return patch_conn(conn) + + +def patch_conn(conn): + tags = {t: getattr(conn, a) for t, a in CONN_ATTR_BY_TAG.items() if getattr(conn, a, "") != ""} + tags[db.SYSTEM] = "mysql" + pin = Pin(tags=tags) + + # grab the metadata from the conn + wrapped = TracedConnection(conn, pin=pin, cfg=config.mysql) + pin.onto(wrapped) + return wrapped diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mysqldb/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mysqldb/__init__.py new file mode 100644 index 0000000..a28d27d --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mysqldb/__init__.py @@ -0,0 +1,88 @@ +"""The mysqldb integration instruments the mysqlclient library to trace MySQL queries. + + +Enabling +~~~~~~~~ + +The integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(mysqldb=True) + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.mysqldb["service"] + + The service name reported by default for spans. + + This option can also be set with the ``DD_MYSQLDB_SERVICE`` environment + variable. + + Default: ``"mysql"`` + +.. py:data:: ddtrace.config.mysqldb["trace_fetch_methods"] + + Whether or not to trace fetch methods. + + Can also configured via the ``DD_MYSQLDB_TRACE_FETCH_METHODS`` environment variable. + + Default: ``False`` + +.. _mysqldb_config_trace_connect: + +.. py:data:: ddtrace.config.mysqldb["trace_connect"] + + Whether or not to trace connecting. + + Can also be configured via the ``DD_MYSQLDB_TRACE_CONNECT`` environment variable. + + Note that if you are overriding the service name via the Pin on an individual cursor, that will not affect + connect traces. The service name must also be overridden on the Pin on the MySQLdb module. + + Default: ``False`` + + +Instance Configuration +~~~~~~~~~~~~~~~~~~~~~~ + +To configure the integration on an per-connection basis use the +``Pin`` API:: + + # Make sure to import MySQLdb and not the 'connect' function, + # otherwise you won't have access to the patched version + from ddtrace import Pin + import MySQLdb + + # This will report a span with the default settings + conn = MySQLdb.connect(user="alice", passwd="b0b", host="localhost", port=3306, db="test") + + # Use a pin to override the service. + Pin.override(conn, service='mysql-users') + + cursor = conn.cursor() + cursor.execute("SELECT 6*7 AS the_answer;") + + +This package works for mysqlclient. Only the default full-Python integration works. The binary C connector provided by +_mysql is not supported. + +Help on mysqlclient can be found on: +https://mysqlclient.readthedocs.io/ + +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["MySQLdb"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + + __all__ = ["patch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mysqldb/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mysqldb/patch.py new file mode 100644 index 0000000..878280d --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/mysqldb/patch.py @@ -0,0 +1,111 @@ +import os + +import MySQLdb + +from ddtrace import Pin +from ddtrace import config +from ddtrace.constants import SPAN_KIND +from ddtrace.constants import SPAN_MEASURED_KEY +from ddtrace.contrib.dbapi import TracedConnection +from ddtrace.contrib.trace_utils import ext_service +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema import schematize_database_operation +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w + +from ...ext import SpanKind +from ...ext import SpanTypes +from ...ext import db +from ...ext import net +from ...internal.schema import schematize_service_name +from ...internal.utils.formats import asbool +from ...internal.utils.wrappers import unwrap as _u + + +config._add( + "mysqldb", + dict( + _default_service=schematize_service_name("mysql"), + _dbapi_span_name_prefix="mysql", + _dbapi_span_operation_name=schematize_database_operation("mysql.query", database_provider="mysql"), + trace_fetch_methods=asbool(os.getenv("DD_MYSQLDB_TRACE_FETCH_METHODS", default=False)), + trace_connect=asbool(os.getenv("DD_MYSQLDB_TRACE_CONNECT", default=False)), + ), +) + +KWPOS_BY_TAG = { + net.TARGET_HOST: ("host", 0), + db.USER: ("user", 1), + db.NAME: ("db", 3), +} + + +def get_version(): + # type: () -> str + return ".".join(map(str, MySQLdb.version_info[0:3])) + + +def patch(): + # patch only once + if getattr(MySQLdb, "__datadog_patch", False): + return + MySQLdb.__datadog_patch = True + + Pin().onto(MySQLdb) + + # `Connection` and `connect` are aliases for + # `Connect`; patch them too + _w("MySQLdb", "Connect", _connect) + if hasattr(MySQLdb, "Connection"): + _w("MySQLdb", "Connection", _connect) + if hasattr(MySQLdb, "connect"): + _w("MySQLdb", "connect", _connect) + + +def unpatch(): + if not getattr(MySQLdb, "__datadog_patch", False): + return + MySQLdb.__datadog_patch = False + + pin = Pin.get_from(MySQLdb) + if pin: + pin.remove_from(MySQLdb) + + # unpatch MySQLdb + _u(MySQLdb, "Connect") + if hasattr(MySQLdb, "Connection"): + _u(MySQLdb, "Connection") + if hasattr(MySQLdb, "connect"): + _u(MySQLdb, "connect") + + +def _connect(func, instance, args, kwargs): + pin = Pin.get_from(MySQLdb) + + if not pin or not pin.enabled() or not config.mysqldb.trace_connect: + conn = func(*args, **kwargs) + else: + with pin.tracer.trace( + "MySQLdb.connection.connect", service=ext_service(pin, config.mysqldb), span_type=SpanTypes.SQL + ) as span: + span.set_tag_str(COMPONENT, config.mysqldb.integration_name) + + # set span.kind to the type of operation being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + span.set_tag(SPAN_MEASURED_KEY) + conn = func(*args, **kwargs) + return patch_conn(conn, *args, **kwargs) + + +def patch_conn(conn, *args, **kwargs): + tags = { + t: kwargs[k] if k in kwargs else args[p] for t, (k, p) in KWPOS_BY_TAG.items() if k in kwargs or len(args) > p + } + tags[db.SYSTEM] = "mysql" + tags[net.TARGET_PORT] = conn.port + pin = Pin(tags=tags) + + # grab the metadata from the conn + wrapped = TracedConnection(conn, pin=pin, cfg=config.mysqldb) + pin.onto(wrapped) + return wrapped diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/openai/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/openai/__init__.py new file mode 100644 index 0000000..3376546 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/openai/__init__.py @@ -0,0 +1,262 @@ +""" +The OpenAI integration instruments the OpenAI Python library to emit metrics, +traces, and logs (logs are disabled by default) for requests made to the models, +completions, chat completions, edits, images, embeddings, audio, files, fine-tunes, +and moderations endpoints. + +All metrics, logs, and traces submitted from the OpenAI integration are tagged by: + +- ``service``, ``env``, ``version``: see the `Unified Service Tagging docs `_. +- ``openai.request.endpoint``: OpenAI API endpoint used in the request. +- ``openai.request.method``: HTTP method type used in the request. +- ``openai.request.model``: OpenAI model used in the request. +- ``openai.organization.name``: OpenAI organization name used in the request. +- ``openai.organization.id``: OpenAI organization ID used in the request (when available). +- ``openai.user.api_key``: OpenAI API key used to make the request (obfuscated to match the OpenAI UI representation ``sk-...XXXX`` where ``XXXX`` is the last 4 digits of the key). + + +Metrics +~~~~~~~ + +The following metrics are collected by default by the OpenAI integration. + +.. important:: + If the Agent is configured to use a non-default Statsd hostname or port, use ``DD_DOGSTATSD_URL`` to configure + ``ddtrace`` to use it. + + +.. important:: + Ratelimit and token metrics only reflect usage of the supported completions, chat completions, and embedding + endpoints. Usage of other OpenAI endpoints will not be recorded as they are not provided. + + +.. py:data:: openai.request.duration + + The duration of the OpenAI request in seconds. + + Type: ``distribution`` + + +.. py:data:: openai.request.error + + The number of errors from requests made to OpenAI. + + Type: ``count`` + + +.. py:data:: openai.ratelimit.requests + + The maximum number of OpenAI requests permitted before exhausting the rate limit. + + Type: ``gauge`` + + +.. py:data:: openai.ratelimit.tokens + + The maximum number of OpenAI tokens permitted before exhausting the rate limit. + + Type: ``gauge`` + + +.. py:data:: openai.ratelimit.remaining.requests + + The remaining number of OpenAI requests permitted before exhausting the rate limit. + + Type: ``gauge`` + + +.. py:data:: openai.ratelimit.remaining.tokens + + The remaining number of OpenAI tokens permitted before exhausting the rate limit. + + Type: ``gauge`` + + +.. py:data:: openai.tokens.prompt + + The number of tokens used in the prompt of an OpenAI request. + + Type: ``distribution`` + + +.. py:data:: openai.tokens.completion + + The number of tokens used in the completion of a OpenAI response. + + Type: ``distribution`` + + +.. py:data:: openai.tokens.total + + The total number of tokens used in the prompt and completion of a OpenAI request/response. + + Type: ``distribution`` + + +(beta) Prompt and Completion Sampling +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following data is collected in span tags with a default sampling rate of ``1.0``: + +- Prompt inputs and completions for the ``completions`` endpoint. +- Message inputs and completions for the ``chat.completions`` endpoint. +- Embedding inputs for the ``embeddings`` endpoint. +- Edit inputs, instructions, and completions for the ``edits`` endpoint. +- Image input filenames and completion URLs for the ``images`` endpoint. +- Audio input filenames and completions for the ``audio`` endpoint. + +Prompt and message inputs and completions can also be emitted as log data. +Logs are **not** emitted by default. When logs are enabled they are sampled at ``0.1``. + +Read the **Global Configuration** section for information about enabling logs and configuring sampling +rates. + +.. important:: + + To submit logs, you must set the ``DD_API_KEY`` environment variable. + + Set ``DD_SITE`` to send logs to a Datadog site such as ``datadoghq.eu``. The default is ``datadoghq.com``. + + +(beta) Streamed Responses Support +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The OpenAI integration **estimates** prompt and completion token counts if streaming is turned on. +This is because the ``usage`` field is no longer returned in streamed completions, which is what +the integration relies on for reporting metrics. + +Streaming responses should produce a ``openai.stream`` span. This span is tagged with estimated +completion and total tokens. The integration will make a best effort attempt to tag the original +parent ``openai.request`` span with completion and total usage information, but this parent span +may be flushed before this information is available. + +The ``_est_tokens`` function implements token count estimations. It returns the average of simple +token estimation techniques that do not rely on installing a tokenizer. + + +Enabling +~~~~~~~~ + +The OpenAI integration is enabled automatically when you use +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Note that these commands also enable the ``requests`` and ``aiohttp`` +integrations which trace HTTP requests from the OpenAI library. + +Alternatively, use :func:`patch() ` to manually enable the OpenAI integration:: + + from ddtrace import config, patch + + # Note: be sure to configure the integration before calling ``patch()``! + # eg. config.openai["logs_enabled"] = True + + patch(openai=True) + + # to trace synchronous HTTP requests from the OpenAI library + # patch(openai=True, requests=True) + + # to trace asynchronous HTTP requests from the OpenAI library + # patch(openai=True, aiohttp=True) + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.openai["service"] + + The service name reported by default for OpenAI requests. + + Alternatively, you can set this option with the ``DD_SERVICE`` or ``DD_OPENAI_SERVICE`` environment + variables. + + Default: ``DD_SERVICE`` + + +.. py:data:: ddtrace.config.openai["logs_enabled"] + + Enable collection of prompts and completions as logs. You can adjust the rate of prompts and completions collected + using the sample rate configuration described below. + + Alternatively, you can set this option with the ``DD_OPENAI_LOGS_ENABLED`` environment + variable. + + Note that you must set the ``DD_API_KEY`` environment variable to enable sending logs. + + Default: ``False`` + + +.. py:data:: ddtrace.config.openai["metrics_enabled"] + + Enable collection of OpenAI metrics. + + If the Datadog Agent is configured to use a non-default Statsd hostname + or port, use ``DD_DOGSTATSD_URL`` to configure ``ddtrace`` to use it. + + Alternatively, you can set this option with the ``DD_OPENAI_METRICS_ENABLED`` environment + variable. + + Default: ``True`` + + +.. py:data:: (beta) ddtrace.config.openai["span_char_limit"] + + Configure the maximum number of characters for the following data within span tags: + + - Prompt inputs and completions + - Message inputs and completions + - Embedding inputs + + Text exceeding the maximum number of characters is truncated to the character limit + and has ``...`` appended to the end. + + Alternatively, you can set this option with the ``DD_OPENAI_SPAN_CHAR_LIMIT`` environment + variable. + + Default: ``128`` + + +.. py:data:: (beta) ddtrace.config.openai["span_prompt_completion_sample_rate"] + + Configure the sample rate for the collection of prompts and completions as span tags. + + Alternatively, you can set this option with the ``DD_OPENAI_SPAN_PROMPT_COMPLETION_SAMPLE_RATE`` environment + variable. + + Default: ``1.0`` + + +.. py:data:: (beta) ddtrace.config.openai["log_prompt_completion_sample_rate"] + + Configure the sample rate for the collection of prompts and completions as logs. + + Alternatively, you can set this option with the ``DD_OPENAI_LOG_PROMPT_COMPLETION_SAMPLE_RATE`` environment + variable. + + Default: ``0.1`` + + +Instance Configuration +~~~~~~~~~~~~~~~~~~~~~~ + +To configure the OpenAI integration on a per-instance basis use the +``Pin`` API:: + + import openai + from ddtrace import Pin, config + + Pin.override(openai, service="my-openai-service") +""" # noqa: E501 +from ...internal.utils.importlib import require_modules + + +required_modules = ["openai"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from . import patch as _patch + + patch = _patch.patch + unpatch = _patch.unpatch + get_version = _patch.get_version + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/openai/_endpoint_hooks.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/openai/_endpoint_hooks.py new file mode 100644 index 0000000..8565399 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/openai/_endpoint_hooks.py @@ -0,0 +1,908 @@ +from .utils import _compute_prompt_token_count +from .utils import _format_openai_api_key +from .utils import _is_async_generator +from .utils import _is_generator +from .utils import _tag_tool_calls + + +API_VERSION = "v1" + + +class _EndpointHook: + """ + Base class for all OpenAI endpoint hooks. + Each new endpoint hook should declare `_request_arg_params` and `_request_kwarg_params`, + which will be tagged automatically by _EndpointHook._record_request(). + For endpoint-specific request/response parameters that requires special casing, add that logic to + the endpoint hook's `_record_request()` after a super call to the base `_EndpointHook._record_request()`. + """ + + # _request_arg_params must include the names of arg parameters in order. + # If a given arg requires special casing, replace with `None` to avoid automatic tagging. + _request_arg_params = () + # _request_kwarg_params must include the names of kwarg parameters to tag automatically. + # If a given kwarg requires special casing, remove from this tuple to avoid automatic tagging. + _request_kwarg_params = () + # _response_attrs is used to automatically tag specific response attributes. + _response_attrs = () + _base_level_tag_args = ("api_base", "api_type", "api_version") + ENDPOINT_NAME = "openai" + HTTP_METHOD_TYPE = "" + OPERATION_ID = "" # Each endpoint hook must provide an operationID as specified in the OpenAI API specs: + # https://raw.githubusercontent.com/openai/openai-openapi/master/openapi.yaml + + def _record_request(self, pin, integration, span, args, kwargs): + """ + Set base-level openai tags, as well as request params from args and kwargs. + All inherited EndpointHook classes should include a super call to this method before performing + endpoint-specific request tagging logic. + """ + endpoint = self.ENDPOINT_NAME + if endpoint is None: + endpoint = "%s" % args[0].OBJECT_NAME + span.set_tag_str("openai.request.endpoint", "/%s/%s" % (API_VERSION, endpoint)) + span.set_tag_str("openai.request.method", self.HTTP_METHOD_TYPE) + + if self._request_arg_params and len(self._request_arg_params) > 1: + for idx, arg in enumerate(self._request_arg_params, 1): + if idx >= len(args): + break + if arg is None or args[idx] is None: + continue + if arg in self._base_level_tag_args: + span.set_tag_str("openai.%s" % arg, str(args[idx])) + elif arg == "organization": + span.set_tag_str("openai.organization.id", args[idx]) + elif arg == "api_key": + span.set_tag_str("openai.user.api_key", _format_openai_api_key(args[idx])) + else: + span.set_tag_str("openai.request.%s" % arg, str(args[idx])) + for kw_attr in self._request_kwarg_params: + if kw_attr not in kwargs: + continue + if isinstance(kwargs[kw_attr], dict): + for k, v in kwargs[kw_attr].items(): + span.set_tag_str("openai.request.%s.%s" % (kw_attr, k), str(v)) + elif kw_attr == "engine": # Azure OpenAI requires using "engine" instead of "model" + span.set_tag_str("openai.request.model", str(kwargs[kw_attr])) + else: + span.set_tag_str("openai.request.%s" % kw_attr, str(kwargs[kw_attr])) + + def handle_request(self, pin, integration, span, args, kwargs): + self._record_request(pin, integration, span, args, kwargs) + resp, error = yield + if hasattr(resp, "parse"): + # Users can request the raw response, in which case we need to process on the parsed response + # and return the original raw APIResponse. + self._record_response(pin, integration, span, args, kwargs, resp.parse(), error) + return resp + return self._record_response(pin, integration, span, args, kwargs, resp, error) + + def _record_response(self, pin, integration, span, args, kwargs, resp, error): + for resp_attr in self._response_attrs: + if hasattr(resp, resp_attr): + span.set_tag_str("openai.response.%s" % resp_attr, str(getattr(resp, resp_attr, ""))) + return resp + + +class _BaseCompletionHook(_EndpointHook): + """ + Share streamed response handling logic between Completion and ChatCompletion endpoints. + """ + + _request_arg_params = ("api_key", "api_base", "api_type", "request_id", "api_version", "organization") + + def _handle_streamed_response(self, integration, span, args, kwargs, resp): + """Handle streamed response objects returned from endpoint calls. + + This method helps with streamed responses by wrapping the generator returned with a + generator that traces the reading of the response. + """ + + def shared_gen(): + try: + num_prompt_tokens = span.get_metric("openai.response.usage.prompt_tokens") or 0 + num_completion_tokens = yield + span.set_metric("openai.response.usage.completion_tokens", num_completion_tokens) + total_tokens = num_prompt_tokens + num_completion_tokens + span.set_metric("openai.response.usage.total_tokens", total_tokens) + if span.get_metric("openai.request.prompt_tokens_estimated") == 0: + integration.metric(span, "dist", "tokens.prompt", num_prompt_tokens) + else: + integration.metric(span, "dist", "tokens.prompt", num_prompt_tokens, tags=["openai.estimated:true"]) + integration.metric( + span, "dist", "tokens.completion", num_completion_tokens, tags=["openai.estimated:true"] + ) + integration.metric(span, "dist", "tokens.total", total_tokens, tags=["openai.estimated:true"]) + finally: + span.finish() + integration.metric(span, "dist", "request.duration", span.duration_ns) + + num_prompt_tokens = 0 + estimated = False + prompt = kwargs.get("prompt", None) + messages = kwargs.get("messages", None) + if prompt is not None: + if isinstance(prompt, str) or isinstance(prompt, list) and isinstance(prompt[0], int): + prompt = [prompt] + for p in prompt: + estimated, prompt_tokens = _compute_prompt_token_count(p, kwargs.get("model")) + num_prompt_tokens += prompt_tokens + if messages is not None: + for m in messages: + estimated, prompt_tokens = _compute_prompt_token_count(m.get("content", ""), kwargs.get("model")) + num_prompt_tokens += prompt_tokens + span.set_metric("openai.request.prompt_tokens_estimated", int(estimated)) + span.set_metric("openai.response.usage.prompt_tokens", num_prompt_tokens) + + # A chunk corresponds to a token: + # https://community.openai.com/t/how-to-get-total-tokens-from-a-stream-of-completioncreaterequests/110700 + # https://community.openai.com/t/openai-api-get-usage-tokens-in-response-when-set-stream-true/141866 + if _is_async_generator(resp): + + async def traced_streamed_response(): + g = shared_gen() + g.send(None) + num_completion_tokens = 0 + try: + async for chunk in resp: + num_completion_tokens += 1 + yield chunk + finally: + try: + g.send(num_completion_tokens) + except StopIteration: + pass + + return traced_streamed_response() + + elif _is_generator(resp): + + def traced_streamed_response(): + g = shared_gen() + g.send(None) + num_completion_tokens = 0 + try: + for chunk in resp: + num_completion_tokens += 1 + yield chunk + finally: + try: + g.send(num_completion_tokens) + except StopIteration: + pass + + return traced_streamed_response() + + return resp + + +class _CompletionHook(_BaseCompletionHook): + _request_kwarg_params = ( + "model", + "engine", + "suffix", + "max_tokens", + "temperature", + "top_p", + "n", + "stream", + "logprobs", + "echo", + "stop", + "presence_penalty", + "frequency_penalty", + "best_of", + "logit_bias", + "user", + ) + _response_attrs = ("created", "id", "model") + ENDPOINT_NAME = "completions" + HTTP_METHOD_TYPE = "POST" + OPERATION_ID = "createCompletion" + + def _record_request(self, pin, integration, span, args, kwargs): + super()._record_request(pin, integration, span, args, kwargs) + if integration.is_pc_sampled_span(span): + prompt = kwargs.get("prompt", "") + if isinstance(prompt, str): + prompt = [prompt] + for idx, p in enumerate(prompt): + span.set_tag_str("openai.request.prompt.%d" % idx, integration.trunc(str(p))) + + def _record_response(self, pin, integration, span, args, kwargs, resp, error): + resp = super()._record_response(pin, integration, span, args, kwargs, resp, error) + if kwargs.get("stream") and error is None: + return self._handle_streamed_response(integration, span, args, kwargs, resp) + if integration.is_pc_sampled_log(span): + attrs_dict = {"prompt": kwargs.get("prompt", "")} + if error is None: + log_choices = resp.choices + if hasattr(resp.choices[0], "model_dump"): + log_choices = [choice.model_dump() for choice in resp.choices] + attrs_dict.update({"choices": log_choices}) + integration.log( + span, "info" if error is None else "error", "sampled %s" % self.OPERATION_ID, attrs=attrs_dict + ) + if integration.is_pc_sampled_llmobs(span): + integration.generate_completion_llm_records(resp, error, span, kwargs) + if not resp: + return + for choice in resp.choices: + span.set_tag_str("openai.response.choices.%d.finish_reason" % choice.index, str(choice.finish_reason)) + if integration.is_pc_sampled_span(span): + span.set_tag_str("openai.response.choices.%d.text" % choice.index, integration.trunc(choice.text)) + integration.record_usage(span, resp.usage) + return resp + + +class _ChatCompletionHook(_BaseCompletionHook): + _request_kwarg_params = ( + "model", + "engine", + "temperature", + "top_p", + "n", + "stream", + "stop", + "max_tokens", + "presence_penalty", + "frequency_penalty", + "logit_bias", + "user", + ) + _response_attrs = ("created", "id", "model") + ENDPOINT_NAME = "chat/completions" + HTTP_METHOD_TYPE = "POST" + OPERATION_ID = "createChatCompletion" + + def _record_request(self, pin, integration, span, args, kwargs): + super()._record_request(pin, integration, span, args, kwargs) + for idx, m in enumerate(kwargs.get("messages", [])): + if integration.is_pc_sampled_span(span): + span.set_tag_str( + "openai.request.messages.%d.content" % idx, integration.trunc(str(m.get("content", ""))) + ) + span.set_tag_str("openai.request.messages.%d.role" % idx, m.get("role", "")) + span.set_tag_str("openai.request.messages.%d.name" % idx, m.get("name", "")) + + def _record_response(self, pin, integration, span, args, kwargs, resp, error): + resp = super()._record_response(pin, integration, span, args, kwargs, resp, error) + if kwargs.get("stream") and error is None: + return self._handle_streamed_response(integration, span, args, kwargs, resp) + if integration.is_pc_sampled_log(span): + log_choices = resp.choices + if hasattr(resp.choices[0], "model_dump"): + log_choices = [choice.model_dump() for choice in resp.choices] + attrs_dict = {"messages": kwargs.get("messages", []), "completion": log_choices} + integration.log( + span, "info" if error is None else "error", "sampled %s" % self.OPERATION_ID, attrs=attrs_dict + ) + if integration.is_pc_sampled_llmobs(span): + integration.generate_chat_llm_records(resp, error, span, kwargs) + if not resp: + return + for choice in resp.choices: + idx = choice.index + finish_reason = getattr(choice, "finish_reason", None) + message = choice.message + span.set_tag_str("openai.response.choices.%d.finish_reason" % idx, str(finish_reason)) + span.set_tag_str("openai.response.choices.%d.message.role" % idx, choice.message.role) + if integration.is_pc_sampled_span(span): + span.set_tag_str( + "openai.response.choices.%d.message.content" % idx, integration.trunc(message.content or "") + ) + if getattr(message, "function_call", None): + _tag_tool_calls(integration, span, [message.function_call], idx) + if getattr(message, "tool_calls", None): + _tag_tool_calls(integration, span, message.tool_calls, idx) + integration.record_usage(span, resp.usage) + return resp + + +class _EmbeddingHook(_EndpointHook): + _request_arg_params = ("api_key", "api_base", "api_type", "request_id", "api_version", "organization") + _request_kwarg_params = ("model", "engine", "user") + _response_attrs = ("model",) + ENDPOINT_NAME = "embeddings" + HTTP_METHOD_TYPE = "POST" + OPERATION_ID = "createEmbedding" + + def _record_request(self, pin, integration, span, args, kwargs): + """ + Embedding endpoint allows multiple inputs, each of which we specify a request tag for, so have to + manually set them in _pre_response(). + """ + super()._record_request(pin, integration, span, args, kwargs) + embedding_input = kwargs.get("input", "") + if integration.is_pc_sampled_span(span): + if isinstance(embedding_input, str) or isinstance(embedding_input[0], int): + embedding_input = [embedding_input] + for idx, inp in enumerate(embedding_input): + span.set_tag_str("openai.request.input.%d" % idx, integration.trunc(str(inp))) + + def _record_response(self, pin, integration, span, args, kwargs, resp, error): + resp = super()._record_response(pin, integration, span, args, kwargs, resp, error) + if not resp: + return + span.set_metric("openai.response.embeddings_count", len(resp.data)) + span.set_metric("openai.response.embedding-length", len(resp.data[0].embedding)) + integration.record_usage(span, resp.usage) + return resp + + +class _ListHook(_EndpointHook): + """ + Hook for openai.ListableAPIResource, which is used by Model.list, File.list, and FineTune.list. + """ + + _request_arg_params = ("api_key", "request_id", "api_version", "organization", "api_base", "api_type") + _request_kwarg_params = ("user",) + ENDPOINT_NAME = None + HTTP_METHOD_TYPE = "GET" + OPERATION_ID = "list" + + def _record_request(self, pin, integration, span, args, kwargs): + super()._record_request(pin, integration, span, args, kwargs) + endpoint = span.get_tag("openai.request.endpoint") + if endpoint.endswith("/models"): + span.resource = "listModels" + elif endpoint.endswith("/files"): + span.resource = "listFiles" + elif endpoint.endswith("/fine-tunes"): + span.resource = "listFineTunes" + + def _record_response(self, pin, integration, span, args, kwargs, resp, error): + resp = super()._record_response(pin, integration, span, args, kwargs, resp, error) + if not resp: + return + span.set_metric("openai.response.count", len(resp.data or [])) + return resp + + +class _ModelListHook(_ListHook): + """ + Hook for openai.resources.models.Models.list (v1) + """ + + ENDPOINT_NAME = "models" + OPERATION_ID = "listModels" + + +class _FileListHook(_ListHook): + """ + Hook for openai.resources.files.Files.list (v1) + """ + + ENDPOINT_NAME = "files" + OPERATION_ID = "listFiles" + + +class _FineTuneListHook(_ListHook): + """ + Hook for openai.resources.fine_tunes.FineTunes.list (v1) + """ + + ENDPOINT_NAME = "fine-tunes" + OPERATION_ID = "listFineTunes" + + +class _RetrieveHook(_EndpointHook): + """Hook for openai.APIResource, which is used by Model.retrieve, File.retrieve, and FineTune.retrieve.""" + + _request_arg_params = (None, "api_key", "request_id", "request_timeout") + _request_kwarg_params = ("user",) + _response_attrs = ( + "id", + "owned_by", + "model", + "parent", + "root", + "bytes", + "created", + "created_at", + "purpose", + "filename", + "fine_tuned_model", + "status", + "status_details", + "updated_at", + ) + ENDPOINT_NAME = None + HTTP_METHOD_TYPE = "GET" + OPERATION_ID = "retrieve" + + def _record_request(self, pin, integration, span, args, kwargs): + super()._record_request(pin, integration, span, args, kwargs) + endpoint = span.get_tag("openai.request.endpoint") + if endpoint.endswith("/models"): + span.resource = "retrieveModel" + span.set_tag_str("openai.request.model", args[1] if len(args) >= 2 else kwargs.get("model", "")) + elif endpoint.endswith("/files"): + span.resource = "retrieveFile" + span.set_tag_str("openai.request.file_id", args[1] if len(args) >= 2 else kwargs.get("file_id", "")) + elif endpoint.endswith("/fine-tunes"): + span.resource = "retrieveFineTune" + span.set_tag_str( + "openai.request.fine_tune_id", args[1] if len(args) >= 2 else kwargs.get("fine_tune_id", "") + ) + span.set_tag_str("openai.request.endpoint", "%s/*" % endpoint) + + def _record_response(self, pin, integration, span, args, kwargs, resp, error): + resp = super()._record_response(pin, integration, span, args, kwargs, resp, error) + if not resp: + return + if hasattr(resp, "hyperparams"): + for hyperparam in ("batch_size", "learning_rate_multiplier", "n_epochs", "prompt_loss_weight"): + val = getattr(resp.hyperparams, hyperparam, "") + span.set_tag_str("openai.response.hyperparams.%s" % hyperparam, str(val)) + for resp_attr in ("result_files", "training_files", "validation_files"): + if hasattr(resp, resp_attr): + span.set_metric("openai.response.%s_count" % resp_attr, len(getattr(resp, resp_attr, []))) + if hasattr(resp, "events"): + span.set_metric("openai.response.events_count", len(resp.events)) + return resp + + +class _ModelRetrieveHook(_RetrieveHook): + """ + Hook for openai.resources.models.Models.retrieve + """ + + ENDPOINT_NAME = "models" + OPERATION_ID = "retrieveModel" + + def _record_request(self, pin, integration, span, args, kwargs): + super()._record_request(pin, integration, span, args, kwargs) + span.set_tag_str("openai.request.model", args[1] if len(args) >= 2 else kwargs.get("model", "")) + + +class _FileRetrieveHook(_RetrieveHook): + """ + Hook for openai.resources.files.Files.retrieve + """ + + ENDPOINT_NAME = "files" + OPERATION_ID = "retrieveFile" + + def _record_request(self, pin, integration, span, args, kwargs): + super()._record_request(pin, integration, span, args, kwargs) + span.set_tag_str("openai.request.file_id", args[1] if len(args) >= 2 else kwargs.get("file_id", "")) + + +class _FineTuneRetrieveHook(_RetrieveHook): + """ + Hook for openai.resources.fine_tunes.FineTunes.retrieve + """ + + ENDPOINT_NAME = "fine-tunes" + OPERATION_ID = "retrieveFineTune" + + def _record_request(self, pin, integration, span, args, kwargs): + super()._record_request(pin, integration, span, args, kwargs) + span.set_tag_str("openai.request.fine_tune_id", args[1] if len(args) >= 2 else kwargs.get("fine_tune_id", "")) + + +class _DeleteHook(_EndpointHook): + """Hook for openai.DeletableAPIResource, which is used by File.delete, and Model.delete.""" + + _request_arg_params = (None, "api_type", "api_version") + _request_kwarg_params = ("user",) + ENDPOINT_NAME = None + HTTP_METHOD_TYPE = "DELETE" + OPERATION_ID = "delete" + + def _record_request(self, pin, integration, span, args, kwargs): + super()._record_request(pin, integration, span, args, kwargs) + endpoint = span.get_tag("openai.request.endpoint") + if endpoint.endswith("/models"): + span.resource = "deleteModel" + span.set_tag_str("openai.request.model", args[1] if len(args) >= 2 else kwargs.get("model", "")) + elif endpoint.endswith("/files"): + span.resource = "deleteFile" + span.set_tag_str("openai.request.file_id", args[1] if len(args) >= 2 else kwargs.get("file_id", "")) + span.set_tag_str("openai.request.endpoint", "%s/*" % endpoint) + + def _record_response(self, pin, integration, span, args, kwargs, resp, error): + resp = super()._record_response(pin, integration, span, args, kwargs, resp, error) + if not resp: + return + if hasattr(resp, "data"): + if resp._headers.get("openai-organization"): + span.set_tag_str("openai.organization.name", resp._headers.get("openai-organization")) + span.set_tag_str("openai.response.id", resp.data.get("id", "")) + span.set_tag_str("openai.response.deleted", str(resp.data.get("deleted", ""))) + else: + span.set_tag_str("openai.response.id", str(resp.id)) + span.set_tag_str("openai.response.deleted", str(resp.deleted)) + return resp + + +class _FileDeleteHook(_DeleteHook): + """ + Hook for openai.resources.files.Files.delete + """ + + ENDPOINT_NAME = "files" + + +class _ModelDeleteHook(_DeleteHook): + """ + Hook for openai.resources.models.Models.delete + """ + + ENDPOINT_NAME = "models" + + +class _EditHook(_EndpointHook): + _request_arg_params = ("api_key", "api_base", "api_type", "request_id", "api_version", "organization") + _request_kwarg_params = ("model", "n", "temperature", "top_p", "user") + _response_attrs = ("created",) + ENDPOINT_NAME = "edits" + HTTP_METHOD_TYPE = "POST" + OPERATION_ID = "createEdit" + + def _record_request(self, pin, integration, span, args, kwargs): + super()._record_request(pin, integration, span, args, kwargs) + if integration.is_pc_sampled_span(span): + instruction = kwargs.get("instruction") + input_text = kwargs.get("input", "") + span.set_tag_str("openai.request.instruction", integration.trunc(instruction)) + span.set_tag_str("openai.request.input", integration.trunc(input_text)) + + def _record_response(self, pin, integration, span, args, kwargs, resp, error): + resp = super()._record_response(pin, integration, span, args, kwargs, resp, error) + if integration.is_pc_sampled_log(span): + log_choices = resp.choices + if hasattr(resp.choices[0], "model_dump"): + log_choices = [choice.model_dump() for choice in resp.choices] + integration.log( + span, + "info" if error is None else "error", + "sampled %s" % self.OPERATION_ID, + attrs={ + "instruction": kwargs.get("instruction"), + "input": kwargs.get("input", ""), + "choices": log_choices, + }, + ) + if not resp: + return + choices = resp.choices + if integration.is_pc_sampled_span(span): + for choice in choices: + idx = choice.index + span.set_tag_str("openai.response.choices.%d.text" % idx, integration.trunc(str(choice.text))) + integration.record_usage(span, resp.usage) + return resp + + +class _ImageHook(_EndpointHook): + _response_attrs = ("created",) + ENDPOINT_NAME = "images" + HTTP_METHOD_TYPE = "POST" + + def _record_request(self, pin, integration, span, args, kwargs): + super()._record_request(pin, integration, span, args, kwargs) + span.set_tag_str("openai.request.model", "dall-e") + + def _record_response(self, pin, integration, span, args, kwargs, resp, error): + resp = super()._record_response(pin, integration, span, args, kwargs, resp, error) + if integration.is_pc_sampled_log(span): + attrs_dict = {} + if kwargs.get("response_format", "") == "b64_json": + attrs_dict.update({"choices": [{"b64_json": "returned"} for _ in resp.data]}) + else: + log_choices = resp.data + if hasattr(resp.data[0], "model_dump"): + log_choices = [choice.model_dump() for choice in resp.data] + attrs_dict.update({"choices": log_choices}) + if "prompt" in self._request_kwarg_params: + attrs_dict.update({"prompt": kwargs.get("prompt", "")}) + if "image" in self._request_kwarg_params: + image = args[1] if len(args) >= 2 else kwargs.get("image", "") + attrs_dict.update({"image": image.name.split("/")[-1]}) + if "mask" in self._request_kwarg_params: + mask = args[2] if len(args) >= 3 else kwargs.get("mask", "") + attrs_dict.update({"mask": mask.name.split("/")[-1]}) + integration.log( + span, "info" if error is None else "error", "sampled %s" % self.OPERATION_ID, attrs=attrs_dict + ) + if not resp: + return + choices = resp.data + span.set_metric("openai.response.images_count", len(choices)) + if integration.is_pc_sampled_span(span): + for idx, choice in enumerate(choices): + if getattr(choice, "b64_json", None) is not None: + span.set_tag_str("openai.response.images.%d.b64_json" % idx, "returned") + else: + span.set_tag_str("openai.response.images.%d.url" % idx, integration.trunc(choice.url)) + return resp + + +class _ImageCreateHook(_ImageHook): + _request_arg_params = ("api_key", "api_base", "api_type", "api_version", "organization") + _request_kwarg_params = ("prompt", "n", "size", "response_format", "user") + ENDPOINT_NAME = "images/generations" + OPERATION_ID = "createImage" + + +class _ImageEditHook(_ImageHook): + _request_arg_params = (None, None, "api_key", "api_base", "api_type", "api_version", "organization") + _request_kwarg_params = ("prompt", "n", "size", "response_format", "user", "image", "mask") + ENDPOINT_NAME = "images/edits" + OPERATION_ID = "createImageEdit" + + def _record_request(self, pin, integration, span, args, kwargs): + super()._record_request(pin, integration, span, args, kwargs) + if not integration.is_pc_sampled_span: + return + image = args[1] if len(args) >= 2 else kwargs.get("image", "") + mask = args[2] if len(args) >= 3 else kwargs.get("mask", "") + if image: + if hasattr(image, "name"): + span.set_tag_str("openai.request.image", integration.trunc(image.name.split("/")[-1])) + else: + span.set_tag_str("openai.request.image", "") + if mask: + if hasattr(mask, "name"): + span.set_tag_str("openai.request.mask", integration.trunc(mask.name.split("/")[-1])) + else: + span.set_tag_str("openai.request.mask", "") + + +class _ImageVariationHook(_ImageHook): + _request_arg_params = (None, "api_key", "api_base", "api_type", "api_version", "organization") + _request_kwarg_params = ("n", "size", "response_format", "user", "image") + ENDPOINT_NAME = "images/variations" + OPERATION_ID = "createImageVariation" + + def _record_request(self, pin, integration, span, args, kwargs): + super()._record_request(pin, integration, span, args, kwargs) + if not integration.is_pc_sampled_span: + return + image = args[1] if len(args) >= 2 else kwargs.get("image", "") + if image: + if hasattr(image, "name"): + span.set_tag_str("openai.request.image", integration.trunc(image.name.split("/")[-1])) + else: + span.set_tag_str("openai.request.image", "") + + +class _BaseAudioHook(_EndpointHook): + _request_arg_params = ("model", None, "api_key", "api_base", "api_type", "api_version", "organization") + _response_attrs = ("language", "duration") + ENDPOINT_NAME = "audio" + HTTP_METHOD_TYPE = "POST" + + def _record_request(self, pin, integration, span, args, kwargs): + super()._record_request(pin, integration, span, args, kwargs) + if not integration.is_pc_sampled_span: + return + audio_file = args[2] if len(args) >= 3 else kwargs.get("file", "") + if audio_file and hasattr(audio_file, "name"): + span.set_tag_str("openai.request.filename", integration.trunc(audio_file.name.split("/")[-1])) + else: + span.set_tag_str("openai.request.filename", "") + + def _record_response(self, pin, integration, span, args, kwargs, resp, error): + resp = super()._record_response(pin, integration, span, args, kwargs, resp, error) + text = "" + if resp: + resp_to_tag = resp.model_dump() if hasattr(resp, "model_dump") else resp + if isinstance(resp_to_tag, str): + text = resp + elif isinstance(resp_to_tag, dict): + text = resp_to_tag.get("text", "") + if "segments" in resp_to_tag: + span.set_metric("openai.response.segments_count", len(resp_to_tag.get("segments"))) + if integration.is_pc_sampled_span(span): + span.set_tag_str("openai.response.text", integration.trunc(text)) + if integration.is_pc_sampled_log(span): + file_input = args[2] if len(args) >= 3 else kwargs.get("file", "") + integration.log( + span, + "info" if error is None else "error", + "sampled %s" % self.OPERATION_ID, + attrs={ + "file": getattr(file_input, "name", "").split("/")[-1], + "prompt": kwargs.get("prompt", ""), + "language": kwargs.get("language", ""), + "text": text, + }, + ) + return resp + + +class _AudioTranscriptionHook(_BaseAudioHook): + _request_kwarg_params = ( + "prompt", + "response_format", + "temperature", + "language", + "user", + ) + ENDPOINT_NAME = "audio/transcriptions" + OPERATION_ID = "createTranscription" + + +class _AudioTranslationHook(_BaseAudioHook): + _request_kwarg_params = ( + "prompt", + "response_format", + "temperature", + "user", + ) + ENDPOINT_NAME = "audio/translations" + OPERATION_ID = "createTranslation" + + +class _ModerationHook(_EndpointHook): + _request_arg_params = ("input", "model", "api_key") + _request_kwarg_params = ("input", "model") + _response_attrs = ("id", "model") + _response_categories = ( + "hate", + "hate/threatening", + "harassment", + "harassment/threatening", + "self-harm", + "self-harm/intent", + "self-harm/instructions", + "sexual", + "sexual/minors", + "violence", + "violence/graphic", + ) + ENDPOINT_NAME = "moderations" + HTTP_METHOD_TYPE = "POST" + OPERATION_ID = "createModeration" + + def _record_request(self, pin, integration, span, args, kwargs): + super()._record_request(pin, integration, span, args, kwargs) + + def _record_response(self, pin, integration, span, args, kwargs, resp, error): + resp = super()._record_response(pin, integration, span, args, kwargs, resp, error) + if not resp: + return + results = resp.results[0] + categories = results.categories + scores = results.category_scores + for category in self._response_categories: + span.set_metric("openai.response.category_scores.%s" % category, getattr(scores, category, 0)) + span.set_metric("openai.response.categories.%s" % category, int(getattr(categories, category))) + span.set_metric("openai.response.flagged", int(results.flagged)) + return resp + + +class _BaseFileHook(_EndpointHook): + ENDPOINT_NAME = "files" + + +class _FileCreateHook(_BaseFileHook): + _request_arg_params = ( + None, + "purpose", + "model", + "api_key", + "api_base", + "api_type", + "api_version", + "organization", + "user_provided_filename", + ) + _request_kwarg_params = ("purpose",) + _response_attrs = ("id", "bytes", "created_at", "filename", "purpose", "status", "status_details") + HTTP_METHOD_TYPE = "POST" + OPERATION_ID = "createFile" + + def _record_request(self, pin, integration, span, args, kwargs): + super()._record_request(pin, integration, span, args, kwargs) + fp = args[1] if len(args) >= 2 else kwargs.get("file", "") + if fp and hasattr(fp, "name"): + span.set_tag_str("openai.request.filename", fp.name.split("/")[-1]) + else: + span.set_tag_str("openai.request.filename", "") + + def _record_response(self, pin, integration, span, args, kwargs, resp, error): + resp = super()._record_response(pin, integration, span, args, kwargs, resp, error) + return resp + + +class _FileDownloadHook(_BaseFileHook): + _request_arg_params = (None, "api_key", "api_base", "api_type", "api_version", "organization") + HTTP_METHOD_TYPE = "GET" + OPERATION_ID = "downloadFile" + ENDPOINT_NAME = "files/*/content" + + def _record_request(self, pin, integration, span, args, kwargs): + super()._record_request(pin, integration, span, args, kwargs) + span.set_tag_str("openai.request.file_id", args[1] if len(args) >= 2 else kwargs.get("file_id", "")) + + def _record_response(self, pin, integration, span, args, kwargs, resp, error): + resp = super()._record_response(pin, integration, span, args, kwargs, resp, error) + if not resp: + return + if isinstance(resp, bytes) or isinstance(resp, str): + span.set_metric("openai.response.total_bytes", len(resp)) + else: + span.set_metric("openai.response.total_bytes", getattr(resp, "total_bytes", 0)) + return resp + + +class _BaseFineTuneHook(_EndpointHook): + _response_attrs = ("id", "model", "fine_tuned_model", "status", "created_at", "updated_at") + + def _record_response(self, pin, integration, span, args, kwargs, resp, error): + resp = super()._record_response(pin, integration, span, args, kwargs, resp, error) + if not resp: + return + span.set_metric("openai.response.events_count", len(resp.events)) + span.set_metric("openai.response.result_files_count", len(resp.result_files)) + span.set_metric("openai.response.training_files_count", len(resp.training_files)) + span.set_metric("openai.response.validation_files_count", len(resp.validation_files)) + hyperparams = resp.hyperparams + for hyperparam in ("batch_size", "learning_rate_multiplier", "n_epochs", "prompt_loss_weight"): + span.set_tag_str("openai.response.hyperparams.%s" % hyperparam, str(getattr(hyperparams, hyperparam, ""))) + + return resp + + +class _FineTuneCreateHook(_BaseFineTuneHook): + _request_arg_params = ("api_key", "api_base", "api_type", "request_id", "api_version", "organization") + _request_kwarg_params = ( + "training_file", + "validation_file", + "model", + "n_epochs", + "batch_size", + "learning_rate_multiplier", + "prompt_loss_weight", + "compute_classification_metrics", + "classification_n_classes", + "classification_positive_class", + "suffix", + ) + ENDPOINT_NAME = "fine-tunes" + HTTP_METHOD_TYPE = "POST" + OPERATION_ID = "createFineTune" + + def _record_request(self, pin, integration, span, args, kwargs): + super()._record_request(pin, integration, span, args, kwargs) + if "classification_betas" in kwargs: + classification_betas = kwargs.get("classification_betas", []) + if classification_betas: + span.set_metric("openai.request.classification_betas_count", len(classification_betas)) + else: + span.set_metric("openai.request.classification_betas_count", 0) + + +class _FineTuneCancelHook(_BaseFineTuneHook): + _request_arg_params = (None, "api_key", "api_type", "request_id", "api_version") + _request_kwarg_params = ("user",) + ENDPOINT_NAME = "fine-tunes/*/cancel" + HTTP_METHOD_TYPE = "POST" + OPERATION_ID = "cancelFineTune" + + def _record_request(self, pin, integration, span, args, kwargs): + super()._record_request(pin, integration, span, args, kwargs) + span.set_tag_str("openai.request.fine_tune_id", args[1] if len(args) >= 2 else kwargs.get("fine_tune_id", "")) + + +class _FineTuneListEventsHook(_EndpointHook): + _request_kwarg_params = ("stream", "user") + ENDPOINT_NAME = "fine-tunes/*/events" + HTTP_METHOD_TYPE = "GET" + OPERATION_ID = "listFineTuneEvents" + + def _record_request(self, pin, integration, span, args, kwargs): + super()._record_request(pin, integration, span, args, kwargs) + span.set_tag_str("openai.request.fine_tune_id", args[1] if len(args) >= 2 else kwargs.get("fine_tune_id", "")) + + def _record_response(self, pin, integration, span, args, kwargs, resp, error): + resp = super()._record_response(pin, integration, span, args, kwargs, resp, error) + if not resp: + return + span.set_metric("openai.response.count", len(resp.data)) + return resp diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/openai/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/openai/patch.py new file mode 100644 index 0000000..cc25324 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/openai/patch.py @@ -0,0 +1,387 @@ +import os +import sys + +from openai import version + +from ddtrace import config +from ddtrace.internal.llmobs.integrations import OpenAIIntegration +from ddtrace.internal.logger import get_logger +from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.utils.formats import asbool +from ddtrace.internal.utils.formats import deep_getattr +from ddtrace.internal.utils.version import parse_version +from ddtrace.internal.wrapping import wrap + +from ...pin import Pin +from . import _endpoint_hooks +from .utils import _format_openai_api_key + + +log = get_logger(__name__) + + +config._add( + "openai", + { + "logs_enabled": asbool(os.getenv("DD_OPENAI_LOGS_ENABLED", False)), + "llmobs_enabled": asbool(os.getenv("DD_OPENAI_LLMOBS_ENABLED", False)), + "metrics_enabled": asbool(os.getenv("DD_OPENAI_METRICS_ENABLED", True)), + "span_prompt_completion_sample_rate": float(os.getenv("DD_OPENAI_SPAN_PROMPT_COMPLETION_SAMPLE_RATE", 1.0)), + "llmobs_prompt_completion_sample_rate": float(os.getenv("DD_OPENAI_LLMOBS_PROMPT_COMPLETION_SAMPLE_RATE", 1.0)), + "log_prompt_completion_sample_rate": float(os.getenv("DD_OPENAI_LOG_PROMPT_COMPLETION_SAMPLE_RATE", 0.1)), + "span_char_limit": int(os.getenv("DD_OPENAI_SPAN_CHAR_LIMIT", 128)), + }, +) + + +def get_version(): + # type: () -> str + return version.VERSION + + +OPENAI_VERSION = parse_version(get_version()) + + +if OPENAI_VERSION >= (1, 0, 0): + _RESOURCES = { + "models.Models": { + "list": _endpoint_hooks._ModelListHook, + "retrieve": _endpoint_hooks._ModelRetrieveHook, + "delete": _endpoint_hooks._ModelDeleteHook, + }, + "completions.Completions": { + "create": _endpoint_hooks._CompletionHook, + }, + "chat.Completions": { + "create": _endpoint_hooks._ChatCompletionHook, + }, + "edits.Edits": { + "create": _endpoint_hooks._EditHook, + }, + "images.Images": { + "generate": _endpoint_hooks._ImageCreateHook, + "edit": _endpoint_hooks._ImageEditHook, + "create_variation": _endpoint_hooks._ImageVariationHook, + }, + "audio.Transcriptions": { + "create": _endpoint_hooks._AudioTranscriptionHook, + }, + "audio.Translations": { + "create": _endpoint_hooks._AudioTranslationHook, + }, + "embeddings.Embeddings": { + "create": _endpoint_hooks._EmbeddingHook, + }, + "moderations.Moderations": { + "create": _endpoint_hooks._ModerationHook, + }, + "files.Files": { + "create": _endpoint_hooks._FileCreateHook, + "retrieve": _endpoint_hooks._FileRetrieveHook, + "list": _endpoint_hooks._FileListHook, + "delete": _endpoint_hooks._FileDeleteHook, + "retrieve_content": _endpoint_hooks._FileDownloadHook, + }, + "fine_tunes.FineTunes": { + "create": _endpoint_hooks._FineTuneCreateHook, + "retrieve": _endpoint_hooks._FineTuneRetrieveHook, + "list": _endpoint_hooks._FineTuneListHook, + "cancel": _endpoint_hooks._FineTuneCancelHook, + "list_events": _endpoint_hooks._FineTuneListEventsHook, + }, + } +else: + _RESOURCES = { + "model.Model": { + "list": _endpoint_hooks._ListHook, + "retrieve": _endpoint_hooks._RetrieveHook, + }, + "completion.Completion": { + "create": _endpoint_hooks._CompletionHook, + }, + "chat_completion.ChatCompletion": { + "create": _endpoint_hooks._ChatCompletionHook, + }, + "edit.Edit": { + "create": _endpoint_hooks._EditHook, + }, + "image.Image": { + "create": _endpoint_hooks._ImageCreateHook, + "create_edit": _endpoint_hooks._ImageEditHook, + "create_variation": _endpoint_hooks._ImageVariationHook, + }, + "audio.Audio": { + "transcribe": _endpoint_hooks._AudioTranscriptionHook, + "translate": _endpoint_hooks._AudioTranslationHook, + }, + "embedding.Embedding": { + "create": _endpoint_hooks._EmbeddingHook, + }, + "moderation.Moderation": { + "create": _endpoint_hooks._ModerationHook, + }, + "file.File": { + # File.list() and File.retrieve() share the same underlying method as Model.list() and Model.retrieve() + # which means they are already wrapped + "create": _endpoint_hooks._FileCreateHook, + "delete": _endpoint_hooks._DeleteHook, + "download": _endpoint_hooks._FileDownloadHook, + }, + "fine_tune.FineTune": { + # FineTune.list()/retrieve() share the same underlying method as Model.list() and Model.retrieve() + # FineTune.delete() share the same underlying method as File.delete() + # which means they are already wrapped + # FineTune.list_events does not have an async version, so have to wrap it separately + "create": _endpoint_hooks._FineTuneCreateHook, + "cancel": _endpoint_hooks._FineTuneCancelHook, + }, + } + + +def _wrap_classmethod(obj, wrapper): + wrap(obj.__func__, wrapper) + + +def patch(): + # Avoid importing openai at the module level, eventually will be an import hook + import openai + + if getattr(openai, "__datadog_patch", False): + return + + Pin().onto(openai) + integration = OpenAIIntegration(integration_config=config.openai, openai=openai) + + if OPENAI_VERSION >= (1, 0, 0): + if OPENAI_VERSION >= (1, 8, 0): + wrap(openai._base_client.SyncAPIClient._process_response, _patched_convert(openai, integration)) + wrap(openai._base_client.AsyncAPIClient._process_response, _patched_convert(openai, integration)) + else: + wrap(openai._base_client.BaseClient._process_response, _patched_convert(openai, integration)) + wrap(openai.OpenAI.__init__, _patched_client_init(openai, integration)) + wrap(openai.AsyncOpenAI.__init__, _patched_client_init(openai, integration)) + wrap(openai.AzureOpenAI.__init__, _patched_client_init(openai, integration)) + wrap(openai.AsyncAzureOpenAI.__init__, _patched_client_init(openai, integration)) + + for resource, method_hook_dict in _RESOURCES.items(): + if deep_getattr(openai.resources, resource) is None: + continue + for method_name, endpoint_hook in method_hook_dict.items(): + sync_method = deep_getattr(openai.resources, "%s.%s" % (resource, method_name)) + async_method = deep_getattr( + openai.resources, "%s.%s" % (".Async".join(resource.split(".")), method_name) + ) + wrap(sync_method, _patched_endpoint(openai, integration, endpoint_hook)) + wrap(async_method, _patched_endpoint_async(openai, integration, endpoint_hook)) + else: + import openai.api_requestor + + wrap(openai.api_requestor._make_session, _patched_make_session) + wrap(openai.util.convert_to_openai_object, _patched_convert(openai, integration)) + + for resource, method_hook_dict in _RESOURCES.items(): + if deep_getattr(openai.api_resources, resource) is None: + continue + for method_name, endpoint_hook in method_hook_dict.items(): + sync_method = deep_getattr(openai.api_resources, "%s.%s" % (resource, method_name)) + async_method = deep_getattr(openai.api_resources, "%s.a%s" % (resource, method_name)) + _wrap_classmethod(sync_method, _patched_endpoint(openai, integration, endpoint_hook)) + _wrap_classmethod(async_method, _patched_endpoint_async(openai, integration, endpoint_hook)) + + # FineTune.list_events is the only traced endpoint that does not have an async version, so have to wrap it here. + _wrap_classmethod( + openai.api_resources.fine_tune.FineTune.list_events, + _patched_endpoint(openai, integration, _endpoint_hooks._FineTuneListEventsHook), + ) + + openai.__datadog_patch = True + + +def unpatch(): + # FIXME: add unpatching. The current wrapping.unwrap method requires + # the wrapper function to be provided which we don't keep a reference to. + pass + + +def _patched_client_init(openai, integration): + """ + Patch for `openai.OpenAI/AsyncOpenAI` client init methods to add the client object to the OpenAIIntegration object. + """ + + def patched_client_init(func, args, kwargs): + func(*args, **kwargs) + client = args[0] + integration._client = client + api_key = kwargs.get("api_key") + if api_key is None: + api_key = client.api_key + if api_key is not None: + integration.user_api_key = api_key + return + + return patched_client_init + + +def _patched_make_session(func, args, kwargs): + """Patch for `openai.api_requestor._make_session` which sets the service name on the + requests session so that spans from the requests integration will use the service name openai. + This is done so that the service break down will include OpenAI time spent querying the OpenAI backend. + + This should technically be a ``peer.service`` but this concept doesn't exist yet. + """ + session = func(*args, **kwargs) + service = schematize_service_name("openai") + Pin.override(session, service=service) + return session + + +def _traced_endpoint(endpoint_hook, integration, pin, args, kwargs): + span = integration.trace(pin, endpoint_hook.OPERATION_ID) + openai_api_key = _format_openai_api_key(kwargs.get("api_key")) + err = None + if openai_api_key: + # API key can either be set on the import or per request + span.set_tag_str("openai.user.api_key", openai_api_key) + try: + # Start the hook + hook = endpoint_hook().handle_request(pin, integration, span, args, kwargs) + hook.send(None) + + resp, err = yield + + # Record any error information + if err is not None: + span.set_exc_info(*sys.exc_info()) + integration.metric(span, "incr", "request.error", 1) + + # Pass the response and the error to the hook + try: + hook.send((resp, err)) + except StopIteration as e: + if err is None: + return e.value + finally: + # Streamed responses will be finished when the generator exits, so finish non-streamed spans here. + # Streamed responses with error will need to be finished manually as well. + if not kwargs.get("stream") or err is not None: + span.finish() + integration.metric(span, "dist", "request.duration", span.duration_ns) + + +def _patched_endpoint(openai, integration, patch_hook): + def patched_endpoint(func, args, kwargs): + # FIXME: this is a temporary workaround for the fact that our bytecode wrapping seems to modify + # a function keyword argument into a cell when it shouldn't. This is only an issue on + # Python 3.11+. + if sys.version_info >= (3, 11) and kwargs.get("encoding_format", None): + kwargs["encoding_format"] = kwargs["encoding_format"].cell_contents + + pin = Pin._find(openai, args[0]) + if not pin or not pin.enabled(): + return func(*args, **kwargs) + + g = _traced_endpoint(patch_hook, integration, pin, args, kwargs) + g.send(None) + resp, err = None, None + try: + resp = func(*args, **kwargs) + return resp + except Exception as e: + err = e + raise + finally: + try: + g.send((resp, err)) + except StopIteration as e: + if err is None: + # This return takes priority over `return resp` + return e.value # noqa: B012 + + return patched_endpoint + + +def _patched_endpoint_async(openai, integration, patch_hook): + # Same as _patched_endpoint but async + async def patched_endpoint(func, args, kwargs): + # FIXME: this is a temporary workaround for the fact that our bytecode wrapping seems to modify + # a function keyword argument into a cell when it shouldn't. This is only an issue on + # Python 3.11+. + if sys.version_info >= (3, 11) and kwargs.get("encoding_format", None): + kwargs["encoding_format"] = kwargs["encoding_format"].cell_contents + + pin = Pin._find(openai, args[0]) + if not pin or not pin.enabled(): + return await func(*args, **kwargs) + g = _traced_endpoint(patch_hook, integration, pin, args, kwargs) + g.send(None) + resp, err = None, None + try: + resp = await func(*args, **kwargs) + return resp + except Exception as e: + err = e + raise + finally: + try: + g.send((resp, err)) + except StopIteration as e: + if err is None: + # This return takes priority over `return resp` + return e.value # noqa: B012 + + return patched_endpoint + + +def _patched_convert(openai, integration): + def patched_convert(func, args, kwargs): + """Patch convert captures header information in the openai response""" + pin = Pin.get_from(openai) + if not pin or not pin.enabled(): + return func(*args, **kwargs) + + span = pin.tracer.current_span() + if not span: + return func(*args, **kwargs) + + if OPENAI_VERSION < (1, 0, 0): + resp = args[0] + if not isinstance(resp, openai.openai_response.OpenAIResponse): + return func(*args, **kwargs) + headers = resp._headers + else: + resp = kwargs.get("response", {}) + headers = resp.headers + # This function is called for each chunk in the stream. + # To prevent needlessly setting the same tags for each chunk, short-circuit here. + if span.get_tag("openai.organization.name") is not None: + return func(*args, **kwargs) + if headers.get("openai-organization"): + org_name = headers.get("openai-organization") + span.set_tag_str("openai.organization.name", org_name) + + # Gauge total rate limit + if headers.get("x-ratelimit-limit-requests"): + v = headers.get("x-ratelimit-limit-requests") + if v is not None: + integration.metric(span, "gauge", "ratelimit.requests", int(v)) + span.set_metric("openai.organization.ratelimit.requests.limit", int(v)) + if headers.get("x-ratelimit-limit-tokens"): + v = headers.get("x-ratelimit-limit-tokens") + if v is not None: + integration.metric(span, "gauge", "ratelimit.tokens", int(v)) + span.set_metric("openai.organization.ratelimit.tokens.limit", int(v)) + # Gauge and set span info for remaining requests and tokens + if headers.get("x-ratelimit-remaining-requests"): + v = headers.get("x-ratelimit-remaining-requests") + if v is not None: + integration.metric(span, "gauge", "ratelimit.remaining.requests", int(v)) + span.set_metric("openai.organization.ratelimit.requests.remaining", int(v)) + if headers.get("x-ratelimit-remaining-tokens"): + v = headers.get("x-ratelimit-remaining-tokens") + if v is not None: + integration.metric(span, "gauge", "ratelimit.remaining.tokens", int(v)) + span.set_metric("openai.organization.ratelimit.tokens.remaining", int(v)) + + return func(*args, **kwargs) + + return patched_convert diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/openai/utils.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/openai/utils.py new file mode 100644 index 0000000..29bf8d2 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/openai/utils.py @@ -0,0 +1,122 @@ +import re +from typing import AsyncGenerator # noqa:F401 +from typing import Generator # noqa:F401 +from typing import List # noqa:F401 +from typing import Optional # noqa:F401 +from typing import Tuple # noqa:F401 +from typing import Union # noqa:F401 + +from ddtrace.internal.logger import get_logger + + +try: + from tiktoken import encoding_for_model + + tiktoken_available = True +except ModuleNotFoundError: + tiktoken_available = False + + +log = get_logger(__name__) + +_punc_regex = re.compile(r"[\w']+|[.,!?;~@#$%^&*()+/-]") + + +def _compute_prompt_token_count(prompt, model): + # type: (Union[str, List[int]], Optional[str]) -> Tuple[bool, int] + """ + Takes in a prompt(s) and model pair, and returns a tuple of whether or not the number of prompt + tokens was estimated, and the estimated/calculated prompt token count. + """ + num_prompt_tokens = 0 + estimated = False + if model is not None and tiktoken_available is True: + try: + enc = encoding_for_model(model) + if isinstance(prompt, str): + num_prompt_tokens += len(enc.encode(prompt)) + elif isinstance(prompt, list) and isinstance(prompt[0], int): + num_prompt_tokens += len(prompt) + return estimated, num_prompt_tokens + except KeyError: + # tiktoken.encoding_for_model() will raise a KeyError if it doesn't have a tokenizer for the model + estimated = True + else: + estimated = True + + # If model is unavailable or tiktoken is not imported, then provide a very rough estimate of the number of tokens + return estimated, _est_tokens(prompt) + + +def _est_tokens(prompt): + # type: (Union[str, List[int]]) -> int + """ + Provide a very rough estimate of the number of tokens in a string prompt. + Note that if the prompt is passed in as a token array (list of ints), the token count + is just the length of the token array. + """ + # If model is unavailable or tiktoken is not imported, then provide a very rough estimate of the number of tokens + # Approximate using the following assumptions: + # * English text + # * 1 token ~= 4 chars + # * 1 token ~= ¾ words + est_tokens = 0 + if isinstance(prompt, str): + est1 = len(prompt) / 4 + est2 = len(_punc_regex.findall(prompt)) * 0.75 + return round((1.5 * est1 + 0.5 * est2) / 2) + elif isinstance(prompt, list) and isinstance(prompt[0], int): + return len(prompt) + return est_tokens + + +def _format_openai_api_key(openai_api_key): + # type: (Optional[str]) -> Optional[str] + """ + Returns `sk-...XXXX`, where XXXX is the last 4 characters of the provided OpenAI API key. + This mimics how OpenAI UI formats the API key. + """ + if not openai_api_key: + return None + return "sk-...%s" % openai_api_key[-4:] + + +def _is_generator(resp): + # type: (...) -> bool + import openai + + # In OpenAI v1, the response is type `openai.Stream` instead of Generator. + if isinstance(resp, Generator): + return True + if hasattr(openai, "Stream") and isinstance(resp, openai.Stream): + return True + return False + + +def _is_async_generator(resp): + # type: (...) -> bool + import openai + + # In OpenAI v1, the response is type `openai.AsyncStream` instead of AsyncGenerator. + if isinstance(resp, AsyncGenerator): + return True + if hasattr(openai, "AsyncStream") and isinstance(resp, openai.AsyncStream): + return True + return False + + +def _tag_tool_calls(integration, span, tool_calls, choice_idx): + # type: (...) -> None + """ + Tagging logic if function_call or tool_calls are provided in the chat response. + Note: since function calls are deprecated and will be replaced with tool calls, apply the same tagging logic/schema. + """ + for idy, tool_call in enumerate(tool_calls): + if hasattr(tool_call, "function"): + # tool_call is further nested in a "function" object + tool_call = tool_call.function + span.set_tag( + "openai.response.choices.%d.message.tool_calls.%d.arguments" % (choice_idx, idy), + integration.trunc(str(tool_call.arguments)), + ) + span.set_tag("openai.response.choices.%d.message.tool_calls.%d.name" % (choice_idx, idy), str(tool_call.name)) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/__init__.py new file mode 100644 index 0000000..6e42817 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/__init__.py @@ -0,0 +1,68 @@ +""" +The psycopg integration instruments the psycopg and psycopg2 libraries to trace Postgres queries. + + +Enabling +~~~~~~~~ + +The psycopg integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(psycopg=True) + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.psycopg["service"] + + The service name reported by default for psycopg spans. + + This option can also be set with the ``DD_PSYCOPG_SERVICE`` environment + variable. + + Default: ``"postgres"`` + +.. py:data:: ddtrace.config.psycopg["trace_fetch_methods"] + + Whether or not to trace fetch methods. + + Can also configured via the ``DD_PSYCOPG_TRACE_FETCH_METHODS`` environment variable. + + Default: ``False`` + + +.. py:data:: ddtrace.config.psycopg["trace_connect"] + + Whether or not to trace ``psycopg.connect`` method. + + Can also configured via the ``DD_PSYCOPG_TRACE_CONNECT`` environment variable. + + Default: ``False`` + + +Instance Configuration +~~~~~~~~~~~~~~~~~~~~~~ + +To configure the psycopg integration on an per-connection basis use the +``Pin`` API:: + + from ddtrace import Pin + import psycopg + + db = psycopg.connect(connection_factory=factory) + # Use a pin to override the service name. + Pin.override(db, service="postgres-users") + + cursor = db.cursor() + cursor.execute("select * from users where id = 1") +""" +from .patch import get_version +from .patch import get_versions +from .patch import patch + + +__all__ = ["patch", "get_version", "get_versions"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/async_connection.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/async_connection.py new file mode 100644 index 0000000..8ac8989 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/async_connection.py @@ -0,0 +1,66 @@ +from ddtrace import Pin +from ddtrace import config +from ddtrace.constants import SPAN_KIND +from ddtrace.constants import SPAN_MEASURED_KEY +from ddtrace.contrib import dbapi_async +from ddtrace.contrib.psycopg.async_cursor import Psycopg3FetchTracedAsyncCursor +from ddtrace.contrib.psycopg.async_cursor import Psycopg3TracedAsyncCursor +from ddtrace.contrib.psycopg.connection import patch_conn +from ddtrace.contrib.trace_utils import ext_service +from ddtrace.ext import SpanKind +from ddtrace.ext import SpanTypes +from ddtrace.ext import db +from ddtrace.internal.constants import COMPONENT + + +class Psycopg3TracedAsyncConnection(dbapi_async.TracedAsyncConnection): + def __init__(self, conn, pin=None, cursor_cls=None): + if not cursor_cls: + # Do not trace `fetch*` methods by default + cursor_cls = ( + Psycopg3FetchTracedAsyncCursor if config.psycopg.trace_fetch_methods else Psycopg3TracedAsyncCursor + ) + + super(Psycopg3TracedAsyncConnection, self).__init__(conn, pin, config.psycopg, cursor_cls=cursor_cls) + + async def execute(self, *args, **kwargs): + """Execute a query and return a cursor to read its results.""" + span_name = "{}.{}".format(self._self_datadog_name, "execute") + + async def patched_execute(*args, **kwargs): + try: + cur = self.cursor() + if kwargs.get("binary", None): + cur.format = 1 # set to 1 for binary or 0 if not + return await cur.execute(*args, **kwargs) + except Exception as ex: + raise ex.with_traceback(None) + + return await self._trace_method(patched_execute, span_name, {}, *args, **kwargs) + + +def patched_connect_async_factory(psycopg_module): + async def patched_connect_async(connect_func, _, args, kwargs): + traced_conn_cls = Psycopg3TracedAsyncConnection + + pin = Pin.get_from(psycopg_module) + + if not pin or not pin.enabled() or not pin._config.trace_connect: + conn = await connect_func(*args, **kwargs) + else: + with pin.tracer.trace( + "{}.{}".format(connect_func.__module__, connect_func.__name__), + service=ext_service(pin, pin._config), + span_type=SpanTypes.SQL, + ) as span: + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + span.set_tag_str(COMPONENT, pin._config.integration_name) + if span.get_tag(db.SYSTEM) is None: + span.set_tag_str(db.SYSTEM, pin._config.dbms_name) + + span.set_tag(SPAN_MEASURED_KEY) + conn = await connect_func(*args, **kwargs) + + return patch_conn(conn, pin=pin, traced_conn_cls=traced_conn_cls) + + return patched_connect_async diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/async_cursor.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/async_cursor.py new file mode 100644 index 0000000..d0dc21e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/async_cursor.py @@ -0,0 +1,26 @@ +from ddtrace.contrib import dbapi_async +from ddtrace.contrib.psycopg.cursor import Psycopg3TracedCursor + + +class Psycopg3TracedAsyncCursor(Psycopg3TracedCursor, dbapi_async.TracedAsyncCursor): + def __init__(self, cursor, pin, cfg, *args, **kwargs): + super(Psycopg3TracedAsyncCursor, self).__init__(cursor, pin, cfg) + + async def __aenter__(self): + # previous versions of the dbapi didn't support context managers. let's + # reference the func that would be called to ensure that errors + # messages will be the same. + await self.__wrapped__.__aenter__() + + # and finally, yield the traced cursor. + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: + # previous versions of the dbapi didn't support context managers. let's + # reference the func that would be called to ensure that errors + # messages will be the same. + return await self.__wrapped__.__aexit__(exc_type, exc_val, exc_tb) + + +class Psycopg3FetchTracedAsyncCursor(Psycopg3TracedAsyncCursor, dbapi_async.FetchTracedAsyncCursor): + """Psycopg3FetchTracedAsyncCursor for psycopg""" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/connection.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/connection.py new file mode 100644 index 0000000..acf19c7 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/connection.py @@ -0,0 +1,109 @@ +from ddtrace import Pin +from ddtrace import config +from ddtrace.constants import SPAN_KIND +from ddtrace.constants import SPAN_MEASURED_KEY +from ddtrace.contrib import dbapi +from ddtrace.contrib.psycopg.cursor import Psycopg2FetchTracedCursor +from ddtrace.contrib.psycopg.cursor import Psycopg2TracedCursor +from ddtrace.contrib.psycopg.cursor import Psycopg3FetchTracedCursor +from ddtrace.contrib.psycopg.cursor import Psycopg3TracedCursor +from ddtrace.contrib.psycopg.extensions import _patch_extensions +from ddtrace.contrib.trace_utils import ext_service +from ddtrace.ext import SpanKind +from ddtrace.ext import SpanTypes +from ddtrace.ext import db +from ddtrace.ext import net +from ddtrace.ext import sql +from ddtrace.internal.constants import COMPONENT + + +class Psycopg3TracedConnection(dbapi.TracedConnection): + def __init__(self, conn, pin=None, cursor_cls=None): + if not cursor_cls: + # Do not trace `fetch*` methods by default + cursor_cls = Psycopg3FetchTracedCursor if config.psycopg.trace_fetch_methods else Psycopg3TracedCursor + + super(Psycopg3TracedConnection, self).__init__(conn, pin, config.psycopg, cursor_cls=cursor_cls) + + def execute(self, *args, **kwargs): + """Execute a query and return a cursor to read its results.""" + + def patched_execute(*args, **kwargs): + try: + cur = self.cursor() + if kwargs.get("binary", None): + cur.format = 1 # set to 1 for binary or 0 if not + return cur.execute(*args, **kwargs) + except Exception as ex: + raise ex.with_traceback(None) + + return patched_execute(*args, **kwargs) + + +class Psycopg2TracedConnection(dbapi.TracedConnection): + """TracedConnection wraps a Connection with tracing code.""" + + def __init__(self, conn, pin=None, cursor_cls=None): + if not cursor_cls: + # Do not trace `fetch*` methods by default + cursor_cls = Psycopg2FetchTracedCursor if config.psycopg.trace_fetch_methods else Psycopg2TracedCursor + + super(Psycopg2TracedConnection, self).__init__(conn, pin, config.psycopg, cursor_cls=cursor_cls) + + +def patch_conn(conn, traced_conn_cls, pin=None): + """Wrap will patch the instance so that its queries are traced.""" + # ensure we've patched extensions (this is idempotent) in + # case we're only tracing some connections. + _config = None + if pin: + extensions_to_patch = pin._config.get("_extensions_to_patch", None) + _config = pin._config + if extensions_to_patch: + _patch_extensions(extensions_to_patch) + + c = traced_conn_cls(conn) + + # if the connection has an info attr, we are using psycopg3 + if hasattr(conn, "dsn"): + dsn = sql.parse_pg_dsn(conn.dsn) + else: + dsn = sql.parse_pg_dsn(conn.info.dsn) + + tags = { + net.TARGET_HOST: dsn.get("host"), + net.TARGET_PORT: dsn.get("port", 5432), + db.NAME: dsn.get("dbname"), + db.USER: dsn.get("user"), + "db.application": dsn.get("application_name"), + db.SYSTEM: "postgresql", + } + Pin(tags=tags, _config=_config).onto(c) + return c + + +def patched_connect_factory(psycopg_module): + def patched_connect(connect_func, _, args, kwargs): + traced_conn_cls = Psycopg3TracedConnection if psycopg_module.__name__ == "psycopg" else Psycopg2TracedConnection + + pin = Pin.get_from(psycopg_module) + + if not pin or not pin.enabled() or not pin._config.trace_connect: + conn = connect_func(*args, **kwargs) + else: + with pin.tracer.trace( + "{}.{}".format(connect_func.__module__, connect_func.__name__), + service=ext_service(pin, pin._config), + span_type=SpanTypes.SQL, + ) as span: + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + span.set_tag_str(COMPONENT, pin._config.integration_name) + if span.get_tag(db.SYSTEM) is None: + span.set_tag_str(db.SYSTEM, pin._config.dbms_name) + + span.set_tag(SPAN_MEASURED_KEY) + conn = connect_func(*args, **kwargs) + + return patch_conn(conn, pin=pin, traced_conn_cls=traced_conn_cls) + + return patched_connect diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/cursor.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/cursor.py new file mode 100644 index 0000000..6596b55 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/cursor.py @@ -0,0 +1,28 @@ +from ddtrace.contrib import dbapi + + +class Psycopg3TracedCursor(dbapi.TracedCursor): + """TracedCursor for psycopg instances""" + + def __init__(self, cursor, pin, cfg, *args, **kwargs): + super(Psycopg3TracedCursor, self).__init__(cursor, pin, cfg) + + def _trace_method(self, method, name, resource, extra_tags, dbm_propagator, *args, **kwargs): + # treat Composable resource objects as strings + if resource.__class__.__name__ == "SQL" or resource.__class__.__name__ == "Composed": + resource = resource.as_string(self.__wrapped__) + return super(Psycopg3TracedCursor, self)._trace_method( + method, name, resource, extra_tags, dbm_propagator, *args, **kwargs + ) + + +class Psycopg3FetchTracedCursor(Psycopg3TracedCursor, dbapi.FetchTracedCursor): + """Psycopg3FetchTracedCursor for psycopg""" + + +class Psycopg2TracedCursor(Psycopg3TracedCursor): + """TracedCursor for psycopg2""" + + +class Psycopg2FetchTracedCursor(Psycopg3FetchTracedCursor): + """FetchTracedCursor for psycopg2""" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/extensions.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/extensions.py new file mode 100644 index 0000000..a801114 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/extensions.py @@ -0,0 +1,180 @@ +""" +Tracing utilities for the psycopg2 potgres client library. +""" +import functools + +from ddtrace import config +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema import schematize_database_operation +from ddtrace.vendor import wrapt + +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from ...ext import db +from ...ext import net + + +def get_psycopg2_extensions(psycopg_module): + class TracedCursor(psycopg_module.extensions.cursor): + """Wrapper around cursor creating one span per query""" + + def __init__(self, *args, **kwargs): + self._datadog_tracer = kwargs.pop("datadog_tracer", None) + self._datadog_service = kwargs.pop("datadog_service", None) + self._datadog_tags = kwargs.pop("datadog_tags", None) + super(TracedCursor, self).__init__(*args, **kwargs) + + def execute(self, query, vars=None): # noqa: A002 + """just wrap the cursor execution in a span""" + if not self._datadog_tracer: + return psycopg_module.extensions.cursor.execute(self, query, vars) + + with self._datadog_tracer.trace( + schematize_database_operation("postgres.query", database_provider="postgresql"), + service=self._datadog_service, + span_type=SpanTypes.SQL, + ) as s: + s.set_tag_str(COMPONENT, config.psycopg.integration_name) + s.set_tag_str(db.SYSTEM, config.psycopg.dbms_name) + + # set span.kind to the type of operation being performed + s.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + s.set_tag(SPAN_MEASURED_KEY) + if not s.sampled: + return super(TracedCursor, self).execute(query, vars) + + s.resource = query + s.set_tags(self._datadog_tags) + try: + return super(TracedCursor, self).execute(query, vars) + finally: + s.set_metric(db.ROWCOUNT, self.rowcount) + + def callproc(self, procname, vars=None): # noqa: A002 + """just wrap the execution in a span""" + return psycopg_module.extensions.cursor.callproc(self, procname, vars) + + class TracedConnection(psycopg_module.extensions.connection): + """Wrapper around psycopg2 for tracing""" + + def __init__(self, *args, **kwargs): + self._datadog_tracer = kwargs.pop("datadog_tracer", None) + self._datadog_service = kwargs.pop("datadog_service", None) + + super(TracedConnection, self).__init__(*args, **kwargs) + + # add metadata (from the connection, string, etc) + dsn = psycopg_module.extensions.parse_dsn(self.dsn) + self._datadog_tags = { + net.TARGET_HOST: dsn.get("host"), + net.TARGET_PORT: dsn.get("port"), + db.NAME: dsn.get("dbname"), + db.USER: dsn.get("user"), + db.SYSTEM: config.psycopg.dbms_name, + "db.application": dsn.get("application_name"), + } + + self._datadog_cursor_class = functools.partial( + TracedCursor, + datadog_tracer=self._datadog_tracer, + datadog_service=self._datadog_service, + datadog_tags=self._datadog_tags, + ) + + def cursor(self, *args, **kwargs): + """register our custom cursor factory""" + kwargs.setdefault("cursor_factory", self._datadog_cursor_class) + return super(TracedConnection, self).cursor(*args, **kwargs) + + # extension hooks + _extensions = [ + ( + psycopg_module.extensions.register_type, + psycopg_module.extensions, + "register_type", + _extensions_register_type, + ), + (psycopg_module._psycopg.register_type, psycopg_module._psycopg, "register_type", _extensions_register_type), + (psycopg_module.extensions.adapt, psycopg_module.extensions, "adapt", _extensions_adapt), + ] + + # `_json` attribute is only available for psycopg >= 2.5 + if getattr(psycopg_module, "_json", None): + _extensions += [ + (psycopg_module._json.register_type, psycopg_module._json, "register_type", _extensions_register_type), + ] + + # `quote_ident` attribute is only available for psycopg >= 2.7 + if getattr(psycopg_module, "extensions", None) and getattr(psycopg_module.extensions, "quote_ident", None): + _extensions += [ + (psycopg_module.extensions.quote_ident, psycopg_module.extensions, "quote_ident", _extensions_quote_ident), + ] + + return _extensions + + +def _extensions_register_type(func, _, args, kwargs): + def _unroll_args(obj, scope=None): + return obj, scope + + obj, scope = _unroll_args(*args, **kwargs) + + # register_type performs a c-level check of the object + # type so we must be sure to pass in the actual db connection + if scope and isinstance(scope, wrapt.ObjectProxy): + scope = scope.__wrapped__ + + return func(obj, scope) if scope else func(obj) + + +def _extensions_quote_ident(func, _, args, kwargs): + def _unroll_args(obj, scope=None): + return obj, scope + + obj, scope = _unroll_args(*args, **kwargs) + + # register_type performs a c-level check of the object + # type so we must be sure to pass in the actual db connection + if scope and isinstance(scope, wrapt.ObjectProxy): + scope = scope.__wrapped__ + + return func(obj, scope) if scope else func(obj) + + +def _extensions_adapt(func, _, args, kwargs): + adapt = func(*args, **kwargs) + if hasattr(adapt, "prepare"): + return AdapterWrapper(adapt) + return adapt + + +class AdapterWrapper(wrapt.ObjectProxy): + def prepare(self, *args, **kwargs): + func = self.__wrapped__.prepare + if not args: + return func(*args, **kwargs) + conn = args[0] + + # prepare performs a c-level check of the object type so + # we must be sure to pass in the actual db connection + if isinstance(conn, wrapt.ObjectProxy): + conn = conn.__wrapped__ + + return func(conn, *args[1:], **kwargs) + + +def _patch_extensions(_extensions): + # we must patch extensions all the time (it's pretty harmless) so split + # from global patching of connections. must be idempotent. + for _, module, func, wrapper in _extensions: + if not hasattr(module, func) or isinstance(getattr(module, func), wrapt.ObjectProxy): + continue + wrapt.wrap_function_wrapper(module, func, wrapper) + + +def _unpatch_extensions(_extensions): + for original, module, func, _ in _extensions: + setattr(module, func, original) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/patch.py new file mode 100644 index 0000000..79abb9b --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/psycopg/patch.py @@ -0,0 +1,214 @@ +from importlib import import_module +import inspect +import os +from typing import List # noqa:F401 + +from ddtrace import Pin +from ddtrace import config +from ddtrace.contrib import dbapi + + +try: + from ddtrace.contrib.psycopg.async_connection import patched_connect_async_factory + from ddtrace.contrib.psycopg.async_cursor import Psycopg3FetchTracedAsyncCursor + from ddtrace.contrib.psycopg.async_cursor import Psycopg3TracedAsyncCursor +# catch async function syntax errors when using Python<3.7 with no async support +except SyntaxError: + pass +from ddtrace.contrib.psycopg.connection import patched_connect_factory +from ddtrace.contrib.psycopg.cursor import Psycopg3FetchTracedCursor +from ddtrace.contrib.psycopg.cursor import Psycopg3TracedCursor +from ddtrace.contrib.psycopg.extensions import _patch_extensions +from ddtrace.contrib.psycopg.extensions import _unpatch_extensions +from ddtrace.contrib.psycopg.extensions import get_psycopg2_extensions +from ddtrace.propagation._database_monitoring import default_sql_injector as _default_sql_injector +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w + +from ...internal.schema import schematize_database_operation +from ...internal.schema import schematize_service_name +from ...internal.utils.formats import asbool +from ...internal.utils.wrappers import unwrap as _u +from ...propagation._database_monitoring import _DBM_Propagator + + +try: + psycopg_import = import_module("psycopg") + + # must get the original connect class method from the class __dict__ to use later in unpatch + # Python 3.11 and wrapt result in the class method being rebinded as an instance method when + # using unwrap + _original_connect = psycopg_import.Connection.__dict__["connect"] + _original_async_connect = psycopg_import.AsyncConnection.__dict__["connect"] +# AttributeError can happen due to circular imports under certain integration methods +except (ImportError, AttributeError): + pass + + +def _psycopg_sql_injector(dbm_comment, sql_statement): + for psycopg_module in config.psycopg["_patched_modules"]: + if ( + hasattr(psycopg_module, "sql") + and hasattr(psycopg_module.sql, "Composable") + and isinstance(sql_statement, psycopg_module.sql.Composable) + ): + return psycopg_module.sql.SQL(dbm_comment) + sql_statement + return _default_sql_injector(dbm_comment, sql_statement) + + +config._add( + "psycopg", + dict( + _default_service=schematize_service_name("postgres"), + _dbapi_span_name_prefix="postgres", + _dbapi_span_operation_name=schematize_database_operation("postgres.query", database_provider="postgresql"), + _patched_modules=set(), + trace_fetch_methods=asbool( + os.getenv("DD_PSYCOPG_TRACE_FETCH_METHODS", default=False) + or os.getenv("DD_PSYCOPG2_TRACE_FETCH_METHODS", default=False) + ), + trace_connect=asbool( + os.getenv("DD_PSYCOPG_TRACE_CONNECT", default=False) + or os.getenv("DD_PSYCOPG2_TRACE_CONNECT", default=False) + ), + _dbm_propagator=_DBM_Propagator(0, "query", _psycopg_sql_injector), + dbms_name="postgresql", + ), +) + + +def get_version(): + # type: () -> str + return "" + + +PATCHED_VERSIONS = {} + + +def get_versions(): + # type: () -> List[str] + return PATCHED_VERSIONS + + +def _psycopg_modules(): + module_names = ( + "psycopg", + "psycopg2", + ) + for module_name in module_names: + try: + module = import_module(module_name) + PATCHED_VERSIONS[module_name] = getattr(module, "__version__", "") + yield module + except ImportError: + pass + + +def patch(): + for psycopg_module in _psycopg_modules(): + _patch(psycopg_module) + + +def _patch(psycopg_module): + """Patch monkey patches psycopg's connection function + so that the connection's functions are traced. + """ + if getattr(psycopg_module, "_datadog_patch", False): + return + psycopg_module._datadog_patch = True + + Pin(_config=config.psycopg).onto(psycopg_module) + + if psycopg_module.__name__ == "psycopg2": + # patch all psycopg2 extensions + _psycopg2_extensions = get_psycopg2_extensions(psycopg_module) + config.psycopg["_extensions_to_patch"] = _psycopg2_extensions + _patch_extensions(_psycopg2_extensions) + + _w(psycopg_module, "connect", patched_connect_factory(psycopg_module)) + + config.psycopg["_patched_modules"].add(psycopg_module) + else: + _w(psycopg_module, "connect", patched_connect_factory(psycopg_module)) + _w(psycopg_module, "Cursor", init_cursor_from_connection_factory(psycopg_module)) + _w(psycopg_module, "AsyncCursor", init_cursor_from_connection_factory(psycopg_module)) + + _w(psycopg_module.Connection, "connect", patched_connect_factory(psycopg_module)) + _w(psycopg_module.AsyncConnection, "connect", patched_connect_async_factory(psycopg_module)) + + config.psycopg["_patched_modules"].add(psycopg_module) + + +def unpatch(): + for psycopg_module in _psycopg_modules(): + _unpatch(psycopg_module) + + +def _unpatch(psycopg_module): + if getattr(psycopg_module, "_datadog_patch", False): + psycopg_module._datadog_patch = False + + if psycopg_module.__name__ == "psycopg2": + _u(psycopg_module, "connect") + + _psycopg2_extensions = get_psycopg2_extensions(psycopg_module) + _unpatch_extensions(_psycopg2_extensions) + else: + _u(psycopg_module, "connect") + _u(psycopg_module, "Cursor") + _u(psycopg_module, "AsyncCursor") + + # _u throws an attribute error for Python 3.11, no __get__ on the BoundFunctionWrapper + # unlike Python Class Methods which implement __get__ + psycopg_module.Connection.connect = _original_connect + psycopg_module.AsyncConnection.connect = _original_async_connect + + pin = Pin.get_from(psycopg_module) + if pin: + pin.remove_from(psycopg_module) + + +def init_cursor_from_connection_factory(psycopg_module): + def init_cursor_from_connection(wrapped_cursor_cls, _, args, kwargs): + connection = kwargs.pop("connection", None) + if not connection: + args = list(args) + index = next((i for i, x in enumerate(args) if isinstance(x, dbapi.TracedConnection)), None) + if index is not None: + connection = args.pop(index) + + # if we do not have an example of a traced connection, call the original cursor function + if not connection: + return wrapped_cursor_cls(*args, **kwargs) + + pin = Pin.get_from(connection).clone() + cfg = config.psycopg + + if cfg and cfg.trace_fetch_methods: + trace_fetch_methods = True + else: + trace_fetch_methods = False + + if issubclass(wrapped_cursor_cls, psycopg_module.AsyncCursor): + traced_cursor_cls = Psycopg3FetchTracedAsyncCursor if trace_fetch_methods else Psycopg3TracedAsyncCursor + else: + traced_cursor_cls = Psycopg3FetchTracedCursor if trace_fetch_methods else Psycopg3TracedCursor + + args_mapping = inspect.signature(wrapped_cursor_cls.__init__).parameters + # inspect.signature returns ordered dict[argument_name: str, parameter_type: type] + if "row_factory" in args_mapping and "row_factory" not in kwargs: + # check for row_factory in args by checking for functions + row_factory = None + for i in range(len(args)): + if callable(args[i]): + row_factory = args.pop(i) + break + # else just use the connection row factory + if row_factory is None: + row_factory = connection.row_factory + cursor = wrapped_cursor_cls(connection=connection, row_factory=row_factory, *args, **kwargs) # noqa: B026 + else: + cursor = wrapped_cursor_cls(connection, *args, **kwargs) + + return traced_cursor_cls(cursor=cursor, pin=pin, cfg=cfg) + + return init_cursor_from_connection diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pylibmc/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pylibmc/__init__.py new file mode 100644 index 0000000..2480dd1 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pylibmc/__init__.py @@ -0,0 +1,33 @@ +"""Instrument pylibmc to report Memcached queries. + +``import ddtrace.auto`` will automatically patch your pylibmc client to make it work. +:: + + # Be sure to import pylibmc and not pylibmc.Client directly, + # otherwise you won't have access to the patched version + from ddtrace import Pin, patch + import pylibmc + + # If not patched yet, you can patch pylibmc specifically + patch(pylibmc=True) + + # One client instrumented with default configuration + client = pylibmc.Client(["localhost:11211"] + client.set("key1", "value1") + + # Use a pin to specify metadata related to this client + Pin.override(client, service="memcached-sessions") +""" + +from ...internal.utils.importlib import require_modules + + +required_modules = ["pylibmc"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .client import TracedClient + from .patch import get_version + from .patch import patch + + __all__ = ["TracedClient", "patch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pylibmc/addrs.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pylibmc/addrs.py new file mode 100644 index 0000000..0f11d2a --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pylibmc/addrs.py @@ -0,0 +1,14 @@ +translate_server_specs = None + +try: + # NOTE: we rely on an undocumented method to parse addresses, + # so be a bit defensive and don't assume it exists. + from pylibmc.client import translate_server_specs +except ImportError: + pass + + +def parse_addresses(addrs): + if not translate_server_specs: + return [] + return translate_server_specs(addrs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pylibmc/client.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pylibmc/client.py new file mode 100644 index 0000000..af788ab --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pylibmc/client.py @@ -0,0 +1,189 @@ +from contextlib import contextmanager +import random + +import pylibmc + +# project +import ddtrace +from ddtrace import config +from ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import SPAN_KIND +from ddtrace.constants import SPAN_MEASURED_KEY +from ddtrace.contrib.pylibmc.addrs import parse_addresses +from ddtrace.ext import SpanKind +from ddtrace.ext import SpanTypes +from ddtrace.ext import db +from ddtrace.ext import memcached +from ddtrace.ext import net +from ddtrace.internal.compat import Iterable +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.logger import get_logger +from ddtrace.internal.schema import schematize_cache_operation +from ddtrace.internal.schema import schematize_service_name +from ddtrace.vendor.wrapt import ObjectProxy + + +# Original Client class +_Client = pylibmc.Client + + +log = get_logger(__name__) + + +class TracedClient(ObjectProxy): + """TracedClient is a proxy for a pylibmc.Client that times it's network operations.""" + + def __init__(self, client=None, service=memcached.SERVICE, tracer=None, *args, **kwargs): + """Create a traced client that wraps the given memcached client.""" + # The client instance/service/tracer attributes are kept for compatibility + # with the old interface: TracedClient(client=pylibmc.Client(['localhost:11211'])) + # TODO(Benjamin): Remove these in favor of patching. + if not isinstance(client, _Client): + # We are in the patched situation, just pass down all arguments to the pylibmc.Client + # Note that, in that case, client isn't a real client (just the first argument) + client = _Client(client, *args, **kwargs) + else: + log.warning( + "TracedClient instantiation is deprecated and will be remove " + "in future versions (0.6.0). Use patching instead (see the docs)." + ) + + super(TracedClient, self).__init__(client) + + schematized_service = schematize_service_name(service) + pin = ddtrace.Pin(service=schematized_service, tracer=tracer) + pin.onto(self) + + # attempt to collect the pool of urls this client talks to + try: + self._addresses = parse_addresses(client.addresses) + except Exception: + log.debug("error setting addresses", exc_info=True) + + def clone(self, *args, **kwargs): + # rewrap new connections. + cloned = self.__wrapped__.clone(*args, **kwargs) + traced_client = TracedClient(cloned) + pin = ddtrace.Pin.get_from(self) + if pin: + pin.clone().onto(traced_client) + return traced_client + + def get(self, *args, **kwargs): + return self._trace_cmd("get", *args, **kwargs) + + def set(self, *args, **kwargs): + return self._trace_cmd("set", *args, **kwargs) + + def delete(self, *args, **kwargs): + return self._trace_cmd("delete", *args, **kwargs) + + def gets(self, *args, **kwargs): + return self._trace_cmd("gets", *args, **kwargs) + + def touch(self, *args, **kwargs): + return self._trace_cmd("touch", *args, **kwargs) + + def cas(self, *args, **kwargs): + return self._trace_cmd("cas", *args, **kwargs) + + def incr(self, *args, **kwargs): + return self._trace_cmd("incr", *args, **kwargs) + + def decr(self, *args, **kwargs): + return self._trace_cmd("decr", *args, **kwargs) + + def append(self, *args, **kwargs): + return self._trace_cmd("append", *args, **kwargs) + + def prepend(self, *args, **kwargs): + return self._trace_cmd("prepend", *args, **kwargs) + + def get_multi(self, *args, **kwargs): + return self._trace_multi_cmd("get_multi", *args, **kwargs) + + def set_multi(self, *args, **kwargs): + return self._trace_multi_cmd("set_multi", *args, **kwargs) + + def delete_multi(self, *args, **kwargs): + return self._trace_multi_cmd("delete_multi", *args, **kwargs) + + def _trace_cmd(self, method_name, *args, **kwargs): + """trace the execution of the method with the given name and will + patch the first arg. + """ + method = getattr(self.__wrapped__, method_name) + with self._span(method_name) as span: + result = method(*args, **kwargs) + if span is None: + return result + + if args: + span.set_tag_str(memcached.QUERY, "%s %s" % (method_name, args[0])) + if method_name == "get": + span.set_metric(db.ROWCOUNT, 1 if result else 0) + elif method_name == "gets": + # returns a tuple object that may be (None, None) + span.set_metric(db.ROWCOUNT, 1 if isinstance(result, Iterable) and len(result) > 0 and result[0] else 0) + return result + + def _trace_multi_cmd(self, method_name, *args, **kwargs): + """trace the execution of the multi command with the given name.""" + method = getattr(self.__wrapped__, method_name) + with self._span(method_name) as span: + result = method(*args, **kwargs) + if span is None: + return result + + pre = kwargs.get("key_prefix") + if pre: + span.set_tag_str(memcached.QUERY, "%s %s" % (method_name, pre)) + + if method_name == "get_multi": + # returns mapping of key -> value if key exists, but does not include a missing key. Empty result = {} + span.set_metric( + db.ROWCOUNT, sum(1 for doc in result if doc) if result and isinstance(result, Iterable) else 0 + ) + return result + + @contextmanager + def _no_span(self): + yield None + + def _span(self, cmd_name): + """Return a span timing the given command.""" + pin = ddtrace.Pin.get_from(self) + if not pin or not pin.enabled(): + return self._no_span() + + span = pin.tracer.trace( + schematize_cache_operation("memcached.cmd", cache_provider="memcached"), + service=pin.service, + resource=cmd_name, + span_type=SpanTypes.CACHE, + ) + + span.set_tag_str(COMPONENT, config.pylibmc.integration_name) + span.set_tag_str(db.SYSTEM, memcached.DBMS_NAME) + + # set span.kind to the type of operation being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + span.set_tag(SPAN_MEASURED_KEY) + + try: + self._tag_span(span) + except Exception: + log.debug("error tagging span", exc_info=True) + return span + + def _tag_span(self, span): + # FIXME[matt] the host selection is buried in c code. we can't tell what it's actually + # using, so fallback to randomly choosing one. can we do better? + if self._addresses: + _, host, port, _ = random.choice(self._addresses) # nosec + span.set_tag_str(net.TARGET_HOST, host) + span.set_tag(net.TARGET_PORT, port) + + # set analytics sample rate + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.pylibmc.get_analytics_sample_rate()) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pylibmc/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pylibmc/patch.py new file mode 100644 index 0000000..9cd2066 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pylibmc/patch.py @@ -0,0 +1,20 @@ +import pylibmc + +from .client import TracedClient + + +# Original Client class +_Client = pylibmc.Client + + +def get_version(): + # type: () -> str + return getattr(pylibmc, "__version__", "") + + +def patch(): + pylibmc.Client = TracedClient + + +def unpatch(): + pylibmc.Client = _Client diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymemcache/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymemcache/__init__.py new file mode 100644 index 0000000..25f9354 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymemcache/__init__.py @@ -0,0 +1,44 @@ +"""Instrument pymemcache to report memcached queries. + +``import ddtrace.auto`` will automatically patch the pymemcache ``Client``:: + + from ddtrace import Pin, patch + + # If not patched yet, patch pymemcache specifically + patch(pymemcache=True) + + # Import reference to Client AFTER patching + import pymemcache + from pymemcache.client.base import Client + + # Use a pin to specify metadata related all clients + Pin.override(pymemcache, service='my-memcached-service') + + # This will report a span with the default settings + client = Client(('localhost', 11211)) + client.set("my-key", "my-val") + + # Use a pin to specify metadata related to this particular client + Pin.override(client, service='my-memcached-service') + + # If using a HashClient, specify metadata on each of its underlying + # Client instances individually + client = HashClient(('localhost', 11211)) + for _c in client.clients.values(): + Pin.override(_c, service="my-service") + +Pymemcache ``HashClient`` will also be indirectly patched as it uses ``Client`` +under the hood. +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["pymemcache"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymemcache/client.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymemcache/client.py new file mode 100644 index 0000000..f332731 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymemcache/client.py @@ -0,0 +1,361 @@ +import os +import sys +from typing import Iterable + +import pymemcache +from pymemcache.client.base import Client +from pymemcache.client.base import PooledClient +from pymemcache.client.hash import HashClient +from pymemcache.exceptions import MemcacheClientError +from pymemcache.exceptions import MemcacheIllegalInputError +from pymemcache.exceptions import MemcacheServerError +from pymemcache.exceptions import MemcacheUnknownCommandError +from pymemcache.exceptions import MemcacheUnknownError + +# 3p +from ddtrace import config +from ddtrace.internal.constants import COMPONENT +from ddtrace.vendor import wrapt + +# project +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from ...ext import db +from ...ext import memcached as memcachedx +from ...ext import net +from ...internal.logger import get_logger +from ...internal.schema import schematize_cache_operation +from ...internal.utils.formats import asbool +from ...pin import Pin + + +log = get_logger(__name__) + + +config._add( + "pymemcache", + { + "command_enabled": asbool(os.getenv("DD_TRACE_MEMCACHED_COMMAND_ENABLED", default=False)), + }, +) + + +# keep a reference to the original unpatched clients +_Client = Client +_HashClient = HashClient + + +class _WrapperBase(wrapt.ObjectProxy): + def __init__(self, wrapped_class, *args, **kwargs): + c = wrapped_class(*args, **kwargs) + super(_WrapperBase, self).__init__(c) + + # tags to apply to each span generated by this client + tags = _get_address_tags(*args, **kwargs) + + parent_pin = Pin.get_from(pymemcache) + + if parent_pin: + pin = parent_pin.clone(tags=tags) + else: + pin = Pin(tags=tags) + + # attach the pin onto this instance + pin.onto(self) + + def _trace_function_as_command(self, func, cmd, *args, **kwargs): + p = Pin.get_from(self) + + if not p or not p.enabled(): + return func(*args, **kwargs) + + return _trace(func, p, cmd, *args, **kwargs) + + +class WrappedClient(_WrapperBase): + """Wrapper providing patched methods of a pymemcache Client. + + Relevant connection information is obtained during initialization and + attached to each span. + + Keys are tagged in spans for methods that act upon a key. + """ + + def __init__(self, *args, **kwargs): + super(WrappedClient, self).__init__(_Client, *args, **kwargs) + + def set(self, *args, **kwargs): + return self._traced_cmd("set", *args, **kwargs) + + def set_many(self, *args, **kwargs): + return self._traced_cmd("set_many", *args, **kwargs) + + def add(self, *args, **kwargs): + return self._traced_cmd("add", *args, **kwargs) + + def replace(self, *args, **kwargs): + return self._traced_cmd("replace", *args, **kwargs) + + def append(self, *args, **kwargs): + return self._traced_cmd("append", *args, **kwargs) + + def prepend(self, *args, **kwargs): + return self._traced_cmd("prepend", *args, **kwargs) + + def cas(self, *args, **kwargs): + return self._traced_cmd("cas", *args, **kwargs) + + def get(self, *args, **kwargs): + return self._traced_cmd("get", *args, **kwargs) + + def get_many(self, *args, **kwargs): + return self._traced_cmd("get_many", *args, **kwargs) + + def gets(self, *args, **kwargs): + return self._traced_cmd("gets", *args, **kwargs) + + def gets_many(self, *args, **kwargs): + return self._traced_cmd("gets_many", *args, **kwargs) + + def delete(self, *args, **kwargs): + return self._traced_cmd("delete", *args, **kwargs) + + def delete_many(self, *args, **kwargs): + return self._traced_cmd("delete_many", *args, **kwargs) + + def incr(self, *args, **kwargs): + return self._traced_cmd("incr", *args, **kwargs) + + def decr(self, *args, **kwargs): + return self._traced_cmd("decr", *args, **kwargs) + + def touch(self, *args, **kwargs): + return self._traced_cmd("touch", *args, **kwargs) + + def stats(self, *args, **kwargs): + return self._traced_cmd("stats", *args, **kwargs) + + def version(self, *args, **kwargs): + return self._traced_cmd("version", *args, **kwargs) + + def flush_all(self, *args, **kwargs): + return self._traced_cmd("flush_all", *args, **kwargs) + + def quit(self, *args, **kwargs): + return self._traced_cmd("quit", *args, **kwargs) + + def set_multi(self, *args, **kwargs): + """set_multi is an alias for set_many""" + return self._traced_cmd("set_many", *args, **kwargs) + + def get_multi(self, *args, **kwargs): + """set_multi is an alias for set_many""" + return self._traced_cmd("get_many", *args, **kwargs) + + def _traced_cmd(self, command, *args, **kwargs): + return self._trace_function_as_command( + lambda *_args, **_kwargs: getattr(self.__wrapped__, command)(*_args, **_kwargs), command, *args, **kwargs + ) + + +class WrappedHashClient(_WrapperBase): + """Wrapper that traces HashClient commands + + This wrapper proxies its command invocations to the underlying HashClient instance. + When the use_pooling setting is in use, this wrapper starts a span before + doing the proxy call. + + This is necessary because the use_pooling setting causes Client instances to be + created and destroyed dynamically in a manner that isn't affected by the + patch() function. + """ + + def _ensure_traced(self, cmd, key, default_val, *args, **kwargs): + """ + PooledClient creates Client instances dynamically on request, which means + those Client instances aren't affected by the wrappers applied in patch(). + We handle this case here by calling trace() before running the command, + specifically when the client that will be used for the command is a + PooledClient. + + To avoid double-tracing when the key's client is not a PooledClient, we + don't create a span and instead rely on patch(). In this case the + underlying Client instance is long-lived and has been patched already. + """ + client_for_key = self._get_client(key) + if isinstance(client_for_key, PooledClient): + return self._traced_cmd(cmd, client_for_key, key, default_val, *args, **kwargs) + else: + return getattr(self.__wrapped__, cmd)(key, *args, **kwargs) + + def __init__(self, *args, **kwargs): + super(WrappedHashClient, self).__init__(_HashClient, *args, **kwargs) + + def set(self, key, *args, **kwargs): + return self._ensure_traced("set", key, False, *args, **kwargs) + + def add(self, key, *args, **kwargs): + return self._ensure_traced("add", key, False, *args, **kwargs) + + def replace(self, key, *args, **kwargs): + return self._ensure_traced("replace", key, False, *args, **kwargs) + + def append(self, key, *args, **kwargs): + return self._ensure_traced("append", key, False, *args, **kwargs) + + def prepend(self, key, *args, **kwargs): + return self._ensure_traced("prepend", key, False, *args, **kwargs) + + def cas(self, key, *args, **kwargs): + return self._ensure_traced("cas", key, False, *args, **kwargs) + + def get(self, key, *args, **kwargs): + return self._ensure_traced("get", key, None, *args, **kwargs) + + def gets(self, key, *args, **kwargs): + return self._ensure_traced("gets", key, None, *args, **kwargs) + + def delete(self, key, *args, **kwargs): + return self._ensure_traced("delete", key, False, *args, **kwargs) + + def incr(self, key, *args, **kwargs): + return self._ensure_traced("incr", key, False, *args, **kwargs) + + def decr(self, key, *args, **kwargs): + return self._ensure_traced("decr", key, False, *args, **kwargs) + + def touch(self, key, *args, **kwargs): + return self._ensure_traced("touch", key, False, *args, **kwargs) + + def _traced_cmd(self, command, client, key, default_val, *args, **kwargs): + # NB this function mimics the logic of HashClient._run_cmd, tracing the call to _safely_run_func + if client is None: + return default_val + + args = list(args) + args.insert(0, key) + + return self._trace_function_as_command( + lambda *_args, **_kwargs: self._safely_run_func( + client, getattr(client, command), default_val, *_args, **_kwargs + ), + command, + *args, + **kwargs, + ) + + +_HashClient.client_class = WrappedClient + + +def _get_address_tags(*args, **kwargs): + """Attempt to get host and port from args passed to Client initializer.""" + tags = {} + try: + if len(args): + host, port = args[0] + tags[net.TARGET_HOST] = host + tags[net.TARGET_PORT] = port + except Exception: + log.debug("Error collecting client address tags") + + return tags + + +def _get_query_string(args): + """Return the query values given the arguments to a pymemcache command. + + If there are multiple query values, they are joined together + space-separated. + """ + keys = "" + + # shortcut if no args + if not args: + return keys + + # pull out the first arg which will contain any key + arg = args[0] + + # if we get a dict, convert to list of keys + if type(arg) is dict: + arg = list(arg) + + if type(arg) is str: + keys = arg + elif type(arg) is bytes: + keys = arg.decode() + elif type(arg) is list and len(arg): + if type(arg[0]) is str: + keys = " ".join(arg) + elif type(arg[0]) is bytes: + keys = b" ".join(arg).decode() + + return keys + + +def _trace(func, p, method_name, *args, **kwargs): + """Run and trace the given command. + + Any pymemcache exception is caught and span error information is + set. The exception is then reraised for the application to handle + appropriately. + + Relevant tags are set in the span. + """ + with p.tracer.trace( + schematize_cache_operation(memcachedx.CMD, cache_provider="memcached"), + service=p.service, + resource=method_name, + span_type=SpanTypes.CACHE, + ) as span: + span.set_tag_str(COMPONENT, config.pymemcache.integration_name) + span.set_tag_str(db.SYSTEM, memcachedx.DBMS_NAME) + + # set span.kind to the type of operation being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + span.set_tag(SPAN_MEASURED_KEY) + # set analytics sample rate + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.pymemcache.get_analytics_sample_rate()) + + # try to set relevant tags, catch any exceptions so we don't mess + # with the application + try: + span.set_tags(p.tags) + if config.pymemcache.command_enabled: + vals = _get_query_string(args) + query = "{}{}{}".format(method_name, " " if vals else "", vals) + span.set_tag_str(memcachedx.QUERY, query) + except Exception: + log.debug("Error setting relevant pymemcache tags") + + try: + result = func(*args, **kwargs) + + if method_name == "get_many" or method_name == "gets_many": + # gets_many returns a map of key -> (value, cas), else an empty dict if no matches + # get many returns a map with values, else an empty map if no matches + span.set_metric( + db.ROWCOUNT, sum(1 for doc in result if doc) if result and isinstance(result, Iterable) else 0 + ) + elif method_name == "get": + # get returns key or None + span.set_metric(db.ROWCOUNT, 1 if result else 0) + elif method_name == "gets": + # gets returns a tuple of (None, None) if key not found, else tuple of (key, index) + span.set_metric(db.ROWCOUNT, 1 if result[0] else 0) + return result + except ( + MemcacheClientError, + MemcacheServerError, + MemcacheUnknownCommandError, + MemcacheUnknownError, + MemcacheIllegalInputError, + ): + (typ, val, tb) = sys.exc_info() + span.set_exc_info(typ, val, tb) + raise diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymemcache/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymemcache/patch.py new file mode 100644 index 0000000..78856c5 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymemcache/patch.py @@ -0,0 +1,49 @@ +import pymemcache +import pymemcache.client.hash + +from ddtrace.ext import memcached as memcachedx +from ddtrace.internal.schema import schematize_service_name +from ddtrace.pin import _DD_PIN_NAME +from ddtrace.pin import _DD_PIN_PROXY_NAME +from ddtrace.pin import Pin + +from .client import WrappedClient +from .client import WrappedHashClient + + +_Client = pymemcache.client.base.Client +_hash_Client = pymemcache.client.hash.Client +_hash_HashClient = pymemcache.client.hash.Client + + +def get_version(): + # type: () -> str + return getattr(pymemcache, "__version__", "") + + +def patch(): + if getattr(pymemcache.client, "_datadog_patch", False): + return + + pymemcache.client._datadog_patch = True + pymemcache.client.base.Client = WrappedClient + pymemcache.client.hash.Client = WrappedClient + pymemcache.client.hash.HashClient = WrappedHashClient + + # Create a global pin with default configuration for our pymemcache clients + service = schematize_service_name(memcachedx.SERVICE) + Pin(service=service).onto(pymemcache) + + +def unpatch(): + """Remove pymemcache tracing""" + if not getattr(pymemcache.client, "_datadog_patch", False): + return + pymemcache.client._datadog_patch = False + pymemcache.client.base.Client = _Client + pymemcache.client.hash.Client = _hash_Client + pymemcache.client.hash.HashClient = _hash_HashClient + + # Remove any pins that may exist on the pymemcache reference + setattr(pymemcache, _DD_PIN_NAME, None) + setattr(pymemcache, _DD_PIN_PROXY_NAME, None) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymongo/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymongo/__init__.py new file mode 100644 index 0000000..c653dce --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymongo/__init__.py @@ -0,0 +1,48 @@ +"""Instrument pymongo to report MongoDB queries. + +The pymongo integration works by wrapping pymongo's MongoClient to trace +network calls. Pymongo 3.0 and greater are the currently supported versions. +``import ddtrace.auto`` will automatically patch your MongoClient instance to make it work. + +:: + + # Be sure to import pymongo and not pymongo.MongoClient directly, + # otherwise you won't have access to the patched version + from ddtrace import Pin, patch + import pymongo + + # If not patched yet, you can patch pymongo specifically + patch(pymongo=True) + + # At that point, pymongo is instrumented with the default settings + client = pymongo.MongoClient() + # Example of instrumented query + db = client["test-db"] + db.teams.find({"name": "Toronto Maple Leafs"}) + + # Use a pin to specify metadata related to this client + client = pymongo.MongoClient() + pin = Pin.override(client, service="mongo-master") + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.pymongo["service"] + The service name reported by default for pymongo spans + + The option can also be set with the ``DD_PYMONGO_SERVICE`` environment variable + + Default: ``"pymongo"`` + +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["pymongo"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + + __all__ = ["patch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymongo/client.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymongo/client.py new file mode 100644 index 0000000..ab1916b --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymongo/client.py @@ -0,0 +1,351 @@ +# stdlib +import contextlib +import json +from typing import Iterable + +# 3p +import pymongo + +# project +import ddtrace +from ddtrace import config +from ddtrace.internal.constants import COMPONENT +from ddtrace.vendor.wrapt import ObjectProxy + +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from ...ext import db +from ...ext import mongo as mongox +from ...ext import net as netx +from ...internal.logger import get_logger +from ...internal.schema import schematize_database_operation +from ...internal.schema import schematize_service_name +from ...internal.utils import get_argument_value +from .parse import parse_msg +from .parse import parse_query +from .parse import parse_spec + + +BATCH_PARTIAL_KEY = "Batch" + +# Original Client class +_MongoClient = pymongo.MongoClient + +VERSION = pymongo.version_tuple + +if VERSION < (3, 6, 0): + from pymongo.helpers import _unpack_response + + +log = get_logger(__name__) + +_DEFAULT_SERVICE = schematize_service_name("pymongo") + + +class TracedMongoClient(ObjectProxy): + def __init__(self, client=None, *args, **kwargs): + # To support the former trace_mongo_client interface, we have to keep this old interface + # TODO(Benjamin): drop it in a later version + if not isinstance(client, _MongoClient): + # Patched interface, instantiate the client + + # client is just the first arg which could be the host if it is + # None, then it could be that the caller: + + # if client is None then __init__ was: + # 1) invoked with host=None + # 2) not given a first argument (client defaults to None) + # we cannot tell which case it is, but it should not matter since + # the default value for host is None, in either case we can simply + # not provide it as an argument + if client is None: + client = _MongoClient(*args, **kwargs) + # else client is a value for host so just pass it along + else: + client = _MongoClient(client, *args, **kwargs) + + super(TracedMongoClient, self).__init__(client) + # NOTE[matt] the TracedMongoClient attempts to trace all of the network + # calls in the trace library. This is good because it measures the + # actual network time. It's bad because it uses a private API which + # could change. We'll see how this goes. + if not isinstance(client._topology, TracedTopology): + client._topology = TracedTopology(client._topology) + + # Default Pin + ddtrace.Pin(service=_DEFAULT_SERVICE).onto(self) + + def __setddpin__(self, pin): + pin.onto(self._topology) + + def __getddpin__(self): + return ddtrace.Pin.get_from(self._topology) + + +class TracedTopology(ObjectProxy): + def __init__(self, topology): + super(TracedTopology, self).__init__(topology) + + def select_server(self, *args, **kwargs): + s = self.__wrapped__.select_server(*args, **kwargs) + if not isinstance(s, TracedServer): + s = TracedServer(s) + # Reattach the pin every time in case it changed since the initial patching + ddtrace.Pin.get_from(self).onto(s) + return s + + +class TracedServer(ObjectProxy): + def __init__(self, server): + super(TracedServer, self).__init__(server) + + def _datadog_trace_operation(self, operation): + cmd = None + # Only try to parse something we think is a query. + if self._is_query(operation): + try: + cmd = parse_query(operation) + except Exception: + log.exception("error parsing query") + + pin = ddtrace.Pin.get_from(self) + # if we couldn't parse or shouldn't trace the message, just go. + if not cmd or not pin or not pin.enabled(): + return None + + span = pin.tracer.trace( + schematize_database_operation("pymongo.cmd", database_provider="mongodb"), + span_type=SpanTypes.MONGODB, + service=pin.service, + ) + + span.set_tag_str(COMPONENT, config.pymongo.integration_name) + + # set span.kind to the operation type being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + span.set_tag(SPAN_MEASURED_KEY) + span.set_tag_str(mongox.DB, cmd.db) + span.set_tag_str(mongox.COLLECTION, cmd.coll) + span.set_tag_str(db.SYSTEM, mongox.SERVICE) + span.set_tags(cmd.tags) + + # set `mongodb.query` tag and resource for span + _set_query_metadata(span, cmd) + + # set analytics sample rate + sample_rate = config.pymongo.get_analytics_sample_rate() + if sample_rate is not None: + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, sample_rate) + return span + + if VERSION >= (4, 5, 0): + + @contextlib.contextmanager + def checkout(self, *args, **kwargs): + with self.__wrapped__.checkout(*args, **kwargs) as s: + if not isinstance(s, TracedSocket): + s = TracedSocket(s) + ddtrace.Pin.get_from(self).onto(s) + yield s + + else: + + @contextlib.contextmanager + def get_socket(self, *args, **kwargs): + with self.__wrapped__.get_socket(*args, **kwargs) as s: + if not isinstance(s, TracedSocket): + s = TracedSocket(s) + ddtrace.Pin.get_from(self).onto(s) + yield s + + if VERSION >= (3, 12, 0): + + def run_operation(self, sock_info, operation, *args, **kwargs): + span = self._datadog_trace_operation(operation) + if span is None: + return self.__wrapped__.run_operation(sock_info, operation, *args, **kwargs) + with span: + result = self.__wrapped__.run_operation(sock_info, operation, *args, **kwargs) + if result: + if hasattr(result, "address"): + set_address_tags(span, result.address) + if self._is_query(operation) and hasattr(result, "docs"): + set_query_rowcount(docs=result.docs, span=span) + return result + + elif (3, 9, 0) <= VERSION < (3, 12, 0): + + def run_operation_with_response(self, sock_info, operation, *args, **kwargs): + span = self._datadog_trace_operation(operation) + if span is None: + return self.__wrapped__.run_operation_with_response(sock_info, operation, *args, **kwargs) + with span: + result = self.__wrapped__.run_operation_with_response(sock_info, operation, *args, **kwargs) + if result: + if hasattr(result, "address"): + set_address_tags(span, result.address) + if self._is_query(operation) and hasattr(result, "docs"): + set_query_rowcount(docs=result.docs, span=span) + return result + + else: + + def send_message_with_response(self, operation, *args, **kwargs): + span = self._datadog_trace_operation(operation) + if span is None: + return self.__wrapped__.send_message_with_response(operation, *args, **kwargs) + with span: + result = self.__wrapped__.send_message_with_response(operation, *args, **kwargs) + if result: + if hasattr(result, "address"): + set_address_tags(span, result.address) + if self._is_query(operation): + if hasattr(result, "data"): + if VERSION >= (3, 6, 0) and hasattr(result.data, "unpack_response"): + set_query_rowcount(docs=result.data.unpack_response(), span=span) + else: + data = _unpack_response(response=result.data) + if VERSION < (3, 2, 0) and data.get("number_returned", None): + span.set_metric(db.ROWCOUNT, data.get("number_returned")) + elif (3, 2, 0) <= VERSION < (3, 6, 0): + docs = data.get("data", None) + set_query_rowcount(docs=docs, span=span) + return result + + @staticmethod + def _is_query(op): + # NOTE: _Query should always have a spec field + return hasattr(op, "spec") + + +class TracedSocket(ObjectProxy): + def __init__(self, socket): + super(TracedSocket, self).__init__(socket) + + def command(self, dbname, spec, *args, **kwargs): + cmd = None + try: + cmd = parse_spec(spec, dbname) + except Exception: + log.exception("error parsing spec. skipping trace") + + pin = ddtrace.Pin.get_from(self) + # skip tracing if we don't have a piece of data we need + if not dbname or not cmd or not pin or not pin.enabled(): + return self.__wrapped__.command(dbname, spec, *args, **kwargs) + + cmd.db = dbname + with self.__trace(cmd): + return self.__wrapped__.command(dbname, spec, *args, **kwargs) + + def write_command(self, *args, **kwargs): + msg = get_argument_value(args, kwargs, 1, "msg") + cmd = None + try: + cmd = parse_msg(msg) + except Exception: + log.exception("error parsing msg") + + pin = ddtrace.Pin.get_from(self) + # if we couldn't parse it, don't try to trace it. + if not cmd or not pin or not pin.enabled(): + return self.__wrapped__.write_command(*args, **kwargs) + + with self.__trace(cmd) as s: + result = self.__wrapped__.write_command(*args, **kwargs) + if result: + s.set_metric(db.ROWCOUNT, result.get("n", -1)) + return result + + def __trace(self, cmd): + pin = ddtrace.Pin.get_from(self) + s = pin.tracer.trace( + schematize_database_operation("pymongo.cmd", database_provider="mongodb"), + span_type=SpanTypes.MONGODB, + service=pin.service, + ) + + s.set_tag_str(COMPONENT, config.pymongo.integration_name) + s.set_tag_str(db.SYSTEM, mongox.SERVICE) + + # set span.kind to the type of operation being performed + s.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + s.set_tag(SPAN_MEASURED_KEY) + if cmd.db: + s.set_tag_str(mongox.DB, cmd.db) + if cmd: + s.set_tag(mongox.COLLECTION, cmd.coll) + s.set_tags(cmd.tags) + s.set_metrics(cmd.metrics) + + # set `mongodb.query` tag and resource for span + _set_query_metadata(s, cmd) + + # set analytics sample rate + s.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.pymongo.get_analytics_sample_rate()) + + if self.address: + set_address_tags(s, self.address) + return s + + +def normalize_filter(f=None): + if f is None: + return {} + elif isinstance(f, list): + # normalize lists of filters + # e.g. {$or: [ { age: { $lt: 30 } }, { type: 1 } ]} + return [normalize_filter(s) for s in f] + elif isinstance(f, dict): + # normalize dicts of filters + # {$or: [ { age: { $lt: 30 } }, { type: 1 } ]}) + out = {} + for k, v in f.items(): + if k == "$in" or k == "$nin": + # special case $in queries so we don't loop over lists. + out[k] = "?" + elif isinstance(v, list) or isinstance(v, dict): + # RECURSION ALERT: needs to move to the agent + out[k] = normalize_filter(v) + else: + # NOTE: this shouldn't happen, but let's have a safeguard. + out[k] = "?" + return out + else: + # FIXME[matt] unexpected type. not sure this should ever happen, but at + # least it won't crash. + return {} + + +def set_address_tags(span, address): + # the address is only set after the cursor is done. + if address: + span.set_tag_str(netx.TARGET_HOST, address[0]) + span.set_tag(netx.TARGET_PORT, address[1]) + + +def _set_query_metadata(span, cmd): + """Sets span `mongodb.query` tag and resource given command query""" + if cmd.query: + nq = normalize_filter(cmd.query) + span.set_tag("mongodb.query", nq) + # needed to dump json so we don't get unicode + # dict keys like {u'foo':'bar'} + q = json.dumps(nq) + span.resource = "{} {} {}".format(cmd.name, cmd.coll, q) + else: + span.resource = "{} {}".format(cmd.name, cmd.coll) + + +def set_query_rowcount(docs, span): + # results returned in batches, get len of each batch + if isinstance(docs, Iterable) and len(docs) > 0: + cursor = docs[0].get("cursor", None) + if cursor: + rowcount = sum([len(documents) for batch_key, documents in cursor.items() if BATCH_PARTIAL_KEY in batch_key]) + span.set_metric(db.ROWCOUNT, rowcount) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymongo/parse.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymongo/parse.py new file mode 100644 index 0000000..1a4330d --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymongo/parse.py @@ -0,0 +1,204 @@ +import ctypes +import struct + +# 3p +import bson +from bson.codec_options import CodecOptions +from bson.son import SON + +# project +from ...ext import net as netx +from ...internal.compat import to_unicode +from ...internal.logger import get_logger + + +log = get_logger(__name__) + + +# MongoDB wire protocol commands +# http://docs.mongodb.com/manual/reference/mongodb-wire-protocol +OP_CODES = { + 1: "reply", + 1000: "msg", # DEV: 1000 was deprecated at some point, use 2013 instead + 2001: "update", + 2002: "insert", + 2003: "reserved", + 2004: "query", + 2005: "get_more", + 2006: "delete", + 2007: "kill_cursors", + 2010: "command", + 2011: "command_reply", + 2013: "msg", +} + +# The maximum message length we'll try to parse +MAX_MSG_PARSE_LEN = 1024 * 1024 + +header_struct = struct.Struct("= 3.1 stores the db and coll separately + coll = getattr(query, "coll", None) + db = getattr(query, "db", None) + + # pymongo < 3.1 _Query does not have a name field, so default to 'query' + cmd = Command(getattr(query, "name", "query"), db, coll) + cmd.query = query.spec + return cmd + + +def parse_spec(spec, db=None): + """Return a Command that has parsed the relevant detail for the given + pymongo SON spec. + """ + + # the first element is the command and collection + items = list(spec.items()) + if not items: + return None + name, coll = items[0] + cmd = Command(name, db or spec.get("$db"), coll) + + if "ordered" in spec: # in insert and update + cmd.tags["mongodb.ordered"] = spec["ordered"] + + if cmd.name == "insert": + if "documents" in spec: + cmd.metrics["mongodb.documents"] = len(spec["documents"]) + + elif cmd.name == "update": + updates = spec.get("updates") + if updates: + # FIXME[matt] is there ever more than one here? + cmd.query = updates[0].get("q") + + elif cmd.name == "delete": + dels = spec.get("deletes") + if dels: + # FIXME[matt] is there ever more than one here? + cmd.query = dels[0].get("q") + + return cmd + + +def _cstring(raw): + """Return the first null terminated cstring from the buffer.""" + return ctypes.create_string_buffer(raw).value + + +def _split_namespace(ns): + """Return a tuple of (db, collection) from the 'db.coll' string.""" + if ns: + # NOTE[matt] ns is unicode or bytes depending on the client version + # so force cast to unicode + split = to_unicode(ns).split(".", 1) + if len(split) == 1: + raise Exception("namespace doesn't contain period: %s" % ns) + return split + return (None, None) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymongo/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymongo/patch.py new file mode 100644 index 0000000..13ee461 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymongo/patch.py @@ -0,0 +1,94 @@ +import contextlib + +import pymongo + +from ddtrace import Pin +from ddtrace import config +from ddtrace.contrib import trace_utils +from ddtrace.internal.constants import COMPONENT +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w + +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from ...ext import db +from ...ext import mongo +from ..trace_utils import unwrap as _u +from .client import TracedMongoClient +from .client import set_address_tags + + +config._add( + "pymongo", + dict(_default_service="pymongo"), +) + + +def get_version(): + # type: () -> str + return getattr(pymongo, "__version__", "") + + +# Original Client class +_MongoClient = pymongo.MongoClient + +_VERSION = pymongo.version_tuple +_CHECKOUT_FN_NAME = "get_socket" if _VERSION < (4, 5) else "checkout" + + +def patch(): + patch_pymongo_module() + # We should progressively get rid of TracedMongoClient. We now try to + # wrap methods individually. cf #1501 + pymongo.MongoClient = TracedMongoClient + + +def unpatch(): + unpatch_pymongo_module() + pymongo.MongoClient = _MongoClient + + +def patch_pymongo_module(): + if getattr(pymongo, "_datadog_patch", False): + return + pymongo._datadog_patch = True + Pin().onto(pymongo.server.Server) + + # Whenever a pymongo command is invoked, the lib either: + # - Creates a new socket & performs a TCP handshake + # - Grabs a socket already initialized before + _w("pymongo.server", "Server.%s" % _CHECKOUT_FN_NAME, traced_get_socket) + + +def unpatch_pymongo_module(): + if not getattr(pymongo, "_datadog_patch", False): + return + pymongo._datadog_patch = False + + _u(pymongo.server.Server, _CHECKOUT_FN_NAME) + + +@contextlib.contextmanager +def traced_get_socket(wrapped, instance, args, kwargs): + pin = Pin._find(wrapped, instance) + if not pin or not pin.enabled(): + with wrapped(*args, **kwargs) as sock_info: + yield sock_info + return + + with pin.tracer.trace( + "pymongo.%s" % _CHECKOUT_FN_NAME, + service=trace_utils.int_service(pin, config.pymongo), + span_type=SpanTypes.MONGODB, + ) as span: + span.set_tag_str(COMPONENT, config.pymongo.integration_name) + span.set_tag_str(db.SYSTEM, mongo.SERVICE) + + # set span.kind tag equal to type of operation being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + with wrapped(*args, **kwargs) as sock_info: + set_address_tags(span, sock_info.address) + span.set_tag(SPAN_MEASURED_KEY) + yield sock_info diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymysql/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymysql/__init__.py new file mode 100644 index 0000000..43283e1 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymysql/__init__.py @@ -0,0 +1,68 @@ +""" +The pymysql integration instruments the pymysql library to trace MySQL queries. + + +Enabling +~~~~~~~~ + +The integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(pymysql=True) + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.pymysql["service"] + + The service name reported by default for pymysql spans. + + This option can also be set with the ``DD_PYMYSQL_SERVICE`` environment + variable. + + Default: ``"mysql"`` + +.. py:data:: ddtrace.config.pymysql["trace_fetch_methods"] + + Whether or not to trace fetch methods. + + Can also configured via the ``DD_PYMYSQL_TRACE_FETCH_METHODS`` environment variable. + + Default: ``False`` + + +Instance Configuration +~~~~~~~~~~~~~~~~~~~~~~ + +To configure the integration on an per-connection basis use the +``Pin`` API:: + + from ddtrace import Pin + from pymysql import connect + + # This will report a span with the default settings + conn = connect(user="alice", password="b0b", host="localhost", port=3306, database="test") + + # Use a pin to override the service name for this connection. + Pin.override(conn, service="pymysql-users") + + + cursor = conn.cursor() + cursor.execute("SELECT 6*7 AS the_answer;") +""" + +from ...internal.utils.importlib import require_modules + + +required_modules = ["pymysql"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + + __all__ = ["patch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymysql/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymysql/patch.py new file mode 100644 index 0000000..fe2fdce --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pymysql/patch.py @@ -0,0 +1,63 @@ +import os + +import pymysql + +from ddtrace import Pin +from ddtrace import config +from ddtrace.contrib.dbapi import TracedConnection +from ddtrace.vendor import wrapt + +from ...ext import db +from ...ext import net +from ...internal.schema import schematize_database_operation +from ...internal.schema import schematize_service_name +from ...internal.utils.formats import asbool + + +config._add( + "pymysql", + dict( + _default_service=schematize_service_name("pymysql"), + _dbapi_span_name_prefix="pymysql", + _dbapi_span_operation_name=schematize_database_operation("pymysql.query", database_provider="mysql"), + trace_fetch_methods=asbool(os.getenv("DD_PYMYSQL_TRACE_FETCH_METHODS", default=False)), + ), +) + + +def get_version(): + # type: () -> str + return getattr(pymysql, "__version__", "") + + +CONN_ATTR_BY_TAG = { + net.TARGET_HOST: "host", + net.TARGET_PORT: "port", + db.USER: "user", + db.NAME: "db", +} + + +def patch(): + wrapt.wrap_function_wrapper("pymysql", "connect", _connect) + + +def unpatch(): + if isinstance(pymysql.connect, wrapt.ObjectProxy): + pymysql.connect = pymysql.connect.__wrapped__ + + +def _connect(func, instance, args, kwargs): + conn = func(*args, **kwargs) + return patch_conn(conn) + + +def patch_conn(conn): + tags = {t: getattr(conn, a, "") for t, a in CONN_ATTR_BY_TAG.items()} + tags[db.SYSTEM] = "mysql" + pin = Pin(tags=tags) + + # grab the metadata from the conn + wrapped = TracedConnection(conn, pin=pin, cfg=config.pymysql) + pin.onto(wrapped) + return wrapped diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pynamodb/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pynamodb/__init__.py new file mode 100644 index 0000000..db9b343 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pynamodb/__init__.py @@ -0,0 +1,42 @@ +""" +The PynamoDB integration traces all db calls made with the pynamodb +library through the connection API. + +Enabling +~~~~~~~~ + +The PynamoDB integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + import pynamodb + from ddtrace import patch, config + patch(pynamodb=True) + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.pynamodb["service"] + + The service name reported by default for the PynamoDB instance. + + This option can also be set with the ``DD_PYNAMODB_SERVICE`` environment + variable. + + Default: ``"pynamodb"`` + +""" + + +from ...internal.utils.importlib import require_modules + + +required_modules = ["pynamodb.connection.base"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + + __all__ = ["patch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pynamodb/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pynamodb/patch.py new file mode 100644 index 0000000..e5bbcb2 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pynamodb/patch.py @@ -0,0 +1,108 @@ +""" +Trace queries to botocore api done via a pynamodb client +""" + +import pynamodb.connection.base + +from ddtrace import config +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema import schematize_cloud_api_operation +from ddtrace.internal.schema import schematize_service_name +from ddtrace.vendor import wrapt + +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from ...ext import db +from ...internal.utils import ArgumentError +from ...internal.utils import get_argument_value +from ...internal.utils.formats import deep_getattr +from ...pin import Pin +from .. import trace_utils +from ..trace_utils import unwrap + + +# Pynamodb connection class +_PynamoDB_client = pynamodb.connection.base.Connection + +config._add( + "pynamodb", + { + "_default_service": schematize_service_name("pynamodb"), + }, +) + + +def get_version(): + # type: () -> str + return getattr(pynamodb, "__version__", "") + + +def patch(): + if getattr(pynamodb.connection.base, "_datadog_patch", False): + return + pynamodb.connection.base._datadog_patch = True + + wrapt.wrap_function_wrapper("pynamodb.connection.base", "Connection._make_api_call", patched_api_call) + Pin(service=None).onto(pynamodb.connection.base.Connection) + + +def unpatch(): + if getattr(pynamodb.connection.base, "_datadog_patch", False): + pynamodb.connection.base._datadog_patch = False + unwrap(pynamodb.connection.base.Connection, "_make_api_call") + + +def patched_api_call(original_func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return original_func(*args, **kwargs) + + with pin.tracer.trace( + schematize_cloud_api_operation("pynamodb.command", cloud_provider="aws", cloud_service="dynamodb"), + service=trace_utils.ext_service(pin, config.pynamodb, "pynamodb"), + span_type=SpanTypes.HTTP, + ) as span: + span.set_tag_str(COMPONENT, config.pynamodb.integration_name) + span.set_tag_str(db.SYSTEM, "dynamodb") + + # set span.kind to the type of operation being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + span.set_tag(SPAN_MEASURED_KEY) + + try: + operation = get_argument_value(args, kwargs, 0, "operation_name") + span.resource = operation + + if args[1] and "TableName" in args[1]: + table_name = args[1]["TableName"] + span.set_tag_str("table_name", table_name) + span.set_tag_str("tablename", table_name) + span.resource = span.resource + " " + table_name + + except ArgumentError: + span.resource = "Unknown" + operation = None + + region_name = deep_getattr(instance, "client.meta.region_name") + + meta = { + "aws.agent": "pynamodb", + "aws.operation": operation, + "aws.region": region_name, + "region": region_name, + } + span.set_tags(meta) + + # set analytics sample rate + sample_rate = config.pynamodb.get_analytics_sample_rate(use_global_config=True) + + if sample_rate is not None: + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, sample_rate) + + result = original_func(*args, **kwargs) + + return result diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pyodbc/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pyodbc/__init__.py new file mode 100644 index 0000000..775c1e1 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pyodbc/__init__.py @@ -0,0 +1,66 @@ +""" +The pyodbc integration instruments the pyodbc library to trace pyodbc queries. + + +Enabling +~~~~~~~~ + +The integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(pyodbc=True) + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.pyodbc["service"] + + The service name reported by default for pyodbc spans. + + This option can also be set with the ``DD_PYODBC_SERVICE`` environment + variable. + + Default: ``"pyodbc"`` + +.. py:data:: ddtrace.config.pyodbc["trace_fetch_methods"] + + Whether or not to trace fetch methods. + + Can also configured via the ``DD_PYODBC_TRACE_FETCH_METHODS`` environment variable. + + Default: ``False`` + + +Instance Configuration +~~~~~~~~~~~~~~~~~~~~~~ + +To configure the integration on an per-connection basis use the +``Pin`` API:: + + from ddtrace import Pin + import pyodbc + + # This will report a span with the default settings + db = pyodbc.connect("") + + # Use a pin to override the service name for the connection. + Pin.override(db, service='pyodbc-users') + + cursor = db.cursor() + cursor.execute("select * from users where id = 1") +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["pyodbc"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + + __all__ = ["patch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pyodbc/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pyodbc/patch.py new file mode 100644 index 0000000..4ca1dbb --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pyodbc/patch.py @@ -0,0 +1,69 @@ +import os + +import pyodbc + +from ddtrace.internal.schema import schematize_service_name + +from ... import Pin +from ... import config +from ...ext import db +from ...internal.utils.formats import asbool +from ..dbapi import TracedConnection +from ..dbapi import TracedCursor +from ..trace_utils import unwrap +from ..trace_utils import wrap + + +config._add( + "pyodbc", + dict( + _default_service=schematize_service_name("pyodbc"), + _dbapi_span_name_prefix="pyodbc", + trace_fetch_methods=asbool(os.getenv("DD_PYODBC_TRACE_FETCH_METHODS", default=False)), + ), +) + + +def get_version(): + # type: () -> str + return pyodbc.version + + +def patch(): + if getattr(pyodbc, "_datadog_patch", False): + return + pyodbc._datadog_patch = True + wrap("pyodbc", "connect", _connect) + + +def unpatch(): + if getattr(pyodbc, "_datadog_patch", False): + pyodbc._datadog_patch = False + unwrap(pyodbc, "connect") + + +def _connect(func, instance, args, kwargs): + conn = func(*args, **kwargs) + return patch_conn(conn) + + +def patch_conn(conn): + try: + tags = {db.SYSTEM: conn.getinfo(pyodbc.SQL_DBMS_NAME), db.USER: conn.getinfo(pyodbc.SQL_USER_NAME)} + except pyodbc.Error: + tags = {} + pin = Pin(service=None, tags=tags) + wrapped = PyODBCTracedConnection(conn, pin=pin) + pin.onto(wrapped) + return wrapped + + +class PyODBCTracedCursor(TracedCursor): + pass + + +class PyODBCTracedConnection(TracedConnection): + def __init__(self, conn, pin=None, cursor_cls=None): + if not cursor_cls: + cursor_cls = PyODBCTracedCursor + super(PyODBCTracedConnection, self).__init__(conn, pin, config.pyodbc, cursor_cls=cursor_cls) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pyramid/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pyramid/__init__.py new file mode 100644 index 0000000..ff5e5ad --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pyramid/__init__.py @@ -0,0 +1,56 @@ +r"""To trace requests from a Pyramid application, trace your application +config:: + + + from pyramid.config import Configurator + from ddtrace.contrib.pyramid import trace_pyramid + + settings = { + 'datadog_trace_service' : 'my-web-app-name', + } + + config = Configurator(settings=settings) + trace_pyramid(config) + + # use your config as normal. + config.add_route('index', '/') + +Available settings are: + +* ``datadog_trace_service``: change the `pyramid` service name +* ``datadog_trace_enabled``: sets if the Tracer is enabled or not +* ``datadog_distributed_tracing``: set it to ``False`` to disable Distributed Tracing + +If you use the ``pyramid.tweens`` settings value to set the tweens for your +application, you need to add ``ddtrace.contrib.pyramid:trace_tween_factory`` +explicitly to the list. For example:: + + settings = { + 'datadog_trace_service' : 'my-web-app-name', + 'pyramid.tweens', 'your_tween_no_1\\nyour_tween_no_2\\nddtrace.contrib.pyramid:trace_tween_factory', + } + + config = Configurator(settings=settings) + trace_pyramid(config) + + # use your config as normal. + config.add_route('index', '/') + +:ref:`All HTTP tags ` are supported for this integration. + +""" + +from ...internal.utils.importlib import require_modules + + +required_modules = ["pyramid"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .trace import includeme + from .trace import trace_pyramid + from .trace import trace_tween_factory + + __all__ = ["patch", "trace_pyramid", "trace_tween_factory", "includeme", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pyramid/constants.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pyramid/constants.py new file mode 100644 index 0000000..4d96877 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pyramid/constants.py @@ -0,0 +1,6 @@ +SETTINGS_SERVICE = "datadog_trace_service" +SETTINGS_TRACER = "datadog_tracer" +SETTINGS_TRACE_ENABLED = "datadog_trace_enabled" +SETTINGS_DISTRIBUTED_TRACING = "datadog_distributed_tracing" +SETTINGS_ANALYTICS_ENABLED = "datadog_analytics_enabled" +SETTINGS_ANALYTICS_SAMPLE_RATE = "datadog_analytics_sample_rate" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pyramid/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pyramid/patch.py new file mode 100644 index 0000000..2a5f942 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pyramid/patch.py @@ -0,0 +1,103 @@ +import os + +import pyramid +import pyramid.config + +from ddtrace import config +from ddtrace.vendor import wrapt + +from ...internal.utils.formats import asbool +from .constants import SETTINGS_ANALYTICS_ENABLED +from .constants import SETTINGS_ANALYTICS_SAMPLE_RATE +from .constants import SETTINGS_DISTRIBUTED_TRACING +from .constants import SETTINGS_SERVICE +from .trace import DD_TWEEN_NAME +from .trace import trace_pyramid + + +config._add( + "pyramid", + dict( + distributed_tracing=asbool(os.getenv("DD_PYRAMID_DISTRIBUTED_TRACING", default=True)), + ), +) + +DD_PATCH = "_datadog_patch" + + +def get_version(): + # type: () -> str + try: + import importlib.metadata as importlib_metadata + except ImportError: + import importlib_metadata # type: ignore[no-redef] + + return str(importlib_metadata.version(pyramid.__package__)) + + +def patch(): + """ + Patch pyramid.config.Configurator + """ + if getattr(pyramid.config, DD_PATCH, False): + return + + setattr(pyramid.config, DD_PATCH, True) + _w = wrapt.wrap_function_wrapper + _w("pyramid.config", "Configurator.__init__", traced_init) + + +def traced_init(wrapped, instance, args, kwargs): + settings = kwargs.pop("settings", {}) + service = config._get_service(default="pyramid") + # DEV: integration-specific analytics flag can be not set but still enabled + # globally for web frameworks + old_analytics_enabled = os.getenv("DD_PYRAMID_ANALYTICS_ENABLED") + analytics_enabled = os.environ.get("DD_TRACE_PYRAMID_ANALYTICS_ENABLED", old_analytics_enabled) + if analytics_enabled is not None: + analytics_enabled = asbool(analytics_enabled) + # TODO: why is analytics sample rate a string or a bool here? + old_analytics_sample_rate = os.getenv("DD_PYRAMID_ANALYTICS_SAMPLE_RATE", default=True) + analytics_sample_rate = os.environ.get("DD_TRACE_PYRAMID_ANALYTICS_SAMPLE_RATE", old_analytics_sample_rate) + trace_settings = { + SETTINGS_SERVICE: service, + SETTINGS_DISTRIBUTED_TRACING: config.pyramid.distributed_tracing, + SETTINGS_ANALYTICS_ENABLED: analytics_enabled, + SETTINGS_ANALYTICS_SAMPLE_RATE: analytics_sample_rate, + } + # Update over top of the defaults + # DEV: If we did `settings.update(trace_settings)` then we would only ever + # have the default values. + trace_settings.update(settings) + # If the tweens are explicitly set with 'pyramid.tweens', we need to + # explicitly set our tween too since `add_tween` will be ignored. + insert_tween_if_needed(trace_settings) + + # The original Configurator.__init__ looks up two levels to find the package + # name if it is not provided. This has to be replicated here since this patched + # call will occur at the same level in the call stack. + if not kwargs.get("package", None): + from pyramid.path import caller_package + + kwargs["package"] = caller_package(level=2) + + kwargs["settings"] = trace_settings + wrapped(*args, **kwargs) + trace_pyramid(instance) + + +def insert_tween_if_needed(settings): + tweens = settings.get("pyramid.tweens") + # If the list is empty, pyramid does not consider the tweens have been + # set explicitly. + # And if our tween is already there, nothing to do + if not tweens or not tweens.strip() or DD_TWEEN_NAME in tweens: + return + # pyramid.tweens.EXCVIEW is the name of built-in exception view provided by + # pyramid. We need our tween to be before it, otherwise unhandled + # exceptions will be caught before they reach our tween. + idx = tweens.find(pyramid.tweens.EXCVIEW) + if idx == -1: + settings["pyramid.tweens"] = tweens + "\n" + DD_TWEEN_NAME + else: + settings["pyramid.tweens"] = tweens[:idx] + DD_TWEEN_NAME + "\n" + tweens[idx:] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pyramid/trace.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pyramid/trace.py new file mode 100644 index 0000000..1cf3b53 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pyramid/trace.py @@ -0,0 +1,138 @@ +from pyramid.httpexceptions import HTTPException +import pyramid.renderers +from pyramid.settings import asbool + +# project +import ddtrace +from ddtrace import config +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.vendor import wrapt + +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from ...internal.logger import get_logger +from ...internal.schema import schematize_service_name +from ...internal.schema import schematize_url_operation +from .. import trace_utils +from .constants import SETTINGS_ANALYTICS_ENABLED +from .constants import SETTINGS_ANALYTICS_SAMPLE_RATE +from .constants import SETTINGS_DISTRIBUTED_TRACING +from .constants import SETTINGS_SERVICE +from .constants import SETTINGS_TRACE_ENABLED +from .constants import SETTINGS_TRACER + + +log = get_logger(__name__) + +DD_TWEEN_NAME = "ddtrace.contrib.pyramid:trace_tween_factory" +DD_TRACER = "_datadog_tracer" + + +def trace_pyramid(config): + config.include("ddtrace.contrib.pyramid") + + +def includeme(config): + # Add our tween just before the default exception handler + config.add_tween(DD_TWEEN_NAME, over=pyramid.tweens.EXCVIEW) + # ensure we only patch the renderer once. + if not isinstance(pyramid.renderers.RendererHelper.render, wrapt.ObjectProxy): + wrapt.wrap_function_wrapper("pyramid.renderers", "RendererHelper.render", trace_render) + + +def trace_render(func, instance, args, kwargs): + # If the request is not traced, we do not trace + request = kwargs.get("request", {}) + if not request: + log.debug("No request passed to render, will not be traced") + return func(*args, **kwargs) + tracer = getattr(request, DD_TRACER, None) + if not tracer: + log.debug("No tracer found in request, will not be traced") + return func(*args, **kwargs) + + with tracer.trace("pyramid.render", span_type=SpanTypes.TEMPLATE) as span: + span.set_tag_str(COMPONENT, config.pyramid.integration_name) + + return func(*args, **kwargs) + + +def trace_tween_factory(handler, registry): + # configuration + settings = registry.settings + service = settings.get(SETTINGS_SERVICE) or schematize_service_name("pyramid") + tracer = settings.get(SETTINGS_TRACER) or ddtrace.tracer + enabled = asbool(settings.get(SETTINGS_TRACE_ENABLED, tracer.enabled)) + distributed_tracing = asbool(settings.get(SETTINGS_DISTRIBUTED_TRACING, True)) + + if enabled: + # make a request tracing function + def trace_tween(request): + trace_utils.activate_distributed_headers( + tracer, int_config=config.pyramid, request_headers=request.headers, override=distributed_tracing + ) + + span_name = schematize_url_operation("pyramid.request", protocol="http", direction=SpanDirection.INBOUND) + with tracer.trace(span_name, service=service, resource="404", span_type=SpanTypes.WEB) as span: + span.set_tag_str(COMPONENT, config.pyramid.integration_name) + + # set span.kind to the type of operation being performed + span.set_tag_str(SPAN_KIND, SpanKind.SERVER) + + span.set_tag(SPAN_MEASURED_KEY) + # Configure trace search sample rate + # DEV: pyramid is special case maintains separate configuration from config api + analytics_enabled = settings.get(SETTINGS_ANALYTICS_ENABLED) + + if (config.analytics_enabled and analytics_enabled is not False) or analytics_enabled is True: + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, settings.get(SETTINGS_ANALYTICS_SAMPLE_RATE, True)) + + setattr(request, DD_TRACER, tracer) # used to find the tracer in templates + response = None + status = None + try: + response = handler(request) + except HTTPException as e: + # If the exception is a pyramid HTTPException, + # that's still valuable information that isn't necessarily + # a 500. For instance, HTTPFound is a 302. + # As described in docs, Pyramid exceptions are all valid + # response types + response = e + raise + except BaseException: + status = 500 + raise + finally: + # set request tags + if request.matched_route: + span.resource = "{} {}".format(request.method, request.matched_route.name) + span.set_tag_str("pyramid.route.name", request.matched_route.name) + # set response tags + if response: + status = response.status_code + response_headers = response.headers + else: + response_headers = None + + trace_utils.set_http_meta( + span, + config.pyramid, + method=request.method, + url=request.path_url, + status_code=status, + query=request.query_string, + request_headers=request.headers, + response_headers=response_headers, + route=request.matched_route.pattern if request.matched_route else None, + ) + return response + + return trace_tween + + # if timing support is not enabled, return the original handler + return handler diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest/__init__.py new file mode 100644 index 0000000..30be678 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest/__init__.py @@ -0,0 +1,86 @@ +""" +The pytest integration traces test executions. + +Enabling +~~~~~~~~ + +Enable traced execution of tests using ``pytest`` runner by +running ``pytest --ddtrace`` or by modifying any configuration +file read by pytest (``pytest.ini``, ``setup.cfg``, ...):: + + [pytest] + ddtrace = 1 + + +If you need to disable it, the option ``--no-ddtrace`` will take +precedence over ``--ddtrace`` and (``pytest.ini``, ``setup.cfg``, ...) + +You can enable all integrations by using the ``--ddtrace-patch-all`` option +alongside ``--ddtrace`` or by adding this to your configuration:: + + [pytest] + ddtrace = 1 + ddtrace-patch-all = 1 + + +.. note:: + The ddtrace plugin for pytest has the side effect of importing the ddtrace + package and starting a global tracer. + + If this is causing issues for your pytest runs where traced execution of + tests is not enabled, you can deactivate the plugin:: + + [pytest] + addopts = -p no:ddtrace + + See the `pytest documentation + `_ + for more details. + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.pytest["service"] + + The service name reported by default for pytest traces. + + This option can also be set with the integration specific ``DD_PYTEST_SERVICE`` environment + variable, or more generally with the `DD_SERVICE` environment variable. + + Default: Name of the repository being tested, otherwise ``"pytest"`` if the repository name cannot be found. + + +.. py:data:: ddtrace.config.pytest["operation_name"] + + The operation name reported by default for pytest traces. + + This option can also be set with the ``DD_PYTEST_OPERATION_NAME`` environment + variable. + + Default: ``"pytest.test"`` +""" + +import os + +from ddtrace import config + + +# pytest default settings +config._add( + "pytest", + dict( + _default_service="pytest", + operation_name=os.getenv("DD_PYTEST_OPERATION_NAME", default="pytest.test"), + ), +) + + +def get_version(): + # type: () -> str + import pytest + + return pytest.__version__ + + +__all__ = ["get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest/constants.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest/constants.py new file mode 100644 index 0000000..f4ecab0 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest/constants.py @@ -0,0 +1,9 @@ +FRAMEWORK = "pytest" +KIND = "test" + +DDTRACE_HELP_MSG = "Enable tracing of pytest functions." +NO_DDTRACE_HELP_MSG = "Disable tracing of pytest functions." +DDTRACE_INCLUDE_CLASS_HELP_MSG = "Prepend 'ClassName.' to names of class-based tests." + +# XFail Reason +XFAIL_REASON = "pytest.xfail.reason" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest/newhooks.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest/newhooks.py new file mode 100644 index 0000000..c44fd0a --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest/newhooks.py @@ -0,0 +1,26 @@ +"""pytest-ddtrace hooks. + +These hooks are used to provide extra data used by the Datadog CI Visibility plugin. + +For example: module, suite, and test names for a given item. + +Note that these names will affect th display and reporting of tests in the Datadog UI, as well as information stored +the Intelligent Test Runner. Differing hook implementations may impact the behavior of Datadog CI Visibility products. +""" + +import pytest + + +@pytest.hookspec(firstresult=True) +def pytest_ddtrace_get_item_module_name(item: pytest.Item) -> str: + """Returns the module name to use when reporting CI Visibility results, should be unique""" + + +@pytest.hookspec(firstresult=True) +def pytest_ddtrace_get_item_suite_name(item: pytest.Item) -> str: + """Returns the suite name to use when reporting CI Visibility result, should be unique""" + + +@pytest.hookspec(firstresult=True) +def pytest_ddtrace_get_item_test_name(item: pytest.Item) -> str: + """Returns the test name to use when reporting CI Visibility result, should be unique""" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest/plugin.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest/plugin.py new file mode 100644 index 0000000..987edd3 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest/plugin.py @@ -0,0 +1,927 @@ +""" +This custom pytest plugin implements tracing for pytest by using pytest hooks. The plugin registers tracing code +to be run at specific points during pytest execution. The most important hooks used are: + + * pytest_sessionstart: during pytest session startup, a custom trace filter is configured to the global tracer to + only send test spans, which are generated by the plugin. + * pytest_runtest_protocol: this wraps around the execution of a pytest test function, which we trace. Most span + tags are generated and added in this function. We also store the span on the underlying pytest test item to + retrieve later when we need to report test status/result. + * pytest_runtest_makereport: this hook is used to set the test status/result tag, including skipped tests and + expected failures. + +""" +from doctest import DocTest +import json +import re +from typing import Dict # noqa:F401 + +from _pytest.nodes import get_fslocation_from_item +import pytest + +import ddtrace +from ddtrace.constants import SPAN_KIND +from ddtrace.contrib.coverage.data import _coverage_data +from ddtrace.contrib.coverage.patch import patch as patch_coverage +from ddtrace.contrib.coverage.patch import run_coverage_report +from ddtrace.contrib.coverage.patch import unpatch as unpatch_coverage +from ddtrace.contrib.coverage.utils import _is_coverage_invoked_by_coverage_run +from ddtrace.contrib.coverage.utils import _is_coverage_patched +from ddtrace.contrib.pytest.constants import DDTRACE_HELP_MSG +from ddtrace.contrib.pytest.constants import DDTRACE_INCLUDE_CLASS_HELP_MSG +from ddtrace.contrib.pytest.constants import FRAMEWORK +from ddtrace.contrib.pytest.constants import KIND +from ddtrace.contrib.pytest.constants import NO_DDTRACE_HELP_MSG +from ddtrace.contrib.pytest.constants import XFAIL_REASON +from ddtrace.contrib.unittest import unpatch as unpatch_unittest +from ddtrace.ext import SpanTypes +from ddtrace.ext import test +from ddtrace.internal.ci_visibility import CIVisibility as _CIVisibility +from ddtrace.internal.ci_visibility.constants import EVENT_TYPE as _EVENT_TYPE +from ddtrace.internal.ci_visibility.constants import ITR_UNSKIPPABLE_REASON +from ddtrace.internal.ci_visibility.constants import MODULE_ID as _MODULE_ID +from ddtrace.internal.ci_visibility.constants import MODULE_TYPE as _MODULE_TYPE +from ddtrace.internal.ci_visibility.constants import SESSION_ID as _SESSION_ID +from ddtrace.internal.ci_visibility.constants import SESSION_TYPE as _SESSION_TYPE +from ddtrace.internal.ci_visibility.constants import SKIPPED_BY_ITR_REASON +from ddtrace.internal.ci_visibility.constants import SUITE +from ddtrace.internal.ci_visibility.constants import SUITE_ID as _SUITE_ID +from ddtrace.internal.ci_visibility.constants import SUITE_TYPE as _SUITE_TYPE +from ddtrace.internal.ci_visibility.constants import TEST +from ddtrace.internal.ci_visibility.coverage import _module_has_dd_coverage_enabled +from ddtrace.internal.ci_visibility.coverage import _report_coverage_to_span +from ddtrace.internal.ci_visibility.coverage import _start_coverage +from ddtrace.internal.ci_visibility.coverage import _stop_coverage +from ddtrace.internal.ci_visibility.coverage import _switch_coverage_context +from ddtrace.internal.ci_visibility.utils import _add_pct_covered_to_span +from ddtrace.internal.ci_visibility.utils import _add_start_end_source_file_path_data_to_span +from ddtrace.internal.ci_visibility.utils import _generate_fully_qualified_module_name +from ddtrace.internal.ci_visibility.utils import _generate_fully_qualified_test_name +from ddtrace.internal.ci_visibility.utils import get_relative_or_absolute_path_for_path +from ddtrace.internal.ci_visibility.utils import take_over_logger_stream_handler +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.logger import get_logger + + +PATCH_ALL_HELP_MSG = "Call ddtrace.patch_all before running tests." + +log = get_logger(__name__) + +_global_skipped_elements = 0 + + +def _is_pytest_8_or_later(): + if hasattr(pytest, "version_tuple"): + return pytest.version_tuple >= (8, 0, 0) + return False + + +def encode_test_parameter(parameter): + param_repr = repr(parameter) + # if the representation includes an id() we'll remove it + # because it isn't constant across executions + return re.sub(r" at 0[xX][0-9a-fA-F]+", "", param_repr) + + +def is_enabled(config): + """Check if the ddtrace plugin is enabled.""" + return (config.getoption("ddtrace") or config.getini("ddtrace")) and not config.getoption("no-ddtrace") + + +def _is_pytest_cov_enabled(config) -> bool: + if not config.pluginmanager.get_plugin("pytest_cov"): + return False + cov_option = config.getoption("--cov", default=False) + nocov_option = config.getoption("--no-cov", default=False) + if nocov_option is True: + return False + if type(cov_option) == list and cov_option == [True] and not nocov_option: + return True + return cov_option + + +def _extract_span(item): + """Extract span from `pytest.Item` instance.""" + return getattr(item, "_datadog_span", None) + + +def _store_span(item, span): + """Store span at `pytest.Item` instance.""" + item._datadog_span = span + + +def _extract_module_span(item): + """Extract span from `pytest.Item` instance.""" + return getattr(item, "_datadog_span_module", None) + + +def _extract_ancestor_module_span(item): + """Return the first ancestor module span found""" + while item: + module_span = _extract_module_span(item) or _extract_span(item) + if module_span is not None and module_span.name == "pytest.test_module": + return module_span + item = _get_parent(item) + + +def _extract_ancestor_suite_span(item): + """Return the first ancestor suite span found""" + while item: + suite_span = _extract_span(item) + if suite_span is not None and suite_span.name == "pytest.test_suite": + return suite_span + item = _get_parent(item) + + +def _store_module_span(item, span): + """Store span at `pytest.Item` instance.""" + item._datadog_span_module = span + + +def _mark_failed(item): + """Store test failed status at `pytest.Item` instance.""" + item_parent = _get_parent(item) + if item_parent: + _mark_failed(item_parent) + item._failed = True + + +def _check_failed(item): + """Extract test failed status from `pytest.Item` instance.""" + return getattr(item, "_failed", False) + + +def _mark_not_skipped(item): + """Mark test suite/module/session `pytest.Item` as not skipped.""" + item_parent = _get_parent(item) + if item_parent: + _mark_not_skipped(item_parent) + item._fully_skipped = False + + +def _mark_not_skipped(item): + """Mark test suite/module/session `pytest.Item` as not skipped.""" + + item_parent = _get_parent(item) + + if item_parent: + _mark_not_skipped(item_parent) + item._fully_skipped = False + + +def _get_parent(item): + """Fetches the nearest parent that is not a directory. + + This is introduced as a workaround for pytest 8.0's introduction pytest.Dir objects. + """ + if item is None or item.parent is None: + return None + + if _is_pytest_8_or_later(): + # In pytest 8.0, the parent of a Package can be another Package. In previous versions, the parent was always + # a session. + if isinstance(item, pytest.Package): + while item.parent is not None and not isinstance(item.parent, pytest.Session): + item = item.parent + return item.parent + + while item.parent is not None and isinstance(item.parent, pytest.Dir): + item = item.parent + + return item.parent + + +def _mark_test_forced(test_item): + # type: (pytest.Test) -> None + test_span = _extract_span(test_item) + test_span.set_tag_str(test.ITR_FORCED_RUN, "true") + + suite_span = _extract_ancestor_suite_span(test_item) + suite_span.set_tag_str(test.ITR_FORCED_RUN, "true") + + module_span = _extract_ancestor_module_span(test_item) + module_span.set_tag_str(test.ITR_FORCED_RUN, "true") + + session_span = _extract_span(test_item.session) + session_span.set_tag_str(test.ITR_FORCED_RUN, "true") + + +def _mark_test_unskippable(test_item): + # type: (pytest.Test) -> None + test_span = _extract_span(test_item) + test_span.set_tag_str(test.ITR_UNSKIPPABLE, "true") + + suite_span = _extract_ancestor_suite_span(test_item) + suite_span.set_tag_str(test.ITR_UNSKIPPABLE, "true") + + module_span = _extract_ancestor_module_span(test_item) + module_span.set_tag_str(test.ITR_UNSKIPPABLE, "true") + + session_span = _extract_span(test_item.session) + session_span.set_tag_str(test.ITR_UNSKIPPABLE, "true") + + +def _check_fully_skipped(item): + """Check if test suite/module/session `pytest.Item` has `_fully_skipped` marker.""" + return getattr(item, "_fully_skipped", True) + + +def _mark_test_status(item, span): + """ + Given a `pytest.Item`, determine and set the test status of the corresponding span. + """ + item_parent = _get_parent(item) + + # If any child has failed, mark span as failed. + if _check_failed(item): + status = test.Status.FAIL.value + if item_parent: + _mark_failed(item_parent) + _mark_not_skipped(item_parent) + # If all children have been skipped, mark span as skipped. + elif _check_fully_skipped(item): + status = test.Status.SKIP.value + else: + status = test.Status.PASS.value + if item_parent: + _mark_not_skipped(item_parent) + span.set_tag_str(test.STATUS, status) + + +def _extract_reason(call): + if call.excinfo is not None: + return call.excinfo.value + + +def _get_pytest_command(config): + """Extract and re-create pytest session command from pytest config.""" + command = "pytest" + if getattr(config, "invocation_params", None): + command += " {}".format(" ".join(config.invocation_params.args)) + return command + + +def _get_module_path(item): + """Extract module path from a `pytest.Item` instance.""" + # type (pytest.Item) -> str + if not isinstance(item, (pytest.Package, pytest.Module)): + return None + + if _is_pytest_8_or_later() and isinstance(item, pytest.Package): + module_path = item.nodeid + + else: + module_path = item.nodeid.rpartition("/")[0] + + return module_path + + +def _is_test_unskippable(item): + return any( + [ + True + for marker in item.iter_markers(name="skipif") + if marker.args[0] is False + and "reason" in marker.kwargs + and marker.kwargs["reason"] is ITR_UNSKIPPABLE_REASON + ] + ) + + +def _module_is_package(pytest_package_item=None, pytest_module_item=None): + # Pytest 8+ module items have a pytest.Dir object as their parent instead of the session object + if _is_pytest_8_or_later(): + return isinstance(pytest_module_item.parent, pytest.Package) + + if pytest_package_item is None and pytest_module_item is not None: + return False + return True + + +def _start_test_module_span(item): + """ + Starts a test module span at the start of a new pytest test package. + Note that ``item`` is a ``pytest.Item`` object referencing the test being run. + """ + pytest_module_item = _find_pytest_item(item, pytest.Module) + pytest_package_item = _find_pytest_item(pytest_module_item, pytest.Package) + + is_package = _module_is_package(pytest_package_item, pytest_module_item) + + if is_package: + span_target_item = pytest_package_item + else: + span_target_item = pytest_module_item + + test_session_span = _extract_span(item.session) + test_module_span = _CIVisibility._instance.tracer._start_span( + "pytest.test_module", + service=_CIVisibility._instance._service, + span_type=SpanTypes.TEST, + activate=True, + child_of=test_session_span, + ) + test_module_span.set_tag_str(COMPONENT, "pytest") + test_module_span.set_tag_str(SPAN_KIND, KIND) + test_module_span.set_tag_str(test.FRAMEWORK, FRAMEWORK) + test_module_span.set_tag_str(test.FRAMEWORK_VERSION, pytest.__version__) + test_module_span.set_tag_str(test.COMMAND, _get_pytest_command(item.config)) + test_module_span.set_tag_str(_EVENT_TYPE, _MODULE_TYPE) + if test_session_span: + test_module_span.set_tag_str(_SESSION_ID, str(test_session_span.span_id)) + test_module_span.set_tag_str(_MODULE_ID, str(test_module_span.span_id)) + test_module_span.set_tag_str(test.MODULE, item.config.hook.pytest_ddtrace_get_item_module_name(item=item)) + test_module_span.set_tag_str(test.MODULE_PATH, _get_module_path(span_target_item)) + if is_package: + _store_span(span_target_item, test_module_span) + else: + _store_module_span(span_target_item, test_module_span) + + test_module_span.set_tag_str( + test.ITR_TEST_CODE_COVERAGE_ENABLED, + "true" if _CIVisibility._instance._collect_coverage_enabled else "false", + ) + + if _CIVisibility.test_skipping_enabled(): + test_module_span.set_tag_str(test.ITR_TEST_SKIPPING_ENABLED, "true") + test_module_span.set_tag( + test.ITR_TEST_SKIPPING_TYPE, SUITE if _CIVisibility._instance._suite_skipping_mode else TEST + ) + test_module_span.set_tag_str(test.ITR_TEST_SKIPPING_TESTS_SKIPPED, "false") + test_module_span.set_tag_str(test.ITR_DD_CI_ITR_TESTS_SKIPPED, "false") + test_module_span.set_tag_str(test.ITR_FORCED_RUN, "false") + test_module_span.set_tag_str(test.ITR_UNSKIPPABLE, "false") + else: + test_module_span.set_tag(test.ITR_TEST_SKIPPING_ENABLED, "false") + + return test_module_span, is_package + + +def _start_test_suite_span(item, test_module_span, should_enable_coverage=False): + """ + Starts a test suite span at the start of a new pytest test module. + """ + pytest_module_item = _find_pytest_item(item, pytest.Module) + test_session_span = _extract_span(pytest_module_item.session) + if test_module_span is None and isinstance(pytest_module_item.parent, pytest.Package): + test_module_span = _extract_span(pytest_module_item.parent) + parent_span = test_module_span + if parent_span is None: + parent_span = test_session_span + + test_suite_span = _CIVisibility._instance.tracer._start_span( + "pytest.test_suite", + service=_CIVisibility._instance._service, + span_type=SpanTypes.TEST, + activate=True, + child_of=parent_span, + ) + test_suite_span.set_tag_str(COMPONENT, "pytest") + test_suite_span.set_tag_str(SPAN_KIND, KIND) + test_suite_span.set_tag_str(test.FRAMEWORK, FRAMEWORK) + test_suite_span.set_tag_str(test.FRAMEWORK_VERSION, pytest.__version__) + test_suite_span.set_tag_str(test.COMMAND, _get_pytest_command(pytest_module_item.config)) + test_suite_span.set_tag_str(_EVENT_TYPE, _SUITE_TYPE) + if test_session_span: + test_suite_span.set_tag_str(_SESSION_ID, str(test_session_span.span_id)) + test_suite_span.set_tag_str(_SUITE_ID, str(test_suite_span.span_id)) + test_module_path = "" + if test_module_span is not None: + test_suite_span.set_tag_str(_MODULE_ID, str(test_module_span.span_id)) + test_suite_span.set_tag_str(test.MODULE, test_module_span.get_tag(test.MODULE)) + test_module_path = test_module_span.get_tag(test.MODULE_PATH) + test_suite_span.set_tag_str(test.MODULE_PATH, test_module_path) + test_suite_name = item.config.hook.pytest_ddtrace_get_item_suite_name(item=item) + test_suite_span.set_tag_str(test.SUITE, test_suite_name) + _store_span(pytest_module_item, test_suite_span) + + if should_enable_coverage and _module_has_dd_coverage_enabled(pytest): + fqn_module = _generate_fully_qualified_module_name(test_module_path, test_suite_name) + _switch_coverage_context(pytest._dd_coverage, fqn_module) + return test_suite_span + + +def pytest_addoption(parser): + """Add ddtrace options.""" + group = parser.getgroup("ddtrace") + + group._addoption( + "--ddtrace", + action="store_true", + dest="ddtrace", + default=False, + help=DDTRACE_HELP_MSG, + ) + + group._addoption( + "--no-ddtrace", + action="store_true", + dest="no-ddtrace", + default=False, + help=NO_DDTRACE_HELP_MSG, + ) + + group._addoption( + "--ddtrace-patch-all", + action="store_true", + dest="ddtrace-patch-all", + default=False, + help=PATCH_ALL_HELP_MSG, + ) + + group._addoption( + "--ddtrace-include-class-name", + action="store_true", + dest="ddtrace-include-class-name", + default=False, + help=DDTRACE_INCLUDE_CLASS_HELP_MSG, + ) + + parser.addini("ddtrace", DDTRACE_HELP_MSG, type="bool") + parser.addini("no-ddtrace", DDTRACE_HELP_MSG, type="bool") + parser.addini("ddtrace-patch-all", PATCH_ALL_HELP_MSG, type="bool") + parser.addini("ddtrace-include-class-name", DDTRACE_INCLUDE_CLASS_HELP_MSG, type="bool") + + +def pytest_configure(config): + unpatch_unittest() + config.addinivalue_line("markers", "dd_tags(**kwargs): add tags to current span") + if is_enabled(config): + take_over_logger_stream_handler() + _CIVisibility.enable(config=ddtrace.config.pytest) + if _is_pytest_cov_enabled(config): + patch_coverage() + + +def pytest_sessionstart(session): + if _CIVisibility.enabled: + log.debug("CI Visibility enabled - starting test session") + global _global_skipped_elements + _global_skipped_elements = 0 + test_session_span = _CIVisibility._instance.tracer.trace( + "pytest.test_session", + service=_CIVisibility._instance._service, + span_type=SpanTypes.TEST, + ) + test_session_span.set_tag_str(COMPONENT, "pytest") + test_session_span.set_tag_str(SPAN_KIND, KIND) + test_session_span.set_tag_str(test.FRAMEWORK, FRAMEWORK) + test_session_span.set_tag_str(test.FRAMEWORK_VERSION, pytest.__version__) + test_session_span.set_tag_str(_EVENT_TYPE, _SESSION_TYPE) + test_session_span.set_tag_str(test.COMMAND, _get_pytest_command(session.config)) + test_session_span.set_tag_str(_SESSION_ID, str(test_session_span.span_id)) + if _CIVisibility.test_skipping_enabled(): + test_session_span.set_tag_str(test.ITR_TEST_SKIPPING_ENABLED, "true") + test_session_span.set_tag( + test.ITR_TEST_SKIPPING_TYPE, SUITE if _CIVisibility._instance._suite_skipping_mode else TEST + ) + test_session_span.set_tag(test.ITR_TEST_SKIPPING_TESTS_SKIPPED, "false") + test_session_span.set_tag(test.ITR_DD_CI_ITR_TESTS_SKIPPED, "false") + test_session_span.set_tag_str(test.ITR_FORCED_RUN, "false") + test_session_span.set_tag_str(test.ITR_UNSKIPPABLE, "false") + else: + test_session_span.set_tag_str(test.ITR_TEST_SKIPPING_ENABLED, "false") + test_session_span.set_tag_str( + test.ITR_TEST_CODE_COVERAGE_ENABLED, + "true" if _CIVisibility._instance._collect_coverage_enabled else "false", + ) + if _is_coverage_invoked_by_coverage_run(): + patch_coverage() + if _CIVisibility._instance._collect_coverage_enabled and not _module_has_dd_coverage_enabled( + pytest, silent_mode=True + ): + pytest._dd_coverage = _start_coverage(session.config.rootdir) + + _store_span(session, test_session_span) + + +def pytest_sessionfinish(session, exitstatus): + if _CIVisibility.enabled: + log.debug("CI Visibility enabled - finishing test session") + test_session_span = _extract_span(session) + if test_session_span is not None: + if _CIVisibility.test_skipping_enabled(): + test_session_span.set_metric(test.ITR_TEST_SKIPPING_COUNT, _global_skipped_elements) + _mark_test_status(session, test_session_span) + pytest_cov_status = _is_pytest_cov_enabled(session.config) + invoked_by_coverage_run_status = _is_coverage_invoked_by_coverage_run() + if _is_coverage_patched() and (pytest_cov_status or invoked_by_coverage_run_status): + if invoked_by_coverage_run_status and not pytest_cov_status: + run_coverage_report() + _add_pct_covered_to_span(_coverage_data, test_session_span) + unpatch_coverage() + test_session_span.finish() + _CIVisibility.disable() + + +@pytest.fixture(scope="function") +def ddspan(request): + """Return the :class:`ddtrace.span.Span` instance associated with the + current test when Datadog CI Visibility is enabled. + """ + if _CIVisibility.enabled: + return _extract_span(request.node) + + +@pytest.fixture(scope="session") +def ddtracer(): + """Return the :class:`ddtrace.tracer.Tracer` instance for Datadog CI + visibility if it is enabled, otherwise return the default Datadog tracer. + """ + if _CIVisibility.enabled: + return _CIVisibility._instance.tracer + return ddtrace.tracer + + +@pytest.fixture(scope="session", autouse=True) +def patch_all(request): + """Patch all available modules for Datadog tracing when ddtrace-patch-all + is specified in command or .ini. + """ + if request.config.getoption("ddtrace-patch-all") or request.config.getini("ddtrace-patch-all"): + ddtrace.patch_all() + + +def _find_pytest_item(item, pytest_item_type): + """ + Given a `pytest.Item`, traverse upwards until we find a specified `pytest.Package` or `pytest.Module` item, + or return None. + """ + if item is None: + return None + if pytest_item_type not in [pytest.Package, pytest.Module]: + return None + parent = _get_parent(item) + while not isinstance(parent, pytest_item_type) and parent is not None: + parent = parent.parent + return parent + + +def _get_test_class_hierarchy(item): + """ + Given a `pytest.Item` function item, traverse upwards to collect and return a string listing the + test class hierarchy, or an empty string if there are no test classes. + """ + parent = _get_parent(item) + test_class_hierarchy = [] + while parent is not None: + if isinstance(parent, pytest.Class): + test_class_hierarchy.insert(0, parent.name) + parent = parent.parent + return ".".join(test_class_hierarchy) + + +def pytest_collection_modifyitems(session, config, items): + if _CIVisibility.test_skipping_enabled(): + skip = pytest.mark.skip(reason=SKIPPED_BY_ITR_REASON) + + items_to_skip_by_module = {} + current_suite_has_unskippable_test = False + + for item in items: + test_is_unskippable = _is_test_unskippable(item) + + item_name = item.config.hook.pytest_ddtrace_get_item_test_name(item=item) + + if test_is_unskippable: + log.debug( + "Test %s in module %s (file: %s ) is marked as unskippable", + item_name, + item.module.__name__, + item.module.__file__, + ) + item._dd_itr_test_unskippable = True + + # Due to suite skipping mode, defer adding ITR skip marker until unskippable status of the suite has been + # fully resolved because Pytest markers cannot be dynamically removed + if _CIVisibility._instance._suite_skipping_mode: + if item.module not in items_to_skip_by_module: + items_to_skip_by_module[item.module] = [] + current_suite_has_unskippable_test = False + + if test_is_unskippable and not current_suite_has_unskippable_test: + current_suite_has_unskippable_test = True + # Retroactively mark collected tests as forced: + for item_to_skip in items_to_skip_by_module[item.module]: + item_to_skip._dd_itr_forced = True + items_to_skip_by_module[item.module] = [] + + if _CIVisibility._instance._should_skip_path(str(get_fslocation_from_item(item)[0]), item_name): + if test_is_unskippable or ( + _CIVisibility._instance._suite_skipping_mode and current_suite_has_unskippable_test + ): + item._dd_itr_forced = True + else: + items_to_skip_by_module.setdefault(item.module, []).append(item) + + # Mark remaining tests that should be skipped + for items_to_skip in items_to_skip_by_module.values(): + for item_to_skip in items_to_skip: + item_to_skip.add_marker(skip) + + +@pytest.hookimpl(tryfirst=True, hookwrapper=True) +def pytest_runtest_protocol(item, nextitem): + if not _CIVisibility.enabled: + yield + return + + is_skipped = bool( + item.get_closest_marker("skip") + or any([marker for marker in item.iter_markers(name="skipif") if marker.args[0] is True]) + ) + is_skipped_by_itr = bool( + is_skipped + and any( + [ + marker + for marker in item.iter_markers(name="skip") + if "reason" in marker.kwargs and marker.kwargs["reason"] == SKIPPED_BY_ITR_REASON + ] + ) + ) + + test_session_span = _extract_span(item.session) + + pytest_module_item = _find_pytest_item(item, pytest.Module) + pytest_package_item = _find_pytest_item(pytest_module_item, pytest.Package) + + module_is_package = True + + test_module_span = _extract_span(pytest_package_item) + if not test_module_span: + test_module_span = _extract_module_span(pytest_module_item) + if test_module_span: + module_is_package = False + + if test_module_span is None: + test_module_span, module_is_package = _start_test_module_span(item) + + if _CIVisibility.test_skipping_enabled() and test_module_span.get_metric(test.ITR_TEST_SKIPPING_COUNT) is None: + test_module_span.set_tag( + test.ITR_TEST_SKIPPING_TYPE, SUITE if _CIVisibility._instance._suite_skipping_mode else TEST + ) + test_module_span.set_metric(test.ITR_TEST_SKIPPING_COUNT, 0) + + test_suite_span = _extract_ancestor_suite_span(item) + if pytest_module_item is not None and test_suite_span is None: + # Start coverage for the test suite if coverage is enabled + # In ITR suite skipping mode, all tests in a skipped suite should be marked + # as skipped + test_suite_span = _start_test_suite_span( + item, + test_module_span, + should_enable_coverage=( + _CIVisibility._instance._suite_skipping_mode + and _CIVisibility._instance._collect_coverage_enabled + and not is_skipped_by_itr + ), + ) + + if is_skipped_by_itr: + test_module_span._metrics[test.ITR_TEST_SKIPPING_COUNT] += 1 + global _global_skipped_elements + _global_skipped_elements += 1 + test_module_span.set_tag_str(test.ITR_TEST_SKIPPING_TESTS_SKIPPED, "true") + test_module_span.set_tag_str(test.ITR_DD_CI_ITR_TESTS_SKIPPED, "true") + + test_session_span.set_tag_str(test.ITR_TEST_SKIPPING_TESTS_SKIPPED, "true") + test_session_span.set_tag_str(test.ITR_DD_CI_ITR_TESTS_SKIPPED, "true") + + with _CIVisibility._instance.tracer._start_span( + ddtrace.config.pytest.operation_name, + service=_CIVisibility._instance._service, + resource=item.nodeid, + span_type=SpanTypes.TEST, + activate=True, + ) as span: + span.set_tag_str(COMPONENT, "pytest") + span.set_tag_str(SPAN_KIND, KIND) + span.set_tag_str(test.FRAMEWORK, FRAMEWORK) + span.set_tag_str(_EVENT_TYPE, SpanTypes.TEST) + test_name = item.config.hook.pytest_ddtrace_get_item_test_name(item=item) + test_module_path = test_module_span.get_tag(test.MODULE_PATH) + span.set_tag_str(test.NAME, test_name) + span.set_tag_str(test.COMMAND, _get_pytest_command(item.config)) + if test_session_span: + span.set_tag_str(_SESSION_ID, str(test_session_span.span_id)) + + span.set_tag_str(_MODULE_ID, str(test_module_span.span_id)) + span.set_tag_str(test.MODULE, test_module_span.get_tag(test.MODULE)) + span.set_tag_str(test.MODULE_PATH, test_module_path) + + span.set_tag_str(_SUITE_ID, str(test_suite_span.span_id)) + test_class_hierarchy = _get_test_class_hierarchy(item) + if test_class_hierarchy: + span.set_tag_str(test.CLASS_HIERARCHY, test_class_hierarchy) + if hasattr(item, "dtest") and isinstance(item.dtest, DocTest): + test_suite_name = "{}.py".format(item.dtest.globs["__name__"]) + span.set_tag_str(test.SUITE, test_suite_name) + else: + test_suite_name = test_suite_span.get_tag(test.SUITE) + span.set_tag_str(test.SUITE, test_suite_name) + + span.set_tag_str(test.TYPE, SpanTypes.TEST) + span.set_tag_str(test.FRAMEWORK_VERSION, pytest.__version__) + + if item.location and item.location[0]: + _CIVisibility.set_codeowners_of(item.location[0], span=span) + if hasattr(item, "_obj"): + test_method_object = item._obj + _add_start_end_source_file_path_data_to_span(span, test_method_object, test_name, item.config.rootdir) + + # We preemptively set FAIL as a status, because if pytest_runtest_makereport is not called + # (where the actual test status is set), it means there was a pytest error + span.set_tag_str(test.STATUS, test.Status.FAIL.value) + + # Parameterized test cases will have a `callspec` attribute attached to the pytest Item object. + # Pytest docs: https://docs.pytest.org/en/6.2.x/reference.html#pytest.Function + if getattr(item, "callspec", None): + parameters = {"arguments": {}, "metadata": {}} # type: Dict[str, Dict[str, str]] + for param_name, param_val in item.callspec.params.items(): + try: + parameters["arguments"][param_name] = encode_test_parameter(param_val) + except Exception: + parameters["arguments"][param_name] = "Could not encode" + log.warning("Failed to encode %r", param_name, exc_info=True) + span.set_tag_str(test.PARAMETERS, json.dumps(parameters)) + + markers = [marker.kwargs for marker in item.iter_markers(name="dd_tags")] + for tags in markers: + span.set_tags(tags) + _store_span(item, span) + + # Items are marked ITR-unskippable regardless of other unrelateed skipping status + if getattr(item, "_dd_itr_test_unskippable", False) or getattr(item, "_dd_itr_suite_unskippable", False): + _mark_test_unskippable(item) + if not is_skipped: + if getattr(item, "_dd_itr_forced", False): + _mark_test_forced(item) + + coverage_per_test = ( + not _CIVisibility._instance._suite_skipping_mode + and _CIVisibility._instance._collect_coverage_enabled + and not is_skipped + ) + root_directory = str(item.config.rootdir) + if coverage_per_test and _module_has_dd_coverage_enabled(pytest): + fqn_test = _generate_fully_qualified_test_name(test_module_path, test_suite_name, test_name) + _switch_coverage_context(pytest._dd_coverage, fqn_test) + # Run the actual test + yield + + # Finish coverage for the test suite if coverage is enabled + if coverage_per_test and _module_has_dd_coverage_enabled(pytest): + _report_coverage_to_span(pytest._dd_coverage, span, root_directory) + + nextitem_pytest_module_item = _find_pytest_item(nextitem, pytest.Module) + if nextitem is None or nextitem_pytest_module_item != pytest_module_item and not test_suite_span.finished: + _mark_test_status(pytest_module_item, test_suite_span) + # Finish coverage for the test suite if coverage is enabled + # In ITR suite skipping mode, all tests in a skipped suite should be marked + # as skipped + if ( + _CIVisibility._instance._suite_skipping_mode + and _CIVisibility._instance._collect_coverage_enabled + and not is_skipped_by_itr + and _module_has_dd_coverage_enabled(pytest) + ): + _report_coverage_to_span(pytest._dd_coverage, test_suite_span, root_directory) + test_suite_span.finish() + + if not module_is_package: + test_module_span.set_tag_str(test.STATUS, test_suite_span.get_tag(test.STATUS)) + test_module_span.finish() + else: + nextitem_pytest_package_item = _find_pytest_item(nextitem, pytest.Package) + if ( + nextitem is None + or nextitem_pytest_package_item != pytest_package_item + and not test_module_span.finished + ): + _mark_test_status(pytest_package_item, test_module_span) + test_module_span.finish() + + if ( + nextitem is None + and _CIVisibility._instance._collect_coverage_enabled + and _module_has_dd_coverage_enabled(pytest) + ): + _stop_coverage(pytest) + + +@pytest.hookimpl(hookwrapper=True) +def pytest_runtest_makereport(item, call): + """Store outcome for tracing.""" + outcome = yield + + if not _CIVisibility.enabled: + return + + span = _extract_span(item) + if span is None: + return + + is_setup_or_teardown = call.when == "setup" or call.when == "teardown" + has_exception = call.excinfo is not None + + if is_setup_or_teardown and not has_exception: + return + + result = outcome.get_result() + xfail = hasattr(result, "wasxfail") or "xfail" in result.keywords + has_skip_keyword = any(x in result.keywords for x in ["skip", "skipif", "skipped"]) + + # If run with --runxfail flag, tests behave as if they were not marked with xfail, + # that's why no XFAIL_REASON or test.RESULT tags will be added. + if result.skipped: + if xfail and not has_skip_keyword: + # XFail tests that fail are recorded skipped by pytest, should be passed instead + span.set_tag_str(test.STATUS, test.Status.PASS.value) + _mark_not_skipped(_get_parent(item)) + if not item.config.option.runxfail: + span.set_tag_str(test.RESULT, test.Status.XFAIL.value) + span.set_tag_str(XFAIL_REASON, getattr(result, "wasxfail", "XFail")) + else: + span.set_tag_str(test.STATUS, test.Status.SKIP.value) + reason = _extract_reason(call) + if reason is not None: + span.set_tag_str(test.SKIP_REASON, str(reason)) + if str(reason) == SKIPPED_BY_ITR_REASON: + if _CIVisibility._instance._suite_skipping_mode: + suite_span = _extract_ancestor_suite_span(item) + if suite_span is not None: + suite_span.set_tag_str(test.ITR_SKIPPED, "true") + span.set_tag_str(test.ITR_SKIPPED, "true") + elif result.passed: + _mark_not_skipped(_get_parent(item)) + span.set_tag_str(test.STATUS, test.Status.PASS.value) + if xfail and not has_skip_keyword and not item.config.option.runxfail: + # XPass (strict=False) are recorded passed by pytest + span.set_tag_str(XFAIL_REASON, getattr(result, "wasxfail", "XFail")) + span.set_tag_str(test.RESULT, test.Status.XPASS.value) + else: + # Store failure in test suite `pytest.Item` to propagate to test suite spans + _mark_failed(_get_parent(item)) + _mark_not_skipped(_get_parent(item)) + span.set_tag_str(test.STATUS, test.Status.FAIL.value) + if xfail and not has_skip_keyword and not item.config.option.runxfail: + # XPass (strict=True) are recorded failed by pytest, longrepr contains reason + span.set_tag_str(XFAIL_REASON, getattr(result, "longrepr", "XFail")) + span.set_tag_str(test.RESULT, test.Status.XPASS.value) + if call.excinfo: + span.set_exc_info(call.excinfo.type, call.excinfo.value, call.excinfo.tb) + + +@pytest.hookimpl +def pytest_addhooks(pluginmanager): + from ddtrace.contrib.pytest import newhooks + + pluginmanager.add_hookspecs(newhooks) + + +@pytest.hookimpl(trylast=True) +def pytest_ddtrace_get_item_module_name(item): + pytest_module_item = _find_pytest_item(item, pytest.Module) + pytest_package_item = _find_pytest_item(pytest_module_item, pytest.Package) + + if _module_is_package(pytest_package_item, pytest_module_item): + if _is_pytest_8_or_later(): + # pytest 8.0.0 no longer treats Packages as Module/File, so we replicate legacy behavior by concatenating + # parent package names in reverse until we hit a non-Package-type item + # https://github.com/pytest-dev/pytest/issues/11137 + package_names = [] + current_package = pytest_package_item + while isinstance(current_package, pytest.Package): + package_names.append(str(current_package.name)) + current_package = current_package.parent + + return ".".join(package_names[::-1]) + + return pytest_package_item.module.__name__ + + return pytest_module_item.nodeid.rpartition("/")[0].replace("/", ".") + + +@pytest.hookimpl(trylast=True) +def pytest_ddtrace_get_item_suite_name(item): + """ + Extract suite name from a `pytest.Item` instance. + If the module path doesn't exist, the suite path will be reported in full. + """ + pytest_module_item = _find_pytest_item(item, pytest.Module) + test_module_path = _get_module_path(pytest_module_item) + if test_module_path: + if not pytest_module_item.nodeid.startswith(test_module_path): + log.warning("Suite path is not under module path: '%s' '%s'", pytest_module_item.nodeid, test_module_path) + return get_relative_or_absolute_path_for_path(pytest_module_item.nodeid, test_module_path) + return pytest_module_item.nodeid + + +@pytest.hookimpl(trylast=True) +def pytest_ddtrace_get_item_test_name(item): + """Extract name from item, prepending class if desired""" + if hasattr(item, "cls") and item.cls: + if item.config.getoption("ddtrace-include-class-name") or item.config.getini("ddtrace-include-class-name"): + return "%s.%s" % (item.cls.__name__, item.name) + return item.name diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest_bdd/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest_bdd/__init__.py new file mode 100644 index 0000000..b1cc670 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest_bdd/__init__.py @@ -0,0 +1,47 @@ +""" +The pytest-bdd integration traces executions of scenarios and steps. + +Enabling +~~~~~~~~ + +Please follow the instructions for enabling `pytest` integration. + +.. note:: + The ddtrace.pytest_bdd plugin for pytest-bdd has the side effect of importing + the ddtrace package and starting a global tracer. + + If this is causing issues for your pytest-bdd runs where traced execution of + tests is not enabled, you can deactivate the plugin:: + + [pytest] + addopts = -p no:ddtrace.pytest_bdd + + See the `pytest documentation + `_ + for more details. + +""" + +from ddtrace import config + + +# pytest-bdd default settings +config._add( + "pytest_bdd", + dict( + _default_service="pytest_bdd", + ), +) + + +def get_version(): + # type: () -> str + try: + import importlib.metadata as importlib_metadata + except ImportError: + import importlib_metadata # type: ignore[no-redef] + + return str(importlib_metadata.version("pytest-bdd")) + + +__all__ = ["get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest_bdd/constants.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest_bdd/constants.py new file mode 100644 index 0000000..2dd377f --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest_bdd/constants.py @@ -0,0 +1,2 @@ +FRAMEWORK = "pytest_bdd" +STEP_KIND = "pytest_bdd.step" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest_bdd/plugin.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest_bdd/plugin.py new file mode 100644 index 0000000..99459d2 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest_bdd/plugin.py @@ -0,0 +1,139 @@ +import json +import os +import sys + +import pytest + +from ddtrace.contrib.pytest.plugin import _extract_span as _extract_feature_span +from ddtrace.contrib.pytest_bdd import get_version +from ddtrace.contrib.pytest_bdd.constants import FRAMEWORK +from ddtrace.contrib.pytest_bdd.constants import STEP_KIND +from ddtrace.ext import test +from ddtrace.internal.ci_visibility import CIVisibility as _CIVisibility +from ddtrace.internal.logger import get_logger + + +log = get_logger(__name__) + + +def _extract_span(item): + """Extract span from `step_func`.""" + return getattr(item, "_datadog_span", None) + + +def _store_span(item, span): + """Store span at `step_func`.""" + item._datadog_span = span + + +def _extract_step_func_args(step, step_func, step_func_args): + """Backwards-compatible get arguments from step_func or step_func_args""" + if not (hasattr(step_func, "parser") or hasattr(step_func, "_pytest_bdd_parsers")): + return step_func_args + + # store parsed step arguments + try: + parsers = [step_func.parser] + except AttributeError: + try: + # pytest-bdd >= 6.0.0 + parsers = step_func._pytest_bdd_parsers + except AttributeError: + parsers = [] + for parser in parsers: + if parser is not None: + converters = getattr(step_func, "converters", {}) + parameters = {} + try: + for arg, value in parser.parse_arguments(step.name).items(): + try: + if arg in converters: + value = converters[arg](value) + except Exception: + log.debug("argument conversion failed.") + parameters[arg] = value + except Exception: + log.debug("argument parsing failed.") + + return parameters or None + + +def _get_step_func_args_json(step, step_func, step_func_args): + """Get step function args as JSON, catching serialization errors""" + try: + extracted_step_func_args = _extract_step_func_args(step, step_func, step_func_args) + if extracted_step_func_args: + return json.dumps(extracted_step_func_args) + return None + except TypeError as err: + log.debug("Could not serialize arguments", exc_info=True) + return json.dumps({"error_serializing_args": str(err)}) + + +def pytest_configure(config): + if config.pluginmanager.hasplugin("pytest-bdd"): + config.pluginmanager.register(_PytestBddPlugin(), "_datadog-pytest-bdd") + + +class _PytestBddPlugin: + def __init__(self): + self.framework_version = get_version() + + @staticmethod + @pytest.hookimpl(tryfirst=True) + def pytest_bdd_before_scenario(request, feature, scenario): + if _CIVisibility.enabled: + span = _extract_feature_span(request.node) + if span is not None: + location = os.path.relpath(scenario.feature.filename, str(request.config.rootdir)) + span.set_tag(test.NAME, scenario.name) + span.set_tag(test.SUITE, location) # override test suite name with .feature location + + _CIVisibility.set_codeowners_of(location, span=span) + + @pytest.hookimpl(tryfirst=True) + def pytest_bdd_before_step(self, request, feature, scenario, step, step_func): + if _CIVisibility.enabled: + feature_span = _extract_feature_span(request.node) + span = _CIVisibility._instance.tracer.start_span( + step.type, + resource=step.name, + span_type=STEP_KIND, + child_of=feature_span, + activate=True, + ) + span.set_tag_str("component", "pytest_bdd") + + span.set_tag(test.FRAMEWORK, FRAMEWORK) + span.set_tag(test.FRAMEWORK_VERSION, self.framework_version) + + location = os.path.relpath(step_func.__code__.co_filename, str(request.config.rootdir)) + span.set_tag(test.FILE, location) + _CIVisibility.set_codeowners_of(location, span=span) + + _store_span(step_func, span) + + @staticmethod + @pytest.hookimpl(trylast=True) + def pytest_bdd_after_step(request, feature, scenario, step, step_func, step_func_args): + span = _extract_span(step_func) + if span is not None: + step_func_args_json = _get_step_func_args_json(step, step_func, step_func_args) + if step_func_args: + span.set_tag(test.PARAMETERS, step_func_args_json) + span.finish() + + @staticmethod + def pytest_bdd_step_error(request, feature, scenario, step, step_func, step_func_args, exception): + span = _extract_span(step_func) + if span is not None: + if hasattr(exception, "__traceback__"): + tb = exception.__traceback__ + else: + # PY2 compatibility workaround + _, _, tb = sys.exc_info() + step_func_args_json = _get_step_func_args_json(step, step_func, step_func_args) + if step_func_args: + span.set_tag(test.PARAMETERS, step_func_args_json) + span.set_exc_info(type(exception), exception, tb) + span.finish() diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest_benchmark/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest_benchmark/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest_benchmark/constants.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest_benchmark/constants.py new file mode 100644 index 0000000..9742085 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest_benchmark/constants.py @@ -0,0 +1,58 @@ +BENCHMARK_INFO = "benchmark.duration.info" +BENCHMARK_MEAN = "benchmark.duration.mean" +BENCHMARK_RUN = "benchmark.duration.runs" + +STATISTICS_HD15IQR = "benchmark.duration.statistics.hd15iqr" +STATISTICS_IQR = "benchmark.duration.statistics.iqr" +STATISTICS_IQR_OUTLIERS = "benchmark.duration.statistics.iqr_outliers" +STATISTICS_LD15IQR = "benchmark.duration.statistics.ld15iqr" +STATISTICS_MAX = "benchmark.duration.statistics.max" +STATISTICS_MEAN = "benchmark.duration.statistics.mean" +STATISTICS_MEDIAN = "benchmark.duration.statistics.median" +STATISTICS_MIN = "benchmark.duration.statistics.min" +STATISTICS_N = "benchmark.duration.statistics.n" +STATISTICS_OPS = "benchmark.duration.statistics.ops" +STATISTICS_OUTLIERS = "benchmark.duration.statistics.outliers" +STATISTICS_Q1 = "benchmark.duration.statistics.q1" +STATISTICS_Q3 = "benchmark.duration.statistics.q3" +STATISTICS_STDDEV = "benchmark.duration.statistics.std_dev" +STATISTICS_STDDEV_OUTLIERS = "benchmark.duration.statistics.std_dev_outliers" +STATISTICS_TOTAL = "benchmark.duration.statistics.total" + +PLUGIN_HD15IQR = "hd15iqr" +PLUGIN_IQR = "iqr" +PLUGIN_IQR_OUTLIERS = "iqr_outliers" +PLUGIN_LD15IQR = "ld15iqr" +PLUGIN_MAX = "max" +PLUGIN_MEAN = "mean" +PLUGIN_MEDIAN = "median" +PLUGIN_MIN = "min" +PLUGIN_OPS = "ops" +PLUGIN_OUTLIERS = "outliers" +PLUGIN_Q1 = "q1" +PLUGIN_Q3 = "q3" +PLUGIN_ROUNDS = "rounds" +PLUGIN_STDDEV = "stddev" +PLUGIN_STDDEV_OUTLIERS = "stddev_outliers" +PLUGIN_TOTAL = "total" + +PLUGIN_METRICS = { + BENCHMARK_MEAN: PLUGIN_MEAN, + BENCHMARK_RUN: PLUGIN_ROUNDS, + STATISTICS_HD15IQR: PLUGIN_HD15IQR, + STATISTICS_IQR: PLUGIN_IQR, + STATISTICS_IQR_OUTLIERS: PLUGIN_IQR_OUTLIERS, + STATISTICS_LD15IQR: PLUGIN_LD15IQR, + STATISTICS_MAX: PLUGIN_MAX, + STATISTICS_MEAN: PLUGIN_MEAN, + STATISTICS_MEDIAN: PLUGIN_MEDIAN, + STATISTICS_MIN: PLUGIN_MIN, + STATISTICS_OPS: PLUGIN_OPS, + STATISTICS_OUTLIERS: PLUGIN_OUTLIERS, + STATISTICS_Q1: PLUGIN_Q1, + STATISTICS_Q3: PLUGIN_Q3, + STATISTICS_N: PLUGIN_ROUNDS, + STATISTICS_STDDEV: PLUGIN_STDDEV, + STATISTICS_STDDEV_OUTLIERS: PLUGIN_STDDEV_OUTLIERS, + STATISTICS_TOTAL: PLUGIN_TOTAL, +} diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest_benchmark/plugin.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest_benchmark/plugin.py new file mode 100644 index 0000000..381cc19 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/pytest_benchmark/plugin.py @@ -0,0 +1,33 @@ +import pytest + +from ddtrace.contrib.pytest.plugin import _extract_span +from ddtrace.contrib.pytest_benchmark.constants import BENCHMARK_INFO +from ddtrace.contrib.pytest_benchmark.constants import PLUGIN_METRICS +from ddtrace.contrib.pytest_benchmark.constants import PLUGIN_OUTLIERS +from ddtrace.ext.test import TEST_TYPE + + +def pytest_configure(config): + if config.pluginmanager.hasplugin("benchmark"): + config.pluginmanager.register(_PytestBenchmarkPlugin(), "_datadog-pytest-benchmark") + + +class _PytestBenchmarkPlugin: + @pytest.hookimpl() + def pytest_runtest_makereport(self, item, call): + fixture = hasattr(item, "funcargs") and item.funcargs.get("benchmark") + if fixture and fixture.stats: + stat_object = fixture.stats.stats + span = _extract_span(item) + + if span is None: + return + + span.set_tag_str(TEST_TYPE, "benchmark") + span.set_tag_str(BENCHMARK_INFO, "Time") + for span_path, tag in PLUGIN_METRICS.items(): + if hasattr(stat_object, tag): + if tag == PLUGIN_OUTLIERS: + span.set_tag_str(span_path, getattr(stat_object, tag)) + continue + span.set_tag(span_path, getattr(stat_object, tag)) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/redis/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/redis/__init__.py new file mode 100644 index 0000000..9a29130 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/redis/__init__.py @@ -0,0 +1,80 @@ +""" +The redis integration traces redis requests. + + +Enabling +~~~~~~~~ + +The redis integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(redis=True) + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.redis["service"] + + The service name reported by default for redis traces. + + This option can also be set with the ``DD_REDIS_SERVICE`` environment + variable. + + Default: ``"redis"`` + + +.. py:data:: ddtrace.config.redis["cmd_max_length"] + + Max allowable size for the redis command span tag. + Anything beyond the max length will be replaced with ``"..."``. + + This option can also be set with the ``DD_REDIS_CMD_MAX_LENGTH`` environment + variable. + + Default: ``1000`` + + +.. py:data:: ddtrace.config.redis["resource_only_command"] + + The span resource will only include the command executed. To include all + arguments in the span resource, set this value to ``False``. + + This option can also be set with the ``DD_REDIS_RESOURCE_ONLY_COMMAND`` environment + variable. + + Default: ``True`` + + +Instance Configuration +~~~~~~~~~~~~~~~~~~~~~~ + +To configure particular redis instances use the :class:`Pin ` API:: + + import redis + from ddtrace import Pin + + client = redis.StrictRedis(host="localhost", port=6379) + + # Override service name for this instance + Pin.override(client, service="my-custom-queue") + + # Traces reported for this client will now have "my-custom-queue" + # as the service name. + client.get("my-key") +""" + +from ...internal.utils.importlib import require_modules + + +required_modules = ["redis", "redis.client"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + + __all__ = ["patch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/redis/asyncio_patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/redis/asyncio_patch.py new file mode 100644 index 0000000..f444fef --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/redis/asyncio_patch.py @@ -0,0 +1,40 @@ +from ddtrace import config + +from ...internal.utils.formats import stringify_cache_args +from ...pin import Pin +from ..trace_utils_redis import _run_redis_command_async +from ..trace_utils_redis import _trace_redis_cmd +from ..trace_utils_redis import _trace_redis_execute_async_cluster_pipeline +from ..trace_utils_redis import _trace_redis_execute_pipeline + + +# +# tracing async functions +# +async def traced_async_execute_command(func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return await func(*args, **kwargs) + + with _trace_redis_cmd(pin, config.redis, instance, args) as span: + return await _run_redis_command_async(span=span, func=func, args=args, kwargs=kwargs) + + +async def traced_async_execute_pipeline(func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return await func(*args, **kwargs) + + cmds = [stringify_cache_args(c, cmd_max_len=config.redis.cmd_max_length) for c, _ in instance.command_stack] + with _trace_redis_execute_pipeline(pin, config.redis, cmds, instance): + return await func(*args, **kwargs) + + +async def traced_async_execute_cluster_pipeline(func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return await func(*args, **kwargs) + + cmds = [stringify_cache_args(c.args, cmd_max_len=config.redis.cmd_max_length) for c in instance._command_stack] + with _trace_redis_execute_async_cluster_pipeline(pin, config.redis, cmds, instance): + return await func(*args, **kwargs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/redis/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/redis/patch.py new file mode 100644 index 0000000..c4bf0f4 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/redis/patch.py @@ -0,0 +1,164 @@ +import os + +import redis + +from ddtrace import config +from ddtrace.vendor import wrapt + +from ...internal.schema import schematize_service_name +from ...internal.utils.formats import CMD_MAX_LEN +from ...internal.utils.formats import asbool +from ...internal.utils.formats import stringify_cache_args +from ...pin import Pin +from ..trace_utils import unwrap +from ..trace_utils_redis import _run_redis_command +from ..trace_utils_redis import _trace_redis_cmd +from ..trace_utils_redis import _trace_redis_execute_pipeline + + +config._add( + "redis", + { + "_default_service": schematize_service_name("redis"), + "cmd_max_length": int(os.getenv("DD_REDIS_CMD_MAX_LENGTH", CMD_MAX_LEN)), + "resource_only_command": asbool(os.getenv("DD_REDIS_RESOURCE_ONLY_COMMAND", True)), + }, +) + + +def get_version(): + # type: () -> str + return getattr(redis, "__version__", "") + + +def patch(): + """Patch the instrumented methods + + This duplicated doesn't look nice. The nicer alternative is to use an ObjectProxy on top + of Redis and StrictRedis. However, it means that any "import redis.Redis" won't be instrumented. + """ + if getattr(redis, "_datadog_patch", False): + return + redis._datadog_patch = True + + _w = wrapt.wrap_function_wrapper + + if redis.VERSION < (3, 0, 0): + _w("redis", "StrictRedis.execute_command", traced_execute_command(config.redis)) + _w("redis", "StrictRedis.pipeline", traced_pipeline) + _w("redis", "Redis.pipeline", traced_pipeline) + _w("redis.client", "BasePipeline.execute", traced_execute_pipeline(config.redis, False)) + _w("redis.client", "BasePipeline.immediate_execute_command", traced_execute_command(config.redis)) + else: + _w("redis", "Redis.execute_command", traced_execute_command(config.redis)) + _w("redis", "Redis.pipeline", traced_pipeline) + _w("redis.client", "Pipeline.execute", traced_execute_pipeline(config.redis, False)) + _w("redis.client", "Pipeline.immediate_execute_command", traced_execute_command(config.redis)) + if redis.VERSION >= (4, 1): + # Redis v4.1 introduced support for redis clusters and rediscluster package was deprecated. + # https://github.com/redis/redis-py/commit/9db1eec71b443b8e7e74ff503bae651dc6edf411 + _w("redis.cluster", "RedisCluster.execute_command", traced_execute_command(config.redis)) + _w("redis.cluster", "RedisCluster.pipeline", traced_pipeline) + _w("redis.cluster", "ClusterPipeline.execute", traced_execute_pipeline(config.redis, True)) + Pin(service=None).onto(redis.cluster.RedisCluster) + # Avoid mypy invalid syntax errors when parsing Python 2 files + if redis.VERSION >= (4, 2, 0): + from .asyncio_patch import traced_async_execute_command + from .asyncio_patch import traced_async_execute_pipeline + + _w("redis.asyncio.client", "Redis.execute_command", traced_async_execute_command) + _w("redis.asyncio.client", "Redis.pipeline", traced_pipeline) + _w("redis.asyncio.client", "Pipeline.execute", traced_async_execute_pipeline) + _w("redis.asyncio.client", "Pipeline.immediate_execute_command", traced_async_execute_command) + Pin(service=None).onto(redis.asyncio.Redis) + + if redis.VERSION >= (4, 3, 0): + from .asyncio_patch import traced_async_execute_command + + _w("redis.asyncio.cluster", "RedisCluster.execute_command", traced_async_execute_command) + + if redis.VERSION >= (4, 3, 2): + from .asyncio_patch import traced_async_execute_cluster_pipeline + + _w("redis.asyncio.cluster", "RedisCluster.pipeline", traced_pipeline) + _w("redis.asyncio.cluster", "ClusterPipeline.execute", traced_async_execute_cluster_pipeline) + + Pin(service=None).onto(redis.asyncio.RedisCluster) + + Pin(service=None).onto(redis.StrictRedis) + + +def unpatch(): + if getattr(redis, "_datadog_patch", False): + redis._datadog_patch = False + + if redis.VERSION < (3, 0, 0): + unwrap(redis.StrictRedis, "execute_command") + unwrap(redis.StrictRedis, "pipeline") + unwrap(redis.Redis, "pipeline") + unwrap(redis.client.BasePipeline, "execute") + unwrap(redis.client.BasePipeline, "immediate_execute_command") + else: + unwrap(redis.Redis, "execute_command") + unwrap(redis.Redis, "pipeline") + unwrap(redis.client.Pipeline, "execute") + unwrap(redis.client.Pipeline, "immediate_execute_command") + if redis.VERSION >= (4, 1, 0): + unwrap(redis.cluster.RedisCluster, "execute_command") + unwrap(redis.cluster.RedisCluster, "pipeline") + unwrap(redis.cluster.ClusterPipeline, "execute") + if redis.VERSION >= (4, 2, 0): + unwrap(redis.asyncio.client.Redis, "execute_command") + unwrap(redis.asyncio.client.Redis, "pipeline") + unwrap(redis.asyncio.client.Pipeline, "execute") + unwrap(redis.asyncio.client.Pipeline, "immediate_execute_command") + if redis.VERSION >= (4, 3, 0): + unwrap(redis.asyncio.cluster.RedisCluster, "execute_command") + if redis.VERSION >= (4, 3, 2): + unwrap(redis.asyncio.cluster.RedisCluster, "pipeline") + unwrap(redis.asyncio.cluster.ClusterPipeline, "execute") + + +# +# tracing functions +# +def traced_execute_command(integration_config): + def _traced_execute_command(func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return func(*args, **kwargs) + + with _trace_redis_cmd(pin, integration_config, instance, args) as span: + return _run_redis_command(span=span, func=func, args=args, kwargs=kwargs) + + return _traced_execute_command + + +def traced_pipeline(func, instance, args, kwargs): + pipeline = func(*args, **kwargs) + pin = Pin.get_from(instance) + if pin: + pin.onto(pipeline) + return pipeline + + +def traced_execute_pipeline(integration_config, is_cluster=False): + def _traced_execute_pipeline(func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return func(*args, **kwargs) + + if is_cluster: + cmds = [ + stringify_cache_args(c.args, cmd_max_len=integration_config.cmd_max_length) + for c in instance.command_stack + ] + else: + cmds = [ + stringify_cache_args(c, cmd_max_len=integration_config.cmd_max_length) + for c, _ in instance.command_stack + ] + with _trace_redis_execute_pipeline(pin, integration_config, cmds, instance, is_cluster): + return func(*args, **kwargs) + + return _traced_execute_pipeline diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/rediscluster/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/rediscluster/__init__.py new file mode 100644 index 0000000..2e7ff05 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/rediscluster/__init__.py @@ -0,0 +1,61 @@ +"""Instrument rediscluster to report Redis Cluster queries. + +``import ddtrace.auto`` will automatically patch your Redis Cluster client to make it work. +:: + + from ddtrace import Pin, patch + import rediscluster + + # If not patched yet, you can patch redis specifically + patch(rediscluster=True) + + # This will report a span with the default settings + client = rediscluster.StrictRedisCluster(startup_nodes=[{'host':'localhost', 'port':'7000'}]) + client.get('my-key') + + # Use a pin to specify metadata related to this client + Pin.override(client, service='redis-queue') + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.rediscluster["service"] + The service name reported by default for rediscluster spans + + The option can also be set with the ``DD_REDISCLUSTER_SERVICE`` environment variable + + Default: ``'rediscluster'`` + + +.. py:data:: ddtrace.config.rediscluster["cmd_max_length"] + + Max allowable size for the rediscluster command span tag. + Anything beyond the max length will be replaced with ``"..."``. + + This option can also be set with the ``DD_REDISCLUSTER_CMD_MAX_LENGTH`` environment + variable. + + Default: ``1000`` + +.. py:data:: ddtrace.config.aredis["resource_only_command"] + + The span resource will only include the command executed. To include all + arguments in the span resource, set this value to ``False``. + + This option can also be set with the ``DD_REDIS_RESOURCE_ONLY_COMMAND`` environment + variable. + + Default: ``True`` +""" + +from ...internal.utils.importlib import require_modules + + +required_modules = ["rediscluster", "rediscluster.client"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + + __all__ = ["patch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/rediscluster/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/rediscluster/patch.py new file mode 100644 index 0000000..0f52160 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/rediscluster/patch.py @@ -0,0 +1,113 @@ +import os + +# 3p +import rediscluster + +# project +from ddtrace import config +from ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import SPAN_KIND +from ddtrace.constants import SPAN_MEASURED_KEY +from ddtrace.contrib.redis.patch import traced_execute_command +from ddtrace.contrib.redis.patch import traced_pipeline +from ddtrace.ext import SpanKind +from ddtrace.ext import SpanTypes +from ddtrace.ext import db +from ddtrace.ext import redis as redisx +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema import schematize_cache_operation +from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.utils.formats import CMD_MAX_LEN +from ddtrace.internal.utils.formats import stringify_cache_args +from ddtrace.internal.utils.wrappers import unwrap +from ddtrace.pin import Pin +from ddtrace.vendor import wrapt + +from ...internal.utils.formats import asbool +from .. import trace_utils + + +# DEV: In `2.0.0` `__version__` is a string and `VERSION` is a tuple, +# but in `1.x.x` `__version__` is a tuple annd `VERSION` does not exist +REDISCLUSTER_VERSION = getattr(rediscluster, "VERSION", rediscluster.__version__) + +config._add( + "rediscluster", + dict( + _default_service=schematize_service_name("rediscluster"), + cmd_max_length=int(os.getenv("DD_REDISCLUSTER_CMD_MAX_LENGTH", CMD_MAX_LEN)), + resource_only_command=asbool(os.getenv("DD_REDIS_RESOURCE_ONLY_COMMAND", True)), + ), +) + + +def get_version(): + # type: () -> str + return getattr(rediscluster, "__version__", "") + + +def patch(): + """Patch the instrumented methods""" + if getattr(rediscluster, "_datadog_patch", False): + return + rediscluster._datadog_patch = True + + _w = wrapt.wrap_function_wrapper + if REDISCLUSTER_VERSION >= (2, 0, 0): + _w("rediscluster", "client.RedisCluster.execute_command", traced_execute_command(config.rediscluster)) + _w("rediscluster", "client.RedisCluster.pipeline", traced_pipeline) + _w("rediscluster", "pipeline.ClusterPipeline.execute", traced_execute_pipeline) + Pin().onto(rediscluster.RedisCluster) + else: + _w("rediscluster", "StrictRedisCluster.execute_command", traced_execute_command(config.rediscluster)) + _w("rediscluster", "StrictRedisCluster.pipeline", traced_pipeline) + _w("rediscluster", "StrictClusterPipeline.execute", traced_execute_pipeline) + Pin().onto(rediscluster.StrictRedisCluster) + + +def unpatch(): + if getattr(rediscluster, "_datadog_patch", False): + rediscluster._datadog_patch = False + + if REDISCLUSTER_VERSION >= (2, 0, 0): + unwrap(rediscluster.client.RedisCluster, "execute_command") + unwrap(rediscluster.client.RedisCluster, "pipeline") + unwrap(rediscluster.pipeline.ClusterPipeline, "execute") + else: + unwrap(rediscluster.StrictRedisCluster, "execute_command") + unwrap(rediscluster.StrictRedisCluster, "pipeline") + unwrap(rediscluster.StrictClusterPipeline, "execute") + + +# +# tracing functions +# + + +def traced_execute_pipeline(func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return func(*args, **kwargs) + + cmds = [ + stringify_cache_args(c.args, cmd_max_len=config.rediscluster.cmd_max_length) for c in instance.command_stack + ] + resource = "\n".join(cmds) + tracer = pin.tracer + with tracer.trace( + schematize_cache_operation(redisx.CMD, cache_provider=redisx.APP), + resource=resource, + service=trace_utils.ext_service(pin, config.rediscluster, "rediscluster"), + span_type=SpanTypes.REDIS, + ) as s: + s.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + s.set_tag_str(COMPONENT, config.rediscluster.integration_name) + s.set_tag_str(db.SYSTEM, redisx.APP) + s.set_tag(SPAN_MEASURED_KEY) + s.set_tag_str(redisx.RAWCMD, resource) + s.set_metric(redisx.PIPELINE_LEN, len(instance.command_stack)) + + # set analytics sample rate if enabled + s.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.rediscluster.get_analytics_sample_rate()) + + return func(*args, **kwargs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/requests/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/requests/__init__.py new file mode 100644 index 0000000..e79120f --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/requests/__init__.py @@ -0,0 +1,87 @@ +""" +The ``requests`` integration traces all HTTP requests made with the ``requests`` +library. + +The default service name used is `requests` but it can be configured to match +the services that the specific requests are made to. + +Enabling +~~~~~~~~ + +The requests integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(requests=True) + + # use requests like usual + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.requests['service'] + + The service name reported by default for requests queries. This value will + be overridden by an instance override or if the split_by_domain setting is + enabled. + + This option can also be set with the ``DD_REQUESTS_SERVICE`` environment + variable. + + Default: ``"requests"`` + + + .. _requests-config-distributed-tracing: +.. py:data:: ddtrace.config.requests['distributed_tracing'] + + Whether or not to parse distributed tracing headers. + + Default: ``True`` + + +.. py:data:: ddtrace.config.requests['trace_query_string'] + + Whether or not to include the query string as a tag. + + Default: ``False`` + + +.. py:data:: ddtrace.config.requests['split_by_domain'] + + Whether or not to use the domain name of requests as the service name. This + setting can be overridden with session overrides (described in the Instance + Configuration section). + + Default: ``False`` + + +Instance Configuration +~~~~~~~~~~~~~~~~~~~~~~ + +To set configuration options for all requests made with a ``requests.Session`` object +use the config API:: + + from ddtrace import config + from requests import Session + + session = Session() + cfg = config.get_from(session) + cfg['service_name'] = 'auth-api' + cfg['distributed_tracing'] = False +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["requests"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + from .session import TracedSession + + __all__ = ["patch", "unpatch", "TracedSession", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/requests/connection.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/requests/connection.py new file mode 100644 index 0000000..7f1f243 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/requests/connection.py @@ -0,0 +1,146 @@ +from typing import Optional # noqa:F401 + +import ddtrace +from ddtrace import config +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.settings.asm import config as asm_config + +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from ...internal.compat import parse +from ...internal.logger import get_logger +from ...internal.schema import schematize_url_operation +from ...internal.utils import get_argument_value +from ...propagation.http import HTTPPropagator +from .. import trace_utils + + +log = get_logger(__name__) + + +def _extract_hostname_and_path(uri): + # type: (str) -> str + parsed_uri = parse.urlparse(uri) + hostname = parsed_uri.hostname + try: + if parsed_uri.port is not None: + hostname = "%s:%s" % (hostname, str(parsed_uri.port)) + except ValueError: + # ValueError is raised in PY>3.5 when parsed_uri.port < 0 or parsed_uri.port > 65535 + hostname = "%s:?" % (hostname,) + return hostname, parsed_uri.path + + +def _extract_query_string(uri): + # type: (str) -> Optional[str] + start = uri.find("?") + 1 + if start == 0: + return None + + end = len(uri) + j = uri.rfind("#", 0, end) + if j != -1: + end = j + + if end <= start: + return None + + return uri[start:end] + + +def _wrap_request(func, instance, args, kwargs): + """This function wraps `request.request`, which includes all request verbs like `request.get` and `request.post`. + IAST needs to wrap this function because `Session.send` is too late, as we require the raw arguments of the request. + """ + if asm_config._iast_enabled: + from ddtrace.appsec._iast.taint_sinks.ssrf import _iast_report_ssrf + + _iast_report_ssrf(func, *args, **kwargs) + return func(*args, **kwargs) + + +def _wrap_send(func, instance, args, kwargs): + """Trace the `Session.send` instance method""" + # TODO[manu]: we already offer a way to provide the Global Tracer + # and is ddtrace.tracer; it's used only inside our tests and can + # be easily changed by providing a TracingTestCase that sets common + # tracing functionalities. + tracer = getattr(instance, "datadog_tracer", ddtrace.tracer) + + # skip if tracing is not enabled + if not tracer.enabled: + return func(*args, **kwargs) + + request = get_argument_value(args, kwargs, 0, "request") + if not request: + return func(*args, **kwargs) + + url = trace_utils._sanitized_url(request.url) + method = "" + if request.method is not None: + method = request.method.upper() + hostname, path = _extract_hostname_and_path(url) + host_without_port = hostname.split(":")[0] if hostname is not None else None + + cfg = config.get_from(instance) + service = None + if cfg["split_by_domain"] and hostname: + service = hostname + if service is None: + service = cfg.get("service", None) + if service is None: + service = cfg.get("service_name", None) + if service is None: + service = trace_utils.ext_service(None, config.requests) + + operation_name = schematize_url_operation("requests.request", protocol="http", direction=SpanDirection.OUTBOUND) + with tracer.trace(operation_name, service=service, resource=f"{method} {path}", span_type=SpanTypes.HTTP) as span: + span.set_tag_str(COMPONENT, config.requests.integration_name) + + # set span.kind to the type of operation being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + span.set_tag(SPAN_MEASURED_KEY) + + # Configure trace search sample rate + # DEV: analytics enabled on per-session basis + cfg = config.get_from(instance) + analytics_enabled = cfg.get("analytics_enabled") + if analytics_enabled: + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, cfg.get("analytics_sample_rate", True)) + + # propagate distributed tracing headers + if cfg.get("distributed_tracing"): + HTTPPropagator.inject(span.context, request.headers) + + response = response_headers = None + try: + response = func(*args, **kwargs) + return response + finally: + try: + status = None + if response is not None: + status = response.status_code + # Storing response headers in the span. + # Note that response.headers is not a dict, but an iterable + # requests custom structure, that we convert to a dict + response_headers = dict(getattr(response, "headers", {})) + + trace_utils.set_http_meta( + span, + config.requests, + request_headers=request.headers, + response_headers=response_headers, + method=method, + url=request.url, + target_host=host_without_port, + status_code=status, + query=_extract_query_string(url), + ) + except Exception: + log.debug("requests: error adding tags", exc_info=True) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/requests/constants.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/requests/constants.py new file mode 100644 index 0000000..c3f5eac --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/requests/constants.py @@ -0,0 +1 @@ +DEFAULT_SERVICE = "requests" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/requests/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/requests/patch.py new file mode 100644 index 0000000..6cb93f3 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/requests/patch.py @@ -0,0 +1,51 @@ +import os + +import requests + +from ddtrace import config +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w + +from ...internal.schema import schematize_service_name +from ...internal.utils.formats import asbool +from ...pin import Pin +from ..trace_utils import unwrap as _u +from .connection import _wrap_request +from .connection import _wrap_send + + +# requests default settings +config._add( + "requests", + { + "distributed_tracing": asbool(os.getenv("DD_REQUESTS_DISTRIBUTED_TRACING", default=True)), + "split_by_domain": asbool(os.getenv("DD_REQUESTS_SPLIT_BY_DOMAIN", default=False)), + "default_http_tag_query_string": os.getenv("DD_HTTP_CLIENT_TAG_QUERY_STRING", "true"), + "_default_service": schematize_service_name("requests"), + }, +) + + +def get_version(): + # type: () -> str + return getattr(requests, "__version__", "") + + +def patch(): + """Activate http calls tracing""" + if getattr(requests, "__datadog_patch", False): + return + requests.__datadog_patch = True + + _w("requests", "Session.send", _wrap_send) + # IAST needs to wrap this function because `Session.send` is too late + _w("requests", "Session.request", _wrap_request) + Pin(_config=config.requests).onto(requests.Session) + + +def unpatch(): + """Disable traced sessions""" + if not getattr(requests, "__datadog_patch", False): + return + requests.__datadog_patch = False + + _u(requests.Session, "send") diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/requests/session.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/requests/session.py new file mode 100644 index 0000000..1e603fb --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/requests/session.py @@ -0,0 +1,21 @@ +import requests + +from ddtrace import Pin +from ddtrace import config +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w + +from .connection import _wrap_send + + +class TracedSession(requests.Session): + """TracedSession is a requests' Session that is already traced. + You can use it if you want a finer grained control for your + HTTP clients. + """ + + pass + + +# always patch our `TracedSession` when imported +_w(TracedSession, "send", _wrap_send) +Pin(_config=config.requests).onto(TracedSession) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/rq/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/rq/__init__.py new file mode 100644 index 0000000..970f45d --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/rq/__init__.py @@ -0,0 +1,283 @@ +""" +The RQ__ integration will trace your jobs. + + +Usage +~~~~~ + +The rq integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(rq=True) + + +Worker Usage +~~~~~~~~~~~~ + +``ddtrace-run`` can be used to easily trace your workers:: + + DD_SERVICE=myworker ddtrace-run rq worker + + + +Instance Configuration +~~~~~~~~~~~~~~~~~~~~~~ + +To override the service name for a queue:: + + from ddtrace import Pin + + connection = redis.Redis() + queue = rq.Queue(connection=connection) + Pin.override(queue, service="custom_queue_service") + + +To override the service name for a particular worker:: + + worker = rq.SimpleWorker([queue], connection=queue.connection) + Pin.override(worker, service="custom_worker_service") + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.rq['distributed_tracing_enabled'] +.. py:data:: ddtrace.config.rq_worker['distributed_tracing_enabled'] + + If ``True`` the integration will connect the traces sent between the enqueuer + and the RQ worker. + + This option can also be set with the ``DD_RQ_DISTRIBUTED_TRACING_ENABLED`` + environment variable on either the enqueuer or worker applications. + + Default: ``True`` + +.. py:data:: ddtrace.config.rq['service'] + + The service name reported by default for RQ spans from the app. + + This option can also be set with the ``DD_SERVICE`` or ``DD_RQ_SERVICE`` + environment variables. + + Default: ``rq`` + +.. py:data:: ddtrace.config.rq_worker['service'] + + The service name reported by default for RQ spans from workers. + + This option can also be set with the ``DD_SERVICE`` environment + variable. + + Default: ``rq-worker`` + +.. __: https://python-rq.org/ + +""" +import os + +from ddtrace import Pin +from ddtrace import config +from ddtrace.constants import SPAN_KIND +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema import schematize_messaging_operation +from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.schema.span_attribute_schema import SpanDirection + +from ...ext import SpanKind +from ...ext import SpanTypes +from ...internal.utils import get_argument_value +from ...internal.utils.formats import asbool +from ...propagation.http import HTTPPropagator +from .. import trace_utils + + +__all__ = ["patch", "unpatch", "get_version"] + + +config._add( + "rq", + dict( + distributed_tracing_enabled=asbool(os.environ.get("DD_RQ_DISTRIBUTED_TRACING_ENABLED", True)), + _default_service=schematize_service_name("rq"), + ), +) + +config._add( + "rq_worker", + dict( + distributed_tracing_enabled=asbool(os.environ.get("DD_RQ_DISTRIBUTED_TRACING_ENABLED", True)), + _default_service=schematize_service_name("rq-worker"), + ), +) + + +def get_version(): + # type: () -> str + import rq + + return str(getattr(rq, "__version__", "")) + + +@trace_utils.with_traced_module +def traced_queue_enqueue_job(rq, pin, func, instance, args, kwargs): + job = get_argument_value(args, kwargs, 0, "f") + + func_name = job.func_name + job_inst = job.instance + job_inst_str = "%s.%s" % (job_inst.__module__, job_inst.__class__.__name__) if job_inst else "" + + if job_inst_str: + resource = "%s.%s" % (job_inst_str, func_name) + else: + resource = func_name + + with pin.tracer.trace( + schematize_messaging_operation("rq.queue.enqueue_job", provider="rq", direction=SpanDirection.OUTBOUND), + service=trace_utils.int_service(pin, config.rq), + resource=resource, + span_type=SpanTypes.WORKER, + ) as span: + span.set_tag_str(COMPONENT, config.rq.integration_name) + + # set span.kind to the type of request being performed + span.set_tag_str(SPAN_KIND, SpanKind.PRODUCER) + + span.set_tag_str("queue.name", instance.name) + span.set_tag_str("job.id", job.get_id()) + span.set_tag_str("job.func_name", job.func_name) + + # If the queue is_async then add distributed tracing headers to the job + if instance.is_async and config.rq.distributed_tracing_enabled: + HTTPPropagator.inject(span.context, job.meta) + return func(*args, **kwargs) + + +@trace_utils.with_traced_module +def traced_queue_fetch_job(rq, pin, func, instance, args, kwargs): + with pin.tracer.trace( + schematize_messaging_operation("rq.queue.fetch_job", provider="rq", direction=SpanDirection.PROCESSING), + service=trace_utils.int_service(pin, config.rq), + ) as span: + span.set_tag_str(COMPONENT, config.rq.integration_name) + + job_id = get_argument_value(args, kwargs, 0, "job_id") + span.set_tag_str("job.id", job_id) + return func(*args, **kwargs) + + +@trace_utils.with_traced_module +def traced_perform_job(rq, pin, func, instance, args, kwargs): + """Trace rq.Worker.perform_job""" + # `perform_job` is executed in a freshly forked, short-lived instance + job = get_argument_value(args, kwargs, 0, "job") + + if config.rq_worker.distributed_tracing_enabled: + ctx = HTTPPropagator.extract(job.meta) + if ctx.trace_id: + pin.tracer.context_provider.activate(ctx) + + try: + with pin.tracer.trace( + "rq.worker.perform_job", + service=trace_utils.int_service(pin, config.rq_worker), + span_type=SpanTypes.WORKER, + resource=job.func_name, + ) as span: + span.set_tag_str(COMPONENT, config.rq.integration_name) + + # set span.kind to the type of request being performed + span.set_tag_str(SPAN_KIND, SpanKind.CONSUMER) + span.set_tag_str("job.id", job.get_id()) + try: + return func(*args, **kwargs) + finally: + # get_status() returns None when ttl=0 + span.set_tag_str("job.status", job.get_status() or "None") + span.set_tag_str("job.origin", job.origin) + if job.is_failed: + span.error = 1 + finally: + # Force flush to agent since the process `os.exit()`s + # immediately after this method returns + pin.tracer.flush() + + +@trace_utils.with_traced_module +def traced_job_perform(rq, pin, func, instance, args, kwargs): + """Trace rq.Job.perform(...)""" + job = instance + + # Inherit the service name from whatever parent exists. + # eg. in a worker, a perform_job parent span will exist with the worker + # service. + with pin.tracer.trace("rq.job.perform", resource=job.func_name) as span: + span.set_tag_str(COMPONENT, config.rq.integration_name) + + span.set_tag("job.id", job.get_id()) + return func(*args, **kwargs) + + +@trace_utils.with_traced_module +def traced_job_fetch_many(rq, pin, func, instance, args, kwargs): + """Trace rq.Job.fetch_many(...)""" + with pin.tracer.trace( + schematize_messaging_operation("rq.job.fetch_many", provider="rq", direction=SpanDirection.PROCESSING), + service=trace_utils.ext_service(pin, config.rq_worker), + ) as span: + span.set_tag_str(COMPONENT, config.rq.integration_name) + + job_ids = get_argument_value(args, kwargs, 0, "job_ids") + span.set_tag("job_ids", job_ids) + return func(*args, **kwargs) + + +def patch(): + # Avoid importing rq at the module level, eventually will be an import hook + import rq + + if getattr(rq, "_datadog_patch", False): + return + + Pin().onto(rq) + + # Patch rq.job.Job + Pin().onto(rq.job.Job) + trace_utils.wrap(rq.job, "Job.perform", traced_job_perform(rq.job.Job)) + + # Patch rq.queue.Queue + Pin().onto(rq.queue.Queue) + trace_utils.wrap("rq.queue", "Queue.enqueue_job", traced_queue_enqueue_job(rq)) + trace_utils.wrap("rq.queue", "Queue.fetch_job", traced_queue_fetch_job(rq)) + + # Patch rq.worker.Worker + Pin().onto(rq.worker.Worker) + trace_utils.wrap(rq.worker, "Worker.perform_job", traced_perform_job(rq)) + + rq._datadog_patch = True + + +def unpatch(): + import rq + + if not getattr(rq, "_datadog_patch", False): + return + + Pin().remove_from(rq) + + # Unpatch rq.job.Job + Pin().remove_from(rq.job.Job) + trace_utils.unwrap(rq.job.Job, "perform") + + # Unpatch rq.queue.Queue + Pin().remove_from(rq.queue.Queue) + trace_utils.unwrap(rq.queue.Queue, "enqueue_job") + trace_utils.unwrap(rq.queue.Queue, "fetch_job") + + # Unpatch rq.worker.Worker + Pin().remove_from(rq.worker.Worker) + trace_utils.unwrap(rq.worker.Worker, "perform_job") + + rq._datadog_patch = False diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sanic/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sanic/__init__.py new file mode 100644 index 0000000..49fbf4c --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sanic/__init__.py @@ -0,0 +1,75 @@ +""" +The Sanic__ integration will trace requests to and from Sanic. + + +Enable Sanic tracing automatically via ``ddtrace-run``:: + + ddtrace-run python app.py + +Sanic tracing can also be enabled explicitly:: + + from ddtrace import patch_all + patch_all(sanic=True) + + from sanic import Sanic + from sanic.response import text + + app = Sanic(__name__) + + @app.route('/') + def index(request): + return text('hello world') + + if __name__ == '__main__': + app.run() + +On Python 3.6 and below, you must enable the legacy ``AsyncioContextProvider`` before using the middleware:: + + from ddtrace.contrib.asyncio.provider import AsyncioContextProvider + from ddtrace import tracer # Or whichever tracer instance you plan to use + tracer.configure(context_provider=AsyncioContextProvider()) + + +Configuration +~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.sanic['distributed_tracing_enabled'] + + Whether to parse distributed tracing headers from requests received by your Sanic app. + + Default: ``True`` + + +.. py:data:: ddtrace.config.sanic['service_name'] + + The service name reported for your Sanic app. + + Can also be configured via the ``DD_SERVICE`` environment variable. + + Default: ``'sanic'`` + + +Example:: + + from ddtrace import config + + # Enable distributed tracing + config.sanic['distributed_tracing_enabled'] = True + + # Override service name + config.sanic['service_name'] = 'custom-service-name' + +.. __: https://sanic.readthedocs.io/en/latest/ +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["sanic"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sanic/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sanic/patch.py new file mode 100644 index 0000000..6fcf002 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sanic/patch.py @@ -0,0 +1,285 @@ +import asyncio + +import sanic + +import ddtrace +from ddtrace import config +from ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import SPAN_KIND +from ddtrace.ext import SpanKind +from ddtrace.ext import SpanTypes +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.schema import schematize_url_operation +from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.internal.utils.wrappers import unwrap as _u +from ddtrace.pin import Pin +from ddtrace.vendor import wrapt +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w + +from ...internal.logger import get_logger +from .. import trace_utils + + +log = get_logger(__name__) + +config._add("sanic", dict(_default_service=schematize_service_name("sanic"), distributed_tracing=True)) + +SANIC_VERSION = (0, 0, 0) + + +def get_version(): + # type: () -> str + return getattr(sanic, "__version__", "") + + +def _get_current_span(request): + pin = Pin._find(request.ctx) + if not pin or not pin.enabled(): + return None + + return pin.tracer.current_span() + + +def update_span(span, response): + # Check for response status or headers on the response object + # DEV: This object can either be a form of BaseResponse or an Exception + # if we do not have a status code, we can assume this is an exception + # and so use 500 + status_code = getattr(response, "status", 500) + response_headers = getattr(response, "headers", None) + trace_utils.set_http_meta(span, config.sanic, status_code=status_code, response_headers=response_headers) + + +def _wrap_response_callback(span, callback): + # Only for sanic 20 and older + # Wrap response callbacks (either sync or async function) to set HTTP + # response span tags + + @wrapt.function_wrapper + def wrap_sync(wrapped, instance, args, kwargs): + r = wrapped(*args, **kwargs) + response = args[0] + update_span(span, response) + return r + + @wrapt.function_wrapper + async def wrap_async(wrapped, instance, args, kwargs): + r = await wrapped(*args, **kwargs) + response = args[0] + update_span(span, response) + return r + + if asyncio.iscoroutinefunction(callback): + return wrap_async(callback) + + return wrap_sync(callback) + + +async def patch_request_respond(wrapped, instance, args, kwargs): + # Only for sanic 21 and newer + # Wrap the framework response to set HTTP response span tags + response = await wrapped(*args, **kwargs) + span = _get_current_span(instance) + if not span: + return response + + update_span(span, response) + + # Sanic 21.9.x does not dispatch `http.lifecycle.response` in `handle_exception` + # so we have to handle finishing the span here instead + if (21, 9, 0) <= SANIC_VERSION < (21, 12, 0) and getattr(instance.ctx, "__dd_span_call_finish", False): + span.finish() + return response + + +def _get_path(request): + """Get path and replace path parameter values with names if route exists.""" + path = request.path + try: + match_info = request.match_info + except sanic.exceptions.SanicException: + return path + for key, value in match_info.items(): + try: + value = str(value) + except Exception: + log.debug("Failed to convert path parameter value to string", exc_info=True) + continue + path = path.replace(value, f"<{key}>") + return path + + +async def patch_run_request_middleware(wrapped, instance, args, kwargs): + # Set span resource from the framework request + request = args[0] + span = _get_current_span(request) + if span is not None: + span.resource = "{} {}".format(request.method, _get_path(request)) + return await wrapped(*args, **kwargs) + + +def patch(): + """Patch the instrumented methods.""" + global SANIC_VERSION + + if getattr(sanic, "__datadog_patch", False): + return + sanic.__datadog_patch = True + + SANIC_VERSION = tuple(map(int, sanic.__version__.split("."))) + + if SANIC_VERSION >= (21, 9, 0): + _w("sanic", "Sanic.__init__", patch_sanic_init) + _w(sanic.request, "Request.respond", patch_request_respond) + else: + _w("sanic", "Sanic.handle_request", patch_handle_request) + if SANIC_VERSION >= (21, 0, 0): + _w("sanic", "Sanic._run_request_middleware", patch_run_request_middleware) + _w(sanic.request, "Request.respond", patch_request_respond) + + +def unpatch(): + """Unpatch the instrumented methods.""" + if not getattr(sanic, "__datadog_patch", False): + return + + if SANIC_VERSION >= (21, 9, 0): + _u(sanic.Sanic, "__init__") + _u(sanic.request.Request, "respond") + else: + _u(sanic.Sanic, "handle_request") + if SANIC_VERSION >= (21, 0, 0): + _u(sanic.Sanic, "_run_request_middleware") + _u(sanic.request.Request, "respond") + + sanic.__datadog_patch = False + + +def patch_sanic_init(wrapped, instance, args, kwargs): + """Wrapper for creating sanic apps to automatically add our signal handlers""" + wrapped(*args, **kwargs) + + instance.add_signal(sanic_http_lifecycle_handle, "http.lifecycle.handle") + instance.add_signal(sanic_http_routing_after, "http.routing.after") + instance.add_signal(sanic_http_lifecycle_exception, "http.lifecycle.exception") + instance.add_signal(sanic_http_lifecycle_response, "http.lifecycle.response") + + +async def patch_handle_request(wrapped, instance, args, kwargs): + """Wrapper for Sanic.handle_request""" + + def unwrap(request, write_callback=None, stream_callback=None, **kwargs): + return request, write_callback, stream_callback, kwargs + + request, write_callback, stream_callback, new_kwargs = unwrap(*args, **kwargs) + + if request.scheme not in ("http", "https"): + return await wrapped(*args, **kwargs) + + with _create_sanic_request_span(request) as span: + if write_callback is not None: + new_kwargs["write_callback"] = _wrap_response_callback(span, write_callback) + if stream_callback is not None: + new_kwargs["stream_callback"] = _wrap_response_callback(span, stream_callback) + + return await wrapped(request, **new_kwargs) + + +def _create_sanic_request_span(request): + """Helper to create sanic.request span and attach a pin to request.ctx""" + pin = Pin() + pin.onto(request.ctx) + + if SANIC_VERSION < (21, 0, 0): + # Set span resource from the framework request + resource = "{} {}".format(request.method, _get_path(request)) + else: + # The path is not available anymore in 21.x. Get it from + # the _run_request_middleware instrumented method. + resource = None + + headers = request.headers.copy() + + trace_utils.activate_distributed_headers(ddtrace.tracer, int_config=config.sanic, request_headers=headers) + + span = pin.tracer.trace( + schematize_url_operation("sanic.request", protocol="http", direction=SpanDirection.INBOUND), + service=trace_utils.int_service(None, config.sanic), + resource=resource, + span_type=SpanTypes.WEB, + ) + span.set_tag_str(COMPONENT, config.sanic.integration_name) + + # set span.kind to the type of operation being performed + span.set_tag_str(SPAN_KIND, SpanKind.SERVER) + + sample_rate = config.sanic.get_analytics_sample_rate(use_global_config=True) + if sample_rate is not None: + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, sample_rate) + + method = request.method + url = "{scheme}://{host}{path}".format(scheme=request.scheme, host=request.host, path=request.path) + query_string = request.query_string + if isinstance(query_string, bytes): + query_string = query_string.decode() + trace_utils.set_http_meta(span, config.sanic, method=method, url=url, query=query_string, request_headers=headers) + + return span + + +async def sanic_http_lifecycle_handle(request): + """Lifecycle signal called when a new request is started.""" + _create_sanic_request_span(request) + + +async def sanic_http_routing_after(request, route, kwargs, handler): + """Lifecycle signal called after routing has been resolved.""" + span = _get_current_span(request) + if not span: + return + + pattern = route.raw_path + # Sanic 21.9.0 and newer strip the leading slash from `route.raw_path` + if not pattern.startswith("/"): + pattern = "/{}".format(pattern) + if route.regex: + pattern = route.pattern + + span.resource = "{} {}".format(request.method, pattern) + span.set_tag_str("sanic.route.name", route.name) + + +async def sanic_http_lifecycle_response(request, response): + """Lifecycle signal called when a response is starting. + + Note: This signal does not get called when exceptions occur + in 21.9.x. The issue was resolved in 21.12.x + """ + span = _get_current_span(request) + if not span: + return + try: + update_span(span, response) + finally: + span.finish() + + +async def sanic_http_lifecycle_exception(request, exception): + """Lifecycle signal called when an exception occurs.""" + span = _get_current_span(request) + if not span: + return + + # Do not attach exception for exceptions not considered as errors + # ex: Http 400s + # DEV: We still need to set `__dd_span_call_finish` below + if not hasattr(exception, "status_code") or config.http_server.is_error_code(exception.status_code): + ex_type = type(exception) + ex_tb = getattr(exception, "__traceback__", None) + span.set_exc_info(ex_type, exception, ex_tb) + + # Sanic 21.9.x does not dispatch `http.lifecycle.response` in `handle_exception` + # so we need to indicate to `patch_request_respond` to finish the span + if (21, 9, 0) <= SANIC_VERSION < (21, 12, 0): + request.ctx.__dd_span_call_finish = True diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/snowflake/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/snowflake/__init__.py new file mode 100644 index 0000000..0deaf33 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/snowflake/__init__.py @@ -0,0 +1,72 @@ +""" +The snowflake integration instruments the ``snowflake-connector-python`` library to trace Snowflake queries. + +Note that this integration is in beta. + +Enabling +~~~~~~~~ + +The integration is not enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch, patch_all + patch(snowflake=True) + patch_all(snowflake=True) + +or the ``DD_TRACE_SNOWFLAKE_ENABLED=true`` to enable it with ``ddtrace-run``. + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.snowflake["service"] + + The service name reported by default for snowflake spans. + + This option can also be set with the ``DD_SNOWFLAKE_SERVICE`` environment + variable. + + Default: ``"snowflake"`` + +.. py:data:: ddtrace.config.snowflake["trace_fetch_methods"] + + Whether or not to trace fetch methods. + + Can also configured via the ``DD_SNOWFLAKE_TRACE_FETCH_METHODS`` environment variable. + + Default: ``False`` + + +Instance Configuration +~~~~~~~~~~~~~~~~~~~~~~ + +To configure the integration on an per-connection basis use the +``Pin`` API:: + + from ddtrace import Pin + from snowflake.connector import connect + + # This will report a span with the default settings + conn = connect(user="alice", password="b0b", account="dev") + + # Use a pin to override the service name for this connection. + Pin.override(conn, service="snowflake-dev") + + + cursor = conn.cursor() + cursor.execute("SELECT current_version()") +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["snowflake.connector"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/snowflake/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/snowflake/patch.py new file mode 100644 index 0000000..607854f --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/snowflake/patch.py @@ -0,0 +1,99 @@ +import os + +from ddtrace import Pin +from ddtrace import config +from ddtrace.vendor import wrapt + +from ...ext import db +from ...ext import net +from ...internal.schema import schematize_service_name +from ...internal.utils.formats import asbool +from ..dbapi import TracedConnection +from ..dbapi import TracedCursor +from ..trace_utils import unwrap + + +config._add( + "snowflake", + dict( + _default_service=schematize_service_name("snowflake"), + # FIXME: consistent prefix span names with other dbapi integrations + # The snowflake integration was introduced following a different pattern + # than all other dbapi-compliant integrations. It sets span names to + # `sql.query` whereas other dbapi-compliant integrations are set to + # `.query`. + _dbapi_span_name_prefix="sql", + trace_fetch_methods=asbool(os.getenv("DD_SNOWFLAKE_TRACE_FETCH_METHODS", default=False)), + ), +) + + +def get_version(): + # type: () -> str + try: + import snowflake.connector as c + except AttributeError: + import sys + + c = sys.modules.get("snowflake.connector") + return str(c.__version__) + + +class _SFTracedCursor(TracedCursor): + def _set_post_execute_tags(self, span): + super(_SFTracedCursor, self)._set_post_execute_tags(span) + span.set_tag_str("sfqid", self.__wrapped__.sfqid) + + +def patch(): + try: + import snowflake.connector as c + except AttributeError: + import sys + + c = sys.modules.get("snowflake.connector") + + if getattr(c, "_datadog_patch", False): + return + c._datadog_patch = True + + wrapt.wrap_function_wrapper(c, "Connect", patched_connect) + wrapt.wrap_function_wrapper(c, "connect", patched_connect) + + +def unpatch(): + try: + import snowflake.connector as c + except AttributeError: + import sys + + c = sys.modules.get("snowflake.connector") + + if getattr(c, "_datadog_patch", False): + c._datadog_patch = False + + unwrap(c, "Connect") + unwrap(c, "connect") + + +def patched_connect(connect_func, _, args, kwargs): + conn = connect_func(*args, **kwargs) + if isinstance(conn, TracedConnection): + return conn + + # Add default tags to each query + tags = { + net.TARGET_HOST: conn.host, + net.TARGET_PORT: conn.port, + db.NAME: conn.database, + db.SYSTEM: "snowflake", + db.USER: conn.user, + "db.application": conn.application, + "db.schema": conn.schema, + "db.warehouse": conn.warehouse, + } + + pin = Pin(tags=tags) + traced_conn = TracedConnection(conn, pin=pin, cfg=config.snowflake, cursor_cls=_SFTracedCursor) + pin.onto(traced_conn) + return traced_conn diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sqlalchemy/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sqlalchemy/__init__.py new file mode 100644 index 0000000..b2ff112 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sqlalchemy/__init__.py @@ -0,0 +1,34 @@ +""" +Enabling the SQLAlchemy integration is only necessary if there is no +instrumentation available or enabled for the underlying database engine (e.g. +pymysql, psycopg, mysql-connector, etc.). + +To trace sqlalchemy queries, add instrumentation to the engine class +using the patch method that **must be called before** importing sqlalchemy:: + + # patch before importing `create_engine` + from ddtrace import Pin, patch + patch(sqlalchemy=True) + + # use SQLAlchemy as usual + from sqlalchemy import create_engine + + engine = create_engine('sqlite:///:memory:') + engine.connect().execute("SELECT COUNT(*) FROM users") + + # Use a PIN to specify metadata related to this engine + Pin.override(engine, service='replica-db') +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["sqlalchemy", "sqlalchemy.event"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .engine import trace_engine + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["trace_engine", "patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sqlalchemy/engine.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sqlalchemy/engine.py new file mode 100644 index 0000000..9addddf --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sqlalchemy/engine.py @@ -0,0 +1,164 @@ +""" +To trace sqlalchemy queries, add instrumentation to the engine class or +instance you are using:: + + from ddtrace import tracer + from ddtrace.contrib.sqlalchemy import trace_engine + from sqlalchemy import create_engine + + engine = create_engine('sqlite:///:memory:') + trace_engine(engine, tracer, 'my-database') + + engine.connect().execute('select count(*) from users') +""" +# 3p +import sqlalchemy +from sqlalchemy.event import listen + +# project +import ddtrace +from ddtrace import config +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema import schematize_database_operation +from ddtrace.internal.schema import schematize_service_name + +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from ...ext import db +from ...ext import net as netx +from ...ext import sql as sqlx +from ...pin import Pin + + +def trace_engine(engine, tracer=None, service=None): + """ + Add tracing instrumentation to the given sqlalchemy engine or instance. + + :param sqlalchemy.Engine engine: a SQLAlchemy engine class or instance + :param ddtrace.Tracer tracer: a tracer instance. will default to the global + :param str service: the name of the service to trace. + """ + tracer = tracer or ddtrace.tracer # by default use global + EngineTracer(tracer, service, engine) + + +def _wrap_create_engine(func, module, args, kwargs): + """Trace the SQLAlchemy engine, creating an `EngineTracer` + object that will listen to SQLAlchemy events. A PIN object + is attached to the engine instance so that it can be + used later. + """ + # the service name is set to `None` so that the engine + # name is used by default; users can update this setting + # using the PIN object + engine = func(*args, **kwargs) + EngineTracer(ddtrace.tracer, None, engine) + return engine + + +class EngineTracer(object): + def __init__(self, tracer, service, engine): + self.tracer = tracer + self.engine = engine + self.vendor = sqlx.normalize_vendor(engine.name) + self.service = schematize_service_name(service or self.vendor) + self.name = schematize_database_operation("%s.query" % self.vendor, database_provider=self.vendor) + + # attach the PIN + Pin(tracer=tracer, service=self.service).onto(engine) + + listen(engine, "before_cursor_execute", self._before_cur_exec) + listen(engine, "after_cursor_execute", self._after_cur_exec) + + # Determine name of error event to listen for + # Ref: https://github.com/DataDog/dd-trace-py/issues/841 + if sqlalchemy.__version__[0] != "0": + error_event = "handle_error" + else: + error_event = "dbapi_error" + listen(engine, error_event, self._handle_db_error) + + def _before_cur_exec(self, conn, cursor, statement, *args): + pin = Pin.get_from(self.engine) + if not pin or not pin.enabled(): + # don't trace the execution + return + + span = pin.tracer.trace( + self.name, + service=pin.service, + span_type=SpanTypes.SQL, + resource=statement, + ) + span.set_tag_str(COMPONENT, config.sqlalchemy.integration_name) + + # set span.kind to the type of operation being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + span.set_tag(SPAN_MEASURED_KEY) + + if not _set_tags_from_url(span, conn.engine.url): + _set_tags_from_cursor(span, self.vendor, cursor) + + # set analytics sample rate + sample_rate = config.sqlalchemy.get_analytics_sample_rate() + if sample_rate is not None: + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, sample_rate) + + def _after_cur_exec(self, conn, cursor, statement, *args): + pin = Pin.get_from(self.engine) + if not pin or not pin.enabled(): + # don't trace the execution + return + + span = pin.tracer.current_span() + if not span: + return + + try: + if cursor and cursor.rowcount >= 0: + span.set_tag(db.ROWCOUNT, cursor.rowcount) + finally: + span.finish() + + def _handle_db_error(self, *args): + pin = Pin.get_from(self.engine) + if not pin or not pin.enabled(): + # don't trace the execution + return + + span = pin.tracer.current_span() + if not span: + return + + try: + span.set_traceback() + finally: + span.finish() + + +def _set_tags_from_url(span, url): + """set connection tags from the url. return true if successful.""" + if url.host: + span.set_tag_str(netx.TARGET_HOST, url.host) + if url.port: + span.set_tag(netx.TARGET_PORT, url.port) + if url.database: + span.set_tag_str(sqlx.DB, url.database) + + return bool(span.get_tag(netx.TARGET_HOST)) + + +def _set_tags_from_cursor(span, vendor, cursor): + """attempt to set db connection tags by introspecting the cursor.""" + if "postgres" == vendor: + if hasattr(cursor, "connection"): + dsn = getattr(cursor.connection, "dsn", None) + if dsn: + d = sqlx.parse_pg_dsn(dsn) + span.set_tag_str(sqlx.DB, d.get("dbname")) + span.set_tag_str(netx.TARGET_HOST, d.get("host")) + span.set_metric(netx.TARGET_PORT, int(d.get("port"))) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sqlalchemy/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sqlalchemy/patch.py new file mode 100644 index 0000000..edd1c43 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sqlalchemy/patch.py @@ -0,0 +1,29 @@ +import sqlalchemy + +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w + +from ..trace_utils import unwrap +from .engine import _wrap_create_engine + + +def get_version(): + # type: () -> str + return getattr(sqlalchemy, "__version__", "") + + +def patch(): + if getattr(sqlalchemy.engine, "__datadog_patch", False): + return + sqlalchemy.engine.__datadog_patch = True + + # patch the engine creation function + _w("sqlalchemy", "create_engine", _wrap_create_engine) + _w("sqlalchemy.engine", "create_engine", _wrap_create_engine) + + +def unpatch(): + # unpatch sqlalchemy + if getattr(sqlalchemy.engine, "__datadog_patch", False): + sqlalchemy.engine.__datadog_patch = False + unwrap(sqlalchemy, "create_engine") + unwrap(sqlalchemy.engine, "create_engine") diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sqlite3/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sqlite3/__init__.py new file mode 100644 index 0000000..bad1f91 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sqlite3/__init__.py @@ -0,0 +1,66 @@ +""" +The sqlite integration instruments the built-in sqlite module to trace SQLite queries. + + +Enabling +~~~~~~~~ + +The integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(sqlite=True) + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.sqlite["service"] + + The service name reported by default for sqlite spans. + + This option can also be set with the ``DD_SQLITE_SERVICE`` environment + variable. + + Default: ``"sqlite"`` + +.. py:data:: ddtrace.config.sqlite["trace_fetch_methods"] + + Whether or not to trace fetch methods. + + Can also configured via the ``DD_SQLITE_TRACE_FETCH_METHODS`` environment variable. + + Default: ``False`` + + +Instance Configuration +~~~~~~~~~~~~~~~~~~~~~~ + +To configure the integration on an per-connection basis use the +``Pin`` API:: + + from ddtrace import Pin + import sqlite3 + + # This will report a span with the default settings + db = sqlite3.connect(":memory:") + + # Use a pin to override the service name for the connection. + Pin.override(db, service='sqlite-users') + + cursor = db.cursor() + cursor.execute("select * from users where id = 1") +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["sqlite3"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + + __all__ = ["patch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sqlite3/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sqlite3/patch.py new file mode 100644 index 0000000..b5e357b --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/sqlite3/patch.py @@ -0,0 +1,97 @@ +import os +import sqlite3 +import sqlite3.dbapi2 +import sys + +from ddtrace import config +from ddtrace.vendor import wrapt + +from ...contrib.dbapi import FetchTracedCursor +from ...contrib.dbapi import TracedConnection +from ...contrib.dbapi import TracedCursor +from ...ext import db +from ...internal.schema import schematize_database_operation +from ...internal.schema import schematize_service_name +from ...internal.utils.formats import asbool +from ...pin import Pin + + +# Original connect method +_connect = sqlite3.connect + +config._add( + "sqlite", + dict( + _default_service=schematize_service_name("sqlite"), + _dbapi_span_name_prefix="sqlite", + _dbapi_span_operation_name=schematize_database_operation("sqlite.query", database_provider="sqlite"), + trace_fetch_methods=asbool(os.getenv("DD_SQLITE_TRACE_FETCH_METHODS", default=False)), + ), +) + + +def get_version(): + # type: () -> str + return sqlite3.sqlite_version + + +def patch(): + wrapped = wrapt.FunctionWrapper(_connect, traced_connect) + + sqlite3.connect = wrapped + sqlite3.dbapi2.connect = wrapped + + +def unpatch(): + sqlite3.connect = _connect + sqlite3.dbapi2.connect = _connect + + +def traced_connect(func, _, args, kwargs): + conn = func(*args, **kwargs) + return patch_conn(conn) + + +def patch_conn(conn): + wrapped = TracedSQLite(conn) + Pin(tags={db.SYSTEM: "sqlite"}).onto(wrapped) + return wrapped + + +class TracedSQLiteCursor(TracedCursor): + def executemany(self, *args, **kwargs): + # DEV: SQLite3 Cursor.execute always returns back the cursor instance + super(TracedSQLiteCursor, self).executemany(*args, **kwargs) + return self + + def execute(self, *args, **kwargs): + # DEV: SQLite3 Cursor.execute always returns back the cursor instance + super(TracedSQLiteCursor, self).execute(*args, **kwargs) + return self + + +class TracedSQLiteFetchCursor(TracedSQLiteCursor, FetchTracedCursor): + pass + + +class TracedSQLite(TracedConnection): + def __init__(self, conn, pin=None, cursor_cls=None): + if not cursor_cls: + # Do not trace `fetch*` methods by default + cursor_cls = TracedSQLiteFetchCursor if config.sqlite.trace_fetch_methods else TracedSQLiteCursor + + super(TracedSQLite, self).__init__(conn, pin=pin, cfg=config.sqlite, cursor_cls=cursor_cls) + + def execute(self, *args, **kwargs): + # sqlite has a few extra sugar functions + return self.cursor().execute(*args, **kwargs) + + # backup was added in Python 3.7 + if sys.version_info >= (3, 7, 0): + + def backup(self, target, *args, **kwargs): + # sqlite3 checks the type of `target`, it cannot be a wrapped connection + # https://github.com/python/cpython/blob/4652093e1b816b78e9a585d671a807ce66427417/Modules/_sqlite/connection.c#L1897-L1899 + if isinstance(target, TracedConnection): + target = target.__wrapped__ + return self.__wrapped__.backup(target, *args, **kwargs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/starlette/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/starlette/__init__.py new file mode 100644 index 0000000..ac0f347 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/starlette/__init__.py @@ -0,0 +1,86 @@ +""" +The Starlette integration will trace requests to and from Starlette. + + +Enabling +~~~~~~~~ + +The starlette integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + from starlette.applications import Starlette + + patch(starlette=True) + app = Starlette() + + +On Python 3.6 and below, you must enable the legacy ``AsyncioContextProvider`` before using the middleware:: + + from ddtrace.contrib.asyncio.provider import AsyncioContextProvider + from ddtrace import tracer # Or whichever tracer instance you plan to use + tracer.configure(context_provider=AsyncioContextProvider()) + + +Configuration +~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.starlette['distributed_tracing'] + + Whether to parse distributed tracing headers from requests received by your Starlette app. + + Can also be enabled with the ``DD_STARLETTE_DISTRIBUTED_TRACING`` environment variable. + + Default: ``True`` + +.. py:data:: ddtrace.config.starlette['analytics_enabled'] + + Whether to analyze spans for starlette in App Analytics. + + Can also be enabled with the ``DD_STARLETTE_ANALYTICS_ENABLED`` environment variable. + + Default: ``None`` + +.. py:data:: ddtrace.config.starlette['service_name'] + + The service name reported for your starlette app. + + Can also be configured via the ``DD_SERVICE`` environment variable. + + Default: ``'starlette'`` + +.. py:data:: ddtrace.config.starlette['request_span_name'] + + The span name for a starlette request. + + Default: ``'starlette.request'`` + + +Example:: + + from ddtrace import config + + # Enable distributed tracing + config.starlette['distributed_tracing'] = True + + # Override service name + config.starlette['service_name'] = 'custom-service-name' + + # Override request span name + config.starlette['request_span_name'] = 'custom-request-span-name' + +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["starlette"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/starlette/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/starlette/patch.py new file mode 100644 index 0000000..a9dfccc --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/starlette/patch.py @@ -0,0 +1,184 @@ +import inspect +from typing import Any # noqa:F401 +from typing import Dict # noqa:F401 +from typing import List # noqa:F401 +from typing import Optional # noqa:F401 + +import starlette +from starlette import requests as starlette_requests +from starlette.concurrency import run_in_threadpool +from starlette.middleware import Middleware + +from ddtrace import Pin +from ddtrace import config +from ddtrace.contrib.asgi.middleware import TraceMiddleware +from ddtrace.ext import http +from ddtrace.internal.constants import HTTP_REQUEST_BLOCKED +from ddtrace.internal.logger import get_logger +from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.utils import get_argument_value +from ddtrace.internal.utils import set_argument_value +from ddtrace.internal.utils.wrappers import unwrap as _u +from ddtrace.span import Span # noqa:F401 +from ddtrace.vendor.wrapt import ObjectProxy +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w + +from ...internal import core +from .. import trace_utils +from ..trace_utils import with_traced_module + + +log = get_logger(__name__) + +config._add( + "starlette", + dict( + _default_service=schematize_service_name("starlette"), + request_span_name="starlette.request", + distributed_tracing=True, + ), +) + + +def get_version(): + # type: () -> str + return getattr(starlette, "__version__", "") + + +def traced_init(wrapped, instance, args, kwargs): + mw = kwargs.pop("middleware", []) + mw.insert(0, Middleware(TraceMiddleware, integration_config=config.starlette)) + kwargs.update({"middleware": mw}) + + wrapped(*args, **kwargs) + + +def patch(): + if getattr(starlette, "_datadog_patch", False): + return + + starlette._datadog_patch = True + + _w("starlette.applications", "Starlette.__init__", traced_init) + Pin().onto(starlette) + + # We need to check that Fastapi instrumentation hasn't already patched these + if not isinstance(starlette.routing.Route.handle, ObjectProxy): + _w("starlette.routing", "Route.handle", traced_handler) + if not isinstance(starlette.routing.Mount.handle, ObjectProxy): + _w("starlette.routing", "Mount.handle", traced_handler) + + if not isinstance(starlette.background.BackgroundTasks.add_task, ObjectProxy): + _w("starlette.background", "BackgroundTasks.add_task", _trace_background_tasks(starlette)) + + +def unpatch(): + if not getattr(starlette, "_datadog_patch", False): + return + + starlette._datadog_patch = False + + _u(starlette.applications.Starlette, "__init__") + + # We need to check that Fastapi instrumentation hasn't already unpatched these + if isinstance(starlette.routing.Route.handle, ObjectProxy): + _u(starlette.routing.Route, "handle") + + if isinstance(starlette.routing.Mount.handle, ObjectProxy): + _u(starlette.routing.Mount, "handle") + + if isinstance(starlette.background.BackgroundTasks.add_task, ObjectProxy): + _u(starlette.background.BackgroundTasks, "add_task") + + +def traced_handler(wrapped, instance, args, kwargs): + # Since handle can be called multiple times for one request, we take the path of each instance + # Then combine them at the end to get the correct resource names + scope = get_argument_value(args, kwargs, 0, "scope") # type: Optional[Dict[str, Any]] + if not scope: + return wrapped(*args, **kwargs) + + # Our ASGI TraceMiddleware has not been called, skip since + # we won't have a request span to attach this information onto + # DEV: This can happen if patching happens after the app has been created + if "datadog" not in scope: + log.warning("datadog context not present in ASGI request scope, trace middleware may be missing") + return wrapped(*args, **kwargs) + + # Add the path to the resource_paths list + if "resource_paths" not in scope["datadog"]: + scope["datadog"]["resource_paths"] = [instance.path] + else: + scope["datadog"]["resource_paths"].append(instance.path) + + request_spans = scope["datadog"].get("request_spans", []) # type: List[Span] + resource_paths = scope["datadog"].get("resource_paths", []) # type: List[str] + + if len(request_spans) == len(resource_paths): + # Iterate through the request_spans and assign the correct resource name to each + for index, span in enumerate(request_spans): + # We want to set the full resource name on the first request span + # And one part less of the full resource name for each proceeding request span + # e.g. full path is /subapp/hello/{name}, first request span gets that as resource name + # Second request span gets /hello/{name} + path = "".join(resource_paths[index:]) + + if scope.get("method"): + span.resource = "{} {}".format(scope["method"], path) + else: + span.resource = path + # route should only be in the root span + if index == 0: + span.set_tag_str(http.ROUTE, path) + # at least always update the root asgi span resource name request_spans[0].resource = "".join(resource_paths) + elif request_spans and resource_paths: + route = "".join(resource_paths) + if scope.get("method"): + request_spans[0].resource = "{} {}".format(scope["method"], route) + else: + request_spans[0].resource = route + request_spans[0].set_tag_str(http.ROUTE, route) + else: + log.debug( + "unable to update the request span resource name, request_spans:%r, resource_paths:%r", + request_spans, + resource_paths, + ) + request_cookies = "" + for name, value in scope.get("headers"): + if name == b"cookie": + request_cookies = value.decode("utf-8", errors="ignore") + break + if request_spans: + trace_utils.set_http_meta( + request_spans[0], + "starlette", + request_path_params=scope.get("path_params"), + request_cookies=starlette_requests.cookie_parser(request_cookies), + route=request_spans[0].get_tag(http.ROUTE), + ) + core.dispatch("asgi.start_request", ("starlette",)) + if core.get_item(HTTP_REQUEST_BLOCKED): + raise trace_utils.InterruptException("starlette") + + return wrapped(*args, **kwargs) + + +@with_traced_module +def _trace_background_tasks(module, pin, wrapped, instance, args, kwargs): + task = get_argument_value(args, kwargs, 0, "func") + current_span = pin.tracer.current_span() + + async def traced_task(*args, **kwargs): + with pin.tracer.start_span( + f"{module.__name__}.background_task", resource=task.__name__, child_of=None, activate=True + ) as span: + if current_span: + span.link_span(current_span.context) + if inspect.iscoroutinefunction(task): + await task(*args, **kwargs) + else: + await run_in_threadpool(task, *args, **kwargs) + + args, kwargs = set_argument_value(args, kwargs, 0, "func", traced_task) + wrapped(*args, **kwargs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/structlog/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/structlog/__init__.py new file mode 100644 index 0000000..e7e14e5 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/structlog/__init__.py @@ -0,0 +1,52 @@ +""" +Datadog APM traces can be integrated with the logs produced by structlog by: + +1. Having ``ddtrace`` patch the ``structlog`` module. This will add a +processor in the beginning of the chain that adds trace attributes +to the event_dict + +2. For log correlation between APM and logs, the easiest format is via JSON +so that no further configuration needs to be done in the Datadog UI assuming +that the Datadog trace values are at the top level of the JSON + +Enabling +-------- + +Patch ``structlog`` +~~~~~~~~~~~~~~~~~~~ + +If using :ref:`ddtrace-run` then set the environment variable ``DD_LOGS_INJECTION=true``. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(structlog=True) + +Proper Formatting +~~~~~~~~~~~~~~~~~ + +The trace attributes are injected via a processor in the processor block of the configuration +whether that be the default processor chain or a user-configured chain. + +An example of a configuration that outputs to a file that can be injected into is as below:: + + structlog.configure( + processors=[structlog.processors.JSONRenderer()], + logger_factory=structlog.WriteLoggerFactory(file=Path("app").with_suffix(".log").open("wt"))) + +For more information, please see the attached guide for the Datadog Logging Product: +https://docs.datadoghq.com/logs/log_collection/python/ +""" + +from ...internal.utils.importlib import require_modules + + +required_modules = ["structlog"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/structlog/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/structlog/patch.py new file mode 100644 index 0000000..64b3f7a --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/structlog/patch.py @@ -0,0 +1,90 @@ +import structlog + +import ddtrace +from ddtrace import config + +from ..logging.constants import RECORD_ATTR_ENV +from ..logging.constants import RECORD_ATTR_SERVICE +from ..logging.constants import RECORD_ATTR_SPAN_ID +from ..logging.constants import RECORD_ATTR_TRACE_ID +from ..logging.constants import RECORD_ATTR_VALUE_EMPTY +from ..logging.constants import RECORD_ATTR_VALUE_ZERO +from ..logging.constants import RECORD_ATTR_VERSION +from ..trace_utils import unwrap as _u +from ..trace_utils import wrap as _w + + +config._add( + "structlog", + dict(), +) + + +def get_version(): + # type: () -> str + return getattr(structlog, "__version__", "") + + +def _tracer_injection(_, __, event_dict): + span = ddtrace.tracer.current_span() + + trace_id = None + span_id = None + if span: + span_id = span.span_id + trace_id = span.trace_id + if config._128_bit_trace_id_enabled and not config._128_bit_trace_id_logging_enabled: + trace_id = span._trace_id_64bits + + # add ids to structlog event dictionary + event_dict[RECORD_ATTR_TRACE_ID] = str(trace_id or RECORD_ATTR_VALUE_ZERO) + event_dict[RECORD_ATTR_SPAN_ID] = str(span_id or RECORD_ATTR_VALUE_ZERO) + # add the env, service, and version configured for the tracer + event_dict[RECORD_ATTR_ENV] = config.env or RECORD_ATTR_VALUE_EMPTY + event_dict[RECORD_ATTR_SERVICE] = config.service or RECORD_ATTR_VALUE_EMPTY + event_dict[RECORD_ATTR_VERSION] = config.version or RECORD_ATTR_VALUE_EMPTY + + return event_dict + + +def _w_get_logger(func, instance, args, kwargs): + """ + Append the tracer injection processor to the ``default_processors`` list used by the logger + The ``default_processors`` list has built in defaults which protects against a user configured ``None`` value. + The argument to configure ``default_processors`` accepts an iterable type: + - List: default use case which has been accounted for + - Tuple: patched via list conversion + - Set: ignored because structlog processors care about order notably the last value to be a Renderer + - Dict: because keys are ignored, this essentially becomes a List + """ + + dd_processor = [_tracer_injection] + structlog._config._CONFIG.default_processors = dd_processor + list(structlog._config._CONFIG.default_processors) + return func(*args, **kwargs) + + +def patch(): + """ + Patch ``structlog`` module for injection of tracer information + by appending a processor before creating a logger via ``structlog.get_logger`` + """ + if getattr(structlog, "_datadog_patch", False): + return + structlog._datadog_patch = True + + if hasattr(structlog, "get_logger"): + _w(structlog, "get_logger", _w_get_logger) + + # getLogger is an alias for get_logger + if hasattr(structlog, "getLogger"): + _w(structlog, "getLogger", _w_get_logger) + + +def unpatch(): + if getattr(structlog, "_datadog_patch", False): + structlog._datadog_patch = False + + if hasattr(structlog, "get_logger"): + _u(structlog, "get_logger") + if hasattr(structlog, "getLogger"): + _u(structlog, "getLogger") diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/subprocess/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/subprocess/__init__.py new file mode 100644 index 0000000..83be0b3 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/subprocess/__init__.py @@ -0,0 +1,33 @@ +""" +The subprocess integration will add tracing to all subprocess executions +started in your application. It will be automatically enabled if Application +Security is enabled with:: + + DD_APPSEC_ENABLED=true + + +Configuration +~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.subprocess['sensitive_wildcards'] + + Comma separated list of fnmatch-style wildcards Subprocess parameters matching these + wildcards will be scrubbed and replaced by a "?". + + Default: ``None`` for the config value but note that there are some wildcards always + enabled in this integration that you can check on + ```ddtrace.contrib.subprocess.constants.SENSITIVE_WORDS_WILDCARDS```. +""" + +from ...internal.utils.importlib import require_modules + + +required_modules = ["os", "subprocess"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/subprocess/constants.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/subprocess/constants.py new file mode 100644 index 0000000..294ef01 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/subprocess/constants.py @@ -0,0 +1,18 @@ +from ddtrace.appsec._constants import Constant_Class + + +class COMMANDS(metaclass=Constant_Class): + """ + string names used by the library for tagging data for subprocess executions in context or span + """ + + SPAN_NAME = "command_execution" + COMPONENT = "component" + SHELL = "cmd.shell" + EXEC = "cmd.exec" + TRUNCATED = "cmd.truncated" + EXIT_CODE = "cmd.exit_code" + CTX_SUBP_IS_SHELL = "subprocess_popen_is_shell" + CTX_SUBP_TRUNCATED = "subprocess_popen_truncated" + CTX_SUBP_LINE = "subprocess_popen_line" + CTX_SUBP_BINARY = "subprocess_popen_binary" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/subprocess/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/subprocess/patch.py new file mode 100644 index 0000000..8d934a0 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/subprocess/patch.py @@ -0,0 +1,410 @@ +import collections +from fnmatch import fnmatch +import os +import re +import shlex +import subprocess # nosec +from threading import RLock +from typing import Deque # noqa:F401 +from typing import Dict # noqa:F401 +from typing import List # noqa:F401 +from typing import Tuple # noqa:F401 +from typing import Union # noqa:F401 +from typing import cast # noqa:F401 + +import attr + +from ddtrace import Pin +from ddtrace import config +from ddtrace.contrib import trace_utils +from ddtrace.contrib.subprocess.constants import COMMANDS +from ddtrace.ext import SpanTypes +from ddtrace.internal import core +from ddtrace.internal.compat import shjoin +from ddtrace.internal.logger import get_logger +from ddtrace.settings.asm import config as asm_config + + +log = get_logger(__name__) + +config._add( + "subprocess", + dict(sensitive_wildcards=os.getenv("DD_SUBPROCESS_SENSITIVE_WILDCARDS", default="").split(",")), +) + + +def get_version(): + # type: () -> str + return "" + + +def patch(): + # type: () -> List[str] + patched = [] # type: List[str] + if not asm_config._asm_enabled: + return patched + + import os + + if not getattr(os, "_datadog_patch", False): + Pin().onto(os) + trace_utils.wrap(os, "system", _traced_ossystem(os)) + trace_utils.wrap(os, "fork", _traced_fork(os)) + + # all os.spawn* variants eventually use this one: + trace_utils.wrap(os, "_spawnvef", _traced_osspawn(os)) + + patched.append("os") + + if not getattr(subprocess, "_datadog_patch", False): + Pin().onto(subprocess) + # We store the parameters on __init__ in the context and set the tags on wait + # (where all the Popen objects eventually arrive, unless killed before it) + trace_utils.wrap(subprocess, "Popen.__init__", _traced_subprocess_init(subprocess)) + trace_utils.wrap(subprocess, "Popen.wait", _traced_subprocess_wait(subprocess)) + + os._datadog_patch = True + subprocess._datadog_patch = True + patched.append("subprocess") + + return patched + + +@attr.s(eq=False) +class SubprocessCmdLineCacheEntry(object): + binary = attr.ib(type=str, default=None) + arguments = attr.ib(type=List, default=None) + truncated = attr.ib(type=bool, default=False) + env_vars = attr.ib(type=List, default=None) + as_list = attr.ib(type=List, default=None) + as_string = attr.ib(type=str, default=None) + + +class SubprocessCmdLine(object): + # This catches the computed values into a SubprocessCmdLineCacheEntry object + _CACHE = {} # type: Dict[str, SubprocessCmdLineCacheEntry] + _CACHE_DEQUE = collections.deque() # type: Deque[str] + _CACHE_MAXSIZE = 32 + _CACHE_LOCK = RLock() + + @classmethod + def _add_new_cache_entry(cls, key, env_vars, binary, arguments, truncated): + if key in cls._CACHE: + return + + cache_entry = SubprocessCmdLineCacheEntry() + cache_entry.binary = binary + cache_entry.arguments = arguments + cache_entry.truncated = truncated + cache_entry.env_vars = env_vars + + with cls._CACHE_LOCK: + if len(cls._CACHE_DEQUE) >= cls._CACHE_MAXSIZE: + # If the cache is full, remove the oldest entry + last_cache_key = cls._CACHE_DEQUE[-1] + del cls._CACHE[last_cache_key] + cls._CACHE_DEQUE.pop() + + cls._CACHE[key] = cache_entry + cls._CACHE_DEQUE.appendleft(key) + + return cache_entry + + @classmethod + def _clear_cache(cls): + with cls._CACHE_LOCK: + cls._CACHE_DEQUE.clear() + cls._CACHE.clear() + + TRUNCATE_LIMIT = 4 * 1024 + + ENV_VARS_ALLOWLIST = {"LD_PRELOAD", "LD_LIBRARY_PATH", "PATH"} + + BINARIES_DENYLIST = { + "md5", + } + + SENSITIVE_WORDS_WILDCARDS = [ + "*password*", + "*passwd*", + "*mysql_pwd*", + "*access_token*", + "*auth_token*", + "*api_key*", + "*apikey*", + "*secret*", + "*credentials*", + "stripetoken", + ] + _COMPILED_ENV_VAR_REGEXP = re.compile(r"\b[A-Z_]+=\w+") + + def __init__(self, shell_args, shell=False): + # type: (Union[str, List[str]], bool) -> None + cache_key = str(shell_args) + str(shell) + self._cache_entry = SubprocessCmdLine._CACHE.get(cache_key) + if self._cache_entry: + self.env_vars = self._cache_entry.env_vars + self.binary = self._cache_entry.binary + self.arguments = self._cache_entry.arguments + self.truncated = self._cache_entry.truncated + else: + self.env_vars = [] + self.binary = "" + self.arguments = [] + self.truncated = False + + if isinstance(shell_args, str): + tokens = shlex.split(shell_args) + else: + tokens = cast(List[str], shell_args) + + # Extract previous environment variables, scrubbing all the ones not + # in ENV_VARS_ALLOWLIST + if shell: + self.scrub_env_vars(tokens) + else: + self.binary = tokens[0] + self.arguments = tokens[1:] + + self.arguments = list(self.arguments) if isinstance(self.arguments, tuple) else self.arguments + self.scrub_arguments() + + # Create a new cache entry to store the computed values except as_list + # and as_string that are computed and stored lazily + self._cache_entry = SubprocessCmdLine._add_new_cache_entry( + cache_key, self.env_vars, self.binary, self.arguments, self.truncated + ) + + def scrub_env_vars(self, tokens): + for idx, token in enumerate(tokens): + if re.match(self._COMPILED_ENV_VAR_REGEXP, token): + var, value = token.split("=") + if var in self.ENV_VARS_ALLOWLIST: + self.env_vars.append(token) + else: + # scrub the value + self.env_vars.append("%s=?" % var) + else: + # Next after vars are the binary and arguments + try: + self.binary = tokens[idx] + self.arguments = tokens[idx + 1 :] + except IndexError: + pass + break + + def scrub_arguments(self): + # if the binary is in the denylist, scrub all arguments + if self.binary.lower() in self.BINARIES_DENYLIST: + self.arguments = ["?" for _ in self.arguments] + return + + param_prefixes = ("-", "/") + # Scrub case by case + new_args = [] + deque_args = collections.deque(self.arguments) + while deque_args: + current = deque_args[0] + for sensitive in self.SENSITIVE_WORDS_WILDCARDS + config.subprocess.sensitive_wildcards: + if fnmatch(current, sensitive): + is_sensitive = True + break + else: + is_sensitive = False + + if not is_sensitive: + new_args.append(current) + deque_args.popleft() + continue + + # sensitive + if current[0] not in param_prefixes: + # potentially not argument, scrub it anyway if it matches a sensitive word + new_args.append("?") + deque_args.popleft() + continue + + # potential --argument + if "=" in current: + # contains "=" like in "--password=foo", scrub it just in case + new_args.append("?") + deque_args.popleft() + continue + + try: + if deque_args[1][0] in param_prefixes: + # Next is another option scrub only the current one + new_args.append("?") + deque_args.popleft() + continue + else: + # Next is not an option but potentially a value, scrub it instead + new_args.extend([current, "?"]) + deque_args.popleft() + deque_args.popleft() + continue + except IndexError: + # No next argument, scrub this one just in case since it's sensitive + new_args.append("?") + deque_args.popleft() + + self.arguments = new_args + + def truncate_string(self, str_): + # type: (str) -> str + oversize = len(str_) - self.TRUNCATE_LIMIT + + if oversize <= 0: + self.truncated = False + return str_ + + self.truncated = True + + msg = ' "4kB argument truncated by %d characters"' % oversize + return str_[0 : -(oversize + len(msg))] + msg + + def _as_list_and_string(self): + # type: () -> Tuple[list[str], str] + + total_list = self.env_vars + [self.binary] + self.arguments + truncated_str = self.truncate_string(shjoin(total_list)) + truncated_list = shlex.split(truncated_str) + return truncated_list, truncated_str + + def as_list(self): + if self._cache_entry.as_list is not None: + return self._cache_entry.as_list + + list_res, str_res = self._as_list_and_string() + self._cache_entry.as_list = list_res + self._cache_entry.as_string = str_res + return list_res + + def as_string(self): + if self._cache_entry.as_string is not None: + return self._cache_entry.as_string + + list_res, str_res = self._as_list_and_string() + self._cache_entry.as_list = list_res + self._cache_entry.as_string = str_res + return str_res + + +def unpatch(): + # type: () -> None + trace_utils.unwrap(os, "system") + trace_utils.unwrap(os, "_spawnvef") + trace_utils.unwrap(subprocess.Popen, "__init__") + trace_utils.unwrap(subprocess.Popen, "wait") + + SubprocessCmdLine._clear_cache() + + os._datadog_patch = False + subprocess._datadog_patch = False + + +@trace_utils.with_traced_module +def _traced_ossystem(module, pin, wrapped, instance, args, kwargs): + try: + shellcmd = SubprocessCmdLine(args[0], shell=True) # nosec + + with pin.tracer.trace(COMMANDS.SPAN_NAME, resource=shellcmd.binary, span_type=SpanTypes.SYSTEM) as span: + span.set_tag_str(COMMANDS.SHELL, shellcmd.as_string()) + if shellcmd.truncated: + span.set_tag_str(COMMANDS.TRUNCATED, "yes") + span.set_tag_str(COMMANDS.COMPONENT, "os") + ret = wrapped(*args, **kwargs) + span.set_tag_str(COMMANDS.EXIT_CODE, str(ret)) + return ret + except: # noqa:E722 + log.debug( + "Could not trace subprocess execution for os.system: [args: %s kwargs: %s]", args, kwargs, exc_info=True + ) + return wrapped(*args, **kwargs) + + +@trace_utils.with_traced_module +def _traced_fork(module, pin, wrapped, instance, args, kwargs): + try: + with pin.tracer.trace(COMMANDS.SPAN_NAME, resource="fork", span_type=SpanTypes.SYSTEM) as span: + span.set_tag(COMMANDS.EXEC, ["os.fork"]) + span.set_tag_str(COMMANDS.COMPONENT, "os") + ret = wrapped(*args, **kwargs) + return ret + except: # noqa:E722 + log.debug( + "Could not trace subprocess execution for os.fork*: [args: %s kwargs: %s]", args, kwargs, exc_info=True + ) + return wrapped(*args, **kwargs) + + +@trace_utils.with_traced_module +def _traced_osspawn(module, pin, wrapped, instance, args, kwargs): + try: + mode, file, func_args, _, _ = args + shellcmd = SubprocessCmdLine(func_args, shell=False) + + with pin.tracer.trace(COMMANDS.SPAN_NAME, resource=shellcmd.binary, span_type=SpanTypes.SYSTEM) as span: + span.set_tag(COMMANDS.EXEC, shellcmd.as_list()) + if shellcmd.truncated: + span.set_tag_str(COMMANDS.TRUNCATED, "true") + span.set_tag_str(COMMANDS.COMPONENT, "os") + + if mode == os.P_WAIT: + ret = wrapped(*args, **kwargs) + span.set_tag_str(COMMANDS.EXIT_CODE, str(ret)) + return ret + except: # noqa:E722 + log.debug( + "Could not trace subprocess execution for os.spawn*: [args: %s kwargs: %s]", args, kwargs, exc_info=True + ) + + return wrapped(*args, **kwargs) + + +@trace_utils.with_traced_module +def _traced_subprocess_init(module, pin, wrapped, instance, args, kwargs): + try: + cmd_args = args[0] if len(args) else kwargs["args"] + cmd_args_list = shlex.split(cmd_args) if isinstance(cmd_args, str) else cmd_args + is_shell = kwargs.get("shell", False) + shellcmd = SubprocessCmdLine(cmd_args_list, shell=is_shell) # nosec + + with pin.tracer.trace(COMMANDS.SPAN_NAME, resource=shellcmd.binary, span_type=SpanTypes.SYSTEM): + core.set_item(COMMANDS.CTX_SUBP_IS_SHELL, is_shell) + + if shellcmd.truncated: + core.set_item(COMMANDS.CTX_SUBP_TRUNCATED, "yes") + + if is_shell: + core.set_item(COMMANDS.CTX_SUBP_LINE, shellcmd.as_string()) + else: + core.set_item(COMMANDS.CTX_SUBP_LINE, shellcmd.as_list()) + core.set_item(COMMANDS.CTX_SUBP_BINARY, shellcmd.binary) + except: # noqa:E722 + log.debug("Could not trace subprocess execution: [args: %s kwargs: %s]", args, kwargs, exc_info=True) + + return wrapped(*args, **kwargs) + + +@trace_utils.with_traced_module +def _traced_subprocess_wait(module, pin, wrapped, instance, args, kwargs): + try: + binary = core.get_item("subprocess_popen_binary") + + with pin.tracer.trace(COMMANDS.SPAN_NAME, resource=binary, span_type=SpanTypes.SYSTEM) as span: + if core.get_item(COMMANDS.CTX_SUBP_IS_SHELL): + span.set_tag_str(COMMANDS.SHELL, core.get_item(COMMANDS.CTX_SUBP_LINE)) + else: + span.set_tag(COMMANDS.EXEC, core.get_item(COMMANDS.CTX_SUBP_LINE)) + + truncated = core.get_item(COMMANDS.CTX_SUBP_TRUNCATED) + if truncated: + span.set_tag_str(COMMANDS.TRUNCATED, "yes") + span.set_tag_str(COMMANDS.COMPONENT, "subprocess") + ret = wrapped(*args, **kwargs) + span.set_tag_str(COMMANDS.EXIT_CODE, str(ret)) + return ret + except: # noqa:E722 + log.debug("Could not trace subprocess execution [args: %s kwargs: %s]", args, kwargs, exc_info=True) + return wrapped(*args, **kwargs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/__init__.py new file mode 100644 index 0000000..2fa3577 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/__init__.py @@ -0,0 +1,130 @@ +r""" +The Tornado integration traces all ``RequestHandler`` defined in a Tornado web application. +Auto instrumentation is available using the ``patch`` function that **must be called before** +importing the tornado library. + +**Note:** This integration requires Python 3.7 and above for Tornado 5 and 6. + +The following is an example:: + + # patch before importing tornado and concurrent.futures + from ddtrace import tracer, patch + patch(tornado=True) + + import tornado.web + import tornado.gen + import tornado.ioloop + + # create your handlers + class MainHandler(tornado.web.RequestHandler): + @tornado.gen.coroutine + def get(self): + self.write("Hello, world") + + # create your application + app = tornado.web.Application([ + (r'/', MainHandler), + ]) + + # and run it as usual + app.listen(8888) + tornado.ioloop.IOLoop.current().start() + +When any type of ``RequestHandler`` is hit, a request root span is automatically created. If +you want to trace more parts of your application, you can use the ``wrap()`` decorator and +the ``trace()`` method as usual:: + + class MainHandler(tornado.web.RequestHandler): + @tornado.gen.coroutine + def get(self): + yield self.notify() + yield self.blocking_method() + with tracer.trace('tornado.before_write') as span: + # trace more work in the handler + + @tracer.wrap('tornado.executor_handler') + @tornado.concurrent.run_on_executor + def blocking_method(self): + # do something expensive + + @tracer.wrap('tornado.notify', service='tornado-notification') + @tornado.gen.coroutine + def notify(self): + # do something + +If you are overriding the ``on_finish`` or ``log_exception`` methods on a +``RequestHandler``, you will need to call the super method to ensure the +tracer's patched methods are called:: + + class MainHandler(tornado.web.RequestHandler): + @tornado.gen.coroutine + def get(self): + self.write("Hello, world") + + def on_finish(self): + super(MainHandler, self).on_finish() + # do other clean-up + + def log_exception(self, typ, value, tb): + super(MainHandler, self).log_exception(typ, value, tb) + # do other logging + +Tornado settings can be used to change some tracing configuration, like:: + + settings = { + 'datadog_trace': { + 'default_service': 'my-tornado-app', + 'tags': {'env': 'production'}, + 'distributed_tracing': False, + 'settings': { + 'FILTERS': [ + FilterRequestsOnUrl(r'http://test\\.example\\.com'), + ], + }, + }, + } + + app = tornado.web.Application([ + (r'/', MainHandler), + ], **settings) + +The available settings are: + +* ``default_service`` (default: `tornado-web`): set the service name used by the tracer. Usually + this configuration must be updated with a meaningful name. Can also be configured via the + ``DD_SERVICE`` environment variable. +* ``tags`` (default: `{}`): set global tags that should be applied to all spans. +* ``enabled`` (default: `True`): define if the tracer is enabled or not. If set to `false`, the + code is still instrumented but no spans are sent to the APM agent. +* ``distributed_tracing`` (default: `None`): enable distributed tracing if this is called + remotely from an instrumented application. Overrides the integration config which is configured via the + ``DD_TORNADO_DISTRIBUTED_TRACING`` environment variable. + We suggest to enable it only for internal services where headers are under your control. +* ``agent_hostname`` (default: `localhost`): define the hostname of the APM agent. +* ``agent_port`` (default: `8126`): define the port of the APM agent. +* ``settings`` (default: ``{}``): Tracer extra settings used to change, for instance, the filtering behavior. +""" +from ...internal.utils.importlib import require_modules + + +required_modules = ["tornado"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .stack_context import TracerStackContext + from .stack_context import run_with_trace_context + + context_provider = TracerStackContext() + + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = [ + "patch", + "unpatch", + "context_provider", + "run_with_trace_context", + "TracerStackContext", + "get_version", + ] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/application.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/application.py new file mode 100644 index 0000000..9e36eeb --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/application.py @@ -0,0 +1,59 @@ +from tornado import template + +import ddtrace +from ddtrace import config +from ddtrace.internal.schema import schematize_service_name + +from . import context_provider +from . import decorators +from .constants import CONFIG_KEY + + +def tracer_config(__init__, app, args, kwargs): + """ + Wrap Tornado web application so that we can configure services info and + tracing settings after the initialization. + """ + # call the Application constructor + __init__(*args, **kwargs) + + # default settings + settings = { + "tracer": ddtrace.tracer, + "default_service": schematize_service_name(config._get_service("tornado-web")), + "distributed_tracing": None, + "analytics_enabled": None, + } + + # update defaults with users settings + user_settings = app.settings.get(CONFIG_KEY) + if user_settings: + settings.update(user_settings) + + app.settings[CONFIG_KEY] = settings + tracer = settings["tracer"] + service = settings["default_service"] + + # extract extra settings + extra_settings = settings.get("settings", {}) + + # the tracer must use the right Context propagation and wrap executor; + # this action is done twice because the patch() method uses the + # global tracer while here we can have a different instance (even if + # this is not usual). + tracer.configure( + context_provider=context_provider, + wrap_executor=decorators.wrap_executor, + enabled=settings.get("enabled", None), + hostname=settings.get("agent_hostname", None), + port=settings.get("agent_port", None), + settings=extra_settings, + ) + + # set global tags if any + tags = settings.get("tags", None) + if tags: + tracer.set_tags(tags) + + # configure the PIN object for template rendering + ddtrace.Pin(service=service, tracer=tracer).onto(template) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/constants.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/constants.py new file mode 100644 index 0000000..18a3a6e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/constants.py @@ -0,0 +1,7 @@ +""" +This module defines Tornado settings that are shared between +integration modules. +""" +CONFIG_KEY = "datadog_trace" +REQUEST_SPAN_KEY = "__datadog_request_span" +FUTURE_SPAN_KEY = "__datadog_future_span" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/decorators.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/decorators.py new file mode 100644 index 0000000..8396962 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/decorators.py @@ -0,0 +1,82 @@ +import sys + +from ddtrace import config +from ddtrace.internal.constants import COMPONENT + +from .constants import FUTURE_SPAN_KEY + + +def _finish_span(future): + """ + Finish the span if it's attached to the given ``Future`` object. + This method is a Tornado callback used to close a decorated function + executed as a coroutine or as a synchronous function in another thread. + """ + span = getattr(future, FUTURE_SPAN_KEY, None) + + if span: + # `tornado.concurrent.Future` in PY3 tornado>=4.0,<5 has `exc_info` + if callable(getattr(future, "exc_info", None)): + # retrieve the exception from the coroutine object + exc_info = future.exc_info() + if exc_info: + span.set_exc_info(*exc_info) + elif callable(getattr(future, "exception", None)): + # in tornado>=4.0,<5 with PY2 `concurrent.futures._base.Future` + # `exception_info()` returns `(exception, traceback)` but + # `exception()` only returns the first element in the tuple + if callable(getattr(future, "exception_info", None)): + exc, exc_tb = future.exception_info() + if exc and exc_tb: + exc_type = type(exc) + span.set_exc_info(exc_type, exc, exc_tb) + # in tornado>=5 with PY3, `tornado.concurrent.Future` is alias to + # `asyncio.Future` in PY3 `exc_info` not available, instead use + # exception method + else: + exc = future.exception() + if exc: + # we expect exception object to have a traceback attached + if hasattr(exc, "__traceback__"): + exc_type = type(exc) + exc_tb = getattr(exc, "__traceback__", None) + span.set_exc_info(exc_type, exc, exc_tb) + # if all else fails use currently handled exception for + # current thread + else: + span.set_exc_info(*sys.exc_info()) + + span.finish() + + +def wrap_executor(tracer, fn, args, kwargs, span_name, service=None, resource=None, span_type=None): + """ + Wrap executor function used to change the default behavior of + ``Tracer.wrap()`` method. A decorated Tornado function can be + a regular function or a coroutine; if a coroutine is decorated, a + span is attached to the returned ``Future`` and a callback is set + so that it will close the span when the ``Future`` is done. + """ + span = tracer.trace(span_name, service=service, resource=resource, span_type=span_type) + + span.set_tag_str(COMPONENT, config.tornado.integration_name) + + # catch standard exceptions raised in synchronous executions + try: + future = fn(*args, **kwargs) + + # duck-typing: if it has `add_done_callback` it's a Future + # object whatever is the underlying implementation + if callable(getattr(future, "add_done_callback", None)): + setattr(future, FUTURE_SPAN_KEY, span) + future.add_done_callback(_finish_span) + else: + # we don't have a future so the `future` variable + # holds the result of the function + span.finish() + except Exception: + span.set_traceback() + span.finish() + raise + + return future diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/handlers.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/handlers.py new file mode 100644 index 0000000..04877dd --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/handlers.py @@ -0,0 +1,148 @@ +from collections import deque + +from tornado.web import HTTPError + +from ddtrace import config +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema.span_attribute_schema import SpanDirection + +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from ...internal.schema import schematize_url_operation +from ...internal.utils import ArgumentError +from ...internal.utils import get_argument_value +from .. import trace_utils +from ..trace_utils import set_http_meta +from .constants import CONFIG_KEY +from .constants import REQUEST_SPAN_KEY +from .stack_context import TracerStackContext + + +def execute(func, handler, args, kwargs): + """ + Wrap the handler execute method so that the entire request is within the same + ``TracerStackContext``. This simplifies users code when the automatic ``Context`` + retrieval is used via ``Tracer.trace()`` method. + """ + # retrieve tracing settings + settings = handler.settings[CONFIG_KEY] + tracer = settings["tracer"] + service = settings["default_service"] + distributed_tracing = settings["distributed_tracing"] + + with TracerStackContext(): + trace_utils.activate_distributed_headers( + tracer, int_config=config.tornado, request_headers=handler.request.headers, override=distributed_tracing + ) + + # store the request span in the request so that it can be used later + request_span = tracer.trace( + schematize_url_operation("tornado.request", protocol="http", direction=SpanDirection.INBOUND), + service=service, + span_type=SpanTypes.WEB, + ) + + request_span.set_tag_str(COMPONENT, config.tornado.integration_name) + + # set span.kind to the type of operation being performed + request_span.set_tag_str(SPAN_KIND, SpanKind.SERVER) + + request_span.set_tag(SPAN_MEASURED_KEY) + # set analytics sample rate + # DEV: tornado is special case maintains separate configuration from config api + analytics_enabled = settings["analytics_enabled"] + if (config.analytics_enabled and analytics_enabled is not False) or analytics_enabled is True: + request_span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, settings.get("analytics_sample_rate", True)) + + http_route = _find_route(handler.application.default_router.rules, handler.request) + request_span.set_tag_str("http.route", http_route) + setattr(handler.request, REQUEST_SPAN_KEY, request_span) + + return func(*args, **kwargs) + + +def _find_route(initial_rule_set, request): + """ + We have to walk through the same chain of rules that tornado does to find a matching rule. + """ + rules = deque() + + for rule in initial_rule_set: + rules.append(rule) + + while len(rules) > 0: + rule = rules.popleft() + if rule.matcher.match(request) is not None: + if hasattr(rule.matcher, "_path"): + return rule.matcher._path + elif hasattr(rule.target, "rules"): + rules.extendleft(rule.target.rules) + + return "^$" + + +def on_finish(func, handler, args, kwargs): + """ + Wrap the ``RequestHandler.on_finish`` method. This is the last executed method + after the response has been sent, and it's used to retrieve and close the + current request span (if available). + """ + request = handler.request + request_span = getattr(request, REQUEST_SPAN_KEY, None) + if request_span: + # use the class name as a resource; if an handler is not available, the + # default handler class will be used so we don't pollute the resource + # space here + klass = handler.__class__ + request_span.resource = "{}.{}".format(klass.__module__, klass.__name__) + set_http_meta( + request_span, + config.tornado, + method=request.method, + url=request.full_url().rsplit("?", 1)[0], + status_code=handler.get_status(), + query=request.query, + ) + request_span.finish() + + return func(*args, **kwargs) + + +def log_exception(func, handler, args, kwargs): + """ + Wrap the ``RequestHandler.log_exception``. This method is called when an + Exception is not handled in the user code. In this case, we save the exception + in the current active span. If the Tornado ``Finish`` exception is raised, this wrapper + will not be called because ``Finish`` is not an exception. + """ + # safe-guard: expected arguments -> log_exception(self, typ, value, tb) + try: + value = get_argument_value(args, kwargs, 1, "value") + except ArgumentError: + value = None + + if not value: + return func(*args, **kwargs) + + # retrieve the current span + tracer = handler.settings[CONFIG_KEY]["tracer"] + current_span = tracer.current_span() + + if not current_span: + return func(*args, **kwargs) + + if isinstance(value, HTTPError): + # Tornado uses HTTPError exceptions to stop and return a status code that + # is not a 2xx. In this case we want to check the status code to be sure that + # only 5xx are traced as errors, while any other HTTPError exception is handled as + # usual. + if config.http_server.is_error_code(value.status_code): + current_span.set_exc_info(*args) + else: + # any other uncaught exception should be reported as error + current_span.set_exc_info(*args) + + return func(*args, **kwargs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/patch.py new file mode 100644 index 0000000..7e7a1b2 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/patch.py @@ -0,0 +1,72 @@ +import os + +import tornado + +import ddtrace +from ddtrace import config +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w + +from ...internal.utils.formats import asbool +from ...internal.utils.wrappers import unwrap as _u +from . import application +from . import context_provider +from . import decorators +from . import handlers +from . import template + + +config._add( + "tornado", + dict( + distributed_tracing=asbool(os.getenv("DD_TORNADO_DISTRIBUTED_TRACING", default=True)), + ), +) + + +def get_version(): + # type: () -> str + return getattr(tornado, "version", "") + + +def patch(): + """ + Tracing function that patches the Tornado web application so that it will be + traced using the given ``tracer``. + """ + # patch only once + if getattr(tornado, "__datadog_patch", False): + return + tornado.__datadog_patch = True + + # patch Application to initialize properly our settings and tracer + _w("tornado.web", "Application.__init__", application.tracer_config) + + # patch RequestHandler to trace all Tornado handlers + _w("tornado.web", "RequestHandler._execute", handlers.execute) + _w("tornado.web", "RequestHandler.on_finish", handlers.on_finish) + _w("tornado.web", "RequestHandler.log_exception", handlers.log_exception) + + # patch Template system + _w("tornado.template", "Template.generate", template.generate) + + # configure the global tracer + ddtrace.tracer.configure( + context_provider=context_provider, + wrap_executor=decorators.wrap_executor, + ) + + +def unpatch(): + """ + Remove all tracing functions in a Tornado web application. + """ + if not getattr(tornado, "__datadog_patch", False): + return + tornado.__datadog_patch = False + + # unpatch Tornado + _u(tornado.web.RequestHandler, "_execute") + _u(tornado.web.RequestHandler, "on_finish") + _u(tornado.web.RequestHandler, "log_exception") + _u(tornado.web.Application, "__init__") + _u(tornado.template.Template, "generate") diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/stack_context.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/stack_context.py new file mode 100644 index 0000000..e6d688e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/stack_context.py @@ -0,0 +1,144 @@ +import tornado +from tornado.ioloop import IOLoop + +from ...provider import BaseContextProvider +from ...provider import DefaultContextProvider +from ...span import Span + + +# tornado.stack_context deprecated in Tornado 5 removed in Tornado 6 +# instead use DefaultContextProvider with ContextVarContextManager for asyncio +_USE_STACK_CONTEXT = not tornado.version_info >= (5, 0) + +if _USE_STACK_CONTEXT: + from tornado.stack_context import StackContextInconsistentError + from tornado.stack_context import _state + + class TracerStackContext(DefaultContextProvider): + """ + A context manager that manages ``Context`` instances in a thread-local state. + It must be used every time a Tornado's handler or coroutine is used within a + tracing Context. It is meant to work like a traditional ``StackContext``, + preserving the state across asynchronous calls. + + Every time a new manager is initialized, a new ``Context()`` is created for + this execution flow. A context created in a ``TracerStackContext`` is not + shared between different threads. + + This implementation follows some suggestions provided here: + https://github.com/tornadoweb/tornado/issues/1063 + """ + + def __init__(self): + # type: (...) -> None + # HACK(jd): this should be using super(), but calling DefaultContextProvider.__init__ + # sets the context to `None` which breaks this code. + # We therefore skip DefaultContextProvider.__init__ and call only BaseContextProvider.__init__. + BaseContextProvider.__init__(self) + self._context = None + + def enter(self): + """ + Required to preserve the ``StackContext`` protocol. + """ + pass + + def exit(self, type, value, traceback): # noqa: A002 + """ + Required to preserve the ``StackContext`` protocol. + """ + pass + + def __enter__(self): + self.old_contexts = _state.contexts + self.new_contexts = (self.old_contexts[0] + (self,), self) + _state.contexts = self.new_contexts + return self + + def __exit__(self, type, value, traceback): # noqa: A002 + final_contexts = _state.contexts + _state.contexts = self.old_contexts + + if final_contexts is not self.new_contexts: + raise StackContextInconsistentError( + "stack_context inconsistency (may be caused by yield " 'within a "with TracerStackContext" block)' + ) + + # break the reference to allow faster GC on CPython + self.new_contexts = None + + def _has_io_loop(self): + """Helper to determine if we are currently in an IO loop""" + return getattr(IOLoop._current, "instance", None) is not None + + def _has_active_context(self): + """Helper to determine if we have an active context or not""" + if not self._has_io_loop(): + return super(TracerStackContext, self)._has_active_context() + else: + # we're inside a Tornado loop so the TracerStackContext is used + return self._get_state_active_context() is not None + + def _get_state_active_context(self): + """Helper to get the currently active context from the TracerStackContext""" + # we're inside a Tornado loop so the TracerStackContext is used + for stack in reversed(_state.contexts[0]): + if isinstance(stack, self.__class__): + ctx = stack._context + if isinstance(ctx, Span): + return self._update_active(ctx) + return ctx + return None + + def active(self): + """ + Return the ``Context`` from the current execution flow. This method can be + used inside a Tornado coroutine to retrieve and use the current tracing context. + If used in a separated Thread, the `_state` thread-local storage is used to + propagate the current Active context from the `MainThread`. + """ + if not self._has_io_loop(): + # if a Tornado loop is not available, it means that this method + # has been called from a synchronous code, so we can rely in a + # thread-local storage + return super(TracerStackContext, self).active() + else: + # we're inside a Tornado loop so the TracerStackContext is used + return self._get_state_active_context() + + def activate(self, ctx): + """ + Set the active ``Context`` for this async execution. If a ``TracerStackContext`` + is not found, the context is discarded. + If used in a separated Thread, the `_state` thread-local storage is used to + propagate the current Active context from the `MainThread`. + """ + if not self._has_io_loop(): + # because we're outside of an asynchronous execution, we store + # the current context in a thread-local storage + super(TracerStackContext, self).activate(ctx) + else: + # we're inside a Tornado loop so the TracerStackContext is used + for stack_ctx in reversed(_state.contexts[0]): + if isinstance(stack_ctx, self.__class__): + stack_ctx._context = ctx + return ctx + +else: + # no-op when not using stack_context + class TracerStackContext(DefaultContextProvider): + def __enter__(self): + pass + + def __exit__(self, *exc): + pass + + +def run_with_trace_context(func, *args, **kwargs): + """ + Run the given function within a traced StackContext. This function is used to + trace Tornado web handlers, but can be used in your code to trace coroutines + execution. + """ + with TracerStackContext(): + return func(*args, **kwargs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/template.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/template.py new file mode 100644 index 0000000..9e6ee15 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/tornado/template.py @@ -0,0 +1,35 @@ +from tornado import template + +from ddtrace import Pin +from ddtrace import config +from ddtrace.internal.constants import COMPONENT + +from ...ext import SpanTypes + + +def generate(func, renderer, args, kwargs): + """ + Wrap the ``generate`` method used in templates rendering. Because the method + may be called everywhere, the execution is traced in a tracer StackContext that + inherits the current one if it's already available. + """ + # get the module pin + pin = Pin.get_from(template) + if not pin or not pin.enabled(): + return func(*args, **kwargs) + + # change the resource and the template name + # if it's created from a string instead of a file + if "" in renderer.name: + resource = template_name = "render_string" + else: + resource = template_name = renderer.name + + # trace the original call + with pin.tracer.trace( + "tornado.template", service=pin.service, resource=resource, span_type=SpanTypes.TEMPLATE + ) as span: + span.set_tag_str(COMPONENT, config.tornado.integration_name) + + span.set_tag_str("tornado.template_name", template_name) + return func(*args, **kwargs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/trace_utils.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/trace_utils.py new file mode 100644 index 0000000..2efbb76 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/trace_utils.py @@ -0,0 +1,682 @@ +""" +This module contains utility functions for writing ddtrace integrations. +""" +from collections import deque +import ipaddress +import re +from typing import TYPE_CHECKING # noqa:F401 +from typing import Any # noqa:F401 +from typing import Callable # noqa:F401 +from typing import Dict # noqa:F401 +from typing import Generator # noqa:F401 +from typing import Iterator # noqa:F401 +from typing import List # noqa:F401 +from typing import Mapping # noqa:F401 +from typing import Optional # noqa:F401 +from typing import Tuple # noqa:F401 +from typing import Union # noqa:F401 +from typing import cast # noqa:F401 + +from ddtrace import Pin +from ddtrace import config +from ddtrace.ext import http +from ddtrace.ext import net +from ddtrace.ext import user +from ddtrace.internal import core +from ddtrace.internal.compat import ip_is_global +from ddtrace.internal.compat import parse +from ddtrace.internal.logger import get_logger +from ddtrace.internal.utils.cache import cached +from ddtrace.internal.utils.http import normalize_header_name +from ddtrace.internal.utils.http import redact_url +from ddtrace.internal.utils.http import strip_query_string +import ddtrace.internal.utils.wrappers +from ddtrace.propagation.http import HTTPPropagator +from ddtrace.settings.asm import config as asm_config +from ddtrace.vendor import wrapt + + +if TYPE_CHECKING: # pragma: no cover + from ddtrace import Span # noqa:F401 + from ddtrace import Tracer # noqa:F401 + from ddtrace.settings import IntegrationConfig # noqa:F401 + + +log = get_logger(__name__) + +wrap = wrapt.wrap_function_wrapper +unwrap = ddtrace.internal.utils.wrappers.unwrap +iswrapped = ddtrace.internal.utils.wrappers.iswrapped + +REQUEST = "request" +RESPONSE = "response" + +# Tag normalization based on: https://docs.datadoghq.com/tagging/#defining-tags +# With the exception of '.' in header names which are replaced with '_' to avoid +# starting a "new object" on the UI. +NORMALIZE_PATTERN = re.compile(r"([^a-z0-9_\-:/]){1}") + +# Possible User Agent header. +USER_AGENT_PATTERNS = ("http-user-agent", "user-agent") + +IP_PATTERNS = ( + "x-forwarded-for", + "x-real-ip", + "true-client-ip", + "x-client-ip", + "x-forwarded", + "forwarded-for", + "x-cluster-client-ip", + "fastly-client-ip", + "cf-connecting-ip", + "cf-connecting-ipv6", +) + + +@cached() +def _normalized_header_name(header_name): + # type: (str) -> str + return NORMALIZE_PATTERN.sub("_", normalize_header_name(header_name)) + + +def _get_header_value_case_insensitive(headers, keyname): + # type: (Mapping[str, str], str) -> Optional[str] + """ + Get a header in a case insensitive way. This function is meant for frameworks + like Django < 2.2 that don't store the headers in a case insensitive mapping. + """ + # just in case we are lucky + shortcut_value = headers.get(keyname) + if shortcut_value is not None: + return shortcut_value + + for key, value in headers.items(): + if key.lower().replace("_", "-") == keyname: + return value + + return None + + +def _normalize_tag_name(request_or_response, header_name): + # type: (str, str) -> str + """ + Given a tag name, e.g. 'Content-Type', returns a corresponding normalized tag name, i.e + 'http.request.headers.content_type'. Rules applied actual header name are: + - any letter is converted to lowercase + - any digit is left unchanged + - any block of any length of different ASCII chars is converted to a single underscore '_' + :param request_or_response: The context of the headers: request|response + :param header_name: The header's name + :type header_name: str + :rtype: str + """ + # Looking at: + # - http://www.iana.org/assignments/message-headers/message-headers.xhtml + # - https://tools.ietf.org/html/rfc6648 + # and for consistency with other language integrations seems safe to assume the following algorithm for header + # names normalization: + # - any letter is converted to lowercase + # - any digit is left unchanged + # - any block of any length of different ASCII chars is converted to a single underscore '_' + normalized_name = _normalized_header_name(header_name) + return "http.{}.headers.{}".format(request_or_response, normalized_name) + + +def _store_headers(headers, span, integration_config, request_or_response): + # type: (Dict[str, str], Span, IntegrationConfig, str) -> None + """ + :param headers: A dict of http headers to be stored in the span + :type headers: dict or list + :param span: The Span instance where tags will be stored + :type span: ddtrace.span.Span + :param integration_config: An integration specific config object. + :type integration_config: ddtrace.settings.IntegrationConfig + """ + if not isinstance(headers, dict): + try: + headers = dict(headers) + except Exception: + return + + if integration_config is None: + log.debug("Skipping headers tracing as no integration config was provided") + return + + for header_name, header_value in headers.items(): + """config._header_tag_name gets an element of the dictionary in config.http._header_tags + which gets the value from DD_TRACE_HEADER_TAGS environment variable.""" + tag_name = integration_config._header_tag_name(header_name) + if tag_name is None: + continue + # An empty tag defaults to a http..headers.

tag + span.set_tag_str(tag_name or _normalize_tag_name(request_or_response, header_name), header_value) + + +def _get_request_header_user_agent(headers, headers_are_case_sensitive=False): + # type: (Mapping[str, str], bool) -> str + """Get user agent from request headers + :param headers: A dict of http headers to be stored in the span + :type headers: dict or list + """ + for key_pattern in USER_AGENT_PATTERNS: + if not headers_are_case_sensitive: + user_agent = headers.get(key_pattern) + else: + user_agent = _get_header_value_case_insensitive(headers, key_pattern) + + if user_agent: + return user_agent + return "" + + +# Used to cache the last header used for the cache. From the same server/framework +# usually the same header will be used on further requests, so we use this to check +# only it. +_USED_IP_HEADER = "" + + +def _get_request_header_client_ip(headers, peer_ip=None, headers_are_case_sensitive=False): + # type: (Optional[Mapping[str, str]], Optional[str], bool) -> str + + global _USED_IP_HEADER + + def get_header_value(key): # type: (str) -> Optional[str] + if not headers_are_case_sensitive: + return headers.get(key) + + return _get_header_value_case_insensitive(headers, key) + + if not headers: + try: + _ = ipaddress.ip_address(str(peer_ip)) + except ValueError: + return "" + return peer_ip + + ip_header_value = "" + user_configured_ip_header = config.client_ip_header + if user_configured_ip_header: + # Used selected the header to use to get the IP + ip_header_value = get_header_value( + user_configured_ip_header.lower().replace("_", "-") + if headers_are_case_sensitive + else user_configured_ip_header + ) + if not ip_header_value: + log.debug("DD_TRACE_CLIENT_IP_HEADER configured but '%s' header missing", user_configured_ip_header) + return "" + + try: + _ = ipaddress.ip_address(str(ip_header_value)) + except ValueError: + log.debug("Invalid IP address from configured %s header: %s", user_configured_ip_header, ip_header_value) + return "" + + else: + # No configured IP header, go through the IP_PATTERNS headers in order + if _USED_IP_HEADER: + # Check first the caught header that previously contained an IP + ip_header_value = get_header_value(_USED_IP_HEADER) + + if not ip_header_value: + for ip_header in IP_PATTERNS: + tmp_ip_header_value = get_header_value(ip_header) + if tmp_ip_header_value: + ip_header_value = tmp_ip_header_value + _USED_IP_HEADER = ip_header + break + + private_ip_from_headers = "" + + if ip_header_value: + # At this point, we have one IP header, check its value and retrieve the first public IP + ip_list = ip_header_value.split(",") + for ip in ip_list: + ip = ip.strip() + if not ip: + continue + + try: + if ip_is_global(ip): + return ip + elif not private_ip_from_headers: + # IP is private, store it just in case we don't find a public one later + private_ip_from_headers = ip + except ValueError: # invalid IP + continue + + # At this point we have none or maybe one private ip from the headers: check the peer ip in + # case it's public and, if not, return either the private_ip from the headers (if we have one) + # or the peer private ip + try: + if ip_is_global(peer_ip) or not private_ip_from_headers: + return peer_ip + except ValueError: + pass + + return private_ip_from_headers + + +def _store_request_headers(headers, span, integration_config): + # type: (Dict[str, str], Span, IntegrationConfig) -> None + """ + Store request headers as a span's tags + :param headers: All the request's http headers, will be filtered through the whitelist + :type headers: dict or list + :param span: The Span instance where tags will be stored + :type span: ddtrace.Span + :param integration_config: An integration specific config object. + :type integration_config: ddtrace.settings.IntegrationConfig + """ + _store_headers(headers, span, integration_config, REQUEST) + + +def _store_response_headers(headers, span, integration_config): + # type: (Dict[str, str], Span, IntegrationConfig) -> None + """ + Store response headers as a span's tags + :param headers: All the response's http headers, will be filtered through the whitelist + :type headers: dict or list + :param span: The Span instance where tags will be stored + :type span: ddtrace.Span + :param integration_config: An integration specific config object. + :type integration_config: ddtrace.settings.IntegrationConfig + """ + _store_headers(headers, span, integration_config, RESPONSE) + + +def _sanitized_url(url): + # type: (str) -> str + """ + Sanitize url by removing parts with potential auth info + """ + if "@" in url: + parsed = parse.urlparse(url) + netloc = parsed.netloc + + if "@" not in netloc: + # Safe url, `@` not in netloc + return url + + netloc = netloc[netloc.index("@") + 1 :] + return parse.urlunparse( + ( + parsed.scheme, + netloc, + parsed.path, + "", + parsed.query, + "", + ) + ) + + return url + + +def with_traced_module(func): + """Helper for providing tracing essentials (module and pin) for tracing + wrappers. + + This helper enables tracing wrappers to dynamically be disabled when the + corresponding pin is disabled. + + Usage:: + + @with_traced_module + def my_traced_wrapper(django, pin, func, instance, args, kwargs): + # Do tracing stuff + pass + + def patch(): + import django + wrap(django.somefunc, my_traced_wrapper(django)) + """ + + def with_mod(mod): + def wrapper(wrapped, instance, args, kwargs): + pin = Pin._find(instance, mod) + if pin and not pin.enabled(): + return wrapped(*args, **kwargs) + elif not pin: + log.debug("Pin not found for traced method %r", wrapped) + return wrapped(*args, **kwargs) + return func(mod, pin, wrapped, instance, args, kwargs) + + return wrapper + + return with_mod + + +def distributed_tracing_enabled(int_config, default=False): + # type: (IntegrationConfig, bool) -> bool + """Returns whether distributed tracing is enabled for this integration config""" + if "distributed_tracing_enabled" in int_config and int_config.distributed_tracing_enabled is not None: + return int_config.distributed_tracing_enabled + elif "distributed_tracing" in int_config and int_config.distributed_tracing is not None: + return int_config.distributed_tracing + return default + + +def int_service(pin, int_config, default=None): + # type: (Optional[Pin], IntegrationConfig, Optional[str]) -> Optional[str] + """Returns the service name for an integration which is internal + to the application. Internal meaning that the work belongs to the + user's application. Eg. Web framework, sqlalchemy, web servers. + + For internal integrations we prioritize overrides, then global defaults and + lastly the default provided by the integration. + """ + # Pin has top priority since it is user defined in code + if pin is not None and pin.service: + return pin.service + + # Config is next since it is also configured via code + # Note that both service and service_name are used by + # integrations. + if "service" in int_config and int_config.service is not None: + return cast(str, int_config.service) + if "service_name" in int_config and int_config.service_name is not None: + return cast(str, int_config.service_name) + + global_service = int_config.global_config._get_service() + if global_service: + return cast(str, global_service) + + if "_default_service" in int_config and int_config._default_service is not None: + return cast(str, int_config._default_service) + + return default + + +def ext_service(pin, int_config, default=None): + # type: (Optional[Pin], IntegrationConfig, Optional[str]) -> Optional[str] + """Returns the service name for an integration which is external + to the application. External meaning that the integration generates + spans wrapping code that is outside the scope of the user's application. Eg. A database, RPC, cache, etc. + """ + if pin is not None and pin.service: + return pin.service + + if "service" in int_config and int_config.service is not None: + return cast(str, int_config.service) + if "service_name" in int_config and int_config.service_name is not None: + return cast(str, int_config.service_name) + + if "_default_service" in int_config and int_config._default_service is not None: + return cast(str, int_config._default_service) + + # A default is required since it's an external service. + return default + + +def _set_url_tag(integration_config, span, url, query): + # type: (IntegrationConfig, Span, str, str) -> None + + if integration_config.http_tag_query_string: # Tagging query string in http.url + if config.global_query_string_obfuscation_disabled: # No redacting of query strings + span.set_tag_str(http.URL, url) + else: # Redact query strings + span.set_tag_str(http.URL, redact_url(url, config._obfuscation_query_string_pattern, query)) + else: # Not tagging query string in http.url + span.set_tag_str(http.URL, strip_query_string(url)) + + +def set_http_meta( + span, # type: Span + integration_config, # type: IntegrationConfig + method=None, # type: Optional[str] + url=None, # type: Optional[str] + target_host=None, # type: Optional[str] + status_code=None, # type: Optional[Union[int, str]] + status_msg=None, # type: Optional[str] + query=None, # type: Optional[str] + parsed_query=None, # type: Optional[Mapping[str, str]] + request_headers=None, # type: Optional[Mapping[str, str]] + response_headers=None, # type: Optional[Mapping[str, str]] + retries_remain=None, # type: Optional[Union[int, str]] + raw_uri=None, # type: Optional[str] + request_cookies=None, # type: Optional[Dict[str, str]] + request_path_params=None, # type: Optional[Dict[str, str]] + request_body=None, # type: Optional[Union[str, Dict[str, List[str]]]] + peer_ip=None, # type: Optional[str] + headers_are_case_sensitive=False, # type: bool + route=None, # type: Optional[str] + response_cookies=None, # type: Optional[Dict[str, str]] +): + # type: (...) -> None + """ + Set HTTP metas on the span + + :param method: the HTTP method + :param url: the HTTP URL + :param status_code: the HTTP status code + :param status_msg: the HTTP status message + :param query: the HTTP query part of the URI as a string + :param parsed_query: the HTTP query part of the URI as parsed by the framework and forwarded to the user code + :param request_headers: the HTTP request headers + :param response_headers: the HTTP response headers + :param raw_uri: the full raw HTTP URI (including ports and query) + :param request_cookies: the HTTP request cookies as a dict + :param request_path_params: the parameters of the HTTP URL as set by the framework: /posts/ would give us + { "id": } + """ + if method is not None: + span.set_tag_str(http.METHOD, method) + + if url is not None: + url = _sanitized_url(url) + _set_url_tag(integration_config, span, url, query) + + if target_host is not None: + span.set_tag_str(net.TARGET_HOST, target_host) + + if status_code is not None: + try: + int_status_code = int(status_code) + except (TypeError, ValueError): + log.debug("failed to convert http status code %r to int", status_code) + else: + span.set_tag_str(http.STATUS_CODE, str(status_code)) + if config.http_server.is_error_code(int_status_code): + span.error = 1 + + if status_msg is not None: + span.set_tag_str(http.STATUS_MSG, status_msg) + + if query is not None and integration_config.trace_query_string: + span.set_tag_str(http.QUERY_STRING, query) + + request_ip = peer_ip + if request_headers: + user_agent = _get_request_header_user_agent(request_headers, headers_are_case_sensitive) + if user_agent: + span.set_tag_str(http.USER_AGENT, user_agent) + + # We always collect the IP if appsec is enabled to report it on potential vulnerabilities. + # https://datadoghq.atlassian.net/wiki/spaces/APS/pages/2118779066/Client+IP+addresses+resolution + if asm_config._asm_enabled or config.retrieve_client_ip: + # Retrieve the IP if it was calculated on AppSecProcessor.on_span_start + request_ip = core.get_item("http.request.remote_ip", span=span) + + if not request_ip: + # Not calculated: framework does not support IP blocking or testing env + request_ip = ( + _get_request_header_client_ip(request_headers, peer_ip, headers_are_case_sensitive) or peer_ip + ) + + if request_ip: + span.set_tag_str(http.CLIENT_IP, request_ip) + span.set_tag_str("network.client.ip", request_ip) + + if integration_config.is_header_tracing_configured: + """We should store both http..headers. and + http.. The last one + is the DD standardized tag for user-agent""" + _store_request_headers(dict(request_headers), span, integration_config) + + if response_headers is not None and integration_config.is_header_tracing_configured: + _store_response_headers(dict(response_headers), span, integration_config) + + if retries_remain is not None: + span.set_tag_str(http.RETRIES_REMAIN, str(retries_remain)) + + core.dispatch( + "set_http_meta_for_asm", + [ + span, + request_ip, + raw_uri, + route, + method, + request_headers, + request_cookies, + parsed_query, + request_path_params, + request_body, + status_code, + response_headers, + response_cookies, + ], + ) + + if route is not None: + span.set_tag_str(http.ROUTE, route) + + +def activate_distributed_headers(tracer, int_config=None, request_headers=None, override=None): + # type: (Tracer, Optional[IntegrationConfig], Optional[Dict[str, str]], Optional[bool]) -> None + """ + Helper for activating a distributed trace headers' context if enabled in integration config. + int_config will be used to check if distributed trace headers context will be activated, but + override will override whatever value is set in int_config if passed any value other than None. + """ + if override is False: + return None + + if override or (int_config and distributed_tracing_enabled(int_config)): + context = HTTPPropagator.extract(request_headers) + + # Only need to activate the new context if something was propagated + if not context.trace_id: + return None + + # Do not reactivate a context with the same trace id + # DEV: An example could be nested web frameworks, when one layer already + # parsed request headers and activated them. + # + # Example:: + # + # app = Flask(__name__) # Traced via Flask instrumentation + # app = DDWSGIMiddleware(app) # Extra layer on top for WSGI + current_context = tracer.current_trace_context() + if current_context and current_context.trace_id == context.trace_id: + log.debug( + "will not activate extracted Context(trace_id=%r, span_id=%r), a context with that trace id is already active", # noqa: E501 + context.trace_id, + context.span_id, + ) + return None + + # We have parsed a trace id from headers, and we do not already + # have a context with the same trace id active + tracer.context_provider.activate(context) + + +def _flatten( + obj, # type: Any + sep=".", # type: str + prefix="", # type: str + exclude_policy=None, # type: Optional[Callable[[str], bool]] +): + # type: (...) -> Generator[Tuple[str, Any], None, None] + s = deque() # type: ignore + s.append((prefix, obj)) + while s: + p, v = s.pop() + if exclude_policy is not None and exclude_policy(p): + continue + if isinstance(v, dict): + s.extend((sep.join((p, k)) if p else k, v) for k, v in v.items()) + else: + yield p, v + + +def set_flattened_tags( + span, # type: Span + items, # type: Iterator[Tuple[str, Any]] + sep=".", # type: str + exclude_policy=None, # type: Optional[Callable[[str], bool]] + processor=None, # type: Optional[Callable[[Any], Any]] +): + # type: (...) -> None + for prefix, value in items: + for tag, v in _flatten(value, sep, prefix, exclude_policy): + span.set_tag(tag, processor(v) if processor is not None else v) + + +def set_user( + tracer, # type: Tracer + user_id, # type: str + name=None, # type: Optional[str] + email=None, # type: Optional[str] + scope=None, # type: Optional[str] + role=None, # type: Optional[str] + session_id=None, # type: Optional[str] + propagate=False, # type bool + span=None, # type: Optional[Span] +): + # type: (...) -> None + """Set user tags. + https://docs.datadoghq.com/logs/log_configuration/attributes_naming_convention/#user-related-attributes + https://docs.datadoghq.com/security_platform/application_security/setup_and_configure/?tab=set_tag&code-lang=python + """ + if span is None: + span = tracer.current_root_span() + if span: + if user_id: + str_user_id = str(user_id) + span.set_tag_str(user.ID, str_user_id) + if propagate: + span.context.dd_user_id = str_user_id + + # All other fields are optional + if name: + span.set_tag_str(user.NAME, name) + if email: + span.set_tag_str(user.EMAIL, email) + if scope: + span.set_tag_str(user.SCOPE, scope) + if role: + span.set_tag_str(user.ROLE, role) + if session_id: + span.set_tag_str(user.SESSION_ID, session_id) + + if asm_config._asm_enabled: + exc = core.dispatch_with_results("set_user_for_asm", [tracer, user_id]).block_user.exception + if exc: + raise exc + + else: + log.warning( + "No root span in the current execution. Skipping set_user tags. " + "See https://docs.datadoghq.com/security_platform/application_security/setup_and_configure/" + "?tab=set_user&code-lang=python for more information.", + ) + + +def extract_netloc_and_query_info_from_url(url): + # type: (str) -> Tuple[str, str] + parse_result = parse.urlparse(url) + query = parse_result.query + + # Relative URLs don't have a netloc, so we force them + if not parse_result.netloc: + parse_result = parse.urlparse("//{url}".format(url=url)) + + netloc = parse_result.netloc.split("@", 1)[-1] # Discard auth info + netloc = netloc.split(":", 1)[0] # Discard port information + return netloc, query + + +class InterruptException(Exception): + pass diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/trace_utils_async.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/trace_utils_async.py new file mode 100644 index 0000000..63a3325 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/trace_utils_async.py @@ -0,0 +1,39 @@ +""" +async tracing utils + +Note that this module should only be imported in Python 3.5+. +""" +from ddtrace import Pin +from ddtrace.internal.logger import get_logger + + +log = get_logger(__name__) + + +def with_traced_module(func): + """Async version of trace_utils.with_traced_module. + Usage:: + + @with_traced_module + async def my_traced_wrapper(django, pin, func, instance, args, kwargs): + # Do tracing stuff + pass + + def patch(): + import django + wrap(django.somefunc, my_traced_wrapper(django)) + """ + + def with_mod(mod): + async def wrapper(wrapped, instance, args, kwargs): + pin = Pin._find(instance, mod) + if pin and not pin.enabled(): + return await wrapped(*args, **kwargs) + elif not pin: + log.debug("Pin not found for traced method %r", wrapped) + return await wrapped(*args, **kwargs) + return await func(mod, pin, wrapped, instance, args, kwargs) + + return wrapper + + return with_mod diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/trace_utils_redis.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/trace_utils_redis.py new file mode 100644 index 0000000..88bdc11 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/trace_utils_redis.py @@ -0,0 +1,184 @@ +""" +Some utils used by the dogtrace redis integration +""" +from contextlib import contextmanager + +from ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import SPAN_KIND +from ddtrace.constants import SPAN_MEASURED_KEY +from ddtrace.contrib import trace_utils +from ddtrace.ext import SpanKind +from ddtrace.ext import SpanTypes +from ddtrace.ext import db +from ddtrace.ext import net +from ddtrace.ext import redis as redisx +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema import schematize_cache_operation +from ddtrace.internal.utils.formats import stringify_cache_args + + +format_command_args = stringify_cache_args + +SINGLE_KEY_COMMANDS = [ + "GET", + "GETDEL", + "GETEX", + "GETRANGE", + "GETSET", + "LINDEX", + "LRANGE", + "RPOP", + "LPOP", + "HGET", + "HGETALL", + "HKEYS", + "HMGET", + "HRANDFIELD", + "HVALS", +] +MULTI_KEY_COMMANDS = ["MGET"] +ROW_RETURNING_COMMANDS = SINGLE_KEY_COMMANDS + MULTI_KEY_COMMANDS + + +def _extract_conn_tags(conn_kwargs): + """Transform redis conn info into dogtrace metas""" + try: + conn_tags = { + net.TARGET_HOST: conn_kwargs["host"], + net.TARGET_PORT: conn_kwargs["port"], + redisx.DB: conn_kwargs.get("db") or 0, + } + client_name = conn_kwargs.get("client_name") + if client_name: + conn_tags[redisx.CLIENT_NAME] = client_name + return conn_tags + except Exception: + return {} + + +def determine_row_count(redis_command, span, result): + empty_results = [b"", [], {}, None] + # result can be an empty list / dict / string + if result not in empty_results: + if redis_command == "MGET": + # only include valid key results within count + result = [x for x in result if x not in empty_results] + span.set_metric(db.ROWCOUNT, len(result)) + elif redis_command == "HMGET": + # only include valid key results within count + result = [x for x in result if x not in empty_results] + span.set_metric(db.ROWCOUNT, 1 if len(result) > 0 else 0) + else: + span.set_metric(db.ROWCOUNT, 1) + else: + # set count equal to 0 if an empty result + span.set_metric(db.ROWCOUNT, 0) + + +def _run_redis_command(span, func, args, kwargs): + parsed_command = stringify_cache_args(args) + redis_command = parsed_command.split(" ")[0] + try: + result = func(*args, **kwargs) + if redis_command in ROW_RETURNING_COMMANDS: + determine_row_count(redis_command=redis_command, span=span, result=result) + return result + except Exception: + if redis_command in ROW_RETURNING_COMMANDS: + span.set_metric(db.ROWCOUNT, 0) + raise + + +@contextmanager +def _trace_redis_cmd(pin, config_integration, instance, args): + """Create a span for the execute command method and tag it""" + query = stringify_cache_args(args, cmd_max_len=config_integration.cmd_max_length) + with pin.tracer.trace( + schematize_cache_operation(redisx.CMD, cache_provider=redisx.APP), + service=trace_utils.ext_service(pin, config_integration), + span_type=SpanTypes.REDIS, + resource=query.split(" ")[0] if config_integration.resource_only_command else query, + ) as span: + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + span.set_tag_str(COMPONENT, config_integration.integration_name) + span.set_tag_str(db.SYSTEM, redisx.APP) + span.set_tag(SPAN_MEASURED_KEY) + span_name = schematize_cache_operation(redisx.RAWCMD, cache_provider=redisx.APP) + span.set_tag_str(span_name, query) + if pin.tags: + span.set_tags(pin.tags) + # some redis clients do not have a connection_pool attribute (ex. aioredis v1.3) + if hasattr(instance, "connection_pool"): + span.set_tags(_extract_conn_tags(instance.connection_pool.connection_kwargs)) + span.set_metric(redisx.ARGS_LEN, len(args)) + # set analytics sample rate if enabled + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config_integration.get_analytics_sample_rate()) + yield span + + +@contextmanager +def _trace_redis_execute_pipeline(pin, config_integration, cmds, instance, is_cluster=False): + """Create a span for the execute pipeline method and tag it""" + cmd_string = resource = "\n".join(cmds) + if config_integration.resource_only_command: + resource = "\n".join([cmd.split(" ")[0] for cmd in cmds]) + + with pin.tracer.trace( + schematize_cache_operation(redisx.CMD, cache_provider=redisx.APP), + resource=resource, + service=trace_utils.ext_service(pin, config_integration), + span_type=SpanTypes.REDIS, + ) as span: + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + span.set_tag_str(COMPONENT, config_integration.integration_name) + span.set_tag_str(db.SYSTEM, redisx.APP) + span.set_tag(SPAN_MEASURED_KEY) + span_name = schematize_cache_operation(redisx.RAWCMD, cache_provider=redisx.APP) + span.set_tag_str(span_name, cmd_string) + if not is_cluster: + span.set_tags(_extract_conn_tags(instance.connection_pool.connection_kwargs)) + span.set_metric(redisx.PIPELINE_LEN, len(instance.command_stack)) + # set analytics sample rate if enabled + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config_integration.get_analytics_sample_rate()) + # yield the span in case the caller wants to build on span + yield span + + +@contextmanager +def _trace_redis_execute_async_cluster_pipeline(pin, config_integration, cmds, instance): + """Create a span for the execute async cluster pipeline method and tag it""" + cmd_string = resource = "\n".join(cmds) + if config_integration.resource_only_command: + resource = "\n".join([cmd.split(" ")[0] for cmd in cmds]) + + with pin.tracer.trace( + schematize_cache_operation(redisx.CMD, cache_provider=redisx.APP), + resource=resource, + service=trace_utils.ext_service(pin, config_integration), + span_type=SpanTypes.REDIS, + ) as span: + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + span.set_tag_str(COMPONENT, config_integration.integration_name) + span.set_tag_str(db.SYSTEM, redisx.APP) + span.set_tag(SPAN_MEASURED_KEY) + span_name = schematize_cache_operation(redisx.RAWCMD, cache_provider=redisx.APP) + span.set_tag_str(span_name, cmd_string) + span.set_metric(redisx.PIPELINE_LEN, len(instance._command_stack)) + # set analytics sample rate if enabled + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config_integration.get_analytics_sample_rate()) + # yield the span in case the caller wants to build on span + yield span + + +async def _run_redis_command_async(span, func, args, kwargs): + parsed_command = stringify_cache_args(args) + redis_command = parsed_command.split(" ")[0] + try: + result = await func(*args, **kwargs) + if redis_command in ROW_RETURNING_COMMANDS: + determine_row_count(redis_command=redis_command, span=span, result=result) + return result + except Exception: + if redis_command in ROW_RETURNING_COMMANDS: + span.set_metric(db.ROWCOUNT, 0) + raise diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/unittest/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/unittest/__init__.py new file mode 100644 index 0000000..5e32184 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/unittest/__init__.py @@ -0,0 +1,47 @@ +""" +The unittest integration traces test executions. + + +Enabling +~~~~~~~~ + +The unittest integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Alternately, use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(unittest=True) + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.unittest["operation_name"] + + The operation name reported by default for unittest traces. + + This option can also be set with the ``DD_UNITTEST_OPERATION_NAME`` environment + variable. + + Default: ``"unittest.test"`` + + .. py:data:: ddtrace.config.unittest["strict_naming"] + + Requires all ``unittest`` tests to start with ``test`` as stated in the Python documentation + + This option can also be set with the ``DD_CIVISIBILITY_UNITTEST_STRICT_NAMING`` environment + variable. + + Default: ``True`` +""" +from ...internal.utils.importlib import require_modules +from .patch import get_version +from .patch import patch +from .patch import unpatch + + +required_modules = ["unittest"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/unittest/constants.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/unittest/constants.py new file mode 100644 index 0000000..dc58863 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/unittest/constants.py @@ -0,0 +1,8 @@ +COMPONENT_VALUE = "unittest" +FRAMEWORK = "unittest" +KIND = "test" + +TEST_OPERATION_NAME = "unittest.test" +SUITE_OPERATION_NAME = "unittest.test_suite" +SESSION_OPERATION_NAME = "unittest.test_session" +MODULE_OPERATION_NAME = "unittest.test_module" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/unittest/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/unittest/patch.py new file mode 100644 index 0000000..30fe31e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/unittest/patch.py @@ -0,0 +1,858 @@ +import inspect +import os +from typing import Union +import unittest + +import ddtrace +from ddtrace import config +from ddtrace.constants import SPAN_KIND +from ddtrace.contrib.coverage.data import _coverage_data +from ddtrace.contrib.coverage.patch import patch as patch_coverage +from ddtrace.contrib.coverage.patch import run_coverage_report +from ddtrace.contrib.coverage.patch import unpatch as unpatch_coverage +from ddtrace.contrib.coverage.utils import _is_coverage_invoked_by_coverage_run +from ddtrace.contrib.coverage.utils import _is_coverage_patched +from ddtrace.contrib.unittest.constants import COMPONENT_VALUE +from ddtrace.contrib.unittest.constants import FRAMEWORK +from ddtrace.contrib.unittest.constants import KIND +from ddtrace.contrib.unittest.constants import MODULE_OPERATION_NAME +from ddtrace.contrib.unittest.constants import SESSION_OPERATION_NAME +from ddtrace.contrib.unittest.constants import SUITE_OPERATION_NAME +from ddtrace.ext import SpanTypes +from ddtrace.ext import test +from ddtrace.ext.ci import RUNTIME_VERSION +from ddtrace.ext.ci import _get_runtime_and_os_metadata +from ddtrace.internal.ci_visibility import CIVisibility as _CIVisibility +from ddtrace.internal.ci_visibility.constants import EVENT_TYPE as _EVENT_TYPE +from ddtrace.internal.ci_visibility.constants import ITR_UNSKIPPABLE_REASON +from ddtrace.internal.ci_visibility.constants import MODULE_ID as _MODULE_ID +from ddtrace.internal.ci_visibility.constants import MODULE_TYPE as _MODULE_TYPE +from ddtrace.internal.ci_visibility.constants import SESSION_ID as _SESSION_ID +from ddtrace.internal.ci_visibility.constants import SESSION_TYPE as _SESSION_TYPE +from ddtrace.internal.ci_visibility.constants import SKIPPED_BY_ITR_REASON +from ddtrace.internal.ci_visibility.constants import SUITE_ID as _SUITE_ID +from ddtrace.internal.ci_visibility.constants import SUITE_TYPE as _SUITE_TYPE +from ddtrace.internal.ci_visibility.constants import TEST +from ddtrace.internal.ci_visibility.coverage import _module_has_dd_coverage_enabled +from ddtrace.internal.ci_visibility.coverage import _report_coverage_to_span +from ddtrace.internal.ci_visibility.coverage import _start_coverage +from ddtrace.internal.ci_visibility.coverage import _stop_coverage +from ddtrace.internal.ci_visibility.coverage import _switch_coverage_context +from ddtrace.internal.ci_visibility.utils import _add_pct_covered_to_span +from ddtrace.internal.ci_visibility.utils import _add_start_end_source_file_path_data_to_span +from ddtrace.internal.ci_visibility.utils import _generate_fully_qualified_test_name +from ddtrace.internal.ci_visibility.utils import get_relative_or_absolute_path_for_path +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.logger import get_logger +from ddtrace.internal.utils.formats import asbool +from ddtrace.internal.utils.wrappers import unwrap as _u +from ddtrace.vendor import wrapt + + +log = get_logger(__name__) +_global_skipped_elements = 0 + +# unittest default settings +config._add( + "unittest", + dict( + _default_service="unittest", + operation_name=os.getenv("DD_UNITTEST_OPERATION_NAME", default="unittest.test"), + strict_naming=asbool(os.getenv("DD_CIVISIBILITY_UNITTEST_STRICT_NAMING", default=True)), + ), +) + + +def get_version(): + # type: () -> str + return "" + + +def _enable_unittest_if_not_started(): + _initialize_unittest_data() + if _CIVisibility.enabled: + return + _CIVisibility.enable(config=ddtrace.config.unittest) + + +def _initialize_unittest_data(): + if not hasattr(_CIVisibility, "_unittest_data"): + _CIVisibility._unittest_data = {} + if "suites" not in _CIVisibility._unittest_data: + _CIVisibility._unittest_data["suites"] = {} + if "modules" not in _CIVisibility._unittest_data: + _CIVisibility._unittest_data["modules"] = {} + if "unskippable_tests" not in _CIVisibility._unittest_data: + _CIVisibility._unittest_data["unskippable_tests"] = set() + + +def _set_tracer(tracer: ddtrace.tracer): + """Manually sets the tracer instance to `unittest.`""" + unittest._datadog_tracer = tracer + + +def _is_test_coverage_enabled(test_object) -> bool: + return _CIVisibility._instance._collect_coverage_enabled and not _is_skipped_test(test_object) + + +def _is_skipped_test(test_object) -> bool: + testMethod = getattr(test_object, test_object._testMethodName, "") + return ( + (hasattr(test_object.__class__, "__unittest_skip__") and test_object.__class__.__unittest_skip__) + or (hasattr(testMethod, "__unittest_skip__") and testMethod.__unittest_skip__) + or _is_skipped_by_itr(test_object) + ) + + +def _is_skipped_by_itr(test_object) -> bool: + return hasattr(test_object, "_dd_itr_skip") and test_object._dd_itr_skip + + +def _should_be_skipped_by_itr(args: tuple, test_module_suite_path: str, test_name: str, test_object) -> bool: + return ( + len(args) + and _CIVisibility._instance._should_skip_path(test_module_suite_path, test_name) + and not _is_skipped_test(test_object) + ) + + +def _is_marked_as_unskippable(test_object) -> bool: + test_suite_name = _extract_suite_name_from_test_method(test_object) + test_name = _extract_test_method_name(test_object) + test_module_path = _extract_module_file_path(test_object) + test_module_suite_name = _generate_fully_qualified_test_name(test_module_path, test_suite_name, test_name) + return ( + hasattr(_CIVisibility, "_unittest_data") + and test_module_suite_name in _CIVisibility._unittest_data["unskippable_tests"] + ) + + +def _update_skipped_elements_and_set_tags(test_module_span: ddtrace.Span, test_session_span: ddtrace.Span): + global _global_skipped_elements + _global_skipped_elements += 1 + + test_module_span._metrics[test.ITR_TEST_SKIPPING_COUNT] += 1 + test_module_span.set_tag_str(test.ITR_TEST_SKIPPING_TESTS_SKIPPED, "true") + test_module_span.set_tag_str(test.ITR_DD_CI_ITR_TESTS_SKIPPED, "true") + + test_session_span.set_tag_str(test.ITR_TEST_SKIPPING_TESTS_SKIPPED, "true") + test_session_span.set_tag_str(test.ITR_DD_CI_ITR_TESTS_SKIPPED, "true") + + +def _store_test_span(item, span: ddtrace.Span): + """Store datadog span at `unittest` test instance.""" + item._datadog_span = span + + +def _store_module_identifier(test_object: unittest.TextTestRunner): + """Store module identifier at `unittest` module instance, this is useful to classify event types.""" + if hasattr(test_object, "test") and hasattr(test_object.test, "_tests"): + for module in test_object.test._tests: + if len(module._tests) and _extract_module_name_from_module(module): + _set_identifier(module, "module") + + +def _store_suite_identifier(module): + """Store suite identifier at `unittest` suite instance, this is useful to classify event types.""" + if hasattr(module, "_tests"): + for suite in module._tests: + if len(suite._tests) and _extract_module_name_from_module(suite): + _set_identifier(suite, "suite") + + +def _is_test(item) -> bool: + if ( + type(item) == unittest.TestSuite + or not hasattr(item, "_testMethodName") + or (ddtrace.config.unittest.strict_naming and not item._testMethodName.startswith("test")) + ): + return False + return True + + +def _extract_span(item) -> Union[ddtrace.Span, None]: + return getattr(item, "_datadog_span", None) + + +def _extract_command_name_from_session(session: unittest.TextTestRunner) -> str: + if not hasattr(session, "progName"): + return "python -m unittest" + return getattr(session, "progName", "") + + +def _extract_test_method_name(test_object) -> str: + """Extract test method name from `unittest` instance.""" + return getattr(test_object, "_testMethodName", "") + + +def _extract_session_span() -> Union[ddtrace.Span, None]: + return getattr(_CIVisibility, "_datadog_session_span", None) + + +def _extract_module_span(module_identifier: str) -> Union[ddtrace.Span, None]: + if hasattr(_CIVisibility, "_unittest_data") and module_identifier in _CIVisibility._unittest_data["modules"]: + return _CIVisibility._unittest_data["modules"][module_identifier].get("module_span") + return None + + +def _extract_suite_span(suite_identifier: str) -> Union[ddtrace.Span, None]: + if hasattr(_CIVisibility, "_unittest_data") and suite_identifier in _CIVisibility._unittest_data["suites"]: + return _CIVisibility._unittest_data["suites"][suite_identifier].get("suite_span") + return None + + +def _update_status_item(item: ddtrace.Span, status: str): + """ + Sets the status for each Span implementing the test FAIL logic override. + """ + existing_status = item.get_tag(test.STATUS) + if existing_status and (status == test.Status.SKIP.value or existing_status == test.Status.FAIL.value): + return None + item.set_tag_str(test.STATUS, status) + return None + + +def _extract_suite_name_from_test_method(item) -> str: + item_type = type(item) + return getattr(item_type, "__name__", "") + + +def _extract_module_name_from_module(item) -> str: + if _is_test(item): + return type(item).__module__ + return "" + + +def _extract_test_reason(item: tuple) -> str: + """ + Given a tuple of type [test_class, str], it returns the test failure/skip reason + """ + return item[1] + + +def _extract_test_file_name(item) -> str: + return os.path.basename(inspect.getfile(item.__class__)) + + +def _extract_module_file_path(item) -> str: + if _is_test(item): + try: + test_module_object = inspect.getfile(item.__class__) + except TypeError: + log.debug( + "Tried to collect module file path but it is a built-in Python function", + ) + return "" + return get_relative_or_absolute_path_for_path(test_module_object, os.getcwd()) + + return "" + + +def _generate_test_resource(suite_name: str, test_name: str) -> str: + return "{}.{}".format(suite_name, test_name) + + +def _generate_suite_resource(test_suite: str) -> str: + return "{}".format(test_suite) + + +def _generate_module_resource(test_module: str) -> str: + return "{}".format(test_module) + + +def _generate_session_resource(test_command: str) -> str: + return "{}".format(test_command) + + +def _set_test_skipping_tags_to_span(span: ddtrace.Span): + span.set_tag_str(test.ITR_TEST_SKIPPING_ENABLED, "true") + span.set_tag_str(test.ITR_TEST_SKIPPING_TYPE, TEST) + span.set_tag_str(test.ITR_TEST_SKIPPING_TESTS_SKIPPED, "false") + span.set_tag_str(test.ITR_DD_CI_ITR_TESTS_SKIPPED, "false") + span.set_tag_str(test.ITR_FORCED_RUN, "false") + span.set_tag_str(test.ITR_UNSKIPPABLE, "false") + + +def _set_identifier(item, name: str): + """ + Adds an event type classification to a `unittest` test. + """ + item._datadog_object = name + + +def _is_valid_result(instance: unittest.TextTestRunner, args: tuple) -> bool: + return instance and isinstance(instance, unittest.runner.TextTestResult) and args + + +def _is_valid_test_call(kwargs: dict) -> bool: + """ + Validates that kwargs is empty to ensure that `unittest` is running a test + """ + return not len(kwargs) + + +def _is_valid_module_suite_call(func) -> bool: + """ + Validates that the mocked function is an actual function from `unittest` + """ + return type(func).__name__ == "method" or type(func).__name__ == "instancemethod" + + +def _is_invoked_by_cli(instance: unittest.TextTestRunner) -> bool: + return ( + hasattr(instance, "progName") + or hasattr(_CIVisibility, "_datadog_entry") + and _CIVisibility._datadog_entry == "cli" + ) + + +def _extract_test_method_object(test_object): + if hasattr(test_object, "_testMethodName"): + return getattr(test_object, test_object._testMethodName, None) + return None + + +def _is_invoked_by_text_test_runner() -> bool: + return hasattr(_CIVisibility, "_datadog_entry") and _CIVisibility._datadog_entry == "TextTestRunner" + + +def _generate_module_suite_path(test_module_path: str, test_suite_name: str) -> str: + return "{}.{}".format(test_module_path, test_suite_name) + + +def _populate_suites_and_modules(test_objects: list, seen_suites: dict, seen_modules: dict): + """ + Discovers suites and modules and initializes the seen_suites and seen_modules dictionaries. + """ + if not hasattr(test_objects, "__iter__"): + return + for test_object in test_objects: + if not _is_test(test_object): + _populate_suites_and_modules(test_object, seen_suites, seen_modules) + continue + test_module_path = _extract_module_file_path(test_object) + test_suite_name = _extract_suite_name_from_test_method(test_object) + test_module_suite_path = _generate_module_suite_path(test_module_path, test_suite_name) + if test_module_path not in seen_modules: + seen_modules[test_module_path] = { + "module_span": None, + "remaining_suites": 0, + } + if test_module_suite_path not in seen_suites: + seen_suites[test_module_suite_path] = { + "suite_span": None, + "remaining_tests": 0, + } + + seen_modules[test_module_path]["remaining_suites"] += 1 + + seen_suites[test_module_suite_path]["remaining_tests"] += 1 + + +def _finish_remaining_suites_and_modules(seen_suites: dict, seen_modules: dict): + """ + Forces all suite and module spans to finish and updates their statuses. + """ + for suite in seen_suites.values(): + test_suite_span = suite["suite_span"] + if test_suite_span and not test_suite_span.finished: + _finish_span(test_suite_span) + + for module in seen_modules.values(): + test_module_span = module["module_span"] + if test_module_span and not test_module_span.finished: + _finish_span(test_module_span) + del _CIVisibility._unittest_data + + +def _update_remaining_suites_and_modules( + test_module_suite_path: str, test_module_path: str, test_module_span: ddtrace.Span, test_suite_span: ddtrace.Span +): + """ + Updates the remaining test suite and test counter and finishes spans when these have finished their execution. + """ + suite_dict = _CIVisibility._unittest_data["suites"][test_module_suite_path] + modules_dict = _CIVisibility._unittest_data["modules"][test_module_path] + + suite_dict["remaining_tests"] -= 1 + if suite_dict["remaining_tests"] == 0: + modules_dict["remaining_suites"] -= 1 + _finish_span(test_suite_span) + if modules_dict["remaining_suites"] == 0: + _finish_span(test_module_span) + + +def _update_test_skipping_count_span(span: ddtrace.Span): + if _CIVisibility.test_skipping_enabled(): + span.set_metric(test.ITR_TEST_SKIPPING_COUNT, _global_skipped_elements) + + +def _extract_skip_if_reason(args, kwargs): + if len(args) >= 2: + return _extract_test_reason(args) + elif kwargs and "reason" in kwargs: + return kwargs["reason"] + return "" + + +def patch(): + """ + Patch the instrumented methods from unittest + """ + if getattr(unittest, "_datadog_patch", False) or _CIVisibility.enabled: + return + _initialize_unittest_data() + + unittest._datadog_patch = True + + _w = wrapt.wrap_function_wrapper + + _w(unittest, "TextTestResult.addSuccess", add_success_test_wrapper) + _w(unittest, "TextTestResult.addFailure", add_failure_test_wrapper) + _w(unittest, "TextTestResult.addError", add_failure_test_wrapper) + _w(unittest, "TextTestResult.addSkip", add_skip_test_wrapper) + _w(unittest, "TextTestResult.addExpectedFailure", add_xfail_test_wrapper) + _w(unittest, "TextTestResult.addUnexpectedSuccess", add_xpass_test_wrapper) + _w(unittest, "skipIf", skip_if_decorator) + _w(unittest, "TestCase.run", handle_test_wrapper) + _w(unittest, "TestSuite.run", collect_text_test_runner_session) + _w(unittest, "TextTestRunner.run", handle_text_test_runner_wrapper) + _w(unittest, "TestProgram.runTests", handle_cli_run) + + +def unpatch(): + """ + Undo patched instrumented methods from unittest + """ + if not getattr(unittest, "_datadog_patch", False): + return + + _u(unittest.TextTestResult, "addSuccess") + _u(unittest.TextTestResult, "addFailure") + _u(unittest.TextTestResult, "addError") + _u(unittest.TextTestResult, "addSkip") + _u(unittest.TextTestResult, "addExpectedFailure") + _u(unittest.TextTestResult, "addUnexpectedSuccess") + _u(unittest, "skipIf") + _u(unittest.TestSuite, "run") + _u(unittest.TestCase, "run") + _u(unittest.TextTestRunner, "run") + _u(unittest.TestProgram, "runTests") + + unittest._datadog_patch = False + _CIVisibility.disable() + + +def _set_test_span_status(test_item, status: str, exc_info: str = None, skip_reason: str = None): + span = _extract_span(test_item) + if not span: + log.debug("Tried setting test result for test but could not find span for %s", test_item) + return None + span.set_tag_str(test.STATUS, status) + if exc_info: + span.set_exc_info(exc_info[0], exc_info[1], exc_info[2]) + if status == test.Status.SKIP.value: + span.set_tag_str(test.SKIP_REASON, skip_reason) + + +def _set_test_xpass_xfail_result(test_item, result: str): + """ + Sets `test.result` and `test.status` to a XFAIL or XPASS test. + """ + span = _extract_span(test_item) + if not span: + log.debug("Tried setting test result for an xpass or xfail test but could not find span for %s", test_item) + return None + span.set_tag_str(test.RESULT, result) + status = span.get_tag(test.STATUS) + if result == test.Status.XFAIL.value: + if status == test.Status.PASS.value: + span.set_tag_str(test.STATUS, test.Status.FAIL.value) + elif status == test.Status.FAIL.value: + span.set_tag_str(test.STATUS, test.Status.PASS.value) + + +def add_success_test_wrapper(func, instance: unittest.TextTestRunner, args: tuple, kwargs: dict): + if _is_valid_result(instance, args): + _set_test_span_status(test_item=args[0], status=test.Status.PASS.value) + + return func(*args, **kwargs) + + +def add_failure_test_wrapper(func, instance: unittest.TextTestRunner, args: tuple, kwargs: dict): + if _is_valid_result(instance, args): + _set_test_span_status(test_item=args[0], exc_info=_extract_test_reason(args), status=test.Status.FAIL.value) + + return func(*args, **kwargs) + + +def add_xfail_test_wrapper(func, instance: unittest.TextTestRunner, args: tuple, kwargs: dict): + if _is_valid_result(instance, args): + _set_test_xpass_xfail_result(test_item=args[0], result=test.Status.XFAIL.value) + + return func(*args, **kwargs) + + +def add_skip_test_wrapper(func, instance: unittest.TextTestRunner, args: tuple, kwargs: dict): + if _is_valid_result(instance, args): + _set_test_span_status(test_item=args[0], skip_reason=_extract_test_reason(args), status=test.Status.SKIP.value) + + return func(*args, **kwargs) + + +def add_xpass_test_wrapper(func, instance, args: tuple, kwargs: dict): + if _is_valid_result(instance, args): + _set_test_xpass_xfail_result(test_item=args[0], result=test.Status.XPASS.value) + + return func(*args, **kwargs) + + +def _mark_test_as_unskippable(obj): + test_name = obj.__name__ + test_suite_name = str(obj).split(".")[0].split()[1] + test_module_path = get_relative_or_absolute_path_for_path(obj.__code__.co_filename, os.getcwd()) + test_module_suite_name = _generate_fully_qualified_test_name(test_module_path, test_suite_name, test_name) + _CIVisibility._unittest_data["unskippable_tests"].add(test_module_suite_name) + return obj + + +def _using_unskippable_decorator(args, kwargs): + return args[0] is False and _extract_skip_if_reason(args, kwargs) == ITR_UNSKIPPABLE_REASON + + +def skip_if_decorator(func, instance, args: tuple, kwargs: dict): + if _using_unskippable_decorator(args, kwargs): + return _mark_test_as_unskippable + return func(*args, **kwargs) + + +def handle_test_wrapper(func, instance, args: tuple, kwargs: dict): + """ + Creates module and suite spans for `unittest` test executions. + """ + if _is_valid_test_call(kwargs) and _is_test(instance) and hasattr(_CIVisibility, "_unittest_data"): + test_name = _extract_test_method_name(instance) + test_suite_name = _extract_suite_name_from_test_method(instance) + test_module_path = _extract_module_file_path(instance) + test_module_suite_path = _generate_module_suite_path(test_module_path, test_suite_name) + test_suite_span = _extract_suite_span(test_module_suite_path) + test_module_span = _extract_module_span(test_module_path) + if test_module_span is None and test_module_path in _CIVisibility._unittest_data["modules"]: + test_module_span = _start_test_module_span(instance) + _CIVisibility._unittest_data["modules"][test_module_path]["module_span"] = test_module_span + if test_suite_span is None and test_module_suite_path in _CIVisibility._unittest_data["suites"]: + test_suite_span = _start_test_suite_span(instance) + suite_dict = _CIVisibility._unittest_data["suites"][test_module_suite_path] + suite_dict["suite_span"] = test_suite_span + if not test_module_span or not test_suite_span: + log.debug("Suite and/or module span not found for test: %s", test_name) + return func(*args, **kwargs) + with _start_test_span(instance, test_suite_span) as span: + test_session_span = _CIVisibility._datadog_session_span + root_directory = os.getcwd() + fqn_test = _generate_fully_qualified_test_name(test_module_path, test_suite_name, test_name) + + if _CIVisibility.test_skipping_enabled(): + if _is_marked_as_unskippable(instance): + span.set_tag_str(test.ITR_UNSKIPPABLE, "true") + test_module_span.set_tag_str(test.ITR_UNSKIPPABLE, "true") + test_session_span.set_tag_str(test.ITR_UNSKIPPABLE, "true") + test_module_suite_path_without_extension = "{}/{}".format( + os.path.splitext(test_module_path)[0], test_suite_name + ) + if _should_be_skipped_by_itr(args, test_module_suite_path_without_extension, test_name, instance): + if _is_marked_as_unskippable(instance): + span.set_tag_str(test.ITR_FORCED_RUN, "true") + test_module_span.set_tag_str(test.ITR_FORCED_RUN, "true") + test_session_span.set_tag_str(test.ITR_FORCED_RUN, "true") + else: + _update_skipped_elements_and_set_tags(test_module_span, test_session_span) + instance._dd_itr_skip = True + span.set_tag_str(test.ITR_SKIPPED, "true") + span.set_tag_str(test.SKIP_REASON, SKIPPED_BY_ITR_REASON) + + if _is_skipped_by_itr(instance): + result = args[0] + result.startTest(test=instance) + result.addSkip(test=instance, reason=SKIPPED_BY_ITR_REASON) + _set_test_span_status( + test_item=instance, skip_reason=SKIPPED_BY_ITR_REASON, status=test.Status.SKIP.value + ) + result.stopTest(test=instance) + else: + if _is_test_coverage_enabled(instance): + if not _module_has_dd_coverage_enabled(unittest, silent_mode=True): + unittest._dd_coverage = _start_coverage(root_directory) + _switch_coverage_context(unittest._dd_coverage, fqn_test) + result = func(*args, **kwargs) + _update_status_item(test_suite_span, span.get_tag(test.STATUS)) + if _is_test_coverage_enabled(instance): + _report_coverage_to_span(unittest._dd_coverage, span, root_directory) + + _update_remaining_suites_and_modules( + test_module_suite_path, test_module_path, test_module_span, test_suite_span + ) + return result + return func(*args, **kwargs) + + +def collect_text_test_runner_session(func, instance: unittest.TestSuite, args: tuple, kwargs: dict): + """ + Discovers test suites and tests for the current `unittest` `TextTestRunner` execution + """ + if not _is_valid_module_suite_call(func): + return func(*args, **kwargs) + _initialize_unittest_data() + if _is_invoked_by_text_test_runner(): + seen_suites = _CIVisibility._unittest_data["suites"] + seen_modules = _CIVisibility._unittest_data["modules"] + _populate_suites_and_modules(instance._tests, seen_suites, seen_modules) + + result = func(*args, **kwargs) + + return result + result = func(*args, **kwargs) + return result + + +def _start_test_session_span(instance) -> ddtrace.Span: + """ + Starts a test session span and sets the required tags for a `unittest` session instance. + """ + tracer = getattr(unittest, "_datadog_tracer", _CIVisibility._instance.tracer) + test_command = _extract_command_name_from_session(instance) + resource_name = _generate_session_resource(test_command) + test_session_span = tracer.trace( + SESSION_OPERATION_NAME, + service=_CIVisibility._instance._service, + span_type=SpanTypes.TEST, + resource=resource_name, + ) + test_session_span.set_tag_str(_EVENT_TYPE, _SESSION_TYPE) + test_session_span.set_tag_str(_SESSION_ID, str(test_session_span.span_id)) + + test_session_span.set_tag_str(COMPONENT, COMPONENT_VALUE) + test_session_span.set_tag_str(SPAN_KIND, KIND) + + test_session_span.set_tag_str(test.COMMAND, test_command) + test_session_span.set_tag_str(test.FRAMEWORK, FRAMEWORK) + test_session_span.set_tag_str(test.FRAMEWORK_VERSION, _get_runtime_and_os_metadata()[RUNTIME_VERSION]) + + test_session_span.set_tag_str(test.TEST_TYPE, SpanTypes.TEST) + test_session_span.set_tag_str( + test.ITR_TEST_CODE_COVERAGE_ENABLED, + "true" if _CIVisibility._instance._collect_coverage_enabled else "false", + ) + if _CIVisibility.test_skipping_enabled(): + _set_test_skipping_tags_to_span(test_session_span) + else: + test_session_span.set_tag_str(test.ITR_TEST_SKIPPING_ENABLED, "false") + _store_module_identifier(instance) + if _is_coverage_invoked_by_coverage_run(): + patch_coverage() + return test_session_span + + +def _start_test_module_span(instance) -> ddtrace.Span: + """ + Starts a test module span and sets the required tags for a `unittest` module instance. + """ + tracer = getattr(unittest, "_datadog_tracer", _CIVisibility._instance.tracer) + test_session_span = _extract_session_span() + test_module_name = _extract_module_name_from_module(instance) + resource_name = _generate_module_resource(test_module_name) + test_module_span = tracer._start_span( + MODULE_OPERATION_NAME, + service=_CIVisibility._instance._service, + span_type=SpanTypes.TEST, + activate=True, + child_of=test_session_span, + resource=resource_name, + ) + test_module_span.set_tag_str(_EVENT_TYPE, _MODULE_TYPE) + test_module_span.set_tag_str(_SESSION_ID, str(test_session_span.span_id)) + test_module_span.set_tag_str(_MODULE_ID, str(test_module_span.span_id)) + + test_module_span.set_tag_str(COMPONENT, COMPONENT_VALUE) + test_module_span.set_tag_str(SPAN_KIND, KIND) + + test_module_span.set_tag_str(test.COMMAND, test_session_span.get_tag(test.COMMAND)) + test_module_span.set_tag_str(test.FRAMEWORK, FRAMEWORK) + test_module_span.set_tag_str(test.FRAMEWORK_VERSION, _get_runtime_and_os_metadata()[RUNTIME_VERSION]) + + test_module_span.set_tag_str(test.TEST_TYPE, SpanTypes.TEST) + test_module_span.set_tag_str(test.MODULE, test_module_name) + test_module_span.set_tag_str(test.MODULE_PATH, _extract_module_file_path(instance)) + test_module_span.set_tag_str( + test.ITR_TEST_CODE_COVERAGE_ENABLED, + "true" if _CIVisibility._instance._collect_coverage_enabled else "false", + ) + if _CIVisibility.test_skipping_enabled(): + _set_test_skipping_tags_to_span(test_module_span) + test_module_span.set_metric(test.ITR_TEST_SKIPPING_COUNT, 0) + else: + test_module_span.set_tag_str(test.ITR_TEST_SKIPPING_ENABLED, "false") + _store_suite_identifier(instance) + return test_module_span + + +def _start_test_suite_span(instance) -> ddtrace.Span: + """ + Starts a test suite span and sets the required tags for a `unittest` suite instance. + """ + tracer = getattr(unittest, "_datadog_tracer", _CIVisibility._instance.tracer) + test_module_path = _extract_module_file_path(instance) + test_module_span = _extract_module_span(test_module_path) + test_suite_name = _extract_suite_name_from_test_method(instance) + resource_name = _generate_suite_resource(test_suite_name) + test_suite_span = tracer._start_span( + SUITE_OPERATION_NAME, + service=_CIVisibility._instance._service, + span_type=SpanTypes.TEST, + child_of=test_module_span, + activate=True, + resource=resource_name, + ) + test_suite_span.set_tag_str(_EVENT_TYPE, _SUITE_TYPE) + test_suite_span.set_tag_str(_SESSION_ID, test_module_span.get_tag(_SESSION_ID)) + test_suite_span.set_tag_str(_SUITE_ID, str(test_suite_span.span_id)) + test_suite_span.set_tag_str(_MODULE_ID, str(test_module_span.span_id)) + + test_suite_span.set_tag_str(COMPONENT, COMPONENT_VALUE) + test_suite_span.set_tag_str(SPAN_KIND, KIND) + + test_suite_span.set_tag_str(test.COMMAND, test_module_span.get_tag(test.COMMAND)) + test_suite_span.set_tag_str(test.FRAMEWORK, FRAMEWORK) + test_suite_span.set_tag_str(test.FRAMEWORK_VERSION, _get_runtime_and_os_metadata()[RUNTIME_VERSION]) + + test_suite_span.set_tag_str(test.TEST_TYPE, SpanTypes.TEST) + test_suite_span.set_tag_str(test.SUITE, test_suite_name) + test_suite_span.set_tag_str(test.MODULE, test_module_span.get_tag(test.MODULE)) + test_suite_span.set_tag_str(test.MODULE_PATH, test_module_path) + return test_suite_span + + +def _start_test_span(instance, test_suite_span: ddtrace.Span) -> ddtrace.Span: + """ + Starts a test span and sets the required tags for a `unittest` test instance. + """ + tracer = getattr(unittest, "_datadog_tracer", _CIVisibility._instance.tracer) + test_name = _extract_test_method_name(instance) + test_method_object = _extract_test_method_object(instance) + test_suite_name = _extract_suite_name_from_test_method(instance) + resource_name = _generate_test_resource(test_suite_name, test_name) + span = tracer._start_span( + ddtrace.config.unittest.operation_name, + service=_CIVisibility._instance._service, + resource=resource_name, + span_type=SpanTypes.TEST, + child_of=test_suite_span, + activate=True, + ) + span.set_tag_str(_EVENT_TYPE, SpanTypes.TEST) + span.set_tag_str(_SESSION_ID, test_suite_span.get_tag(_SESSION_ID)) + span.set_tag_str(_MODULE_ID, test_suite_span.get_tag(_MODULE_ID)) + span.set_tag_str(_SUITE_ID, test_suite_span.get_tag(_SUITE_ID)) + + span.set_tag_str(COMPONENT, COMPONENT_VALUE) + span.set_tag_str(SPAN_KIND, KIND) + + span.set_tag_str(test.COMMAND, test_suite_span.get_tag(test.COMMAND)) + span.set_tag_str(test.FRAMEWORK, FRAMEWORK) + span.set_tag_str(test.FRAMEWORK_VERSION, _get_runtime_and_os_metadata()[RUNTIME_VERSION]) + + span.set_tag_str(test.TYPE, SpanTypes.TEST) + span.set_tag_str(test.NAME, test_name) + span.set_tag_str(test.SUITE, test_suite_name) + span.set_tag_str(test.MODULE, test_suite_span.get_tag(test.MODULE)) + span.set_tag_str(test.MODULE_PATH, test_suite_span.get_tag(test.MODULE_PATH)) + span.set_tag_str(test.STATUS, test.Status.FAIL.value) + span.set_tag_str(test.CLASS_HIERARCHY, test_suite_name) + + _CIVisibility.set_codeowners_of(_extract_test_file_name(instance), span=span) + + _add_start_end_source_file_path_data_to_span(span, test_method_object, test_name, os.getcwd()) + + _store_test_span(instance, span) + return span + + +def _finish_span(current_span: ddtrace.Span): + """ + Finishes active span and populates span status upwards + """ + current_status = current_span.get_tag(test.STATUS) + parent_span = current_span._parent + if current_status and parent_span: + _update_status_item(parent_span, current_status) + elif not current_status: + current_span.set_tag_str(test.SUITE, test.Status.FAIL.value) + current_span.finish() + + +def _finish_test_session_span(): + _finish_remaining_suites_and_modules( + _CIVisibility._unittest_data["suites"], _CIVisibility._unittest_data["modules"] + ) + _update_test_skipping_count_span(_CIVisibility._datadog_session_span) + if _CIVisibility._instance._collect_coverage_enabled and _module_has_dd_coverage_enabled(unittest): + _stop_coverage(unittest) + if _is_coverage_patched() and _is_coverage_invoked_by_coverage_run(): + run_coverage_report() + _add_pct_covered_to_span(_coverage_data, _CIVisibility._datadog_session_span) + unpatch_coverage() + _finish_span(_CIVisibility._datadog_session_span) + + +def handle_cli_run(func, instance: unittest.TestProgram, args: tuple, kwargs: dict): + """ + Creates session span and discovers test suites and tests for the current `unittest` CLI execution + """ + if _is_invoked_by_cli(instance): + _enable_unittest_if_not_started() + for parent_module in instance.test._tests: + for module in parent_module._tests: + _populate_suites_and_modules( + module, _CIVisibility._unittest_data["suites"], _CIVisibility._unittest_data["modules"] + ) + + test_session_span = _start_test_session_span(instance) + _CIVisibility._datadog_entry = "cli" + _CIVisibility._datadog_session_span = test_session_span + + try: + result = func(*args, **kwargs) + except SystemExit as e: + if _CIVisibility.enabled and _CIVisibility._datadog_session_span and hasattr(_CIVisibility, "_unittest_data"): + _finish_test_session_span() + + raise e + return result + + +def handle_text_test_runner_wrapper(func, instance: unittest.TextTestRunner, args: tuple, kwargs: dict): + """ + Creates session span if unittest is called through the `TextTestRunner` method + """ + if _is_invoked_by_cli(instance): + return func(*args, **kwargs) + _enable_unittest_if_not_started() + _CIVisibility._datadog_entry = "TextTestRunner" + if not hasattr(_CIVisibility, "_datadog_session_span"): + _CIVisibility._datadog_session_span = _start_test_session_span(instance) + _CIVisibility._datadog_expected_sessions = 0 + _CIVisibility._datadog_finished_sessions = 0 + _CIVisibility._datadog_expected_sessions += 1 + try: + result = func(*args, **kwargs) + except SystemExit as e: + _CIVisibility._datadog_finished_sessions += 1 + if _CIVisibility._datadog_finished_sessions == _CIVisibility._datadog_expected_sessions: + _finish_test_session_span() + del _CIVisibility._datadog_session_span + raise e + _CIVisibility._datadog_finished_sessions += 1 + if _CIVisibility._datadog_finished_sessions == _CIVisibility._datadog_expected_sessions: + _finish_test_session_span() + del _CIVisibility._datadog_session_span + return result diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/urllib3/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/urllib3/__init__.py new file mode 100644 index 0000000..206588f --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/urllib3/__init__.py @@ -0,0 +1,63 @@ +""" +The ``urllib3`` integration instruments tracing on http calls with optional +support for distributed tracing across services the client communicates with. + + +Enabling +~~~~~~~~ + +The ``urllib3`` integration is not enabled by default. Use ``patch_all()`` +with the environment variable ``DD_TRACE_URLLIB3_ENABLED`` set, or call +:func:`patch()` with the ``urllib3`` argument set to ``True`` to manually +enable the integration, before importing and using ``urllib3``:: + + from ddtrace import patch + patch(urllib3=True) + + # use urllib3 like usual + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.urllib3['service'] + + The service name reported by default for urllib3 client instances. + + This option can also be set with the ``DD_URLLIB3_SERVICE`` environment + variable. + + Default: ``"urllib3"`` + + +.. py:data:: ddtrace.config.urllib3['distributed_tracing'] + + Whether or not to parse distributed tracing headers. + + Default: ``True`` + + +.. py:data:: ddtrace.config.urllib3['trace_query_string'] + + Whether or not to include the query string as a tag. + + Default: ``False`` + + +.. py:data:: ddtrace.config.urllib3['split_by_domain'] + + Whether or not to use the domain name of requests as the service name. + + Default: ``False`` +""" +from ...internal.utils.importlib import require_modules +from .patch import get_version +from .patch import patch +from .patch import unpatch + + +required_modules = ["urllib3"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/urllib3/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/urllib3/patch.py new file mode 100644 index 0000000..a2ae3df --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/urllib3/patch.py @@ -0,0 +1,150 @@ +import os + +import urllib3 + +from ddtrace import config +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.pin import Pin +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w + +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import SPAN_KIND +from ...ext import SpanKind +from ...ext import SpanTypes +from ...internal.compat import parse +from ...internal.schema import schematize_service_name +from ...internal.schema import schematize_url_operation +from ...internal.utils import ArgumentError +from ...internal.utils import get_argument_value +from ...internal.utils.formats import asbool +from ...internal.utils.wrappers import unwrap as _u +from ...propagation.http import HTTPPropagator +from .. import trace_utils + + +# Ports which, if set, will not be used in hostnames/service names +DROP_PORTS = (80, 443) + +# Initialize the default config vars +config._add( + "urllib3", + { + "_default_service": schematize_service_name("urllib3"), + "distributed_tracing": asbool(os.getenv("DD_URLLIB3_DISTRIBUTED_TRACING", default=True)), + "default_http_tag_query_string": os.getenv("DD_HTTP_CLIENT_TAG_QUERY_STRING", "true"), + "split_by_domain": asbool(os.getenv("DD_URLLIB3_SPLIT_BY_DOMAIN", default=False)), + }, +) + + +def get_version(): + # type: () -> str + return getattr(urllib3, "__version__", "") + + +def patch(): + """Enable tracing for all urllib3 requests""" + if getattr(urllib3, "__datadog_patch", False): + return + urllib3.__datadog_patch = True + + _w("urllib3", "connectionpool.HTTPConnectionPool.urlopen", _wrap_urlopen) + Pin().onto(urllib3.connectionpool.HTTPConnectionPool) + + +def unpatch(): + """Disable trace for all urllib3 requests""" + if getattr(urllib3, "__datadog_patch", False): + urllib3.__datadog_patch = False + + _u(urllib3.connectionpool.HTTPConnectionPool, "urlopen") + + +def _wrap_urlopen(func, instance, args, kwargs): + """ + Wrapper function for the lower-level urlopen in urllib3 + + :param func: The original target function "urlopen" + :param instance: The patched instance of ``HTTPConnectionPool`` + :param args: Positional arguments from the target function + :param kwargs: Keyword arguments from the target function + :return: The ``HTTPResponse`` from the target function + """ + request_method = get_argument_value(args, kwargs, 0, "method") + request_url = get_argument_value(args, kwargs, 1, "url") + try: + request_headers = get_argument_value(args, kwargs, 3, "headers") + except ArgumentError: + request_headers = None + try: + request_retries = get_argument_value(args, kwargs, 4, "retries") + except ArgumentError: + request_retries = None + + # HTTPConnectionPool allows relative path requests; convert the request_url to an absolute url + if request_url.startswith("/"): + request_url = parse.urlunparse( + ( + instance.scheme, + "{}:{}".format(instance.host, instance.port) + if instance.port and instance.port not in DROP_PORTS + else str(instance.host), + request_url, + None, + None, + None, + ) + ) + + parsed_uri = parse.urlparse(request_url) + hostname = parsed_uri.netloc + + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return func(*args, **kwargs) + + with pin.tracer.trace( + schematize_url_operation("urllib3.request", protocol="http", direction=SpanDirection.OUTBOUND), + service=trace_utils.ext_service(pin, config.urllib3), + span_type=SpanTypes.HTTP, + ) as span: + span.set_tag_str(COMPONENT, config.urllib3.integration_name) + + # set span.kind to the type of operation being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + if config.urllib3.split_by_domain: + span.service = hostname + + # If distributed tracing is enabled, propagate the tracing headers to downstream services + if config.urllib3.distributed_tracing: + if request_headers is None: + request_headers = {} + kwargs["headers"] = request_headers + HTTPPropagator.inject(span.context, request_headers) + + if config.urllib3.analytics_enabled: + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.urllib3.get_analytics_sample_rate()) + + retries = request_retries.total if isinstance(request_retries, urllib3.util.retry.Retry) else None + + # Call the target function + response = None + try: + response = func(*args, **kwargs) + finally: + trace_utils.set_http_meta( + span, + integration_config=config.urllib3, + method=request_method, + url=request_url, + target_host=instance.host, + status_code=None if response is None else response.status, + query=parsed_uri.query, + request_headers=request_headers, + response_headers={} if response is None else dict(response.headers), + retries_remain=retries, + ) + + return response diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/vertica/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/vertica/__init__.py new file mode 100644 index 0000000..f688f95 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/vertica/__init__.py @@ -0,0 +1,53 @@ +""" +The Vertica integration will trace queries made using the vertica-python +library. + +Vertica will be automatically instrumented with ``import ddtrace.auto``, or when using +the ``ddtrace-run`` command. + +Vertica is instrumented on import. To instrument Vertica manually use the +``patch`` function. Note the ordering of the following statements:: + + from ddtrace import patch + patch(vertica=True) + + import vertica_python + + # use vertica_python like usual + + +To configure the Vertica integration globally you can use the ``Config`` API:: + + from ddtrace import config, patch + patch(vertica=True) + + config.vertica['service_name'] = 'my-vertica-database' + + +To configure the Vertica integration on an instance-per-instance basis use the +``Pin`` API:: + + from ddtrace import Pin, patch, Tracer + patch(vertica=True) + + import vertica_python + + custom_tracer = Tracer() + conn = vertica_python.connect(**YOUR_VERTICA_CONFIG) + + # override the service and tracer to be used + Pin.override(conn, service='myverticaservice', tracer=custom_tracer) +""" + +from ...internal.utils.importlib import require_modules + + +required_modules = ["vertica_python"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + from .patch import unpatch + + __all__ = ["patch", "unpatch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/vertica/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/vertica/patch.py new file mode 100644 index 0000000..66cebc3 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/vertica/patch.py @@ -0,0 +1,263 @@ +import importlib + +import ddtrace +from ddtrace import config +from ddtrace.internal.constants import COMPONENT +from ddtrace.vendor import wrapt + +from ...constants import ANALYTICS_SAMPLE_RATE_KEY +from ...constants import SPAN_KIND +from ...constants import SPAN_MEASURED_KEY +from ...ext import SpanKind +from ...ext import SpanTypes +from ...ext import db as dbx +from ...ext import net +from ...internal.logger import get_logger +from ...internal.schema import schematize_database_operation +from ...internal.schema import schematize_service_name +from ...internal.utils import get_argument_value +from ...internal.utils.wrappers import unwrap +from ...pin import Pin +from .. import trace_utils + + +log = get_logger(__name__) + + +_PATCHED = False + + +def copy_span_start(instance, span, conf, *args, **kwargs): + span.resource = get_argument_value(args, kwargs, 0, "sql") + + +def execute_span_start(instance, span, conf, *args, **kwargs): + span.resource = get_argument_value(args, kwargs, 0, "operation") + + +def execute_span_end(instance, result, span, conf, *args, **kwargs): + span.set_metric(dbx.ROWCOUNT, instance.rowcount) + + +def fetch_span_end(instance, result, span, conf, *args, **kwargs): + span.set_metric(dbx.ROWCOUNT, instance.rowcount) + + +def cursor_span_end(instance, cursor, _, conf, *args, **kwargs): + tags = {} + tags[net.TARGET_HOST] = instance.options["host"] + tags[net.TARGET_PORT] = instance.options["port"] + if "user" in instance.options: + tags[dbx.USER] = instance.options["user"] + if "database" in instance.options: + tags[dbx.NAME] = instance.options["database"] + + pin = Pin( + tags=tags, + _config=config.vertica["patch"]["vertica_python.vertica.cursor.Cursor"], + ) + pin.onto(cursor) + + +# tracing configuration +config._add( + "vertica", + { + "_default_service": schematize_service_name("vertica"), + "_dbapi_span_name_prefix": "vertica", + "patch": { + "vertica_python.vertica.connection.Connection": { + "routines": { + "cursor": { + "trace_enabled": False, + "span_end": cursor_span_end, + }, + }, + }, + "vertica_python.vertica.cursor.Cursor": { + "routines": { + "execute": { + "operation_name": schematize_database_operation("vertica.query", database_provider="vertica"), + "span_type": SpanTypes.SQL, + "span_start": execute_span_start, + "span_end": execute_span_end, + "measured": True, + }, + "copy": { + "operation_name": "vertica.copy", + "span_type": SpanTypes.SQL, + "span_start": copy_span_start, + "measured": False, + }, + "fetchone": { + "operation_name": schematize_database_operation( + "vertica.fetchone", database_provider="vertica" + ), + "span_type": SpanTypes.SQL, + "span_end": fetch_span_end, + "measured": False, + }, + "fetchall": { + "operation_name": schematize_database_operation( + "vertica.fetchall", database_provider="vertica" + ), + "span_type": SpanTypes.SQL, + "span_end": fetch_span_end, + "measured": False, + }, + "nextset": { + "operation_name": schematize_database_operation("vertica.nextset", database_provider="vertica"), + "span_type": SpanTypes.SQL, + "span_end": fetch_span_end, + "measured": False, + }, + }, + }, + }, + }, +) + + +def get_version(): + # type: () -> str + import vertica_python + + return vertica_python.__version__ + + +def patch(): + global _PATCHED + if _PATCHED: + return + + _install(config.vertica) + _PATCHED = True + + +def unpatch(): + global _PATCHED + if _PATCHED: + _uninstall(config.vertica) + _PATCHED = False + + +def _uninstall(config): + for patch_class_path in config["patch"]: + patch_mod, _, patch_class = patch_class_path.rpartition(".") + mod = importlib.import_module(patch_mod) + cls = getattr(mod, patch_class, None) + + if not cls: + log.debug( + """ + Unable to find corresponding class for tracing configuration. + This version may not be supported. + """ + ) + continue + + for patch_routine in config["patch"][patch_class_path]["routines"]: + unwrap(cls, patch_routine) + + +def _find_routine_config(config, instance, routine_name): + """Attempts to find the config for a routine based on the bases of the + class of the instance. + """ + bases = instance.__class__.__mro__ + for base in bases: + full_name = "{}.{}".format(base.__module__, base.__name__) + if full_name not in config["patch"]: + continue + + config_routines = config["patch"][full_name]["routines"] + + if routine_name in config_routines: + return config_routines[routine_name] + return {} + + +def _install_init(patch_item, patch_class, patch_mod, config): + patch_class_routine = "{}.{}".format(patch_class, "__init__") + + # patch the __init__ of the class with a Pin instance containing the defaults + @wrapt.patch_function_wrapper(patch_mod, patch_class_routine) + def init_wrapper(wrapped, instance, args, kwargs): + r = wrapped(*args, **kwargs) + + # create and attach a pin with the defaults + Pin( + tags=config.get("tags", {}), + tracer=config.get("tracer", ddtrace.tracer), + _config=config["patch"][patch_item], + ).onto(instance) + return r + + +def _install_routine(patch_routine, patch_class, patch_mod, config): + patch_class_routine = "{}.{}".format(patch_class, patch_routine) + + @wrapt.patch_function_wrapper(patch_mod, patch_class_routine) + def wrapper(wrapped, instance, args, kwargs): + # TODO?: remove Pin dependence + pin = Pin.get_from(instance) + + if patch_routine in pin._config["routines"]: + conf = pin._config["routines"][patch_routine] + else: + conf = _find_routine_config(config, instance, patch_routine) + + enabled = conf.get("trace_enabled", True) + + span = None + + try: + # shortcut if not enabled + if not enabled: + result = wrapped(*args, **kwargs) + return result + + operation_name = conf["operation_name"] + tracer = pin.tracer + with tracer.trace( + operation_name, + service=trace_utils.ext_service(pin, config), + span_type=conf.get("span_type"), + ) as span: + span.set_tag_str(COMPONENT, config.integration_name) + span.set_tag_str(dbx.SYSTEM, "vertica") + + # set span.kind to the type of operation being performed + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + + if conf.get("measured", False): + span.set_tag(SPAN_MEASURED_KEY) + span.set_tags(pin.tags) + + if "span_start" in conf: + conf["span_start"](instance, span, conf, *args, **kwargs) + + # set analytics sample rate + span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.get_analytics_sample_rate()) + + result = wrapped(*args, **kwargs) + return result + except Exception as err: + if "on_error" in conf: + conf["on_error"](instance, err, span, conf, *args, **kwargs) + raise + finally: + # if an exception is raised result will not exist + if "result" not in locals(): + result = None + if "span_end" in conf: + conf["span_end"](instance, result, span, conf, *args, **kwargs) + + +def _install(config): + for patch_class_path in config["patch"]: + patch_mod, _, patch_class = patch_class_path.rpartition(".") + _install_init(patch_class_path, patch_class, patch_mod, config) + + for patch_routine in config["patch"][patch_class_path]["routines"]: + _install_routine(patch_routine, patch_class, patch_mod, config) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/wsgi/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/wsgi/__init__.py new file mode 100644 index 0000000..84b8d14 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/wsgi/__init__.py @@ -0,0 +1,43 @@ +""" +The Datadog WSGI middleware traces all WSGI requests. + + +Usage +~~~~~ + +The middleware can be used manually via the following command:: + + + from ddtrace.contrib.wsgi import DDWSGIMiddleware + + # application is a WSGI application + application = DDWSGIMiddleware(application) + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.wsgi["service"] + + The service name reported for the WSGI application. + + This option can also be set with the ``DD_SERVICE`` environment + variable. + + Default: ``"wsgi"`` + +.. py:data:: ddtrace.config.wsgi["distributed_tracing"] + + Configuration that allows distributed tracing to be enabled. + + Default: ``True`` + + +:ref:`All HTTP tags ` are supported for this integration. + +""" +from .wsgi import DDWSGIMiddleware +from .wsgi import get_version + + +__all__ = ["DDWSGIMiddleware", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/wsgi/wsgi.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/wsgi/wsgi.py new file mode 100644 index 0000000..fc2b78e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/wsgi/wsgi.py @@ -0,0 +1,270 @@ +from typing import TYPE_CHECKING +from typing import Callable +from typing import Iterable + +from ddtrace.internal.schema.span_attribute_schema import SpanDirection + + +if TYPE_CHECKING: # pragma: no cover + from typing import Any # noqa:F401 + from typing import Dict # noqa:F401 + from typing import Mapping # noqa:F401 + from typing import Optional # noqa:F401 + + from ddtrace import Pin # noqa:F401 + from ddtrace import Span # noqa:F401 + from ddtrace import Tracer # noqa:F401 + from ddtrace.settings import Config # noqa:F401 + +from urllib.parse import quote + +import ddtrace +from ddtrace import config +from ddtrace.constants import SPAN_KIND +from ddtrace.contrib import trace_utils +from ddtrace.ext import SpanKind +from ddtrace.ext import SpanTypes +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.constants import HTTP_REQUEST_BLOCKED +from ddtrace.internal.logger import get_logger +from ddtrace.internal.schema import schematize_url_operation +from ddtrace.propagation._utils import from_wsgi_header +from ddtrace.propagation.http import HTTPPropagator +from ddtrace.vendor import wrapt + +from ...internal import core + + +log = get_logger(__name__) + +propagator = HTTPPropagator + +config._add( + "wsgi", + dict( + _default_service="wsgi", + distributed_tracing=True, + ), +) + + +def get_version(): + # type: () -> str + return "" + + +class _DDWSGIMiddlewareBase(object): + """Base WSGI middleware class. + + :param application: The WSGI application to apply the middleware to. + :param tracer: Tracer instance to use the middleware with. Defaults to the global tracer. + :param int_config: Integration specific configuration object. + :param pin: Set tracing metadata on a particular traced connection + :param app_is_iterator: Boolean indicating whether the wrapped app is a Python iterator + """ + + def __init__(self, application, tracer, int_config, pin, app_is_iterator=False): + # type: (Iterable, Tracer, Config, Pin, bool) -> None + self.app = application + self.tracer = tracer + self._config = int_config + self._pin = pin + self.app_is_iterator = app_is_iterator + + @property + def _request_span_name(self): + # type: () -> str + "Returns the name of a request span. Example: `flask.request`" + raise NotImplementedError + + @property + def _application_span_name(self): + # type: () -> str + "Returns the name of an application span. Example: `flask.application`" + raise NotImplementedError + + @property + def _response_span_name(self): + # type: () -> str + "Returns the name of a response span. Example: `flask.response`" + raise NotImplementedError + + def __call__(self, environ: Iterable, start_response: Callable) -> wrapt.ObjectProxy: + headers = get_request_headers(environ) + closing_iterable = () + not_blocked = True + with core.context_with_data( + "wsgi.__call__", + remote_addr=environ.get("REMOTE_ADDR"), + headers=headers, + headers_case_sensitive=True, + service=trace_utils.int_service(self._pin, self._config), + span_type=SpanTypes.WEB, + span_name=(self._request_call_name if hasattr(self, "_request_call_name") else self._request_span_name), + middleware_config=self._config, + distributed_headers_config=self._config, + distributed_headers=environ, + environ=environ, + middleware=self, + call_key="req_span", + ) as ctx: + if core.get_item(HTTP_REQUEST_BLOCKED): + result = core.dispatch_with_results("wsgi.block.started", (ctx, construct_url)).status_headers_content + if result: + status, headers, content = result.value + else: + status, headers, content = 403, [], "" + start_response(str(status), headers) + closing_iterable = [content] + not_blocked = False + + def blocked_view(): + result = core.dispatch_with_results("wsgi.block.started", (ctx, construct_url)).status_headers_content + if result: + status, headers, content = result.value + else: + status, headers, content = 403, [], "" + return content, status, headers + + core.dispatch("wsgi.block_decided", (blocked_view,)) + + if not_blocked: + core.dispatch("wsgi.request.prepare", (ctx, start_response)) + try: + closing_iterable = self.app(environ, ctx.get_item("intercept_start_response")) + except BaseException: + core.dispatch("wsgi.app.exception", (ctx,)) + raise + else: + core.dispatch("wsgi.app.success", (ctx, closing_iterable)) + if core.get_item(HTTP_REQUEST_BLOCKED): + _, _, content = core.dispatch_with_results( + "wsgi.block.started", (ctx, construct_url) + ).status_headers_content.value or (None, None, "") + closing_iterable = [content] + + result = core.dispatch_with_results( + "wsgi.request.complete", (ctx, closing_iterable, self.app_is_iterator) + ).traced_iterable + return result.value if result else [] + + def _traced_start_response(self, start_response, request_span, app_span, status, environ, exc_info=None): + # type: (Callable, Span, Span, str, Dict, Any) -> None + """sets the status code on a request span when start_response is called""" + with core.context_with_data( + "wsgi.response", + middleware=self, + request_span=request_span, + parent_call=app_span, + status=status, + environ=environ, + span_type=SpanTypes.WEB, + service=trace_utils.int_service(None, self._config), + start_span=False, + tags={COMPONENT: self._config.integration_name, SPAN_KIND: SpanKind.SERVER}, + call_key="response_span", + ): + return start_response(status, environ, exc_info) + + def _request_span_modifier(self, req_span, environ, parsed_headers=None): + # type: (Span, Dict, Optional[Dict]) -> None + """Implement to modify span attributes on the request_span""" + + def _application_span_modifier(self, app_span, environ, result): + # type: (Span, Dict, Iterable) -> None + """Implement to modify span attributes on the application_span""" + + def _response_span_modifier(self, resp_span, response): + # type: (Span, Dict) -> None + """Implement to modify span attributes on the request_span""" + + +def construct_url(environ): + """ + https://www.python.org/dev/peps/pep-3333/#url-reconstruction + """ + url = environ["wsgi.url_scheme"] + "://" + + if environ.get("HTTP_HOST"): + url += environ["HTTP_HOST"] + else: + url += environ["SERVER_NAME"] + + if environ["wsgi.url_scheme"] == "https": + if environ["SERVER_PORT"] != "443": + url += ":" + environ["SERVER_PORT"] + else: + if environ["SERVER_PORT"] != "80": + url += ":" + environ["SERVER_PORT"] + + url += quote(environ.get("SCRIPT_NAME", "")) + url += quote(environ.get("PATH_INFO", "")) + if environ.get("QUERY_STRING"): + url += "?" + environ["QUERY_STRING"] + + return url + + +def get_request_headers(environ): + # type: (Mapping[str, str]) -> Mapping[str, str] + """ + Manually grab the request headers from the environ dictionary. + """ + request_headers = {} # type: Mapping[str, str] + for key in environ.keys(): + if key.startswith("HTTP_"): + name = from_wsgi_header(key) + if name: + request_headers[name] = environ[key] + return request_headers + + +def default_wsgi_span_modifier(span, environ): + span.resource = "{} {}".format(environ["REQUEST_METHOD"], environ["PATH_INFO"]) + + +class DDWSGIMiddleware(_DDWSGIMiddlewareBase): + """WSGI middleware providing tracing around an application. + + :param application: The WSGI application to apply the middleware to. + :param tracer: Tracer instance to use the middleware with. Defaults to the global tracer. + :param span_modifier: Span modifier that can add tags to the root span. + Defaults to using the request method and url in the resource. + :param app_is_iterator: Boolean indicating whether the wrapped WSGI app is a Python iterator + """ + + _request_span_name = schematize_url_operation("wsgi.request", protocol="http", direction=SpanDirection.INBOUND) + _application_span_name = "wsgi.application" + _response_span_name = "wsgi.response" + + def __init__(self, application, tracer=None, span_modifier=default_wsgi_span_modifier, app_is_iterator=False): + # type: (Iterable, Optional[Tracer], Callable[[Span, Dict[str, str]], None], bool) -> None + super(DDWSGIMiddleware, self).__init__( + application, tracer or ddtrace.tracer, config.wsgi, None, app_is_iterator=app_is_iterator + ) + self.span_modifier = span_modifier + + def _traced_start_response(self, start_response, request_span, app_span, status, environ, exc_info=None): + with core.context_with_data( + "wsgi.response", + middleware=self, + request_span=request_span, + parent_call=app_span, + status=status, + environ=environ, + span_type=SpanTypes.WEB, + span_name="wsgi.start_response", + service=trace_utils.int_service(None, self._config), + start_span=True, + tags={COMPONENT: self._config.integration_name, SPAN_KIND: SpanKind.SERVER}, + call_key="response_span", + ) as ctx, ctx.get_item("response_span"): + return start_response(status, environ, exc_info) + + def _request_span_modifier(self, req_span, environ, parsed_headers=None): + url = construct_url(environ) + request_headers = parsed_headers if parsed_headers is not None else get_request_headers(environ) + core.dispatch("wsgi.request.prepared", (self, req_span, url, request_headers, environ)) + + def _response_span_modifier(self, resp_span, response): + core.dispatch("wsgi.response.prepared", (resp_span, response)) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/yaaredis/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/yaaredis/__init__.py new file mode 100644 index 0000000..1b9eade --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/yaaredis/__init__.py @@ -0,0 +1,79 @@ +""" +The yaaredis integration traces yaaredis requests. + + +Enabling +~~~~~~~~ + +The yaaredis integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(yaaredis=True) + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.yaaredis["service"] + + The service name reported by default for yaaredis traces. + + This option can also be set with the ``DD_YAAREDIS_SERVICE`` environment + variable. + + Default: ``"redis"`` + +.. py:data:: ddtrace.config.yaaredis["cmd_max_length"] + + Max allowable size for the yaaredis command span tag. + Anything beyond the max length will be replaced with ``"..."``. + + This option can also be set with the ``DD_YAAREDIS_CMD_MAX_LENGTH`` environment + variable. + + Default: ``1000`` + +.. py:data:: ddtrace.config.aredis["resource_only_command"] + + The span resource will only include the command executed. To include all + arguments in the span resource, set this value to ``False``. + + This option can also be set with the ``DD_REDIS_RESOURCE_ONLY_COMMAND`` environment + variable. + + Default: ``True`` + + +Instance Configuration +~~~~~~~~~~~~~~~~~~~~~~ + +To configure particular yaaredis instances use the :class:`Pin ` API:: + + import yaaredis + from ddtrace import Pin + + client = yaaredis.StrictRedis(host="localhost", port=6379) + + # Override service name for this instance + Pin.override(client, service="my-custom-queue") + + # Traces reported for this client will now have "my-custom-queue" + # as the service name. + async def example(): + await client.get("my-key") +""" + +from ...internal.utils.importlib import require_modules + + +required_modules = ["yaaredis", "yaaredis.client"] + +with require_modules(required_modules) as missing_modules: + if not missing_modules: + from .patch import get_version + from .patch import patch + + __all__ = ["patch", "get_version"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/yaaredis/patch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/yaaredis/patch.py new file mode 100644 index 0000000..5166e0d --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/contrib/yaaredis/patch.py @@ -0,0 +1,83 @@ +import os + +import yaaredis + +from ddtrace import config +from ddtrace.vendor import wrapt + +from ...internal.schema import schematize_service_name +from ...internal.utils.formats import CMD_MAX_LEN +from ...internal.utils.formats import asbool +from ...internal.utils.formats import stringify_cache_args +from ...internal.utils.wrappers import unwrap +from ...pin import Pin +from ..trace_utils_redis import _run_redis_command_async +from ..trace_utils_redis import _trace_redis_cmd +from ..trace_utils_redis import _trace_redis_execute_pipeline + + +config._add( + "yaaredis", + dict( + _default_service=schematize_service_name("redis"), + cmd_max_length=int(os.getenv("DD_YAAREDIS_CMD_MAX_LENGTH", CMD_MAX_LEN)), + resource_only_command=asbool(os.getenv("DD_REDIS_RESOURCE_ONLY_COMMAND", True)), + ), +) + + +def get_version(): + # type: () -> str + return getattr(yaaredis, "__version__", "") + + +def patch(): + """Patch the instrumented methods""" + if getattr(yaaredis, "_datadog_patch", False): + return + yaaredis._datadog_patch = True + + _w = wrapt.wrap_function_wrapper + + _w("yaaredis.client", "StrictRedis.execute_command", traced_execute_command) + _w("yaaredis.client", "StrictRedis.pipeline", traced_pipeline) + _w("yaaredis.pipeline", "StrictPipeline.execute", traced_execute_pipeline) + _w("yaaredis.pipeline", "StrictPipeline.immediate_execute_command", traced_execute_command) + Pin().onto(yaaredis.StrictRedis) + + +def unpatch(): + if getattr(yaaredis, "_datadog_patch", False): + yaaredis._datadog_patch = False + + unwrap(yaaredis.client.StrictRedis, "execute_command") + unwrap(yaaredis.client.StrictRedis, "pipeline") + unwrap(yaaredis.pipeline.StrictPipeline, "execute") + unwrap(yaaredis.pipeline.StrictPipeline, "immediate_execute_command") + + +async def traced_execute_command(func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return await func(*args, **kwargs) + + with _trace_redis_cmd(pin, config.yaaredis, instance, args) as span: + return await _run_redis_command_async(span=span, func=func, args=args, kwargs=kwargs) + + +async def traced_pipeline(func, instance, args, kwargs): + pipeline = await func(*args, **kwargs) + pin = Pin.get_from(instance) + if pin: + pin.onto(pipeline) + return pipeline + + +async def traced_execute_pipeline(func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return await func(*args, **kwargs) + + cmds = [stringify_cache_args(c, cmd_max_len=config.yaaredis.cmd_max_length) for c, _ in instance.command_stack] + with _trace_redis_execute_pipeline(pin, config.yaaredis, cmds, instance): + return await func(*args, **kwargs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/data_streams.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/data_streams.py new file mode 100644 index 0000000..8330b7e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/data_streams.py @@ -0,0 +1,36 @@ +import ddtrace +from ddtrace.internal.datastreams.processor import PROPAGATION_KEY_BASE_64 + + +def set_consume_checkpoint(typ, source, carrier_get): + """ + :param typ: The type of the checkpoint, usually the streaming technology being used. + Examples include kafka, kinesis, sns etc. (str) + :param source: The source of data. This can be a topic, exchange or stream name. (str) + :param carrier_get: A function used to extract context from the carrier (function (str) -> str) + + :returns DataStreamsCtx | None + """ + if ddtrace.config._data_streams_enabled: + processor = ddtrace.tracer.data_streams_processor + processor.decode_pathway_b64(carrier_get(PROPAGATION_KEY_BASE_64)) + return processor.set_checkpoint(["type:" + typ, "topic:" + source, "direction:in", "manual_checkpoint:true"]) + + +def set_produce_checkpoint(typ, target, carrier_set): + """ + :param typ: The type of the checkpoint, usually the streaming technology being used. Examples include + kafka, kinesis, sns etc. (str) + :param target: The destination to which the data is being sent. For instance: topic, exchange or + stream name. (str) + :param carrier_set: A function used to inject the context into the carrier (function (str, str) -> None) + + :returns DataStreamsCtx | None + """ + if ddtrace.config._data_streams_enabled: + pathway = ddtrace.tracer.data_streams_processor.set_checkpoint( + ["type:" + typ, "topic:" + target, "direction:out", "manual_checkpoint:true"] + ) + if pathway is not None: + carrier_set(PROPAGATION_KEY_BASE_64, pathway.encode_b64()) + return pathway diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/__init__.py new file mode 100644 index 0000000..343bfbb --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/__init__.py @@ -0,0 +1,34 @@ +""" +Dynamic Instrumentation +======================= + +Enablement +---------- + +Dynamic Instrumentation can be enabled by setting the +``DD_DYNAMIC_INSTRUMENTATION_ENABLED`` variable to ``true`` in the environment, +when using the ``ddtrace-run`` command. Alternatively, when ``dtrace-run`` +cannot be used, it can be enabled programmatically with:: + + from ddtrace.debugging import DynamicInstrumentation + + # Enable dynamic instrumentation + DynamicInstrumentation.enable() + + ... + + # Disable dynamic instrumentation + DynamicInstrumentation.disable() + + +Configuration +------------- + +See the :ref:`Configuration` page for more details on how to configure +Dynamic Instrumentation. +""" + +from ddtrace.debugging._debugger import Debugger as DynamicInstrumentation + + +__all__ = ["DynamicInstrumentation"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_async.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_async.py new file mode 100644 index 0000000..b351afd --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_async.py @@ -0,0 +1,27 @@ +import sys +from types import CoroutineType +from typing import Iterable + +from ddtrace.debugging._signal.collector import SignalContext +from ddtrace.internal import compat + + +async def dd_coroutine_wrapper(coro: CoroutineType, contexts: Iterable[SignalContext]) -> CoroutineType: + start_time = compat.monotonic_ns() + try: + retval = await coro + end_time = compat.monotonic_ns() + exc_info = (None, None, None) + except Exception: + end_time = compat.monotonic_ns() + retval = None + exc_info = sys.exc_info() # type: ignore[assignment] + + for context in contexts: + context.exit(retval, exc_info, end_time - start_time) + + _, exc, _ = exc_info + if exc is not None: + raise exc + + return retval diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_config.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_config.py new file mode 100644 index 0000000..fb7b76d --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_config.py @@ -0,0 +1,6 @@ +from ddtrace.internal.logger import get_logger +from ddtrace.settings.dynamic_instrumentation import config as di_config # noqa: F401 +from ddtrace.settings.exception_debugging import config as ed_config # noqa: F401 + + +log = get_logger(__name__) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_debugger.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_debugger.py new file mode 100644 index 0000000..3c09476 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_debugger.py @@ -0,0 +1,730 @@ +from collections import defaultdict +from collections import deque +from itertools import chain +import linecache +import os +from pathlib import Path +import sys +import threading +from types import CoroutineType +from types import FunctionType +from types import ModuleType +from typing import Any +from typing import Deque +from typing import Dict +from typing import Iterable +from typing import List +from typing import Optional +from typing import Set +from typing import Tuple +from typing import cast + +import ddtrace +from ddtrace import config as ddconfig +from ddtrace.debugging._async import dd_coroutine_wrapper +from ddtrace.debugging._config import di_config +from ddtrace.debugging._config import ed_config +from ddtrace.debugging._encoding import LogSignalJsonEncoder +from ddtrace.debugging._encoding import SignalQueue +from ddtrace.debugging._exception.auto_instrument import SpanExceptionProcessor +from ddtrace.debugging._function.discovery import FunctionDiscovery +from ddtrace.debugging._function.store import FullyNamedWrappedFunction +from ddtrace.debugging._function.store import FunctionStore +from ddtrace.debugging._metrics import metrics +from ddtrace.debugging._probe.model import FunctionLocationMixin +from ddtrace.debugging._probe.model import FunctionProbe +from ddtrace.debugging._probe.model import LineLocationMixin +from ddtrace.debugging._probe.model import LineProbe +from ddtrace.debugging._probe.model import LogFunctionProbe +from ddtrace.debugging._probe.model import LogLineProbe +from ddtrace.debugging._probe.model import MetricFunctionProbe +from ddtrace.debugging._probe.model import MetricLineProbe +from ddtrace.debugging._probe.model import Probe +from ddtrace.debugging._probe.model import SpanDecorationFunctionProbe +from ddtrace.debugging._probe.model import SpanDecorationLineProbe +from ddtrace.debugging._probe.model import SpanFunctionProbe +from ddtrace.debugging._probe.registry import ProbeRegistry +from ddtrace.debugging._probe.remoteconfig import ProbePollerEvent +from ddtrace.debugging._probe.remoteconfig import ProbePollerEventType +from ddtrace.debugging._probe.remoteconfig import ProbeRCAdapter +from ddtrace.debugging._probe.status import ProbeStatusLogger +from ddtrace.debugging._signal.collector import SignalCollector +from ddtrace.debugging._signal.collector import SignalContext +from ddtrace.debugging._signal.metric_sample import MetricSample +from ddtrace.debugging._signal.model import Signal +from ddtrace.debugging._signal.model import SignalState +from ddtrace.debugging._signal.snapshot import Snapshot +from ddtrace.debugging._signal.tracing import DynamicSpan +from ddtrace.debugging._signal.tracing import SpanDecoration +from ddtrace.debugging._uploader import LogsIntakeUploaderV1 +from ddtrace.internal import atexit +from ddtrace.internal import compat +from ddtrace.internal import forksafe +from ddtrace.internal.logger import get_logger +from ddtrace.internal.metrics import Metrics +from ddtrace.internal.module import ModuleHookType +from ddtrace.internal.module import ModuleWatchdog +from ddtrace.internal.module import origin +from ddtrace.internal.module import register_post_run_module_hook +from ddtrace.internal.module import unregister_post_run_module_hook +from ddtrace.internal.rate_limiter import BudgetRateLimiterWithJitter as RateLimiter +from ddtrace.internal.rate_limiter import RateLimitExceeded +from ddtrace.internal.remoteconfig.worker import remoteconfig_poller +from ddtrace.internal.safety import _isinstance +from ddtrace.internal.service import Service +from ddtrace.internal.wrapping import Wrapper +from ddtrace.tracer import Tracer + + +log = get_logger(__name__) + +_probe_metrics = Metrics(namespace="dynamic.instrumentation.metric") +_probe_metrics.enable() + + +class DebuggerError(Exception): + """Generic debugger error.""" + + pass + + +class DebuggerModuleWatchdog(ModuleWatchdog): + _locations: Set[str] = set() + + @classmethod + def register_origin_hook(cls, origin: Path, hook: ModuleHookType) -> None: + if origin in cls._locations: + # We already have a hook for this origin, don't register a new one + # but invoke it directly instead, if the module was already loaded. + module = cls.get_by_origin(origin) + if module is not None: + hook(module) + + return + + cls._locations.add(str(origin)) + + super().register_origin_hook(origin, hook) + + @classmethod + def unregister_origin_hook(cls, origin: Path, hook: ModuleHookType) -> None: + try: + cls._locations.remove(str(origin)) + except KeyError: + # Nothing to unregister. + return + + return super().unregister_origin_hook(origin, hook) + + @classmethod + def register_module_hook(cls, module_name: str, hook: ModuleHookType) -> None: + if module_name in cls._locations: + # We already have a hook for this origin, don't register a new one + # but invoke it directly instead, if the module was already loaded. + module = sys.modules.get(module_name) + if module is not None: + hook(module) + + return + + cls._locations.add(module_name) + + super().register_module_hook(module_name, hook) + + @classmethod + def unregister_module_hook(cls, module_name: str, hook: ModuleHookType) -> None: + try: + cls._locations.remove(module_name) + except KeyError: + # Nothing to unregister. + return + + return super().unregister_module_hook(module_name, hook) + + @classmethod + def on_run_module(cls, module: ModuleType) -> None: + if cls._instance is not None: + # Treat run module as an import to trigger import hooks and register + # the module's origin. + cls._instance.after_import(module) + + +class Debugger(Service): + _instance: Optional["Debugger"] = None + _probe_meter = _probe_metrics.get_meter("probe") + _span_processor: Optional[SpanExceptionProcessor] = None + + __rc_adapter__ = ProbeRCAdapter + __uploader__ = LogsIntakeUploaderV1 + __collector__ = SignalCollector + __watchdog__ = DebuggerModuleWatchdog + __logger__ = ProbeStatusLogger + + @classmethod + def enable(cls, run_module: bool = False) -> None: + """Enable dynamic instrumentation + + This class method is idempotent. Dynamic instrumentation will be + disabled automatically at exit. + """ + if cls._instance is not None: + log.debug("%s already enabled", cls.__name__) + return + + log.debug("Enabling %s", cls.__name__) + + di_config.enabled = True + + cls.__watchdog__.install() + + if di_config.metrics: + metrics.enable() + + cls._instance = debugger = cls() + + debugger.start() + + forksafe.register(cls._restart) + atexit.register(cls.disable) + register_post_run_module_hook(cls._on_run_module) + + log.debug("%s enabled", cls.__name__) + + @classmethod + def disable(cls, join: bool = True) -> None: + """Disable dynamic instrumentation. + + This class method is idempotent. Called automatically at exit, if + dynamic instrumentation was enabled. + """ + if cls._instance is None: + log.debug("%s not enabled", cls.__name__) + return + + log.debug("Disabling %s", cls.__name__) + + remoteconfig_poller.unregister("LIVE_DEBUGGING") + + forksafe.unregister(cls._restart) + atexit.unregister(cls.disable) + unregister_post_run_module_hook(cls._on_run_module) + + if cls._instance._span_processor: + cls._instance._span_processor.unregister() + + cls._instance.stop(join=join) + cls._instance = None + + cls.__watchdog__.uninstall() + if di_config.metrics: + metrics.disable() + + di_config.enabled = False + + log.debug("%s disabled", cls.__name__) + + def __init__(self, tracer: Optional[Tracer] = None) -> None: + super().__init__() + + self._tracer = tracer or ddtrace.tracer + service_name = di_config.service_name + + self._signal_queue = SignalQueue( + encoder=LogSignalJsonEncoder(service_name), + on_full=self._on_encoder_buffer_full, + ) + self._status_logger = status_logger = self.__logger__(service_name) + + self._probe_registry = ProbeRegistry(status_logger=status_logger) + self._uploader = self.__uploader__(self._signal_queue) + self._collector = self.__collector__(self._signal_queue) + self._services = [self._uploader] + + self._function_store = FunctionStore(extra_attrs=["__dd_wrappers__"]) + + log_limiter = RateLimiter(limit_rate=1.0, raise_on_exceed=False) + self._global_rate_limiter = RateLimiter( + limit_rate=di_config.global_rate_limit, # TODO: Make it configurable. Note that this is per-process! + on_exceed=lambda: log_limiter.limit(log.warning, "Global rate limit exceeded"), + call_once=True, + raise_on_exceed=False, + ) + + if ed_config.enabled: + from ddtrace.debugging._exception.auto_instrument import SpanExceptionProcessor + + self._span_processor = SpanExceptionProcessor(collector=self._collector) + self._span_processor.register() + else: + self._span_processor = None + + if di_config.enabled: + # TODO: this is only temporary and will be reverted once the DD_REMOTE_CONFIGURATION_ENABLED variable + # has been removed + if ddconfig._remote_config_enabled is False: + ddconfig._remote_config_enabled = True + log.info("Disabled Remote Configuration enabled by Dynamic Instrumentation.") + + # Register the debugger with the RCM client. + if not remoteconfig_poller.update_product_callback("LIVE_DEBUGGING", self._on_configuration): + di_callback = self.__rc_adapter__(None, self._on_configuration, status_logger=status_logger) + remoteconfig_poller.register("LIVE_DEBUGGING", di_callback) + + log.debug("%s initialized (service name: %s)", self.__class__.__name__, service_name) + + def _on_encoder_buffer_full(self, item, encoded): + # type (Any, bytes) -> None + # Send upload request + self._uploader.upload() + + def _dd_debugger_hook(self, probe: Probe) -> None: + """Debugger probe hook. + + This gets called with a reference to the probe. We only check whether + the probe is active. If so, we push the collected data to the collector + for bulk processing. This way we avoid adding delay while the + instrumented code is running. + """ + try: + actual_frame = sys._getframe(1) + signal: Optional[Signal] = None + if isinstance(probe, MetricLineProbe): + signal = MetricSample( + probe=probe, + frame=actual_frame, + thread=threading.current_thread(), + trace_context=self._tracer.current_trace_context(), + meter=self._probe_meter, + ) + elif isinstance(probe, LogLineProbe): + if probe.take_snapshot: + # TODO: Global limit evaluated before probe conditions + if self._global_rate_limiter.limit() is RateLimitExceeded: + return + + signal = Snapshot( + probe=probe, + frame=actual_frame, + thread=threading.current_thread(), + trace_context=self._tracer.current_trace_context(), + ) + elif isinstance(probe, SpanDecorationLineProbe): + signal = SpanDecoration( + probe=probe, + frame=actual_frame, + thread=threading.current_thread(), + ) + else: + log.error("Unsupported probe type: %r", type(probe)) + return + + signal.line() + + log.debug("[%s][P: %s] Debugger. Report signal %s", os.getpid(), os.getppid(), signal) + self._collector.push(signal) + + if signal.state is SignalState.DONE: + self._probe_registry.set_emitting(probe) + + except Exception: + log.error("Failed to execute probe hook", exc_info=True) + + def _dd_debugger_wrapper(self, wrappers: Dict[str, FunctionProbe]) -> Wrapper: + """Debugger wrapper. + + This gets called with a reference to the wrapped function and the probe, + together with the arguments to pass. We only check + whether the probe is active and the debugger is enabled. If so, we + capture all the relevant debugging context. + """ + + def _(wrapped: FunctionType, args: Tuple[Any], kwargs: Dict[str, Any]) -> Any: + if not wrappers: + return wrapped(*args, **kwargs) + + argnames = wrapped.__code__.co_varnames + actual_frame = sys._getframe(1) + allargs = list(chain(zip(argnames, args), kwargs.items())) + thread = threading.current_thread() + + open_contexts: Deque[SignalContext] = deque() + signal: Optional[Signal] = None + + # Group probes on the basis of whether they create new context. + context_creators: List[Probe] = [] + context_consumers: List[Probe] = [] + for p in wrappers.values(): + (context_creators if p.__context_creator__ else context_consumers).append(p) + + # Trigger the context creators first, so that the new context can be + # consumed by the consumers. + for probe in chain(context_creators, context_consumers): + # Because new context might be created, we need to recompute it + # for each probe. + trace_context = self._tracer.current_trace_context() + + if isinstance(probe, MetricFunctionProbe): + signal = MetricSample( + probe=probe, + frame=actual_frame, + thread=thread, + args=allargs, + trace_context=trace_context, + meter=self._probe_meter, + ) + elif isinstance(probe, LogFunctionProbe): + signal = Snapshot( + probe=probe, + frame=actual_frame, + thread=thread, + args=allargs, + trace_context=trace_context, + ) + elif isinstance(probe, SpanFunctionProbe): + signal = DynamicSpan( + probe=probe, + frame=actual_frame, + thread=thread, + args=allargs, + trace_context=trace_context, + ) + elif isinstance(probe, SpanDecorationFunctionProbe): + signal = SpanDecoration( + probe=probe, + frame=actual_frame, + thread=thread, + args=allargs, + ) + else: + log.error("Unsupported probe type: %s", type(probe)) + continue + + # Open probe signal contexts are ordered, with those that have + # created new tracing context first. We need to finalise them in + # reverse order, so we append them to the beginning. + open_contexts.appendleft(self._collector.attach(signal)) + + if not open_contexts: + return wrapped(*args, **kwargs) + + start_time = compat.monotonic_ns() + try: + retval = wrapped(*args, **kwargs) + end_time = compat.monotonic_ns() + exc_info = (None, None, None) + except Exception: + end_time = compat.monotonic_ns() + retval = None + exc_info = sys.exc_info() # type: ignore[assignment] + else: + # DEV: We do not unwind generators here as they might result in + # tight loops. We return the result as a generator object + # instead. + if _isinstance(retval, CoroutineType): + return dd_coroutine_wrapper(retval, open_contexts) + + for context in open_contexts: + context.exit(retval, exc_info, end_time - start_time) + signal = context.signal + if signal.state is SignalState.DONE: + self._probe_registry.set_emitting(signal.probe) + + exc = exc_info[1] + if exc is not None: + raise exc + + return retval + + return _ + + def _probe_injection_hook(self, module: ModuleType) -> None: + # This hook is invoked by the ModuleWatchdog or the post run module hook + # to inject probes. + + # Group probes by function so that we decompile each function once and + # bulk-inject the probes. + probes_for_function: Dict[FullyNamedWrappedFunction, List[Probe]] = defaultdict(list) + for probe in self._probe_registry.get_pending(str(origin(module))): + if not isinstance(probe, LineLocationMixin): + continue + line = probe.line + assert line is not None # nosec + functions = FunctionDiscovery.from_module(module).at_line(line) + if not functions: + module_origin = str(origin(module)) + if linecache.getline(module_origin, line): + # The source actually has a line at the given line number + message = ( + f"Cannot install probe {probe.probe_id}: " + f"function at line {line} within source file {module_origin} " + "is likely decorated with an unsupported decorator." + ) + else: + message = ( + f"Cannot install probe {probe.probe_id}: " + f"no functions at line {line} within source file {module_origin} found" + ) + log.error(message) + self._probe_registry.set_error(probe, "NoFunctionsAtLine", message) + continue + for function in (cast(FullyNamedWrappedFunction, _) for _ in functions): + probes_for_function[function].append(cast(LineProbe, probe)) + + for function, probes in probes_for_function.items(): + failed = self._function_store.inject_hooks( + function, [(self._dd_debugger_hook, cast(LineProbe, probe).line, probe) for probe in probes] + ) + + for probe in probes: + if probe.probe_id in failed: + self._probe_registry.set_error(probe, "InjectionFailure", "Failed to inject") + else: + self._probe_registry.set_installed(probe) + + if failed: + log.error("[%s][P: %s] Failed to inject probes %r", os.getpid(), os.getppid(), failed) + + log.debug( + "[%s][P: %s] Injected probes %r in %r", + os.getpid(), + os.getppid(), + [probe.probe_id for probe in probes if probe.probe_id not in failed], + function, + ) + + def _inject_probes(self, probes: List[LineProbe]) -> None: + for probe in probes: + if probe not in self._probe_registry: + if len(self._probe_registry) >= di_config.max_probes: + log.warning("Too many active probes. Ignoring new ones.") + return + log.debug("[%s][P: %s] Received new %s.", os.getpid(), os.getppid(), probe) + self._probe_registry.register(probe) + + resolved_source = probe.source_file + if resolved_source is None: + log.error( + "Cannot inject probe %s: source file %s cannot be resolved", probe.probe_id, probe.source_file + ) + self._probe_registry.set_error(probe, "NoSourceFile", "Source file location cannot be resolved") + continue + + for source in {probe.source_file for probe in probes if probe.source_file is not None}: + try: + self.__watchdog__.register_origin_hook(source, self._probe_injection_hook) + except Exception as exc: + for probe in probes: + if probe.source_file != source: + continue + exc_type = type(exc) + self._probe_registry.set_error(probe, exc_type.__name__, str(exc)) + log.error("Cannot register probe injection hook on source '%s'", source, exc_info=True) + + def _eject_probes(self, probes_to_eject: List[LineProbe]) -> None: + # TODO[perf]: Bulk-collect probes as for injection. This is lower + # priority as probes are normally removed manually by users. + unregistered_probes: List[LineProbe] = [] + for probe in probes_to_eject: + if probe not in self._probe_registry: + log.error("Attempted to eject unregistered probe %r", probe) + continue + + (registered_probe,) = self._probe_registry.unregister(probe) + unregistered_probes.append(cast(LineProbe, registered_probe)) + + probes_for_source: Dict[Path, List[LineProbe]] = defaultdict(list) + for probe in unregistered_probes: + if probe.source_file is None: + continue + probes_for_source[probe.source_file].append(probe) + + for resolved_source, probes in probes_for_source.items(): + module = self.__watchdog__.get_by_origin(resolved_source) + if module is not None: + # The module is still loaded, so we can try to eject the hooks + probes_for_function: Dict[FullyNamedWrappedFunction, List[LineProbe]] = defaultdict(list) + for probe in probes: + if not isinstance(probe, LineLocationMixin): + continue + line = probe.line + assert line is not None, probe # nosec + functions = FunctionDiscovery.from_module(module).at_line(line) + for function in (cast(FullyNamedWrappedFunction, _) for _ in functions): + probes_for_function[function].append(probe) + + for function, ps in probes_for_function.items(): + failed = self._function_store.eject_hooks( + cast(FunctionType, function), + [(self._dd_debugger_hook, probe.line, probe) for probe in ps if probe.line is not None], + ) + for probe in ps: + if probe.probe_id in failed: + log.error("Failed to eject %r from %r", probe, function) + else: + log.debug("Ejected %r from %r", probe, function) + + if not self._probe_registry.has_probes(str(resolved_source)): + try: + self.__watchdog__.unregister_origin_hook(resolved_source, self._probe_injection_hook) + log.debug("Unregistered injection hook on source '%s'", resolved_source) + except ValueError: + log.error("Cannot unregister injection hook for %r", probe, exc_info=True) + + def _probe_wrapping_hook(self, module: ModuleType) -> None: + probes = self._probe_registry.get_pending(module.__name__) + for probe in probes: + if not isinstance(probe, FunctionLocationMixin): + continue + + try: + assert probe.module is not None and probe.func_qname is not None # nosec + function = FunctionDiscovery.from_module(module).by_name(probe.func_qname) + except ValueError: + message = ( + f"Cannot install probe {probe.probe_id}: no function '{probe.func_qname}' in module {probe.module}" + "found (note: if the function exists, it might be decorated with an unsupported decorator)" + ) + self._probe_registry.set_error(probe, "NoFunctionInModule", message) + log.error(message) + continue + + if hasattr(function, "__dd_wrappers__"): + # TODO: Check if this can be made into a set instead + wrapper = cast(FullyNamedWrappedFunction, function) + assert wrapper.__dd_wrappers__, "Function has debugger wrappers" # nosec + wrapper.__dd_wrappers__[probe.probe_id] = probe + log.debug( + "[%s][P: %s] Function probe %r added to already wrapped %r", + os.getpid(), + os.getppid(), + probe.probe_id, + function, + ) + else: + wrappers = cast(FullyNamedWrappedFunction, function).__dd_wrappers__ = {probe.probe_id: probe} + self._function_store.wrap(cast(FunctionType, function), self._dd_debugger_wrapper(wrappers)) + log.debug( + "[%s][P: %s] Function probe %r wrapped around %r", + os.getpid(), + os.getppid(), + probe.probe_id, + function, + ) + self._probe_registry.set_installed(probe) + + def _wrap_functions(self, probes: List[FunctionProbe]) -> None: + for probe in probes: + if len(self._probe_registry) >= di_config.max_probes: + log.warning("Too many active probes. Ignoring new ones.") + return + + self._probe_registry.register(probe) + try: + assert probe.module is not None # nosec + self.__watchdog__.register_module_hook(probe.module, self._probe_wrapping_hook) + except Exception as exc: + exc_type = type(exc) + self._probe_registry.set_error(probe, exc_type.__name__, str(exc)) + log.error("Cannot register probe wrapping hook on module '%s'", probe.module, exc_info=True) + + def _unwrap_functions(self, probes: List[FunctionProbe]) -> None: + # Keep track of all the modules involved to see if there are any import + # hooks that we can clean up at the end. + touched_modules: Set[str] = set() + + for probe in probes: + registered_probes = self._probe_registry.unregister(probe) + if not registered_probes: + log.error("Attempted to eject unregistered probe %r", probe) + continue + + (registered_probe,) = registered_probes + + assert probe.module is not None # nosec + module = sys.modules.get(probe.module, None) + if module is not None: + # The module is still loaded, so we can try to unwrap the function + touched_modules.add(probe.module) + assert probe.func_qname is not None # nosec + function = FunctionDiscovery.from_module(module).by_name(probe.func_qname) + if hasattr(function, "__dd_wrappers__"): + wrapper = cast(FullyNamedWrappedFunction, function) + assert wrapper.__dd_wrappers__, "Function has debugger wrappers" # nosec + del wrapper.__dd_wrappers__[probe.probe_id] + if not wrapper.__dd_wrappers__: + del wrapper.__dd_wrappers__ + self._function_store.unwrap(wrapper) + log.debug("Unwrapped %r", registered_probe) + else: + log.error("Attempted to unwrap %r, but no wrapper found", registered_probe) + + # Clean up import hooks. + for module_name in touched_modules: + if not self._probe_registry.has_probes(module_name): + try: + self.__watchdog__.unregister_module_hook(module_name, self._probe_wrapping_hook) + log.debug("Unregistered wrapping import hook on module %s", module_name) + except ValueError: + log.error("Cannot unregister wrapping import hook for module %r", module_name, exc_info=True) + + def _on_configuration(self, event: ProbePollerEventType, probes: Iterable[Probe]) -> None: + log.debug("[%s][P: %s] Received poller event %r with probes %r", os.getpid(), os.getppid(), event, probes) + + if event == ProbePollerEvent.STATUS_UPDATE: + self._probe_registry.log_probes_status() + return + + if event == ProbePollerEvent.MODIFIED_PROBES: + for probe in probes: + if probe in self._probe_registry: + registered_probe = self._probe_registry.get(probe.probe_id) + if registered_probe is None: + # We didn't have the probe. This shouldn't have happened! + log.error("Modified probe %r was not found in registry.", probe) + continue + self._probe_registry.update(probe) + + return + + line_probes: List[LineProbe] = [] + function_probes: List[FunctionProbe] = [] + for probe in probes: + if isinstance(probe, LineLocationMixin): + line_probes.append(cast(LineProbe, probe)) + elif isinstance(probe, FunctionLocationMixin): + function_probes.append(cast(FunctionProbe, probe)) + else: + log.warning("Skipping probe '%r': not supported.", probe) + + if event == ProbePollerEvent.NEW_PROBES: + self._inject_probes(line_probes) + self._wrap_functions(function_probes) + elif event == ProbePollerEvent.DELETED_PROBES: + self._eject_probes(line_probes) + self._unwrap_functions(function_probes) + else: + raise ValueError("Unknown probe poller event %r" % event) + + def _stop_service(self, join: bool = True) -> None: + self._function_store.restore_all() + for service in self._services: + service.stop() + if join: + service.join() + + def _start_service(self) -> None: + for service in self._services: + log.debug("[%s][P: %s] Debugger. Start service %s", os.getpid(), os.getppid(), service) + service.start() + + @classmethod + def _restart(cls): + log.info("[%s][P: %s] Restarting the debugger in child process", os.getpid(), os.getppid()) + cls.disable(join=False) + cls.enable() + + @classmethod + def _on_run_module(cls, module: ModuleType) -> None: + debugger = cls._instance + if debugger is not None: + debugger.__watchdog__.on_run_module(module) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_encoding.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_encoding.py new file mode 100644 index 0000000..a7a3c70 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_encoding.py @@ -0,0 +1,324 @@ +import abc +from dataclasses import dataclass +from heapq import heapify +from heapq import heappop +from heapq import heappush +import json +import os +from threading import Thread +from types import FrameType +from typing import Any +from typing import Callable +from typing import Dict +from typing import List +from typing import Optional + +from ddtrace.debugging._config import di_config +from ddtrace.debugging._signal.model import LogSignal +from ddtrace.debugging._signal.snapshot import Snapshot +from ddtrace.internal import forksafe +from ddtrace.internal._encoding import BufferFull +from ddtrace.internal.logger import get_logger + + +log = get_logger(__name__) + + +class JsonBuffer(object): + def __init__(self, max_size=None): + self.max_size = max_size + self._reset() + + def put(self, item: bytes) -> int: + if self._flushed: + self._reset() + + size = len(item) + if self.size + size > self.max_size: + raise BufferFull(self.size, size) + + if self.size > 2: + self.size += 1 + self._buffer += b"," + self._buffer += item + self.size += size + return size + + def _reset(self): + self.size = 2 + self._buffer = bytearray(b"[") + self._flushed = False + + def flush(self): + self._buffer += b"]" + try: + return self._buffer + finally: + self._flushed = True + + +class Encoder(abc.ABC): + @abc.abstractmethod + def encode(self, item: Any) -> bytes: + """Encode the given snapshot.""" + + +class BufferedEncoder(abc.ABC): + count = 0 + + @abc.abstractmethod + def put(self, item: Any) -> int: + """Enqueue the given item and returns its encoded size.""" + + @abc.abstractmethod + def flush(self) -> Optional[bytes]: + """Flush the buffer and return the encoded data.""" + + +def _logs_track_logger_details(thread: Thread, frame: FrameType) -> Dict[str, Any]: + code = frame.f_code + + return { + "name": code.co_filename, + "method": code.co_name, + "thread_name": "%s;pid:%d" % (thread.name, os.getpid()), + "thread_id": thread.ident, + "version": 2, + } + + +def add_tags(payload): + if not di_config._tags_in_qs and di_config.tags: + payload["ddtags"] = di_config.tags + + +def _build_log_track_payload( + service: str, + signal: LogSignal, + host: Optional[str], +) -> Dict[str, Any]: + context = signal.trace_context + + payload = { + "service": service, + "debugger.snapshot": signal.snapshot, + "host": host, + "logger": _logs_track_logger_details(signal.thread, signal.frame), + "dd.trace_id": context.trace_id if context else None, + "dd.span_id": context.span_id if context else None, + "ddsource": "dd_debugger", + "message": signal.message, + "timestamp": int(signal.timestamp * 1e3), # milliseconds, + } + add_tags(payload) + return payload + + +class JSONTree: + @dataclass + class Node: + start: int + end: int + level: int + parent: Optional["JSONTree.Node"] + children: List["JSONTree.Node"] + + pruned: int = 0 + not_captured_depth: bool = False + not_captured: bool = False + + @property + def key(self): + return self.not_captured_depth, self.level, self.not_captured, len(self) + + def __len__(self): + return self.end - self.start + + def __lt__(self, other): + # The Python heapq pops the smallest item, so we reverse the + # comparison. + return self.key > other.key + + @property + def leaves(self): + if not self.children: + yield self + else: + for child in self.children[::-1]: + yield from child.leaves + + def __init__(self, data): + self._iter = enumerate(data) + self._stack: List["JSONTree.Node"] = [] # TODO: deque + self.root = None + self.level = 0 + + self._string_iter = None + + self._state = self._object + self._on_string_match = self._not_captured + + self._parse() + + def _depth_string(self): + self._stack[-1].not_captured_depth = True + return self._object + + def _not_captured(self, i, c): + if c == '"': + self._string_iter = iter("depth") + self._on_string_match = self._depth_string + self._state = self._string + + elif c not in " :\n\t\r": + self._state = self._object + + def _not_captured_string(self): + self._stack[-1].not_captured = True + return self._not_captured + + def _escape(self, i, c): + self._state = self._string + + def _string(self, i, c): + if c == '"': + self._state = ( + self._on_string_match() + if self._string_iter is not None and next(self._string_iter, None) is None + else self._object + ) + + elif c == "\\": + # If we are escaping a character, we are not parsing the + # "notCapturedReason" string. + self._string_iter = None + self._state = self._escape + + if self._string_iter is not None and c != next(self._string_iter, None): + self._string_iter = None + + def _object(self, i, c): + if c == "}": + o = self._stack.pop() + o.end = i + 1 + self.level -= 1 + if not self._stack: + self.root = o + + elif c == '"': + self._string_iter = iter("notCapturedReason") + self._on_string_match = self._not_captured_string + self._state = self._string + + elif c == "{": + o = self.Node(i, 0, self.level, None, []) + self.level += 1 + if self._stack: + o.parent = self._stack[-1] + o.parent.children.append(o) + self._stack.append(o) + + def _parse(self): + for i, c in self._iter: + self._state(i, c) + if self.root is not None: + return + + @property + def leaves(self): + return list(self.root.leaves) + + +class LogSignalJsonEncoder(Encoder): + MAX_SIGNAL_SIZE = (1 << 20) - 2 + MIN_LEVEL = 5 + + def __init__(self, service: str, host: Optional[str] = None) -> None: + self._service = service + self._host = host + + def encode(self, log_signal: LogSignal) -> bytes: + return self.pruned(json.dumps(_build_log_track_payload(self._service, log_signal, self._host))).encode("utf-8") + + def pruned(self, log_signal_json: str) -> str: + if len(log_signal_json) <= self.MAX_SIGNAL_SIZE: + return log_signal_json + + PRUNED_PROPERTY = '{"pruned":true}' + PRUNED_LEN = len(PRUNED_PROPERTY) + + tree = JSONTree(log_signal_json) + + delta = len(tree.root) - self.MAX_SIGNAL_SIZE + nodes, s = {}, 0 + + leaves = [_ for _ in tree.leaves if _.level >= self.MIN_LEVEL] + heapify(leaves) + while leaves: + leaf = heappop(leaves) + nodes[leaf.start] = leaf + s += len(leaf) - PRUNED_LEN + if s > delta: + break + + parent = leaf.parent + parent.pruned += 1 + if parent.pruned >= len(parent.children): + # We have pruned all the children of this parent node so we can + # treat it as a leaf now. + parent.not_captured_depth = parent.not_captured = True + heappush(leaves, parent) + for c in parent.children: + del nodes[c.start] + s -= len(c) - PRUNED_LEN + + pruned_nodes = sorted(nodes.values(), key=lambda n: n.start) # Leaf nodes don't overlap + + segments = [log_signal_json[: pruned_nodes[0].start]] + for n, m in zip(pruned_nodes, pruned_nodes[1:]): + segments.append(PRUNED_PROPERTY) + segments.append(log_signal_json[n.end : m.start]) + segments.append(PRUNED_PROPERTY) + segments.append(log_signal_json[pruned_nodes[-1].end :]) + + return "".join(segments) + + +class SignalQueue(BufferedEncoder): + def __init__( + self, + encoder: Encoder, + buffer_size: int = 4 * (1 << 20), + on_full: Optional[Callable[[Any, bytes], None]] = None, + ) -> None: + self._encoder = encoder + self._buffer = JsonBuffer(buffer_size) + self._lock = forksafe.Lock() + self._on_full = on_full + self.count = 0 + self.max_size = buffer_size - self._buffer.size + + def put(self, item: Snapshot) -> int: + return self.put_encoded(item, self._encoder.encode(item)) + + def put_encoded(self, item: Snapshot, encoded: bytes) -> int: + try: + with self._lock: + size = self._buffer.put(encoded) + self.count += 1 + return size + except BufferFull: + if self._on_full is not None: + self._on_full(item, encoded) + raise + + def flush(self) -> Optional[bytes]: + with self._lock: + if self.count == 0: + # Reclaim memory + self._buffer._reset() + return None + + encoded = self._buffer.flush() + self.count = 0 + return encoded diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_exception/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_exception/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_exception/auto_instrument.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_exception/auto_instrument.py new file mode 100644 index 0000000..ebc1dc5 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_exception/auto_instrument.py @@ -0,0 +1,210 @@ +from collections import deque +from itertools import count +import sys +from threading import current_thread +from types import TracebackType +import typing as t +import uuid + +import attr + +from ddtrace.debugging._probe.model import LiteralTemplateSegment +from ddtrace.debugging._probe.model import LogLineProbe +from ddtrace.debugging._signal.collector import SignalCollector +from ddtrace.debugging._signal.snapshot import DEFAULT_CAPTURE_LIMITS +from ddtrace.debugging._signal.snapshot import Snapshot +from ddtrace.internal.processor import SpanProcessor +from ddtrace.internal.rate_limiter import BudgetRateLimiterWithJitter as RateLimiter +from ddtrace.internal.rate_limiter import RateLimitExceeded +from ddtrace.span import Span + + +GLOBAL_RATE_LIMITER = RateLimiter( + limit_rate=1, # one trace per second + raise_on_exceed=False, +) + +# used to mark that the span have debug info captured, visible to users +DEBUG_INFO_TAG = "error.debug_info_captured" + +# used to rate limit decision on the entire local trace (stored at the root span) +CAPTURE_TRACE_TAG = "_dd.debug.error.trace_captured" + +# unique exception id +EXCEPTION_ID_TAG = "_dd.debug.error.exception_id" + +# link to matching snapshot for every frame in the traceback +FRAME_SNAPSHOT_ID_TAG = "_dd.debug.error.%d.snapshot_id" +FRAME_FUNCTION_TAG = "_dd.debug.error.%d.function" +FRAME_FILE_TAG = "_dd.debug.error.%d.file" +FRAME_LINE_TAG = "_dd.debug.error.%d.line" + + +def unwind_exception_chain( + exc: t.Optional[BaseException], + tb: t.Optional[TracebackType], +) -> t.Tuple[t.Deque[t.Tuple[BaseException, t.Optional[TracebackType]]], t.Optional[uuid.UUID]]: + """Unwind the exception chain and assign it an ID.""" + chain: t.Deque[t.Tuple[BaseException, t.Optional[TracebackType]]] = deque() + + while exc is not None: + chain.append((exc, tb)) + + if exc.__cause__ is not None: + exc = exc.__cause__ + elif exc.__context__ is not None and not exc.__suppress_context__: + exc = exc.__context__ + else: + exc = None + + tb = getattr(exc, "__traceback__", None) + + exc_id = None + if chain: + # If the chain is not trivial we generate an ID for the whole chain and + # store it on the outermost exception, if not already generated. + exc, _ = chain[-1] + try: + exc_id = exc._dd_exc_id # type: ignore[attr-defined] + except AttributeError: + exc._dd_exc_id = exc_id = uuid.uuid4() # type: ignore[attr-defined] + + return chain, exc_id + + +@attr.s +class SpanExceptionProbe(LogLineProbe): + @classmethod + def build(cls, exc_id: uuid.UUID, tb: TracebackType) -> "SpanExceptionProbe": + _exc_id = str(exc_id) + frame = tb.tb_frame + filename = frame.f_code.co_filename + line = tb.tb_lineno + name = frame.f_code.co_name + message = f"exception info for {name}, in {filename}, line {line} (exception ID {_exc_id})" + + return cls( + probe_id=_exc_id, + version=0, + tags={}, + source_file=filename, + line=line, + template=message, + segments=[LiteralTemplateSegment(message)], + take_snapshot=True, + limits=DEFAULT_CAPTURE_LIMITS, + condition=None, + condition_error_rate=0.0, + rate=float("inf"), + ) + + +@attr.s +class SpanExceptionSnapshot(Snapshot): + exc_id = attr.ib(type=t.Optional[uuid.UUID], default=None) + + @property + def data(self) -> t.Dict[str, t.Any]: + data = super().data + + data.update({"exception-id": str(self.exc_id)}) + + return data + + +def can_capture(span: Span) -> bool: + # We determine if we should capture the exception information from the span + # by looking at its local root. If we have budget to capture, we mark the + # root as "info captured" and return True. If we don't have budget, we mark + # the root as "info not captured" and return False. If the root is already + # marked, we return the mark. + root = span._local_root + if root is None: + return False + + info_captured = root.get_tag(CAPTURE_TRACE_TAG) + + if info_captured == "false": + return False + + if info_captured == "true": + return True + + if info_captured is None: + result = GLOBAL_RATE_LIMITER.limit() is not RateLimitExceeded + root.set_tag_str(CAPTURE_TRACE_TAG, str(result).lower()) + return result + + msg = f"unexpected value for {CAPTURE_TRACE_TAG}: {info_captured}" + raise ValueError(msg) + + +@attr.s +class SpanExceptionProcessor(SpanProcessor): + collector = attr.ib(type=SignalCollector) + + def on_span_start(self, span: Span) -> None: + pass + + def on_span_finish(self, span: Span) -> None: + if not (span.error and can_capture(span)): + # No error or budget to capture + return + + _, exc, _tb = sys.exc_info() + + chain, exc_id = unwind_exception_chain(exc, _tb) + if not chain or exc_id is None: + # No exceptions to capture + return + + seq = count(1) # 1-based sequence number + + while chain: + exc, _tb = chain.pop() # LIFO: reverse the chain + + if _tb is None or _tb.tb_frame is None: + # If we don't have a traceback there isn't much we can do + continue + + # DEV: We go from the handler up to the root exception + while _tb and _tb.tb_frame: + frame = _tb.tb_frame + code = frame.f_code + seq_nr = next(seq) + + # TODO: Check if it is user code; if not, skip. We still + # generate a sequence number. + + try: + snapshot_id = frame.f_locals["_dd_debug_snapshot_id"] + except KeyError: + # We don't have a snapshot for the frame so we create one + snapshot = SpanExceptionSnapshot( + probe=SpanExceptionProbe.build(exc_id, _tb), + frame=frame, + thread=current_thread(), + trace_context=span, + exc_id=exc_id, + ) + + # Capture + snapshot.line() + + # Collect + self.collector.push(snapshot) + + # Memoize + frame.f_locals["_dd_debug_snapshot_id"] = snapshot_id = snapshot.uuid + + # Add correlation tags on the span + span.set_tag_str(FRAME_SNAPSHOT_ID_TAG % seq_nr, snapshot_id) + span.set_tag_str(FRAME_FUNCTION_TAG % seq_nr, code.co_name) + span.set_tag_str(FRAME_FILE_TAG % seq_nr, code.co_filename) + span.set_tag_str(FRAME_LINE_TAG % seq_nr, str(_tb.tb_lineno)) + + # Move up the stack + _tb = _tb.tb_next + + span.set_tag_str(DEBUG_INFO_TAG, "true") + span.set_tag_str(EXCEPTION_ID_TAG, str(exc_id)) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_expressions.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_expressions.py new file mode 100644 index 0000000..db19a9f --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_expressions.py @@ -0,0 +1,365 @@ +r"""Debugger expression language + +This module implements the debugger expression language that is used in the UI +to define probe conditions and metric expressions. The JSON AST is compiled into +Python bytecode. + +Full grammar: + + predicate => | | + direct_predicate => {"": } + direct_predicate_type => not | isEmpty | isUndefined + value_source => | + literal => | true | false | "string" + number => 0 | ([1-9][0-9]*\.[0-9]+) + identifier => + arg_predicate => {"": []} + arg_predicate_type => eq | ne | gt | ge | lt | le | any | all | and | or + | startsWith | endsWith | contains | matches + argument_list => (,)+ + operation => | + direct_opearation => {"": } + direct_op_type => len | count | ref + arg_operation => {"": []} + arg_op_type => filter | substring | getmember | index +""" # noqa +from itertools import chain +import re +import sys +from types import FunctionType +from typing import Any +from typing import Callable +from typing import Dict +from typing import List +from typing import Optional +from typing import Tuple +from typing import Union + +import attr +from bytecode import Bytecode +from bytecode import Compare +from bytecode import Instr + +from ddtrace.debugging._safety import safe_getitem +from ddtrace.internal.compat import PYTHON_VERSION_INFO as PY +from ddtrace.internal.logger import get_logger + + +DDASTType = Union[Dict[str, Any], Dict[str, List[Any]], Any] + +log = get_logger(__name__) + + +def _is_identifier(name: str) -> bool: + return isinstance(name, str) and name.isidentifier() + + +IN_OPERATOR_INSTR = Instr("COMPARE_OP", Compare.IN) if PY < (3, 9) else Instr("CONTAINS_OP", 0) +NOT_IN_OPERATOR_INSTR = Instr("COMPARE_OP", Compare.NOT_IN) if PY < (3, 9) else Instr("CONTAINS_OP", 1) + + +class DDCompiler: + @classmethod + def __getmember__(cls, o, a): + return object.__getattribute__(o, a) + + @classmethod + def __index__(cls, o, i): + return safe_getitem(o, i) + + @classmethod + def __ref__(cls, x): + return x + + def _make_function(self, ast: DDASTType, args: Tuple[str, ...], name: str) -> FunctionType: + compiled = self._compile_predicate(ast) + if compiled is None: + raise ValueError("Invalid predicate: %r" % ast) + + instrs = compiled + [Instr("RETURN_VALUE")] + if sys.version_info >= (3, 11): + instrs.insert(0, Instr("RESUME", 0)) + + abstract_code = Bytecode(instrs) + abstract_code.argcount = len(args) + abstract_code.argnames = args + abstract_code.name = name + + return FunctionType(abstract_code.to_code(), {}, name, (), None) + + def _make_lambda(self, ast: DDASTType) -> Callable[[Any, Any], Any]: + return self._make_function(ast, ("_dd_it", "_locals"), "") + + def _compile_direct_predicate(self, ast: DDASTType) -> Optional[List[Instr]]: + # direct_predicate => {"": } + # direct_predicate_type => not | isEmpty | isUndefined + if not isinstance(ast, dict): + return None + + _type, arg = next(iter(ast.items())) + + if _type not in {"not", "isEmpty", "isUndefined"}: + return None + + value = self._compile_predicate(arg) + if value is None: + raise ValueError("Invalid argument: %r" % arg) + + if _type == "isUndefined": + value.append(Instr("LOAD_FAST", "_locals")) + value.append(NOT_IN_OPERATOR_INSTR) + else: + value.append(Instr("UNARY_NOT")) + + return value + + def _compile_arg_predicate(self, ast: DDASTType) -> Optional[List[Instr]]: + # arg_predicate => {"": []} + # arg_predicate_type => eq | ne | gt | ge | lt | le | any | all | and | or + # | startsWith | endsWith | contains | matches + if not isinstance(ast, dict): + return None + + _type, args = next(iter(ast.items())) + + if _type in {"or", "and"}: + a, b = args + ca, cb = self._compile_predicate(a), self._compile_predicate(b) + if ca is None: + raise ValueError("Invalid argument: %r" % a) + if cb is None: + raise ValueError("Invalid argument: %r" % b) + return ca + cb + [Instr("BINARY_%s" % _type.upper())] + + if _type in {"eq", "ge", "gt", "le", "lt", "ne"}: + a, b = args + ca, cb = self._compile_predicate(a), self._compile_predicate(b) + if ca is None: + raise ValueError("Invalid argument: %r" % a) + if cb is None: + raise ValueError("Invalid argument: %r" % b) + return ca + cb + [Instr("COMPARE_OP", getattr(Compare, _type.upper()))] + + if _type == "contains": + a, b = args + ca, cb = self._compile_predicate(a), self._compile_predicate(b) + if ca is None: + raise ValueError("Invalid argument: %r" % a) + if cb is None: + raise ValueError("Invalid argument: %r" % b) + return cb + ca + [IN_OPERATOR_INSTR] + + if _type in {"any", "all"}: + a, b = args + f = __builtins__[_type] # type: ignore[index] + ca, fb = self._compile_predicate(a), self._make_lambda(b) + + if ca is None: + raise ValueError("Invalid argument: %r" % a) + + return self._call_function( + lambda i, c, _locals: f(c(_, _locals) for _ in i), + ca, + [Instr("LOAD_CONST", fb)], + [Instr("LOAD_FAST", "_locals")], + ) + + if _type in {"startsWith", "endsWith"}: + a, b = args + ca, cb = self._compile_predicate(a), self._compile_predicate(b) + if ca is None: + raise ValueError("Invalid argument: %r" % a) + if cb is None: + raise ValueError("Invalid argument: %r" % b) + return self._call_function(getattr(str, _type.lower()), ca, cb) + + if _type == "matches": + a, b = args + string, pattern = self._compile_predicate(a), self._compile_predicate(b) + if string is None: + raise ValueError("Invalid argument: %r" % a) + if pattern is None: + raise ValueError("Invalid argument: %r" % b) + return self._call_function(lambda p, s: re.match(p, s) is not None, pattern, string) + + return None + + def _compile_direct_operation(self, ast: DDASTType) -> Optional[List[Instr]]: + # direct_opearation => {"": } + # direct_op_type => len | count | ref + if not isinstance(ast, dict): + return None + + _type, arg = next(iter(ast.items())) + + if _type in {"len", "count"}: + value = self._compile_value_source(arg) + if value is None: + raise ValueError("Invalid argument: %r" % arg) + return self._call_function(len, value) + + if _type == "ref": + if not isinstance(arg, str): + return None + + if arg == "@it": + return [Instr("LOAD_FAST", "_dd_it")] + + return [ + Instr("LOAD_FAST", "_locals"), + Instr("LOAD_CONST", self.__ref__(arg)), + Instr("BINARY_SUBSCR"), + ] + + return None + + def _call_function(self, func: Callable, *args: List[Instr]) -> List[Instr]: + if PY < (3, 11): + return [Instr("LOAD_CONST", func)] + list(chain(*args)) + [Instr("CALL_FUNCTION", len(args))] + elif PY >= (3, 12): + return [Instr("PUSH_NULL"), Instr("LOAD_CONST", func)] + list(chain(*args)) + [Instr("CALL", len(args))] + + # Python 3.11 + return ( + [Instr("PUSH_NULL"), Instr("LOAD_CONST", func)] + + list(chain(*args)) + + [Instr("PRECALL", len(args)), Instr("CALL", len(args))] + ) + + def _compile_arg_operation(self, ast: DDASTType) -> Optional[List[Instr]]: + # arg_operation => {"": []} + # arg_op_type => filter | substring + if not isinstance(ast, dict): + return None + + _type, args = next(iter(ast.items())) + + if _type not in {"filter", "substring", "getmember", "index"}: + return None + + if _type == "substring": + v, a, b = args + cv, ca, cb = self._compile_predicate(v), self._compile_predicate(a), self._compile_predicate(b) + if cv is None: + raise ValueError("Invalid argument: %r" % v) + if ca is None: + raise ValueError("Invalid argument: %r" % a) + if cb is None: + raise ValueError("Invalid argument: %r" % b) + return cv + ca + cb + [Instr("BUILD_SLICE", 2), Instr("BINARY_SUBSCR")] + + if _type == "filter": + a, b = args + ca, fb = self._compile_predicate(a), self._make_lambda(b) + + if ca is None: + raise ValueError("Invalid argument: %r" % a) + + return self._call_function( + lambda i, c, _locals: type(i)(_ for _ in i if c(_, _locals)), + ca, + [Instr("LOAD_CONST", fb)], + [Instr("LOAD_FAST", "_locals")], + ) + + if _type == "getmember": + v, attr = args + if not _is_identifier(attr): + raise ValueError("Invalid identifier: %r" % attr) + + cv = self._compile_predicate(v) + if not cv: + return None + + return self._call_function(self.__getmember__, cv, [Instr("LOAD_CONST", attr)]) + + if _type == "index": + v, i = args + cv = self._compile_predicate(v) + if not cv: + return None + ci = self._compile_predicate(i) + if not ci: + return None + return self._call_function(self.__index__, cv, ci) + + return None + + def _compile_operation(self, ast: DDASTType) -> Optional[List[Instr]]: + # operation => | + return self._compile_direct_operation(ast) or self._compile_arg_operation(ast) + + def _compile_literal(self, ast: DDASTType) -> Optional[List[Instr]]: + # literal => | true | false | "string" | null + if not (isinstance(ast, (str, int, float, bool)) or ast is None): + return None + + return [Instr("LOAD_CONST", ast)] + + def _compile_value_source(self, ast: DDASTType) -> Optional[List[Instr]]: + # value_source => | + return self._compile_operation(ast) or self._compile_literal(ast) + + def _compile_predicate(self, ast: DDASTType) -> Optional[List[Instr]]: + # predicate => | | + return ( + self._compile_direct_predicate(ast) or self._compile_arg_predicate(ast) or self._compile_value_source(ast) + ) + + def compile(self, ast: DDASTType) -> Callable[[Dict[str, Any]], Any]: + return self._make_function(ast, ("_locals",), "") + + +dd_compile = DDCompiler().compile + + +class DDExpressionEvaluationError(Exception): + """Thrown when an error occurs while evaluating a dsl expression.""" + + def __init__(self, dsl, e): + super().__init__('Failed to evaluate expression "%s": %s' % (dsl, str(e))) + self.dsl = dsl + self.error = str(e) + + +def _invalid_expression(_): + """Forces probes with invalid expression/conditions to never trigger. + + Any signs of invalid conditions in logs is an indication of a problem with + the expression compiler. + """ + return None + + +@attr.s +class DDExpression(object): + __compiler__ = dd_compile + + dsl = attr.ib(type=str) + callable = attr.ib(type=Callable[[Dict[str, Any]], Any]) + + def eval(self, _locals): + try: + return self.callable(_locals) + except Exception as e: + raise DDExpressionEvaluationError(self.dsl, e) from e + + def __call__(self, _locals): + return self.eval(_locals) + + @classmethod + def on_compiler_error(cls, dsl: str, exc: Exception) -> Callable[[Dict[str, Any]], Any]: + log.error("Cannot compile expression: %s", dsl, exc_info=True) + return _invalid_expression + + @classmethod + def compile(cls, expr: Dict[str, Any]) -> "DDExpression": + ast = expr["json"] + dsl = expr["dsl"] + + try: + compiled = cls.__compiler__(ast) + except Exception as e: + compiled = cls.on_compiler_error(dsl, e) + + return cls(dsl=dsl, callable=compiled) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_function/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_function/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_function/discovery.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_function/discovery.py new file mode 100644 index 0000000..176e909 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_function/discovery.py @@ -0,0 +1,240 @@ +from collections import defaultdict +from collections import deque +from pathlib import Path + +from ddtrace.internal.utils.inspection import undecorated +from ddtrace.vendor.wrapt.wrappers import FunctionWrapper + + +try: + from typing import Protocol +except ImportError: + from typing_extensions import Protocol # type: ignore[assignment] + +from types import FunctionType +from types import ModuleType +from typing import Any +from typing import Dict +from typing import Iterator +from typing import List +from typing import Optional +from typing import Tuple +from typing import Type +from typing import Union +from typing import cast + +from ddtrace.internal.logger import get_logger +from ddtrace.internal.module import origin +from ddtrace.internal.safety import _isinstance +from ddtrace.internal.utils.inspection import linenos + + +log = get_logger(__name__) + +FunctionContainerType = Union[type, property, classmethod, staticmethod, Tuple, ModuleType] + +ContainerKey = Union[str, int, Type[staticmethod], Type[classmethod]] + +CONTAINER_TYPES = (type, property, classmethod, staticmethod) + + +class FullyNamed(Protocol): + """A fully named object.""" + + __name__: Optional[str] = None + __fullname__: Optional[str] = None + + +class FullyNamedFunction(FullyNamed): + """A fully named function object.""" + + def __call__(self, *args, **kwargs): + pass + + +class ContainerIterator(Iterator, FullyNamedFunction): + """Wrapper around different types of function containers. + + A container comes with an origin, i.e. a parent container and a position + within it in the form of a key. + """ + + def __init__( + self, + container: FunctionContainerType, + origin: Optional[Union[Tuple["ContainerIterator", ContainerKey], Tuple[FullyNamedFunction, str]]] = None, + ) -> None: + if isinstance(container, (type, ModuleType)): + self._iter = iter(container.__dict__.items()) + self.__name__ = container.__name__ + + elif isinstance(container, tuple): + self._iter = iter(enumerate(_.cell_contents for _ in container)) # type: ignore[arg-type] + self.__name__ = "" + + elif isinstance(container, property): + self._iter = iter( + (m, getattr(container, a)) for m, a in {("getter", "fget"), ("setter", "fset"), ("deleter", "fdel")} + ) + assert container.fget is not None # nosec + self.__name__ = container.fget.__name__ + + elif isinstance(container, (classmethod, staticmethod)): + self._iter = iter([(type(container), container.__func__)]) # type: ignore[list-item] + self.__name__ = None + + else: + raise TypeError("Unsupported container type: %s", type(container)) + + self._container = container + + if origin is not None and origin[0].__fullname__ is not None: + origin_fullname = origin[0].__fullname__ + self.__fullname__ = ".".join((origin_fullname, self.__name__)) if self.__name__ else origin_fullname + else: + self.__fullname__ = self.__name__ + + def __iter__(self) -> Iterator[Tuple[ContainerKey, Any]]: + return self._iter + + def __next__(self) -> Tuple[ContainerKey, Any]: + return next(self._iter) + + next = __next__ + + +def _local_name(name: str, f: FunctionType) -> str: + func_name = f.__name__ + if func_name.startswith("__") and name.endswith(func_name): + # Quite likely a mangled name + return func_name + + if name != func_name: + # Brought into scope by an import, or a decorator + return "..".join((name, func_name)) + + return func_name + + +def _collect_functions(module: ModuleType) -> Dict[str, FullyNamedFunction]: + """Collect functions from a given module. + + All the collected functions are augmented with a ``__fullname__`` attribute + to disambiguate the same functions assigned to different names. + """ + path = origin(module) + if path is None: + # We are not able to determine what this module actually exports. + return {} + + containers = deque([ContainerIterator(module)]) + functions = {} + seen_containers = set() + seen_functions = set() + + while containers: + c = containers.popleft() + + if id(c._container) in seen_containers: + continue + seen_containers.add(id(c._container)) + + for k, o in c: + code = getattr(o, "__code__", None) if _isinstance(o, (FunctionType, FunctionWrapper)) else None + if code is not None: + local_name = _local_name(k, o) if isinstance(k, str) else o.__name__ + + if o not in seen_functions: + seen_functions.add(o) + o = cast(FullyNamedFunction, o) + o.__fullname__ = ".".join((c.__fullname__, local_name)) if c.__fullname__ else local_name + + for name in (k, local_name) if isinstance(k, str) and k != local_name else (local_name,): + fullname = ".".join((c.__fullname__, name)) if c.__fullname__ else name + if fullname not in functions or Path(code.co_filename).resolve() == path: + # Give precedence to code objects from the module and + # try to retrieve any potentially decorated function so + # that we don't end up returning the decorator function + # instead of the original function. + functions[fullname] = undecorated(o, name, path) if name == k else o + + try: + if o.__closure__: + containers.append(ContainerIterator(o.__closure__, origin=(o, ""))) + except AttributeError: + pass + + elif _isinstance(o, CONTAINER_TYPES): + if _isinstance(o, property) and not isinstance(o.fget, FunctionType): + continue + containers.append(ContainerIterator(o, origin=(c, k))) + + return functions + + +class FunctionDiscovery(defaultdict): + """Discover all function objects in a module. + + The discovered functions can be retrieved by line number or by their + qualified name. In principle one wants to create a function discovery + object per module and then cache the information. For this reason, + instances of this class should be obtained with the ``from_module`` class + method. This builds the discovery object and caches the information on the + module object itself. + """ + + def __init__(self, module: ModuleType) -> None: + super().__init__(list) + self._module = module + self._fullname_index = {} + + functions = _collect_functions(module) + seen_functions = set() + module_path = origin(module) + if module_path is None: + # We are not going to collect anything because no code objects will + # match the origin. + return + + for fname, function in functions.items(): + if ( + function not in seen_functions + and Path(cast(FunctionType, function).__code__.co_filename).resolve() == module_path + ): + # We only map line numbers for functions that actually belong to + # the module. + for lineno in linenos(cast(FunctionType, function)): + self[lineno].append(function) + self._fullname_index[fname] = function + seen_functions.add(function) + + def at_line(self, line: int) -> List[FullyNamedFunction]: + """Get the functions at the given line. + + Note that, in general, there can be multiple copies of the same + functions. This can happen as a result, e.g., of using decorators. + """ + return self[line] + + def by_name(self, qualname: str) -> FullyNamedFunction: + """Get the function by its qualified name.""" + fullname = ".".join((self._module.__name__, qualname)) + try: + return self._fullname_index[fullname] + except KeyError: + raise ValueError("Function '%s' not found" % fullname) + + @classmethod + def from_module(cls, module: ModuleType) -> "FunctionDiscovery": + """Return a function discovery object from the given module. + + If this is called on a module for the first time, it caches the + information on the module object itself. Subsequent calls will + return the cached information. + """ + # Cache the function tree on the module + try: + return module.__function_discovery__ + except AttributeError: + fd = module.__function_discovery__ = cls(module) # type: ignore[attr-defined] + return fd diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_function/store.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_function/store.py new file mode 100644 index 0000000..bdc8988 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_function/store.py @@ -0,0 +1,111 @@ +from types import CodeType +from types import FunctionType +from typing import Any +from typing import Callable +from typing import Dict +from typing import List +from typing import Optional +from typing import Set +from typing import cast + +from ddtrace.debugging._function.discovery import FullyNamed +from ddtrace.internal.injection import HookInfoType +from ddtrace.internal.injection import HookType +from ddtrace.internal.injection import eject_hooks +from ddtrace.internal.injection import inject_hooks +from ddtrace.internal.wrapping import WrappedFunction +from ddtrace.internal.wrapping import Wrapper +from ddtrace.internal.wrapping import unwrap +from ddtrace.internal.wrapping import wrap + + +WrapperType = Callable[[FunctionType, Any, Any, Any], Any] + + +class FullyNamedWrappedFunction(FullyNamed, WrappedFunction): + """A fully named wrapper function.""" + + +class FunctionStore(object): + """Function object store. + + This class provides a storage layer for patching operations, which allows us + to store the original code object of functions being patched with either + hook injections or wrapping. This also enforce a single wrapping layer. + Multiple wrapping is implemented as a list of wrappers handled by the single + wrapper function. + + If extra attributes are defined during the patching process, they will get + removed when the functions are restored. + """ + + def __init__(self, extra_attrs: Optional[List[str]] = None) -> None: + self._code_map: Dict[FunctionType, CodeType] = {} + self._wrapper_map: Dict[FunctionType, Wrapper] = {} + self._extra_attrs = ["__dd_wrapped__"] + if extra_attrs: + self._extra_attrs.extend(extra_attrs) + + def __enter__(self): + return self + + def __exit__(self, *exc): + self.restore_all() + + def _store(self, function: FunctionType) -> None: + if function not in self._code_map: + self._code_map[function] = function.__code__ + + def inject_hooks(self, function: FullyNamedWrappedFunction, hooks: List[HookInfoType]) -> Set[str]: + """Bulk-inject hooks into a function. + + Returns the set of probe IDs for those probes that failed to inject. + """ + try: + return self.inject_hooks(cast(FullyNamedWrappedFunction, function.__dd_wrapped__), hooks) + except AttributeError: + f = cast(FunctionType, function) + self._store(f) + return {p.probe_id for _, _, p in inject_hooks(f, hooks)} + + def eject_hooks(self, function: FunctionType, hooks: List[HookInfoType]) -> Set[str]: + """Bulk-eject hooks from a function. + + Returns the set of probe IDs for those probes that failed to eject. + """ + try: + wrapped = cast(FullyNamedWrappedFunction, function).__dd_wrapped__ + except AttributeError: + # Not a wrapped function so we can actually eject from it + return {p.probe_id for _, _, p in eject_hooks(function, hooks)} + else: + # Try on the wrapped function. + return self.eject_hooks(cast(FunctionType, wrapped), hooks) + + def inject_hook(self, function: FullyNamedWrappedFunction, hook: HookType, line: int, arg: Any) -> bool: + """Inject a hook into a function.""" + return not not self.inject_hooks(function, [(hook, line, arg)]) + + def eject_hook(self, function: FunctionType, hook: HookType, line: int, arg: Any) -> bool: + """Eject a hook from a function.""" + return not not self.eject_hooks(function, [(hook, line, arg)]) + + def wrap(self, function: FunctionType, wrapper: Wrapper) -> None: + """Wrap a function with a hook.""" + self._store(function) + self._wrapper_map[function] = wrapper + wrap(function, wrapper) + + def unwrap(self, function: FullyNamedWrappedFunction) -> None: + """Unwrap a hook around a wrapped function.""" + unwrap(function, self._wrapper_map.pop(cast(FunctionType, function))) + + def restore_all(self) -> None: + """Restore all the patched functions to their original form.""" + for function, code in self._code_map.items(): + function.__code__ = code + for attr in self._extra_attrs: + try: + delattr(function, attr) + except AttributeError: + pass diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_metrics.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_metrics.py new file mode 100644 index 0000000..fe731b5 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_metrics.py @@ -0,0 +1,9 @@ +from ddtrace.internal.metrics import Metrics + + +# Debugger metrics +metrics = Metrics(namespace="debugger") + +# Metric probe metrics (always enabled) +probe_metrics = Metrics(namespace="debugger.metric") +probe_metrics.enable() diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_probe/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_probe/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_probe/model.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_probe/model.py new file mode 100644 index 0000000..d516692 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_probe/model.py @@ -0,0 +1,290 @@ +import abc +from enum import Enum +from pathlib import Path +from typing import Any +from typing import Callable +from typing import Dict +from typing import List +from typing import Optional +from typing import Tuple +from typing import Union + +import attr + +from ddtrace.debugging._expressions import DDExpression +from ddtrace.internal.logger import get_logger +from ddtrace.internal.module import _resolve +from ddtrace.internal.rate_limiter import BudgetRateLimiterWithJitter as RateLimiter +from ddtrace.internal.safety import _isinstance +from ddtrace.internal.utils.cache import cached + + +log = get_logger(__name__) + +DEFAULT_PROBE_RATE = 5000.0 +DEFAULT_SNAPSHOT_PROBE_RATE = 1.0 +DEFAULT_PROBE_CONDITION_ERROR_RATE = 1.0 / 60 / 5 + + +@cached() +def _resolve_source_file(_path: str) -> Optional[Path]: + """Resolve the source path for the given path. + + This recursively strips parent directories until it finds a file that + exists according to sys.path. + """ + path = Path(_path) + if path.is_file(): + return path.resolve() + + for relpath in (path.relative_to(_) for _ in path.parents): + resolved_path = _resolve(relpath) + if resolved_path is not None: + return resolved_path + + return None + + +MAXLEVEL = 2 +MAXSIZE = 100 +MAXLEN = 255 +MAXFIELDS = 20 + + +@attr.s +class CaptureLimits(object): + max_level = attr.ib(type=int, default=MAXLEVEL) + max_size = attr.ib(type=int, default=MAXSIZE) + max_len = attr.ib(type=int, default=MAXLEN) + max_fields = attr.ib(type=int, default=MAXFIELDS) + + +DEFAULT_CAPTURE_LIMITS = CaptureLimits() + + +@attr.s +class Probe(abc.ABC): + __context_creator__ = False + + probe_id = attr.ib(type=str) + version = attr.ib(type=int) + tags = attr.ib(type=dict, eq=False) + + def update(self, other: "Probe") -> None: + """Update the mutable fields from another probe.""" + if self.probe_id != other.probe_id: + log.error("Probe ID mismatch when updating mutable fields") + return + + if self.version == other.version: + return + + for attrib in (_.name for _ in self.__attrs_attrs__ if _.eq): + setattr(self, attrib, getattr(other, attrib)) + + def __hash__(self): + return hash(self.probe_id) + + +@attr.s +class RateLimitMixin(abc.ABC): + rate = attr.ib(type=float, eq=False) + limiter = attr.ib(type=RateLimiter, init=False, repr=False, eq=False) + + @limiter.default + def _(self): + return RateLimiter( + limit_rate=self.rate, + tau=1.0 / self.rate if self.rate else 1.0, + on_exceed=lambda: log.warning("Rate limit exceeeded for %r", self), + call_once=True, + raise_on_exceed=False, + ) + + +@attr.s +class ProbeConditionMixin(object): + """Conditional probe. + + If the condition is ``None``, then this is equivalent to a non-conditional + probe. + """ + + condition = attr.ib(type=Optional[DDExpression]) + condition_error_rate = attr.ib(type=float, eq=False) + condition_error_limiter = attr.ib(type=RateLimiter, init=False, repr=False, eq=False) + + @condition_error_limiter.default + def _(self): + return RateLimiter( + limit_rate=self.condition_error_rate, + tau=1.0 / self.condition_error_rate if self.condition_error_rate else 1.0, + on_exceed=lambda: log.debug("Condition error rate limit exceeeded for %r", self), + call_once=True, + raise_on_exceed=False, + ) + + +@attr.s +class ProbeLocationMixin(object): + def location(self) -> Tuple[Optional[str], Optional[Union[str, int]]]: + """return a tuple of (location,sublocation) for the probe. + For example, line probe returns the (file,line) and method probe return (module,method) + """ + return (None, None) + + +@attr.s +class LineLocationMixin(ProbeLocationMixin): + source_file = attr.ib(type=Path, converter=_resolve_source_file, eq=False) # type: ignore[misc] + line = attr.ib(type=int, eq=False) + + def location(self): + return (str(self.source_file) if self.source_file is not None else None, self.line) + + +class ProbeEvaluateTimingForMethod(str, Enum): + DEFAULT = "DEFAULT" + ENTER = "ENTER" + EXIT = "EXIT" + + +@attr.s +class FunctionLocationMixin(ProbeLocationMixin): + module = attr.ib(type=str, eq=False) + func_qname = attr.ib(type=str, eq=False) + evaluate_at = attr.ib(type=ProbeEvaluateTimingForMethod) + + def location(self): + return (self.module, self.func_qname) + + +class MetricProbeKind(str, Enum): + COUNTER = "COUNT" + GAUGE = "GAUGE" + HISTOGRAM = "HISTOGRAM" + DISTRIBUTION = "DISTRIBUTION" + + +@attr.s +class MetricProbeMixin(object): + kind = attr.ib(type=str) + name = attr.ib(type=str) + value = attr.ib(type=Optional[DDExpression]) + + +@attr.s +class MetricLineProbe(Probe, LineLocationMixin, MetricProbeMixin, ProbeConditionMixin): + pass + + +@attr.s +class MetricFunctionProbe(Probe, FunctionLocationMixin, MetricProbeMixin, ProbeConditionMixin): + pass + + +@attr.s +class TemplateSegment(abc.ABC): + @abc.abstractmethod + def eval(self, _locals: Dict[str, Any]) -> str: + pass + + +@attr.s +class LiteralTemplateSegment(TemplateSegment): + str_value = attr.ib(type=str, default=None) + + def eval(self, _locals: Dict[str, Any]) -> Any: + return self.str_value + + +@attr.s +class ExpressionTemplateSegment(TemplateSegment): + expr = attr.ib(type=DDExpression, default=None) + + def eval(self, _locals: Dict[str, Any]) -> Any: + return self.expr.eval(_locals) + + +@attr.s +class StringTemplate(object): + template = attr.ib(type=str) + segments = attr.ib(type=List[TemplateSegment]) + + def render(self, _locals: Dict[str, Any], serializer: Callable[[Any], str]) -> str: + def _to_str(value): + return value if _isinstance(value, str) else serializer(value) + + return "".join([_to_str(s.eval(_locals)) for s in self.segments]) + + +@attr.s +class LogProbeMixin(object): + template = attr.ib(type=str) + segments = attr.ib(type=List[TemplateSegment]) + take_snapshot = attr.ib(type=bool) + limits = attr.ib(type=CaptureLimits, eq=False) + + +@attr.s +class LogLineProbe(Probe, LineLocationMixin, LogProbeMixin, ProbeConditionMixin, RateLimitMixin): + pass + + +@attr.s +class LogFunctionProbe(Probe, FunctionLocationMixin, LogProbeMixin, ProbeConditionMixin, RateLimitMixin): + pass + + +@attr.s +class SpanProbeMixin(object): + pass + + +@attr.s +class SpanFunctionProbe(Probe, FunctionLocationMixin, SpanProbeMixin, ProbeConditionMixin): + __context_creator__ = True + + +class SpanDecorationTargetSpan(object): + ROOT = "ROOT" + ACTIVE = "ACTIVE" + + +@attr.s +class SpanDecorationTag(object): + name = attr.ib(type=str) + value = attr.ib(type=StringTemplate) + + +@attr.s +class SpanDecoration(object): + when = attr.ib(type=Optional[DDExpression]) + tags = attr.ib(type=List[SpanDecorationTag]) + + +@attr.s +class SpanDecorationMixin(object): + target_span = attr.ib(type=SpanDecorationTargetSpan) + decorations = attr.ib(type=List[SpanDecoration]) + + +@attr.s +class SpanDecorationLineProbe(Probe, LineLocationMixin, SpanDecorationMixin): + pass + + +@attr.s +class SpanDecorationFunctionProbe(Probe, FunctionLocationMixin, SpanDecorationMixin): + pass + + +LineProbe = Union[LogLineProbe, MetricLineProbe, SpanDecorationLineProbe] +FunctionProbe = Union[LogFunctionProbe, MetricFunctionProbe, SpanFunctionProbe, SpanDecorationFunctionProbe] + + +class ProbeType(object): + LOG_PROBE = "LOG_PROBE" + METRIC_PROBE = "METRIC_PROBE" + SPAN_PROBE = "SPAN_PROBE" + SPAN_DECORATION_PROBE = "SPAN_DECORATION_PROBE" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_probe/registry.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_probe/registry.py new file mode 100644 index 0000000..32e8f44 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_probe/registry.py @@ -0,0 +1,204 @@ +from collections import defaultdict +from typing import Any +from typing import Dict +from typing import List +from typing import Optional +from typing import cast + +from ddtrace.debugging._probe.model import Probe +from ddtrace.debugging._probe.model import ProbeLocationMixin +from ddtrace.debugging._probe.status import ProbeStatusLogger +from ddtrace.internal import forksafe +from ddtrace.internal.logger import get_logger + + +logger = get_logger(__name__) + + +class ProbeRegistryEntry(object): + __slots__ = ( + "probe", + "installed", + "emitting", + "error_type", + "message", + ) + + def __init__(self, probe: Probe) -> None: + self.probe = probe + self.installed = False + self.emitting = False + self.error_type: Optional[str] = None + self.message: Optional[str] = None + + def set_installed(self) -> None: + self.installed = True + + def set_emitting(self) -> None: + self.emitting = True + + def set_error(self, error_type: str, message: str) -> None: + self.error_type = error_type + self.message = message + + def update(self, probe: Probe) -> None: + self.probe.update(probe) + + +def _get_probe_location(probe: Probe) -> Optional[str]: + if isinstance(probe, ProbeLocationMixin): + return probe.location()[0] + else: + raise ValueError("Unsupported probe type: {}".format(type(probe))) + + +class ProbeRegistry(dict): + """Keep track of all the registered probes. + + New probes are also registered as pending, on a location basis, until they + are processed (e.g. installed, generally by some import hook). Pending + probes can be retrieved with the ``get_pending`` method. + """ + + def __init__(self, status_logger: ProbeStatusLogger, *args: Any, **kwargs: Any) -> None: + """Initialize the probe registry.""" + super().__init__(*args, **kwargs) + self.logger = status_logger + + # Used to keep track of probes pending installation + self._pending: Dict[str, List[Probe]] = defaultdict(list) + + self._lock = forksafe.RLock() + + def register(self, *probes: Probe) -> None: + """Register a probe.""" + with self._lock: + for probe in probes: + if probe in self: + # Already registered. + continue + + self[probe.probe_id] = ProbeRegistryEntry(probe) + + location = _get_probe_location(probe) + if location is None: + self.set_error( + probe, + "UnresolvedLocation", + "Unable to resolve location information for probe {}".format(probe.probe_id), + ) + continue + + self._pending[location].append(probe) + + self.logger.received(probe) + + def update(self, probe): + with self._lock: + if probe not in self: + logger.error("Attempted to update unregistered probe %s", probe.probe_id) + return + + self[probe.probe_id].update(probe) + + self.log_probe_status(probe) + + def set_installed(self, probe: Probe) -> None: + """Set the installed flag for a probe.""" + with self._lock: + self[probe.probe_id].set_installed() + + # No longer pending + self._remove_pending(probe) + + self.logger.installed(probe) + + def set_emitting(self, probe: Probe) -> None: + """Set the emitting flag for a probe.""" + with self._lock: + entry = cast(ProbeRegistryEntry, self[probe.probe_id]) + if not entry.emitting: + entry.set_emitting() + self.logger.emitting(probe) + + def set_error(self, probe: Probe, error_type: str, message: str) -> None: + """Set the error message for a probe.""" + with self._lock: + self[probe.probe_id].set_error(error_type, message) + self.logger.error(probe, (error_type, message)) + + def _log_probe_status_unlocked(self, entry: ProbeRegistryEntry) -> None: + if entry.emitting: + self.logger.emitting(entry.probe) + elif entry.installed: + self.logger.installed(entry.probe) + elif entry.error_type: + assert entry.message is not None, entry # nosec + self.logger.error(entry.probe, error=(entry.error_type, entry.message)) + else: + self.logger.received(entry.probe) + + def log_probe_status(self, probe: Probe) -> None: + """Log the status of a probe using the status logger.""" + with self._lock: + self._log_probe_status_unlocked(self[probe.probe_id]) + + def log_probes_status(self) -> None: + """Log the status of all the probes using the status logger.""" + with self._lock: + for entry in self.values(): + self._log_probe_status_unlocked(entry) + + def _remove_pending(self, probe: Probe) -> None: + location = _get_probe_location(probe) + + # Pending probes must have valid location information + assert location is not None, probe # nosec + + pending_probes = self._pending[location] + try: + # DEV: Note that this is O(n), which is fine with a conservative + # number of probes. + pending_probes.remove(probe) + except ValueError: + # The probe wasn't pending + pass + if not pending_probes: + del self._pending[location] + + def has_probes(self, location: str) -> bool: + for entry in self.values(): + if _get_probe_location(entry.probe) == location: + return True + return False + + def unregister(self, *probes: Probe) -> List[Probe]: + """Unregister a collection of probes. + + This also ensures that any pending probes are removed if they haven't + been processed yet. + """ + unregistered_probes = [] + with self._lock: + for probe in probes: + try: + entry = self.pop(probe.probe_id) + except KeyError: + # We don't seem to have the probe + logger.warning("Tried to unregister unregistered probe %s", probe.probe_id) + else: + probe = entry.probe + self._remove_pending(probe) + unregistered_probes.append(probe) + return unregistered_probes + + def get_pending(self, location: str) -> List[Probe]: + """Get the currently pending probes by location.""" + return self._pending[location] + + def __contains__(self, probe: object) -> bool: + """Check if a probe is in the registry.""" + assert isinstance(probe, Probe), probe # nosec + + with self._lock: + return super().__contains__(probe.probe_id) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_probe/remoteconfig.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_probe/remoteconfig.py new file mode 100644 index 0000000..392cd6d --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_probe/remoteconfig.py @@ -0,0 +1,341 @@ +from itertools import count +import os +import time +from typing import Any +from typing import Callable +from typing import Dict +from typing import Iterable +from typing import Optional +from typing import Type + +from ddtrace import config as tracer_config +from ddtrace.debugging._config import di_config +from ddtrace.debugging._probe.model import DEFAULT_PROBE_CONDITION_ERROR_RATE +from ddtrace.debugging._probe.model import DEFAULT_PROBE_RATE +from ddtrace.debugging._probe.model import DEFAULT_SNAPSHOT_PROBE_RATE +from ddtrace.debugging._probe.model import CaptureLimits +from ddtrace.debugging._probe.model import ExpressionTemplateSegment +from ddtrace.debugging._probe.model import FunctionProbe +from ddtrace.debugging._probe.model import LineProbe +from ddtrace.debugging._probe.model import LiteralTemplateSegment +from ddtrace.debugging._probe.model import LogFunctionProbe +from ddtrace.debugging._probe.model import LogLineProbe +from ddtrace.debugging._probe.model import MetricFunctionProbe +from ddtrace.debugging._probe.model import MetricLineProbe +from ddtrace.debugging._probe.model import Probe +from ddtrace.debugging._probe.model import ProbeType +from ddtrace.debugging._probe.model import SpanDecoration +from ddtrace.debugging._probe.model import SpanDecorationFunctionProbe +from ddtrace.debugging._probe.model import SpanDecorationLineProbe +from ddtrace.debugging._probe.model import SpanDecorationTag +from ddtrace.debugging._probe.model import SpanFunctionProbe +from ddtrace.debugging._probe.model import StringTemplate +from ddtrace.debugging._probe.model import TemplateSegment +from ddtrace.debugging._probe.status import ProbeStatusLogger +from ddtrace.debugging._redaction import DDRedactedExpression +from ddtrace.internal.logger import get_logger +from ddtrace.internal.remoteconfig._connectors import PublisherSubscriberConnector +from ddtrace.internal.remoteconfig._publishers import RemoteConfigPublisher +from ddtrace.internal.remoteconfig._pubsub import PubSub +from ddtrace.internal.remoteconfig._subscribers import RemoteConfigSubscriber + + +log = get_logger(__name__) + + +def xlate_keys(d: Dict[str, Any], mapping: Dict[str, str]) -> Dict[str, Any]: + return {mapping.get(k, k): v for k, v in d.items()} + + +def _compile_segment(segment: dict) -> Optional[TemplateSegment]: + if "str" in segment: + return LiteralTemplateSegment(str_value=segment["str"]) + + if "json" in segment: + return ExpressionTemplateSegment(expr=DDRedactedExpression.compile(segment)) + + # what type of error we should show here? + return None + + +def _match_env_and_version(probe: Probe) -> bool: + probe_version = probe.tags.get("version", None) + probe_env = probe.tags.get("env", None) + + return (probe_version is None or probe_version == tracer_config.version) and ( + probe_env is None or probe_env == tracer_config.env + ) + + +def _filter_by_env_and_version(f: Callable[..., Iterable[Probe]]) -> Callable[..., Iterable[Probe]]: + def _wrapper(*args: Any, **kwargs: Any) -> Iterable[Probe]: + return [_ for _ in f(*args, **kwargs) if _match_env_and_version(_)] + + return _wrapper + + +class ProbeFactory(object): + __line_class__: Optional[Type[LineProbe]] = None + __function_class__: Optional[Type[FunctionProbe]] = None + + @classmethod + def update_args(cls, args, attribs): + raise NotImplementedError() + + @classmethod + def build(cls, args: Dict[str, Any], attribs: Dict[str, Any]) -> Any: + cls.update_args(args, attribs) + + where = attribs["where"] + if where.get("sourceFile", None) is not None: + if cls.__line_class__ is None: + raise TypeError("Line probe type is not supported") + + args["source_file"] = where["sourceFile"] + args["line"] = int(where["lines"][0]) + + return cls.__line_class__(**args) + + if cls.__function_class__ is None: + raise TypeError("Function probe type is not supported") + + args["module"] = where.get("type") or where["typeName"] + args["func_qname"] = where.get("method") or where["methodName"] + args["evaluate_at"] = attribs.get("evaluateAt") + + return cls.__function_class__(**args) + + +class LogProbeFactory(ProbeFactory): + __line_class__ = LogLineProbe + __function_class__ = LogFunctionProbe + + @classmethod + def update_args(cls, args, attribs): + take_snapshot = attribs.get("captureSnapshot", False) + + rate = DEFAULT_SNAPSHOT_PROBE_RATE if take_snapshot else DEFAULT_PROBE_RATE + sampling = attribs.get("sampling") + if sampling is not None: + rate = sampling.get("snapshotsPerSecond", rate) + + args.update( + condition=DDRedactedExpression.compile(attribs["when"]) if "when" in attribs else None, + rate=rate, + limits=CaptureLimits( + **xlate_keys( + attribs["capture"], + { + "maxReferenceDepth": "max_level", + "maxCollectionSize": "max_size", + "maxLength": "max_len", + "maxFieldCount": "max_fields", + }, + ) + ) + if "capture" in attribs + else None, + condition_error_rate=DEFAULT_PROBE_CONDITION_ERROR_RATE, # TODO: should we take rate limit out of Probe? + take_snapshot=take_snapshot, + template=attribs.get("template"), + segments=[_compile_segment(segment) for segment in attribs.get("segments", [])], + ) + + +class MetricProbeFactory(ProbeFactory): + __line_class__ = MetricLineProbe + __function_class__ = MetricFunctionProbe + + @classmethod + def update_args(cls, args, attribs): + # adding probe_id to probe-tags so it would be recorded as a metric tag + args["tags"]["debugger.probeid"] = args["probe_id"] + + args.update( + condition=DDRedactedExpression.compile(attribs["when"]) if "when" in attribs else None, + name=attribs["metricName"], + kind=attribs["kind"], + condition_error_rate=DEFAULT_PROBE_CONDITION_ERROR_RATE, # TODO: should we take rate limit out of Probe? + value=DDRedactedExpression.compile(attribs["value"]) if "value" in attribs else None, + ) + + +class SpanProbeFactory(ProbeFactory): + __function_class__ = SpanFunctionProbe + + @classmethod + def update_args(cls, args, attribs): + args.update( + condition=DDRedactedExpression.compile(attribs["when"]) if "when" in attribs else None, + condition_error_rate=DEFAULT_PROBE_CONDITION_ERROR_RATE, # TODO: should we take rate limit out of Probe? + ) + + +class SpanDecorationProbeFactory(ProbeFactory): + __line_class__ = SpanDecorationLineProbe + __function_class__ = SpanDecorationFunctionProbe + + @classmethod + def update_args(cls, args, attribs): + args.update( + target_span=attribs["targetSpan"], + decorations=[ + SpanDecoration( + when=DDRedactedExpression.compile(d["when"]) if "when" in d else None, + tags=[ + SpanDecorationTag( + name=t["name"], + value=StringTemplate( + template=t["value"].get("template"), + segments=[_compile_segment(segment) for segment in t["value"].get("segments", [])], + ), + ) + for t in d.get("tags", []) + ], + ) + for d in attribs["decorations"] + ], + ) + + +class InvalidProbeConfiguration(ValueError): + pass + + +def build_probe(attribs: Dict[str, Any]) -> Probe: + """ + Create a new Probe instance. + """ + try: + _type = attribs["type"] + _id = attribs["id"] + except KeyError as e: + raise InvalidProbeConfiguration("Invalid probe attributes: %s" % e) + + args = dict( + probe_id=_id, + version=attribs.get("version", 0), + tags=dict(_.split(":", 1) for _ in attribs.get("tags", [])), + ) + + if _type == ProbeType.LOG_PROBE: + return LogProbeFactory.build(args, attribs) + if _type == ProbeType.METRIC_PROBE: + return MetricProbeFactory.build(args, attribs) + if _type == ProbeType.SPAN_PROBE: + return SpanProbeFactory.build(args, attribs) + if _type == ProbeType.SPAN_DECORATION_PROBE: + return SpanDecorationProbeFactory.build(args, attribs) + + raise InvalidProbeConfiguration("Unsupported probe type: %s" % _type) + + +@_filter_by_env_and_version +def get_probes(config: dict, status_logger: ProbeStatusLogger) -> Iterable[Probe]: + try: + return [build_probe(config)] + except InvalidProbeConfiguration: + raise + except Exception as e: + status_logger.error( + probe=Probe(probe_id=config["id"], version=config["version"], tags={}), + error=(type(e).__name__, str(e)), + ) + return [] + + +class ProbePollerEvent(object): + NEW_PROBES = 0 + DELETED_PROBES = 1 + MODIFIED_PROBES = 2 + STATUS_UPDATE = 3 + + +ProbePollerEventType = int + + +class DebuggerRemoteConfigSubscriber(RemoteConfigSubscriber): + """Probe configuration adapter for the RCM client. + + This adapter turns configuration events from the RCM client into probe + events that can be handled easily by the debugger. + """ + + def __init__(self, data_connector, callback, name, status_logger): + super().__init__(data_connector, callback, name) + self._configs: Dict[str, Dict[str, Probe]] = {} + self._status_timestamp_sequence = count( + time.time() + di_config.diagnostics_interval, di_config.diagnostics_interval + ) + self._status_timestamp = next(self._status_timestamp_sequence) + self._status_logger = status_logger + + def _exec_callback(self, data, test_tracer=None): + # Check if it is time to re-emit probe status messages. + # DEV: We use the periodic signal from the remote config client worker + # thread to avoid having to spawn a separate thread for this. + if time.time() > self._status_timestamp: + self._send_status_update() + self._status_timestamp = next(self._status_timestamp_sequence) + + if data: + log.debug("[%s][P: %s] Dynamic Instrumentation Updated", os.getpid(), os.getppid()) + for metadata, config in zip(data["metadata"], data["config"]): + if metadata is None: + log.debug( + "[%s][P: %s] Dynamic Instrumentation: no RCM metadata for configuration; skipping", + os.getpid(), + os.getppid(), + ) + continue + + self._update_probes_for_config(metadata["id"], config) + + # Flush any probe status messages that migh have been generated + self._status_logger.flush() + + def _send_status_update(self): + log.debug( + "[%s][P: %s] Dynamic Instrumentation: emitting probe status log messages", + os.getpid(), + os.getppid(), + ) + + self._callback(ProbePollerEvent.STATUS_UPDATE, []) + + def _dispatch_probe_events(self, prev_probes: Dict[str, Probe], next_probes: Dict[str, Probe]) -> None: + new_probes = [p for _, p in next_probes.items() if _ not in prev_probes] + deleted_probes = [p for _, p in prev_probes.items() if _ not in next_probes] + modified_probes = [p for _, p in next_probes.items() if _ in prev_probes and p != prev_probes[_]] + + if deleted_probes: + self._callback(ProbePollerEvent.DELETED_PROBES, deleted_probes) + if modified_probes: + self._callback(ProbePollerEvent.MODIFIED_PROBES, modified_probes) + if new_probes: + self._callback(ProbePollerEvent.NEW_PROBES, new_probes) + + def _update_probes_for_config(self, config_id: str, config: Any) -> None: + prev_probes: Dict[str, Probe] = self._configs.get(config_id, {}) + next_probes: Dict[str, Probe] = ( + {probe.probe_id: probe for probe in get_probes(config, self._status_logger)} + if config not in (None, False) + else {} + ) + log.debug("[%s][P: %s] Dynamic Instrumentation, dispatch probe events", os.getpid(), os.getppid()) + self._dispatch_probe_events(prev_probes, next_probes) + + if next_probes: + self._configs[config_id] = next_probes + else: + self._configs.pop(config_id, None) + + +class ProbeRCAdapter(PubSub): + __publisher_class__ = RemoteConfigPublisher + __subscriber_class__ = DebuggerRemoteConfigSubscriber + __shared_data__ = PublisherSubscriberConnector() + + def __init__(self, _preprocess_results, callback, status_logger): + self._publisher = self.__publisher_class__(self.__shared_data__, _preprocess_results) + self._subscriber = self.__subscriber_class__(self.__shared_data__, callback, "DEBUGGER", status_logger) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_probe/status.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_probe/status.py new file mode 100644 index 0000000..a12db52 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_probe/status.py @@ -0,0 +1,147 @@ +import json +from queue import SimpleQueue as Queue +import time +import typing as t +from urllib.parse import quote + +from ddtrace.debugging._config import di_config +from ddtrace.debugging._encoding import add_tags +from ddtrace.debugging._metrics import metrics +from ddtrace.debugging._probe.model import Probe +from ddtrace.internal import compat +from ddtrace.internal import runtime +from ddtrace.internal.logger import get_logger +from ddtrace.internal.utils.http import FormData +from ddtrace.internal.utils.http import connector +from ddtrace.internal.utils.http import multipart +from ddtrace.internal.utils.retry import fibonacci_backoff_with_jitter + + +log = get_logger(__name__) +meter = metrics.get_meter("probe.status") + + +ErrorInfo = t.Tuple[str, str] + + +class ProbeStatusLogger: + RETRY_ATTEMPTS = 3 + RETRY_INTERVAL = 1 + ENDPOINT = "/debugger/v1/diagnostics" + + def __init__(self, service: str) -> None: + self._service = service + self._queue: Queue[str] = Queue() + self._connect = connector(di_config._intake_url, timeout=di_config.upload_timeout) + # Make it retryable + self._write_payload_with_backoff = fibonacci_backoff_with_jitter( + initial_wait=0.618 * self.RETRY_INTERVAL / (1.618**self.RETRY_ATTEMPTS) / 2, + attempts=self.RETRY_ATTEMPTS, + )(self._write_payload) + + if di_config._tags_in_qs and di_config.tags: + self.ENDPOINT += f"?ddtags={quote(di_config.tags)}" + + def _payload( + self, probe: Probe, status: str, message: str, timestamp: float, error: t.Optional[ErrorInfo] = None + ) -> str: + payload = { + "service": self._service, + "timestamp": int(timestamp * 1e3), # milliseconds + "message": message, + "ddsource": "dd_debugger", + "debugger": { + "diagnostics": { + "probeId": probe.probe_id, + "probeVersion": probe.version, + "runtimeId": runtime.get_runtime_id(), + "parentId": runtime.get_ancestor_runtime_id(), + "status": status, + } + }, + } + + add_tags(payload) + + if error is not None: + error_type, message = error + payload["debugger"]["diagnostics"]["exception"] = { # type: ignore[index] + "type": error_type, + "message": message, + } + + return json.dumps(payload) + + def _write_payload(self, data: t.Tuple[bytes, dict]) -> None: + body, headers = data + try: + log.debug("Sending probe status payload: %r", body) + with self._connect() as conn: + conn.request( + "POST", + "/debugger/v1/diagnostics", + body, + headers=headers, + ) + resp = compat.get_connection_response(conn) + if not (200 <= resp.status < 300): + log.error("Failed to upload payload: [%d] %r", resp.status, resp.read()) + meter.increment("upload.error", tags={"status": str(resp.status)}) + else: + meter.increment("upload.success") + meter.distribution("upload.size", len(body)) + except Exception: + log.error("Failed to write payload", exc_info=True) + meter.increment("error") + + def _enqueue(self, probe: Probe, status: str, message: str, error: t.Optional[ErrorInfo] = None) -> None: + self._queue.put_nowait(self._payload(probe, status, message, time.time(), error)) + log.debug("Probe status %s for probe %s enqueued", status, probe.probe_id) + + def flush(self) -> None: + if self._queue.empty(): + return + + msgs: t.List[str] = [] + while not self._queue.empty(): + msgs.append(self._queue.get_nowait()) + + try: + self._write_payload_with_backoff( + multipart( + parts=[ + FormData( + name="event", + filename="event.json", + data=f"[{','.join(msgs)}]", + content_type="json", + ) + ] + ) + ) + except Exception: + log.error("Failed to write probe status after retries", exc_info=True) + + def received(self, probe: Probe, message: t.Optional[str] = None) -> None: + self._enqueue( + probe, + "RECEIVED", + message or "Probe %s has been received correctly" % probe.probe_id, + ) + + def installed(self, probe: Probe, message: t.Optional[str] = None) -> None: + self._enqueue( + probe, + "INSTALLED", + message or "Probe %s instrumented correctly" % probe.probe_id, + ) + + def emitting(self, probe: Probe, message: t.Optional[str] = None) -> None: + self._enqueue( + probe, + "EMITTING", + message or "Probe %s is emitting data" % probe.probe_id, + ) + + def error(self, probe: Probe, error: t.Optional[ErrorInfo] = None) -> None: + self._enqueue(probe, "ERROR", "Failed to instrument probe %s" % probe.probe_id, error) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_redaction.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_redaction.py new file mode 100644 index 0000000..f1bb98e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_redaction.py @@ -0,0 +1,167 @@ +from ddtrace.debugging._expressions import DDCompiler +from ddtrace.debugging._expressions import DDExpression +from ddtrace.internal.logger import get_logger +from ddtrace.internal.utils.cache import cached +from ddtrace.settings.dynamic_instrumentation import config +from ddtrace.settings.dynamic_instrumentation import normalize_ident + + +log = get_logger(__name__) + +# The following identifier represent function argument/local variable/object +# attribute names that should be redacted from the payload. +REDACTED_IDENTIFIERS = ( + frozenset( + { + "2fa", + "accesstoken", + "aiohttpsession", + "apikey", + "apisecret", + "apisignature", + "auth", + "authorization", + "authtoken", + "ccnumber", + "certificatepin", + "cipher", + "clientid", + "clientsecret", + "config", + "connectsid", + "cookie", + "credentials", + "creditcard", + "csrf", + "csrftoken", + "cvv", + "databaseurl", + "dburl", + "encryptionkey", + "encryptionkeyid", + "env", + "gpgkey", + "jti", + "jwt", + "licensekey", + "masterkey", + "mysqlpwd", + "nonce", + "oauth", + "oauthtoken", + "otp", + "passhash", + "passwd", + "password", + "passwordb", + "pemfile", + "pgpkey", + "phpsessid", + "pin", + "pincode", + "pkcs8", + "plateno", + "platenum", + "platenumber", + "privatekey", + "publickey", + "pwd", + "recaptchakey", + "refreshtoken", + "routingnumber", + "salt", + "secret", + "secretkey", + "secrettoken", + "securityanswer", + "securitycode", + "securityquestion", + "serviceaccountcredentials", + "session", + "sessionid", + "sessionkey", + "setcookie", + "signature", + "signaturekey", + "sshkey", + "ssn", + "symfony", + "token", + "transactionid", + "twiliotoken", + "usersession", + "voterid", + "xapikey", + "xauthtoken", + "xcsrftoken", + "xforwardedfor", + "xrealip", + "xsrftoken", + } + ) + | config.redacted_identifiers +) + + +REDACTED_PLACEHOLDER = r"{redacted}" + + +@cached() +def redact(ident: str) -> bool: + return normalize_ident(ident) in REDACTED_IDENTIFIERS + + +@cached() +def redact_type(_type: str) -> bool: + _re = config.redacted_types_re + if _re is None: + return False + return _re.search(_type) is not None + + +class DDRedactedExpressionError(Exception): + pass + + +class DDRedactedCompiler(DDCompiler): + @classmethod + def __getmember__(cls, s, a): + if redact(a): + raise DDRedactedExpressionError(f"Access to attribute {a!r} is not allowed") + + return super().__getmember__(s, a) + + @classmethod + def __index__(cls, o, i): + if isinstance(i, str) and redact(i): + raise DDRedactedExpressionError(f"Access to entry {i!r} is not allowed") + + return super().__index__(o, i) + + @classmethod + def __ref__(cls, s): + if redact(s): + raise DDRedactedExpressionError(f"Access to local {s!r} is not allowed") + + return s + + +dd_compile_redacted = DDRedactedCompiler().compile + + +def _redacted_expr(exc): + def _(_): + raise exc + + return _ + + +class DDRedactedExpression(DDExpression): + __compiler__ = dd_compile_redacted + + @classmethod + def on_compiler_error(cls, dsl, exc): + if isinstance(exc, DDRedactedExpressionError): + log.error("Cannot compile expression that references potential PII: %s", dsl, exc_info=True) + return _redacted_expr(exc) + return super().on_compiler_error(dsl, exc) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_safety.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_safety.py new file mode 100644 index 0000000..50142fc --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_safety.py @@ -0,0 +1,73 @@ +from inspect import CO_VARARGS +from inspect import CO_VARKEYWORDS +from types import FrameType +from typing import Any +from typing import Dict +from typing import Iterator +from typing import Tuple + +from ddtrace.internal.safety import get_slots + + +GetSetDescriptor = type(type.__dict__["__dict__"]) # type: ignore[index] # noqa: F821 + + +def get_args(frame: FrameType) -> Iterator[Tuple[str, Any]]: + code = frame.f_code + nargs = code.co_argcount + bool(code.co_flags & CO_VARARGS) + bool(code.co_flags & CO_VARKEYWORDS) + arg_names = code.co_varnames[:nargs] + arg_values = (frame.f_locals[name] for name in arg_names) + + return zip(arg_names, arg_values) + + +def get_locals(frame: FrameType) -> Iterator[Tuple[str, Any]]: + code = frame.f_code + nargs = code.co_argcount + bool(code.co_flags & CO_VARARGS) + bool(code.co_flags & CO_VARKEYWORDS) + names = code.co_varnames[nargs:] + values = (frame.f_locals.get(name) for name in names) + + return zip(names, values) + + +def get_globals(frame: FrameType) -> Iterator[Tuple[str, Any]]: + nonlocal_names = frame.f_code.co_names + _globals = globals() + + return ((name, _globals[name]) for name in nonlocal_names if name in _globals) + + +def safe_getattr(obj: Any, name: str) -> Any: + try: + return object.__getattribute__(obj, name) + except Exception as e: + return e + + +def safe_getitem(obj, index): + if isinstance(obj, list): + return list.__getitem__(obj, index) + elif isinstance(obj, dict): + return dict.__getitem__(obj, index) + elif isinstance(obj, tuple): + return tuple.__getitem__(obj, index) + raise TypeError("Type is not indexable collection " + str(type(obj))) + + +def _safe_dict(o: Any) -> Dict[str, Any]: + try: + __dict__ = object.__getattribute__(o, "__dict__") + if type(__dict__) is dict: + return __dict__ + except Exception: + pass # nosec + + raise AttributeError("No safe __dict__") + + +def get_fields(obj: Any) -> Dict[str, Any]: + try: + return _safe_dict(obj) + except AttributeError: + # Check for slots + return {s: safe_getattr(obj, s) for s in get_slots(obj)} diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/collector.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/collector.py new file mode 100644 index 0000000..4c5debc --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/collector.py @@ -0,0 +1,117 @@ +import os +from typing import Any +from typing import Callable +from typing import List +from typing import Optional +from typing import Tuple + +from ddtrace.debugging._encoding import BufferedEncoder +from ddtrace.debugging._metrics import metrics +from ddtrace.debugging._signal.model import LogSignal +from ddtrace.debugging._signal.model import Signal +from ddtrace.debugging._signal.model import SignalState +from ddtrace.internal._encoding import BufferFull +from ddtrace.internal.compat import ExcInfoType +from ddtrace.internal.logger import get_logger + + +CaptorType = Callable[[List[Tuple[str, Any]], List[Tuple[str, Any]], ExcInfoType, int], Any] + +log = get_logger(__name__) +meter = metrics.get_meter("signal.collector") + + +NO_RETURN_VALUE = object() + + +class SignalContext(object): + """Debugger signal context manager. + + This is used to capture data for function invocations. The SignalContext + call ``Signal.enter`` on entry ``Signal.exit`` on function return. + + The handler is triggered just after ``Signal.exit`` returns. + """ + + def __init__( + self, + signal: Signal, + handler: Callable[[Signal], None], + ) -> None: + self._on_exit_handler = handler + self.signal = signal + self.return_value: Any = NO_RETURN_VALUE + self.duration: Optional[int] = None + + self.signal.enter() + + def exit(self, retval: Any, exc_info: ExcInfoType, duration_ns: int) -> None: + """Exit the snapshot context. + + The arguments are used to record either the return value or the exception, and + the duration of the wrapped call. + """ + self.return_value = retval + self.duration = duration_ns + + return self.__exit__(*exc_info) + + def __enter__(self): + return self + + def __exit__(self, *exc_info: ExcInfoType) -> None: + self.signal.exit(self.return_value, exc_info, self.duration) + self._on_exit_handler(self.signal) + + +class SignalCollector(object): + """Debugger signal collector. + + This is used to collect and encode signals emitted by probes as soon as + requested. The ``push`` method is intended to be called after a line-level + signal is fully emitted, and information is available and ready to be + encoded, or the signal status indicate it should be skipped. For function + instrumentation (e.g. function probes), we use the ``attach`` method to + create a ``SignalContext`` instance that can be used to capture additional + data, such as the return value of the wrapped function. + """ + + def __init__(self, encoder: BufferedEncoder) -> None: + self._encoder = encoder + + def _enqueue(self, log_signal: LogSignal) -> None: + try: + log.debug( + "[%s][P: %s] SignalCollector. _encoder (%s) _enqueue signal", os.getpid(), os.getppid(), self._encoder + ) + self._encoder.put(log_signal) + except BufferFull: + log.debug("Encoder buffer full") + meter.increment("encoder.buffer.full") + + def push(self, signal: Signal) -> None: + if signal.state == SignalState.SKIP_COND: + meter.increment("skip", tags={"cause": "cond", "probe_id": signal.probe.probe_id}) + elif signal.state in {SignalState.SKIP_COND_ERROR, SignalState.COND_ERROR}: + meter.increment("skip", tags={"cause": "cond_error", "probe_id": signal.probe.probe_id}) + elif signal.state == SignalState.SKIP_RATE: + meter.increment("skip", tags={"cause": "rate", "probe_id": signal.probe.probe_id}) + elif signal.state == SignalState.DONE: + meter.increment("signal", tags={"probe_id": signal.probe.probe_id}) + + if ( + isinstance(signal, LogSignal) + and signal.state in {SignalState.DONE, SignalState.COND_ERROR} + and signal.has_message() + ): + log.debug("Enqueueing signal %s", signal) + # This signal emits a log message + self._enqueue(signal) + else: + log.debug( + "Skipping signal %s (has message: %s)", signal, isinstance(signal, LogSignal) and signal.has_message() + ) + + def attach(self, signal: Signal) -> SignalContext: + """Collect via a probe signal context manager.""" + return SignalContext(signal, lambda e: self.push(e)) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/metric_sample.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/metric_sample.py new file mode 100644 index 0000000..c096763 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/metric_sample.py @@ -0,0 +1,86 @@ +from typing import Optional +from typing import cast + +import attr + +from ddtrace.debugging._metrics import probe_metrics +from ddtrace.debugging._probe.model import MetricFunctionProbe +from ddtrace.debugging._probe.model import MetricProbeKind +from ddtrace.debugging._probe.model import MetricProbeMixin +from ddtrace.debugging._probe.model import ProbeEvaluateTimingForMethod +from ddtrace.debugging._signal.model import LogSignal +from ddtrace.debugging._signal.model import SignalState +from ddtrace.internal.metrics import Metrics + + +@attr.s +class MetricSample(LogSignal): + """wrapper for making a metric sample""" + + meter = attr.ib(type=Optional[Metrics.Meter], factory=lambda: probe_metrics.get_meter("probe")) + + def enter(self): + if not isinstance(self.probe, MetricFunctionProbe): + return + + probe = self.probe + + if probe.evaluate_at == ProbeEvaluateTimingForMethod.EXIT: + return + + _args = dict(self.args) if self.args else {} + if not self._eval_condition(_args): + return + + self.sample(_args) + self.state = SignalState.DONE + + def exit(self, retval, exc_info, duration): + if not isinstance(self.probe, MetricFunctionProbe): + return + + probe = self.probe + _args = self._enrich_args(retval, exc_info, duration) + + if probe.evaluate_at != ProbeEvaluateTimingForMethod.EXIT: + return + if not self._eval_condition(_args): + return + + self.sample(_args) + self.state = SignalState.DONE + + def line(self): + frame = self.frame + + if not self._eval_condition(frame.f_locals): + return + + self.sample(frame.f_locals) + self.state = SignalState.DONE + + def sample(self, _locals): + probe = cast(MetricProbeMixin, self.probe) + + assert probe.kind is not None and probe.name is not None # nosec + + value = float(probe.value(_locals)) if probe.value is not None else 1 + + # TODO[perf]: We know the tags in advance so we can avoid the + # list comprehension. + if probe.kind == MetricProbeKind.COUNTER: + self.meter.increment(probe.name, value, probe.tags) + elif probe.kind == MetricProbeKind.GAUGE: + self.meter.gauge(probe.name, value, probe.tags) + elif probe.kind == MetricProbeKind.HISTOGRAM: + self.meter.histogram(probe.name, value, probe.tags) + elif probe.kind == MetricProbeKind.DISTRIBUTION: + self.meter.distribution(probe.name, value, probe.tags) + + @property + def message(self): + return ("Evaluation errors for probe id %s" % self.probe.probe_id) if self.errors else None + + def has_message(self): + # type () -> bool + return bool(self.errors) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/model.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/model.py new file mode 100644 index 0000000..bea38fa --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/model.py @@ -0,0 +1,168 @@ +import abc +from enum import Enum +from threading import Thread +import time +from types import FrameType +from typing import Any +from typing import Dict +from typing import List +from typing import Optional +from typing import Tuple +from typing import Union +from typing import cast +from uuid import uuid4 + +import attr + +from ddtrace.context import Context +from ddtrace.debugging import _safety +from ddtrace.debugging._expressions import DDExpressionEvaluationError +from ddtrace.debugging._probe.model import FunctionLocationMixin +from ddtrace.debugging._probe.model import LineLocationMixin +from ddtrace.debugging._probe.model import Probe +from ddtrace.debugging._probe.model import ProbeConditionMixin +from ddtrace.internal.rate_limiter import RateLimitExceeded +from ddtrace.span import Span + + +@attr.s +class EvaluationError(object): + expr = attr.ib(type=str) + message = attr.ib(type=str) + + +class SignalState(str, Enum): + NONE = "NONE" + SKIP_COND = "SKIP_COND" + SKIP_COND_ERROR = "SKIP_COND_ERROR" + SKIP_RATE = "SKIP_RATE" + COND_ERROR = "COND_ERROR" + DONE = "DONE" + + +@attr.s +class Signal(abc.ABC): + """Debugger signal base class. + + Used to model the data carried by the signal emitted by a probe when it is + triggered. + """ + + probe = attr.ib(type=Probe) + frame = attr.ib(type=FrameType) + thread = attr.ib(type=Thread) + + trace_context = attr.ib(type=Optional[Union[Span, Context]], default=None) + args = attr.ib(type=Optional[List[Tuple[str, Any]]], default=None) + state = attr.ib(type=str, default=SignalState.NONE) + errors = attr.ib(type=List[EvaluationError], factory=lambda: list()) + timestamp = attr.ib(type=float, factory=time.time) + uuid = attr.ib(type=str, init=False, factory=lambda: str(uuid4())) + + def _eval_condition(self, _locals: Optional[Dict[str, Any]] = None) -> bool: + """Evaluate the probe condition against the collected frame.""" + probe = cast(ProbeConditionMixin, self.probe) + condition = probe.condition + if condition is None: + return True + + try: + if bool(condition.eval(_locals or self.frame.f_locals)): + return True + except DDExpressionEvaluationError as e: + self.errors.append(EvaluationError(expr=e.dsl, message=e.error)) + self.state = ( + SignalState.SKIP_COND_ERROR + if probe.condition_error_limiter.limit() is RateLimitExceeded + else SignalState.COND_ERROR + ) + else: + self.state = SignalState.SKIP_COND + + return False + + def _enrich_args(self, retval, exc_info, duration): + _locals = list(self.args or _safety.get_args(self.frame)) + _locals.append(("@duration", duration / 1e6)) # milliseconds + + exc = exc_info[1] + _locals.append(("@return", retval) if exc is None else ("@exception", exc)) + + return dict(_locals) + + @abc.abstractmethod + def enter(self): + pass + + @abc.abstractmethod + def exit(self, retval, exc_info, duration): + pass + + @abc.abstractmethod + def line(self): + pass + + +@attr.s +class LogSignal(Signal): + """A signal that also emits a log message. + + Some signals might require sending a log message along with the base signal + data. For example, all the collected errors from expression evaluations + (e.g. conditions) might need to be reported. + """ + + @property + @abc.abstractmethod + def message(self): + # type () -> Optional[str] + """The log message to emit.""" + pass + + @abc.abstractmethod + def has_message(self): + # type () -> bool + """Whether the signal has a log message to emit.""" + pass + + @property + def data(self): + # type () -> Dict[str, Any] + """Extra data to include in the snapshot portion of the log message.""" + return {} + + def _probe_details(self): + # type () -> Dict[str, Any] + probe = self.probe + if isinstance(probe, LineLocationMixin): + location = { + "file": str(probe.source_file), + "lines": [probe.line], + } + elif isinstance(probe, FunctionLocationMixin): + location = { + "type": probe.module, + "method": probe.func_qname, + } + else: + return {} + + return { + "id": probe.probe_id, + "version": probe.version, + "location": location, + } + + @property + def snapshot(self): + # type () -> Dict[str, Any] + full_data = { + "id": self.uuid, + "timestamp": int(self.timestamp * 1e3), # milliseconds + "evaluationErrors": [{"expr": e.expr, "message": e.message} for e in self.errors], + "probe": self._probe_details(), + "language": "python", + } + full_data.update(self.data) + + return full_data diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/snapshot.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/snapshot.py new file mode 100644 index 0000000..f860376 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/snapshot.py @@ -0,0 +1,249 @@ +import sys +from typing import Any +from typing import Dict +from typing import List +from typing import Optional +from typing import Tuple +from typing import cast + +import attr + +from ddtrace.debugging import _safety +from ddtrace.debugging._expressions import DDExpressionEvaluationError +from ddtrace.debugging._probe.model import DEFAULT_CAPTURE_LIMITS +from ddtrace.debugging._probe.model import CaptureLimits +from ddtrace.debugging._probe.model import FunctionLocationMixin +from ddtrace.debugging._probe.model import LineLocationMixin +from ddtrace.debugging._probe.model import LiteralTemplateSegment +from ddtrace.debugging._probe.model import LogFunctionProbe +from ddtrace.debugging._probe.model import LogLineProbe +from ddtrace.debugging._probe.model import LogProbeMixin +from ddtrace.debugging._probe.model import ProbeEvaluateTimingForMethod +from ddtrace.debugging._probe.model import TemplateSegment +from ddtrace.debugging._redaction import REDACTED_PLACEHOLDER +from ddtrace.debugging._redaction import DDRedactedExpressionError +from ddtrace.debugging._signal import utils +from ddtrace.debugging._signal.model import EvaluationError +from ddtrace.debugging._signal.model import LogSignal +from ddtrace.debugging._signal.model import SignalState +from ddtrace.debugging._signal.utils import serialize +from ddtrace.internal.compat import ExcInfoType +from ddtrace.internal.rate_limiter import RateLimitExceeded +from ddtrace.internal.utils.time import HourGlass + + +CAPTURE_TIME_BUDGET = 0.2 # seconds + + +def _capture_context( + arguments: List[Tuple[str, Any]], + _locals: List[Tuple[str, Any]], + throwable: ExcInfoType, + limits: CaptureLimits = DEFAULT_CAPTURE_LIMITS, +) -> Dict[str, Any]: + with HourGlass(duration=CAPTURE_TIME_BUDGET) as hg: + + def timeout(_): + return not hg.trickling() + + return { + "arguments": utils.capture_pairs( + arguments, limits.max_level, limits.max_len, limits.max_size, limits.max_fields, timeout + ) + if arguments is not None + else {}, + "locals": utils.capture_pairs( + _locals, limits.max_level, limits.max_len, limits.max_size, limits.max_fields, timeout + ) + if _locals is not None + else {}, + "throwable": utils.capture_exc_info(throwable), + } + + +_EMPTY_CAPTURED_CONTEXT = _capture_context([], [], (None, None, None), DEFAULT_CAPTURE_LIMITS) + + +def format_captured_value(value: Any) -> str: + v = value.get("value") + if v is not None: + return v + elif value.get("isNull"): + return "None" + + es = value.get("elements") + if es is not None: + return "%s(%s)" % (value["type"], ", ".join(format_captured_value(e) for e in es)) + + es = value.get("entries") + if es is not None: + return "{%s}" % ", ".join(format_captured_value(k) + ": " + format_captured_value(v) for k, v in es) + + fs = value.get("fields") + if fs is not None: + return "%s(%s)" % (value["type"], ", ".join("%s=%s" % (k, format_captured_value(v)) for k, v in fs.items())) + + return "%s()" % value["type"] + + +def format_message(function: str, args: Dict[str, Any], retval: Optional[Any] = None) -> str: + message = "%s(%s)" % ( + function, + ", ".join(("=".join((n, format_captured_value(a))) for n, a in args.items())), + ) + + if retval is not None: + return "\n".join((message, "=".join(("@return", format_captured_value(retval))))) + + return message + + +@attr.s +class Snapshot(LogSignal): + """Raw snapshot. + + Used to collect the minimum amount of information from a firing probe. + """ + + entry_capture = attr.ib(type=Optional[dict], default=None) + return_capture = attr.ib(type=Optional[dict], default=None) + line_capture = attr.ib(type=Optional[dict], default=None) + + _message = attr.ib(type=Optional[str], default=None) + duration = attr.ib(type=Optional[int], default=None) # nanoseconds + + def _eval_segment(self, segment: TemplateSegment, _locals: Dict[str, Any]) -> str: + probe = cast(LogProbeMixin, self.probe) + capture = probe.limits + try: + if isinstance(segment, LiteralTemplateSegment): + return segment.eval(_locals) + return serialize( + segment.eval(_locals), + level=capture.max_level, + maxsize=capture.max_size, + maxlen=capture.max_len, + maxfields=capture.max_fields, + ) + except DDExpressionEvaluationError as e: + self.errors.append(EvaluationError(expr=e.dsl, message=e.error)) + return REDACTED_PLACEHOLDER if isinstance(e.__cause__, DDRedactedExpressionError) else "ERROR" + + def _eval_message(self, _locals: Dict[str, Any]) -> None: + probe = cast(LogProbeMixin, self.probe) + self._message = "".join([self._eval_segment(s, _locals) for s in probe.segments]) + + def enter(self): + if not isinstance(self.probe, LogFunctionProbe): + return + + probe = self.probe + frame = self.frame + _args = list(self.args or _safety.get_args(frame)) + + if probe.evaluate_at == ProbeEvaluateTimingForMethod.EXIT: + return + + if not self._eval_condition(dict(_args)): + return + + if probe.limiter.limit() is RateLimitExceeded: + self.state = SignalState.SKIP_RATE + return + + if probe.take_snapshot: + self.entry_capture = _capture_context( + _args, + [], + (None, None, None), + limits=probe.limits, + ) + + if probe.evaluate_at == ProbeEvaluateTimingForMethod.ENTER: + self._eval_message(dict(_args)) + self.state = SignalState.DONE + + def exit(self, retval, exc_info, duration): + if not isinstance(self.probe, LogFunctionProbe): + return + + probe = self.probe + _args = self._enrich_args(retval, exc_info, duration) + + if probe.evaluate_at == ProbeEvaluateTimingForMethod.EXIT: + if not self._eval_condition(_args): + return + if probe.limiter.limit() is RateLimitExceeded: + self.state = SignalState.SKIP_RATE + return + elif self.state not in {SignalState.NONE, SignalState.DONE}: + return + + _locals = [] + _, exc, _ = exc_info + if exc is None: + _locals.append(("@return", retval)) + else: + _locals.append(("@exception", exc)) + + if probe.take_snapshot: + self.return_capture = _capture_context( + self.args or _safety.get_args(self.frame), _locals, exc_info, limits=probe.limits + ) + self.duration = duration + self.state = SignalState.DONE + if probe.evaluate_at != ProbeEvaluateTimingForMethod.ENTER: + self._eval_message(dict(_args)) + + def line(self): + if not isinstance(self.probe, LogLineProbe): + return + + frame = self.frame + probe = self.probe + + if not self._eval_condition(frame.f_locals): + return + + if probe.take_snapshot: + if probe.limiter.limit() is RateLimitExceeded: + self.state = SignalState.SKIP_RATE + return + + self.line_capture = _capture_context( + self.args or _safety.get_args(frame), + _safety.get_locals(frame), + sys.exc_info(), + limits=probe.limits, + ) + + self._eval_message(frame.f_locals) + self.state = SignalState.DONE + + @property + def message(self) -> Optional[str]: + return self._message + + def has_message(self) -> bool: + return self._message is not None or bool(self.errors) + + @property + def data(self): + frame = self.frame + probe = self.probe + + captures = None + if isinstance(probe, LogProbeMixin) and probe.take_snapshot: + if isinstance(probe, LineLocationMixin): + captures = {"lines": {probe.line: self.line_capture or _EMPTY_CAPTURED_CONTEXT}} + elif isinstance(probe, FunctionLocationMixin): + captures = { + "entry": self.entry_capture or _EMPTY_CAPTURED_CONTEXT, + "return": self.return_capture or _EMPTY_CAPTURED_CONTEXT, + } + + return { + "stack": utils.capture_stack(frame), + "captures": captures, + "duration": self.duration, + } diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/tracing.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/tracing.py new file mode 100644 index 0000000..22e9cc8 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/tracing.py @@ -0,0 +1,149 @@ +import typing as t + +import attr + +import ddtrace +from ddtrace import Span +from ddtrace.debugging._expressions import DDExpressionEvaluationError +from ddtrace.debugging._probe.model import Probe +from ddtrace.debugging._probe.model import ProbeEvaluateTimingForMethod +from ddtrace.debugging._probe.model import SpanDecorationFunctionProbe +from ddtrace.debugging._probe.model import SpanDecorationLineProbe +from ddtrace.debugging._probe.model import SpanDecorationMixin +from ddtrace.debugging._probe.model import SpanDecorationTargetSpan +from ddtrace.debugging._probe.model import SpanFunctionProbe +from ddtrace.debugging._signal.model import EvaluationError +from ddtrace.debugging._signal.model import LogSignal +from ddtrace.debugging._signal.model import Signal +from ddtrace.debugging._signal.model import SignalState +from ddtrace.debugging._signal.utils import serialize +from ddtrace.internal.compat import ExcInfoType +from ddtrace.internal.logger import get_logger +from ddtrace.internal.safety import _isinstance + + +log = get_logger(__name__) + +SPAN_NAME = "dd.dynamic.span" +PROBE_ID_TAG_NAME = "debugger.probeid" + + +@attr.s +class DynamicSpan(Signal): + """Dynamically created span""" + + _span_cm = attr.ib(type=t.Optional[t.ContextManager[Span]], init=False) + + def __attrs_post_init__(self) -> None: + self._span_cm = None + + def enter(self) -> None: + probe = self.probe + if not isinstance(probe, SpanFunctionProbe): + log.debug("Dynamic span entered with non-span probe: %s", self.probe) + return + + if not self._eval_condition(dict(self.args) if self.args else {}): + return + + self._span_cm = ddtrace.tracer.trace( + SPAN_NAME, + service=None, # Currently unused + resource=probe.func_qname, + span_type=None, # Currently unused + ) + span = self._span_cm.__enter__() + + span.set_tags(probe.tags) + span.set_tag(PROBE_ID_TAG_NAME, probe.probe_id) + + self.state = SignalState.DONE + + def exit(self, retval: t.Any, exc_info: ExcInfoType, duration: float) -> None: + if not isinstance(self.probe, SpanFunctionProbe): + log.debug("Dynamic span exited with non-span probe: %s", self.probe) + return + + if self._span_cm is not None: + # Condition evaluated to true so we created a span. Finish it. + self._span_cm.__exit__(*exc_info) + + def line(self): + raise NotImplementedError("Dynamic line spans are not supported in Python") + + +@attr.s +class SpanDecoration(LogSignal): + """Decorate a span.""" + + def _decorate_span(self, _locals: t.Dict[str, t.Any]) -> None: + probe = t.cast(SpanDecorationMixin, self.probe) + + if probe.target_span == SpanDecorationTargetSpan.ACTIVE: + span = ddtrace.tracer.current_span() + elif probe.target_span == SpanDecorationTargetSpan.ROOT: + span = ddtrace.tracer.current_root_span() + else: + log.error("Invalid target span for span decoration: %s", probe.target_span) + return + + if span is not None: + log.debug("Decorating span %r according to span decoration probe %r", span, probe) + for d in probe.decorations: + try: + if not (d.when is None or d.when(_locals)): + continue + except DDExpressionEvaluationError as e: + self.errors.append( + EvaluationError(expr=e.dsl, message="Failed to evaluate condition: %s" % e.error) + ) + continue + for tag in d.tags: + try: + tag_value = tag.value.render(_locals, serialize) + except DDExpressionEvaluationError as e: + span.set_tag_str( + "_dd.di.%s.evaluation_error" % tag.name, ", ".join([serialize(v) for v in e.args]) + ) + else: + span.set_tag_str(tag.name, tag_value if _isinstance(tag_value, str) else serialize(tag_value)) + span.set_tag_str("_dd.di.%s.probe_id" % tag.name, t.cast(Probe, probe).probe_id) + + def enter(self) -> None: + probe = self.probe + if not isinstance(probe, SpanDecorationFunctionProbe): + log.debug("Span decoration entered with non-span decoration probe: %s", self.probe) + return + + if probe.evaluate_at == ProbeEvaluateTimingForMethod.ENTER: + self._decorate_span(dict(self.args) if self.args else {}) + self.state = SignalState.DONE + + def exit(self, retval: t.Any, exc_info: ExcInfoType, duration: float) -> None: + probe = self.probe + + if not isinstance(probe, SpanDecorationFunctionProbe): + log.debug("Span decoration exited with non-span decoration probe: %s", self.probe) + return + + if probe.evaluate_at == ProbeEvaluateTimingForMethod.EXIT: + self._decorate_span(self._enrich_args(retval, exc_info, duration)) + self.state = SignalState.DONE + + def line(self): + probe = self.probe + if not isinstance(probe, SpanDecorationLineProbe): + log.debug("Span decoration on line with non-span decoration probe: %s", self.probe) + return + + self._decorate_span(self.frame.f_locals) + + self.state = SignalState.DONE + + @property + def message(self): + return ("Condition evaluation errors for probe %s" % self.probe.probe_id) if self.errors else None + + def has_message(self): + # type () -> bool + return bool(self.errors) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/utils.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/utils.py new file mode 100644 index 0000000..8c52cc6 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_signal/utils.py @@ -0,0 +1,312 @@ +from itertools import islice +from itertools import takewhile +from types import FrameType +from typing import Any +from typing import Callable +from typing import Dict +from typing import Iterable +from typing import List +from typing import Optional +from typing import Tuple +from typing import Type + +from ddtrace.debugging._probe.model import MAXFIELDS +from ddtrace.debugging._probe.model import MAXLEN +from ddtrace.debugging._probe.model import MAXLEVEL +from ddtrace.debugging._probe.model import MAXSIZE +from ddtrace.debugging._redaction import REDACTED_PLACEHOLDER +from ddtrace.debugging._redaction import redact +from ddtrace.debugging._redaction import redact_type +from ddtrace.debugging._safety import get_fields +from ddtrace.internal.compat import BUILTIN_CONTAINER_TYPES +from ddtrace.internal.compat import BUILTIN_MAPPNG_TYPES +from ddtrace.internal.compat import BUILTIN_SIMPLE_TYPES +from ddtrace.internal.compat import CALLABLE_TYPES +from ddtrace.internal.compat import Collection +from ddtrace.internal.compat import ExcInfoType +from ddtrace.internal.compat import NoneType +from ddtrace.internal.safety import _isinstance +from ddtrace.internal.utils.cache import cached + + +EXCLUDED_FIELDS = frozenset(["__class__", "__dict__", "__weakref__", "__doc__", "__module__", "__hash__"]) + + +@cached() +def qualname(_type: Type) -> str: + try: + return _type.__qualname__ + except AttributeError: + try: + return _type.__name__ + except AttributeError: + return repr(_type) + + +def _serialize_collection( + value: Collection, brackets: str, level: int, maxsize: int, maxlen: int, maxfields: int +) -> str: + o, c = brackets[0], brackets[1] + ellipsis = ", ..." if len(value) > maxsize else "" + return "".join( + (o, ", ".join(serialize(_, level - 1, maxsize, maxlen, maxfields) for _ in islice(value, maxsize)), ellipsis, c) + ) + + +def serialize( + value: Any, level: int = MAXLEVEL, maxsize: int = MAXSIZE, maxlen: int = MAXLEN, maxfields: int = MAXFIELDS +) -> str: + """Python object serializer. + + We provide our own serializer to avoid any potential side effects of calling + ``str`` directly on arbitrary objects. + """ + + if _isinstance(value, CALLABLE_TYPES): + return object.__repr__(value) + + if type(value) in BUILTIN_SIMPLE_TYPES: + r = repr(value) + return "".join((r[:maxlen], "..." + ("'" if r[0] == "'" else "") if len(r) > maxlen else "")) + + if not level: + return repr(type(value)) + + if type(value) not in BUILTIN_CONTAINER_TYPES: + return "%s(%s)" % ( + type(value).__name__, + ", ".join( + ( + "=".join((k, serialize(v, level - 1, maxsize, maxlen, maxfields))) + for k, v in islice(get_fields(value).items(), maxfields) + if not redact(k) + ) + ), + ) + + if type(value) is dict: + return "{%s}" % ", ".join( + ( + ": ".join( + ( + serialize(_, level - 1, maxsize, maxlen, maxfields) + for _ in (k, v if not (_isinstance(k, str) and redact(k)) else REDACTED_PLACEHOLDER) + ) + ) + for k, v in islice(value.items(), maxsize) + ) + ) + elif type(value) is list: + return _serialize_collection(value, "[]", level, maxsize, maxlen, maxfields) + elif type(value) is tuple: + return _serialize_collection(value, "()", level, maxsize, maxlen, maxfields) + elif type(value) is set: + return _serialize_collection(value, r"{}", level, maxsize, maxlen, maxfields) if value else "set()" + + msg = f"Unhandled type: {type(value)}" + raise TypeError(msg) + + +def capture_stack(top_frame: FrameType, max_height: int = 4096) -> List[dict]: + frame: Optional[FrameType] = top_frame + stack = [] + h = 0 + while frame and h < max_height: + code = frame.f_code + stack.append( + { + "fileName": code.co_filename, + "function": code.co_name, + "lineNumber": frame.f_lineno, + } + ) + frame = frame.f_back + h += 1 + return stack + + +def capture_exc_info(exc_info: ExcInfoType) -> Optional[Dict[str, Any]]: + _type, value, tb = exc_info + if _type is None or value is None: + return None + + top_tb = tb + if top_tb is not None: + while top_tb.tb_next is not None: + top_tb = top_tb.tb_next + + return { + "type": _type.__name__, + "message": ", ".join([serialize(v) for v in value.args]), + "stacktrace": capture_stack(top_tb.tb_frame) if top_tb is not None else None, + } + + +def redacted_value(v: Any) -> dict: + return {"type": qualname(type(v)), "notCapturedReason": "redactedIdent"} + + +def redacted_type(t: Any) -> dict: + return {"type": qualname(t), "notCapturedReason": "redactedType"} + + +def capture_pairs( + pairs: Iterable[Tuple[str, Any]], + level: int = MAXLEVEL, + maxlen: int = MAXLEN, + maxsize: int = MAXSIZE, + maxfields: int = MAXFIELDS, + stopping_cond: Optional[Callable[[Any], bool]] = None, +) -> Dict[str, Any]: + return { + n: (capture_value(v, level, maxlen, maxsize, maxfields, stopping_cond) if not redact(n) else redacted_value(v)) + for n, v in pairs + } + + +def capture_value( + value: Any, + level: int = MAXLEVEL, + maxlen: int = MAXLEN, + maxsize: int = MAXSIZE, + maxfields: int = MAXFIELDS, + stopping_cond: Optional[Callable[[Any], bool]] = None, +) -> Dict[str, Any]: + cond = stopping_cond if stopping_cond is not None else (lambda _: False) + + _type = type(value) + + if _type in BUILTIN_SIMPLE_TYPES: + if _type is NoneType: + return {"type": "NoneType", "isNull": True} + + if cond(value): + return { + "type": qualname(_type), + "notCapturedReason": cond.__name__, + } + + value_repr = serialize(value) + value_repr_len = len(value_repr) + return ( + { + "type": qualname(_type), + "value": value_repr, + } + if value_repr_len <= maxlen + else { + "type": qualname(_type), + "value": value_repr[:maxlen], + "truncated": True, + "size": value_repr_len, + } + ) + + if _type in BUILTIN_CONTAINER_TYPES: + if level < 0: + return { + "type": qualname(_type), + "notCapturedReason": "depth", + "size": len(value), + } + + if cond(value): + return { + "type": qualname(_type), + "notCapturedReason": cond.__name__, + "size": len(value), + } + + collection: Optional[List[Any]] = None + if _type in BUILTIN_MAPPNG_TYPES: + # Mapping + collection = [ + ( + capture_value( + k, + level=level - 1, + maxlen=maxlen, + maxsize=maxsize, + maxfields=maxfields, + stopping_cond=cond, + ), + capture_value( + v, + level=level - 1, + maxlen=maxlen, + maxsize=maxsize, + maxfields=maxfields, + stopping_cond=cond, + ) + if not (_isinstance(k, str) and redact(k)) + else redacted_value(v), + ) + for k, v in takewhile(lambda _: not cond(_), islice(value.items(), maxsize)) + ] + data = { + "type": qualname(_type), + "entries": collection, + "size": len(value), + } + + else: + # Sequence + collection = [ + capture_value( + v, + level=level - 1, + maxlen=maxlen, + maxsize=maxsize, + maxfields=maxfields, + stopping_cond=cond, + ) + for v in takewhile(lambda _: not cond(_), islice(value, maxsize)) + ] + data = { + "type": qualname(_type), + "elements": collection, + "size": len(value), + } + + if len(collection) < min(maxsize, len(value)): + data["notCapturedReason"] = cond.__name__ + elif len(value) > maxsize: + data["notCapturedReason"] = "collectionSize" + + return data + + # Arbitrary object + if level < 0: + return { + "type": qualname(_type), + "notCapturedReason": "depth", + } + + if redact_type(qualname(_type)): + return redacted_type(_type) + + if cond(value): + return { + "type": qualname(_type), + "notCapturedReason": cond.__name__, + } + + fields = get_fields(value) + captured_fields = { + n: ( + capture_value(v, level=level - 1, maxlen=maxlen, maxsize=maxsize, maxfields=maxfields, stopping_cond=cond) + if not redact(n) + else redacted_value(v) + ) + for n, v in takewhile(lambda _: not cond(_), islice(fields.items(), maxfields)) + } + data = { + "type": qualname(_type), + "fields": captured_fields, + } + if len(captured_fields) < min(maxfields, len(fields)): + data["notCapturedReason"] = cond.__name__ + elif len(fields) > maxfields: + data["notCapturedReason"] = "fieldCount" + + return data diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_uploader.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_uploader.py new file mode 100644 index 0000000..d05d9c0 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/debugging/_uploader.py @@ -0,0 +1,97 @@ +from typing import Optional +from urllib.parse import quote + +from ddtrace.debugging._config import di_config +from ddtrace.debugging._encoding import BufferedEncoder +from ddtrace.debugging._metrics import metrics +from ddtrace.internal import compat +from ddtrace.internal.logger import get_logger +from ddtrace.internal.periodic import AwakeablePeriodicService +from ddtrace.internal.runtime import container +from ddtrace.internal.utils.http import connector +from ddtrace.internal.utils.retry import fibonacci_backoff_with_jitter + + +log = get_logger(__name__) +meter = metrics.get_meter("uploader") + + +class LogsIntakeUploaderV1(AwakeablePeriodicService): + """Logs intake uploader. + + This class implements an interface with the debugger logs intake for both + the debugger and the events platform. + """ + + ENDPOINT = di_config._intake_endpoint + + RETRY_ATTEMPTS = 3 + + def __init__(self, queue: BufferedEncoder, interval: Optional[float] = None) -> None: + super().__init__(interval or di_config.upload_flush_interval) + self._queue = queue + self._headers = { + "Content-type": "application/json; charset=utf-8", + "Accept": "text/plain", + } + + container_info = container.get_container_info() + if container_info is not None: + container_id = container_info.container_id + if container_id is not None: + self._headers["Datadog-Container-Id"] = container_id + + if di_config._tags_in_qs and di_config.tags: + self.ENDPOINT += f"?ddtags={quote(di_config.tags)}" + self._connect = connector(di_config._intake_url, timeout=di_config.upload_timeout) + + # Make it retryable + self._write_with_backoff = fibonacci_backoff_with_jitter( + initial_wait=0.618 * self.interval / (1.618**self.RETRY_ATTEMPTS) / 2, + attempts=self.RETRY_ATTEMPTS, + )(self._write) + + log.debug( + "Logs intake uploader initialized (url: %s, endpoint: %s, interval: %f)", + di_config._intake_url, + self.ENDPOINT, + self.interval, + ) + + def _write(self, payload: bytes) -> None: + try: + with self._connect() as conn: + conn.request( + "POST", + self.ENDPOINT, + payload, + headers=self._headers, + ) + resp = compat.get_connection_response(conn) + if not (200 <= resp.status < 300): + log.error("Failed to upload payload: [%d] %r", resp.status, resp.read()) + meter.increment("upload.error", tags={"status": str(resp.status)}) + else: + meter.increment("upload.success") + meter.distribution("upload.size", len(payload)) + except Exception: + log.error("Failed to write payload", exc_info=True) + meter.increment("error") + + def upload(self) -> None: + """Upload request.""" + self.awake() + + def periodic(self) -> None: + """Upload the buffer content to the logs intake.""" + count = self._queue.count + if count: + payload = self._queue.flush() + if payload is not None: + try: + self._write_with_backoff(payload) + meter.distribution("batch.cardinality", count) + except Exception: + log.debug("Cannot upload logs payload", exc_info=True) + + on_shutdown = periodic diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/__init__.py new file mode 100644 index 0000000..3ac50eb --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/__init__.py @@ -0,0 +1,23 @@ +class SpanTypes(object): + CACHE = "cache" + CASSANDRA = "cassandra" + ELASTICSEARCH = "elasticsearch" + GRPC = "grpc" + GRAPHQL = "graphql" + HTTP = "http" + MONGODB = "mongodb" + REDIS = "redis" + SQL = "sql" + TEMPLATE = "template" + TEST = "test" + WEB = "web" + WORKER = "worker" + AUTH = "auth" + SYSTEM = "system" + + +class SpanKind(object): + CLIENT = "client" + SERVER = "server" + PRODUCER = "producer" + CONSUMER = "consumer" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/aws.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/aws.py new file mode 100644 index 0000000..a4aea07 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/aws.py @@ -0,0 +1,88 @@ +from typing import TYPE_CHECKING # noqa:F401 +from typing import Any # noqa:F401 +from typing import Dict # noqa:F401 + + +if TYPE_CHECKING: # pragma: no cover + from ddtrace.span import Span # noqa:F401 + + +def truncate_arg_value(value, max_len=1024): + # type: (Any, int) -> Any + """Truncate values which are bytes and greater than `max_len`. + Useful for parameters like 'Body' in `put_object` operations. + """ + if isinstance(value, bytes) and len(value) > max_len: + return b"..." + + return value + + +def _add_api_param_span_tags(span, endpoint_name, params): + # type: (Span, str, Dict[str, Any]) -> None + # Note: Only some boto3 requests will supply these params + # i.e. that might explain why you see these tags being set to empty strings + if endpoint_name == "cloudwatch": + log_group_name = params.get("logGroupName") + if log_group_name: + span.set_tag_str("aws.cloudwatch.logs.log_group_name", log_group_name) + span.set_tag_str("loggroupname", log_group_name) + elif endpoint_name == "dynamodb": + table_name = params.get("TableName") + if table_name: + span.set_tag_str("aws.dynamodb.table_name", table_name) + span.set_tag_str("tablename", table_name) + elif endpoint_name == "kinesis": + stream_name = params.get("StreamName") + if stream_name: + span.set_tag_str("aws.kinesis.stream_name", stream_name) + span.set_tag_str("streamname", stream_name) + elif endpoint_name == "redshift": + cluster_identifier = params.get("ClusterIdentifier") + if cluster_identifier: + span.set_tag_str("aws.redshift.cluster_identifier", cluster_identifier) + span.set_tag_str("clusteridentifier", cluster_identifier) + elif endpoint_name == "s3": + bucket_name = params.get("Bucket") + if bucket_name: + span.set_tag_str("aws.s3.bucket_name", bucket_name) + span.set_tag_str("bucketname", bucket_name) + + elif endpoint_name == "sns": + topic_arn = params.get("TopicArn") + if topic_arn: + # example topicArn: arn:aws:sns:sa-east-1:1234:topicname + span.set_tag_str("aws.sns.topic_arn", topic_arn) + topicname = topic_arn.split(":")[-1] + aws_account = topic_arn.split(":")[-2] + span.set_tag_str("aws_account", aws_account) + span.set_tag_str("topicname", topicname) + + elif endpoint_name == "sqs": + queue_name = params.get("QueueName", "") + queue_url = params.get("QueueUrl") + if queue_url and (queue_url.startswith("sqs:") or queue_url.startswith("http")): + # example queue_url: https://sqs.sa-east-1.amazonaws.com/12345678/queuename + queue_name = queue_url.split("/")[-1] + aws_account = queue_url.split("/")[-2] + span.set_tag_str("aws_account", aws_account) + span.set_tag_str("aws.sqs.queue_name", queue_name) + span.set_tag_str("queuename", queue_name) + + elif endpoint_name == "lambda": + function_name = params.get("FunctionName", "") + span.set_tag_str("functionname", function_name) + + elif endpoint_name == "events": + rule_name = params.get("Name", "") + span.set_tag_str("rulename", rule_name) + + elif endpoint_name == "states": + state_machine_arn = params.get("stateMachineArn", "") + span.set_tag_str("statemachinearn", state_machine_arn) + + +AWSREGION = "aws.region" +REGION = "region" +AGENT = "aws.agent" +OPERATION = "aws.operation" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/cassandra.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/cassandra.py new file mode 100644 index 0000000..d510897 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/cassandra.py @@ -0,0 +1,6 @@ +# tags +CLUSTER = "cassandra.cluster" +KEYSPACE = "cassandra.keyspace" +CONSISTENCY_LEVEL = "cassandra.consistency_level" +PAGINATED = "cassandra.paginated" +PAGE_NUMBER = "cassandra.page_number" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/ci.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/ci.py new file mode 100644 index 0000000..1b4029f --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/ci.py @@ -0,0 +1,576 @@ +""" +Tags for common CI attributes +""" +import json +import logging +import os +import platform +import re +from typing import Dict # noqa:F401 +from typing import List # noqa:F401 +from typing import MutableMapping # noqa:F401 +from typing import Optional # noqa:F401 + +from ddtrace.ext import git +from ddtrace.internal.logger import get_logger + + +# CI app dd_origin tag +CI_APP_TEST_ORIGIN = "ciapp-test" + +# Stage Name +STAGE_NAME = "ci.stage.name" + +# Job Name +JOB_NAME = "ci.job.name" + +# Job URL +JOB_URL = "ci.job.url" + +# Pipeline ID +PIPELINE_ID = "ci.pipeline.id" + +# Pipeline Name +PIPELINE_NAME = "ci.pipeline.name" + +# Pipeline Number +PIPELINE_NUMBER = "ci.pipeline.number" + +# Pipeline URL +PIPELINE_URL = "ci.pipeline.url" + +# Provider +PROVIDER_NAME = "ci.provider.name" + +# CI Node Name +NODE_NAME = "ci.node.name" + +# CI Node Labels +NODE_LABELS = "ci.node.labels" + +# Workspace Path +WORKSPACE_PATH = "ci.workspace_path" + +# Architecture +OS_ARCHITECTURE = "os.architecture" + +# Platform +OS_PLATFORM = "os.platform" + +# Version +OS_VERSION = "os.version" + +# Runtime Name +RUNTIME_NAME = "runtime.name" + +# Runtime Version +RUNTIME_VERSION = "runtime.version" + +# Version of the ddtrace library +LIBRARY_VERSION = "library_version" + +# CI Visibility env vars used for pipeline correlation ID +_CI_ENV_VARS = "_dd.ci.env_vars" + +_RE_URL = re.compile(r"(https?://|ssh://)[^/]*@") + + +log = get_logger(__name__) + + +def _filter_sensitive_info(url): + # type: (Optional[str]) -> Optional[str] + return _RE_URL.sub("\\1", url) if url is not None else None + + +def _get_runtime_and_os_metadata(): + """Extract configuration facet tags for OS and Python runtime.""" + return { + OS_ARCHITECTURE: platform.machine(), + OS_PLATFORM: platform.system(), + OS_VERSION: platform.release(), + RUNTIME_NAME: platform.python_implementation(), + RUNTIME_VERSION: platform.python_version(), + } + + +def tags(env=None, cwd=None): + # type: (Optional[MutableMapping[str, str]], Optional[str]) -> Dict[str, str] + """Extract and set tags from provider environ, as well as git metadata.""" + env = os.environ if env is None else env + tags = {} # type: Dict[str, Optional[str]] + for key, extract in PROVIDERS: + if key in env: + tags = extract(env) + break + + git_info = git.extract_git_metadata(cwd=cwd) + try: + git_info[WORKSPACE_PATH] = git.extract_workspace_path(cwd=cwd) + except git.GitNotFoundError: + log.error("Git executable not found, cannot extract git metadata.") + except ValueError as e: + debug_mode = log.isEnabledFor(logging.DEBUG) + stderr = str(e) + log.error("Error extracting git metadata: %s", stderr, exc_info=debug_mode) + + # Tags collected from CI provider take precedence over extracted git metadata, but any CI provider value + # is None or "" should be overwritten. + tags.update({k: v for k, v in git_info.items() if not tags.get(k)}) + + user_specified_git_info = git.extract_user_git_metadata(env) + + # Tags provided by the user take precedence over everything + tags.update({k: v for k, v in user_specified_git_info.items() if v}) + + # if git.BRANCH is a tag, we associate its value to TAG instead of BRANCH + if git.is_ref_a_tag(tags.get(git.BRANCH)): + if not tags.get(git.TAG): + tags[git.TAG] = git.normalize_ref(tags.get(git.BRANCH)) + else: + tags[git.TAG] = git.normalize_ref(tags.get(git.TAG)) + del tags[git.BRANCH] + else: + tags[git.BRANCH] = git.normalize_ref(tags.get(git.BRANCH)) + tags[git.TAG] = git.normalize_ref(tags.get(git.TAG)) + + tags[git.REPOSITORY_URL] = _filter_sensitive_info(tags.get(git.REPOSITORY_URL)) + + workspace_path = tags.get(WORKSPACE_PATH) + if workspace_path: + tags[WORKSPACE_PATH] = os.path.expanduser(workspace_path) + + tags.update(_get_runtime_and_os_metadata()) + + return {k: v for k, v in tags.items() if v is not None} + + +def extract_appveyor(env): + # type: (MutableMapping[str, str]) -> Dict[str, Optional[str]] + """Extract CI tags from Appveyor environ.""" + url = "https://ci.appveyor.com/project/{0}/builds/{1}".format( + env.get("APPVEYOR_REPO_NAME"), env.get("APPVEYOR_BUILD_ID") + ) + if env.get("APPVEYOR_REPO_PROVIDER") == "github": + repository = "https://github.com/{0}.git".format(env.get("APPVEYOR_REPO_NAME")) # type: Optional[str] + commit = env.get("APPVEYOR_REPO_COMMIT") # type: Optional[str] + branch = env.get("APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH") or env.get( + "APPVEYOR_REPO_BRANCH" + ) # type: Optional[str] + tag = env.get("APPVEYOR_REPO_TAG_NAME") # type: Optional[str] + else: + repository = commit = branch = tag = None + + commit_message = env.get("APPVEYOR_REPO_COMMIT_MESSAGE") + if commit_message: + extended = env.get("APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED") + if extended: + commit_message += "\n" + extended + + return { + PROVIDER_NAME: "appveyor", + git.REPOSITORY_URL: repository, + git.COMMIT_SHA: commit, + WORKSPACE_PATH: env.get("APPVEYOR_BUILD_FOLDER"), + PIPELINE_ID: env.get("APPVEYOR_BUILD_ID"), + PIPELINE_NAME: env.get("APPVEYOR_REPO_NAME"), + PIPELINE_NUMBER: env.get("APPVEYOR_BUILD_NUMBER"), + PIPELINE_URL: url, + JOB_URL: url, + git.BRANCH: branch, + git.TAG: tag, + git.COMMIT_MESSAGE: commit_message, + git.COMMIT_AUTHOR_NAME: env.get("APPVEYOR_REPO_COMMIT_AUTHOR"), + git.COMMIT_AUTHOR_EMAIL: env.get("APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL"), + } + + +def extract_azure_pipelines(env): + # type: (MutableMapping[str, str]) -> Dict[str, Optional[str]] + """Extract CI tags from Azure pipelines environ.""" + if env.get("SYSTEM_TEAMFOUNDATIONSERVERURI") and env.get("SYSTEM_TEAMPROJECTID") and env.get("BUILD_BUILDID"): + base_url = "{0}{1}/_build/results?buildId={2}".format( + env.get("SYSTEM_TEAMFOUNDATIONSERVERURI"), env.get("SYSTEM_TEAMPROJECTID"), env.get("BUILD_BUILDID") + ) + pipeline_url = base_url # type: Optional[str] + job_url = base_url + "&view=logs&j={0}&t={1}".format( + env.get("SYSTEM_JOBID"), env.get("SYSTEM_TASKINSTANCEID") + ) # type: Optional[str] + else: + pipeline_url = job_url = None + + return { + PROVIDER_NAME: "azurepipelines", + WORKSPACE_PATH: env.get("BUILD_SOURCESDIRECTORY"), + PIPELINE_ID: env.get("BUILD_BUILDID"), + PIPELINE_NAME: env.get("BUILD_DEFINITIONNAME"), + PIPELINE_NUMBER: env.get("BUILD_BUILDID"), + PIPELINE_URL: pipeline_url, + JOB_URL: job_url, + git.REPOSITORY_URL: env.get("SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI") or env.get("BUILD_REPOSITORY_URI"), + git.COMMIT_SHA: env.get("SYSTEM_PULLREQUEST_SOURCECOMMITID") or env.get("BUILD_SOURCEVERSION"), + git.BRANCH: env.get("SYSTEM_PULLREQUEST_SOURCEBRANCH") + or env.get("BUILD_SOURCEBRANCH") + or env.get("BUILD_SOURCEBRANCHNAME"), + git.COMMIT_MESSAGE: env.get("BUILD_SOURCEVERSIONMESSAGE"), + git.COMMIT_AUTHOR_NAME: env.get("BUILD_REQUESTEDFORID"), + git.COMMIT_AUTHOR_EMAIL: env.get("BUILD_REQUESTEDFOREMAIL"), + STAGE_NAME: env.get("SYSTEM_STAGEDISPLAYNAME"), + JOB_NAME: env.get("SYSTEM_JOBDISPLAYNAME"), + _CI_ENV_VARS: json.dumps( + { + "SYSTEM_TEAMPROJECTID": env.get("SYSTEM_TEAMPROJECTID"), + "BUILD_BUILDID": env.get("BUILD_BUILDID"), + "SYSTEM_JOBID": env.get("SYSTEM_JOBID"), + }, + separators=(",", ":"), + ), + } + + +def extract_bitbucket(env): + # type: (MutableMapping[str, str]) -> Dict[str, Optional[str]] + """Extract CI tags from Bitbucket environ.""" + url = "https://bitbucket.org/{0}/addon/pipelines/home#!/results/{1}".format( + env.get("BITBUCKET_REPO_FULL_NAME"), env.get("BITBUCKET_BUILD_NUMBER") + ) + return { + git.BRANCH: env.get("BITBUCKET_BRANCH"), + git.COMMIT_SHA: env.get("BITBUCKET_COMMIT"), + git.REPOSITORY_URL: env.get("BITBUCKET_GIT_SSH_ORIGIN") or env.get("BITBUCKET_GIT_HTTP_ORIGIN"), + git.TAG: env.get("BITBUCKET_TAG"), + JOB_URL: url, + PIPELINE_ID: env.get("BITBUCKET_PIPELINE_UUID", "").strip("{}}") or None, # noqa: B005 + PIPELINE_NAME: env.get("BITBUCKET_REPO_FULL_NAME"), + PIPELINE_NUMBER: env.get("BITBUCKET_BUILD_NUMBER"), + PIPELINE_URL: url, + PROVIDER_NAME: "bitbucket", + WORKSPACE_PATH: env.get("BITBUCKET_CLONE_DIR"), + } + + +def extract_buildkite(env): + # type: (MutableMapping[str, str]) -> Dict[str, Optional[str]] + """Extract CI tags from Buildkite environ.""" + # Get all keys which start with BUILDKITE_AGENT_META_DATA_x + node_label_list = [] # type: List[str] + buildkite_agent_meta_data_prefix = "BUILDKITE_AGENT_META_DATA_" + for env_variable in env: + if env_variable.startswith(buildkite_agent_meta_data_prefix): + key = env_variable.replace(buildkite_agent_meta_data_prefix, "").lower() + value = env.get(env_variable) + node_label_list.append("{}:{}".format(key, value)) + return { + git.BRANCH: env.get("BUILDKITE_BRANCH"), + git.COMMIT_SHA: env.get("BUILDKITE_COMMIT"), + git.REPOSITORY_URL: env.get("BUILDKITE_REPO"), + git.TAG: env.get("BUILDKITE_TAG"), + PIPELINE_ID: env.get("BUILDKITE_BUILD_ID"), + PIPELINE_NAME: env.get("BUILDKITE_PIPELINE_SLUG"), + PIPELINE_NUMBER: env.get("BUILDKITE_BUILD_NUMBER"), + PIPELINE_URL: env.get("BUILDKITE_BUILD_URL"), + JOB_URL: "{0}#{1}".format(env.get("BUILDKITE_BUILD_URL"), env.get("BUILDKITE_JOB_ID")), + PROVIDER_NAME: "buildkite", + WORKSPACE_PATH: env.get("BUILDKITE_BUILD_CHECKOUT_PATH"), + git.COMMIT_MESSAGE: env.get("BUILDKITE_MESSAGE"), + git.COMMIT_AUTHOR_NAME: env.get("BUILDKITE_BUILD_AUTHOR"), + git.COMMIT_AUTHOR_EMAIL: env.get("BUILDKITE_BUILD_AUTHOR_EMAIL"), + git.COMMIT_COMMITTER_NAME: env.get("BUILDKITE_BUILD_CREATOR"), + git.COMMIT_COMMITTER_EMAIL: env.get("BUILDKITE_BUILD_CREATOR_EMAIL"), + _CI_ENV_VARS: json.dumps( + { + "BUILDKITE_BUILD_ID": env.get("BUILDKITE_BUILD_ID"), + "BUILDKITE_JOB_ID": env.get("BUILDKITE_JOB_ID"), + }, + separators=(",", ":"), + ), + NODE_LABELS: json.dumps(node_label_list, separators=(",", ":")), + NODE_NAME: env.get("BUILDKITE_AGENT_ID"), + } + + +def extract_circle_ci(env): + # type: (MutableMapping[str, str]) -> Dict[str, Optional[str]] + """Extract CI tags from CircleCI environ.""" + return { + git.BRANCH: env.get("CIRCLE_BRANCH"), + git.COMMIT_SHA: env.get("CIRCLE_SHA1"), + git.REPOSITORY_URL: env.get("CIRCLE_REPOSITORY_URL"), + git.TAG: env.get("CIRCLE_TAG"), + PIPELINE_ID: env.get("CIRCLE_WORKFLOW_ID"), + PIPELINE_NAME: env.get("CIRCLE_PROJECT_REPONAME"), + PIPELINE_NUMBER: env.get("CIRCLE_BUILD_NUM"), + PIPELINE_URL: "https://app.circleci.com/pipelines/workflows/{0}".format(env.get("CIRCLE_WORKFLOW_ID")), + JOB_URL: env.get("CIRCLE_BUILD_URL"), + JOB_NAME: env.get("CIRCLE_JOB"), + PROVIDER_NAME: "circleci", + WORKSPACE_PATH: env.get("CIRCLE_WORKING_DIRECTORY"), + _CI_ENV_VARS: json.dumps( + { + "CIRCLE_WORKFLOW_ID": env.get("CIRCLE_WORKFLOW_ID"), + "CIRCLE_BUILD_NUM": env.get("CIRCLE_BUILD_NUM"), + }, + separators=(",", ":"), + ), + } + + +def extract_codefresh(env): + # type: (MutableMapping[str, str]) -> Dict[str, Optional[str]] + """Extract CI tags from Codefresh environ.""" + build_id = env.get("CF_BUILD_ID") + return { + git.BRANCH: env.get("CF_BRANCH"), + PIPELINE_ID: build_id, + PIPELINE_NAME: env.get("CF_PIPELINE_NAME"), + PIPELINE_URL: env.get("CF_BUILD_URL"), + JOB_NAME: env.get("CF_STEP_NAME"), + PROVIDER_NAME: "codefresh", + _CI_ENV_VARS: json.dumps( + {"CF_BUILD_ID": build_id}, + separators=(",", ":"), + ), + } + + +def extract_github_actions(env): + # type: (MutableMapping[str, str]) -> Dict[str, Optional[str]] + """Extract CI tags from Github environ.""" + + github_server_url = _filter_sensitive_info(env.get("GITHUB_SERVER_URL")) + github_repository = env.get("GITHUB_REPOSITORY") + git_commit_sha = env.get("GITHUB_SHA") + github_run_id = env.get("GITHUB_RUN_ID") + run_attempt = env.get("GITHUB_RUN_ATTEMPT") + + pipeline_url = "{0}/{1}/actions/runs/{2}".format( + github_server_url, + github_repository, + github_run_id, + ) + + env_vars = { + "GITHUB_SERVER_URL": github_server_url, + "GITHUB_REPOSITORY": github_repository, + "GITHUB_RUN_ID": github_run_id, + } + if run_attempt: + env_vars["GITHUB_RUN_ATTEMPT"] = run_attempt + pipeline_url = "{0}/attempts/{1}".format(pipeline_url, run_attempt) + + return { + git.BRANCH: env.get("GITHUB_HEAD_REF") or env.get("GITHUB_REF"), + git.COMMIT_SHA: git_commit_sha, + git.REPOSITORY_URL: "{0}/{1}.git".format(github_server_url, github_repository), + JOB_URL: "{0}/{1}/commit/{2}/checks".format(github_server_url, github_repository, git_commit_sha), + PIPELINE_ID: github_run_id, + PIPELINE_NAME: env.get("GITHUB_WORKFLOW"), + PIPELINE_NUMBER: env.get("GITHUB_RUN_NUMBER"), + PIPELINE_URL: pipeline_url, + JOB_NAME: env.get("GITHUB_JOB"), + PROVIDER_NAME: "github", + WORKSPACE_PATH: env.get("GITHUB_WORKSPACE"), + _CI_ENV_VARS: json.dumps(env_vars, separators=(",", ":")), + } + + +def extract_gitlab(env): + # type: (MutableMapping[str, str]) -> Dict[str, Optional[str]] + """Extract CI tags from Gitlab environ.""" + author = env.get("CI_COMMIT_AUTHOR") + author_name = None # type: Optional[str] + author_email = None # type: Optional[str] + if author: + # Extract name and email from `author` which is in the form "name " + author_name, author_email = author.strip("> ").split(" <") + commit_timestamp = env.get("CI_COMMIT_TIMESTAMP") + return { + git.BRANCH: env.get("CI_COMMIT_REF_NAME"), + git.COMMIT_SHA: env.get("CI_COMMIT_SHA"), + git.REPOSITORY_URL: env.get("CI_REPOSITORY_URL"), + git.TAG: env.get("CI_COMMIT_TAG"), + STAGE_NAME: env.get("CI_JOB_STAGE"), + JOB_NAME: env.get("CI_JOB_NAME"), + JOB_URL: env.get("CI_JOB_URL"), + PIPELINE_ID: env.get("CI_PIPELINE_ID"), + PIPELINE_NAME: env.get("CI_PROJECT_PATH"), + PIPELINE_NUMBER: env.get("CI_PIPELINE_IID"), + PIPELINE_URL: env.get("CI_PIPELINE_URL"), + PROVIDER_NAME: "gitlab", + WORKSPACE_PATH: env.get("CI_PROJECT_DIR"), + git.COMMIT_MESSAGE: env.get("CI_COMMIT_MESSAGE"), + git.COMMIT_AUTHOR_NAME: author_name, + git.COMMIT_AUTHOR_EMAIL: author_email, + git.COMMIT_AUTHOR_DATE: commit_timestamp, + _CI_ENV_VARS: json.dumps( + { + "CI_PROJECT_URL": env.get("CI_PROJECT_URL"), + "CI_PIPELINE_ID": env.get("CI_PIPELINE_ID"), + "CI_JOB_ID": env.get("CI_JOB_ID"), + }, + separators=(",", ":"), + ), + NODE_LABELS: env.get("CI_RUNNER_TAGS"), + NODE_NAME: env.get("CI_RUNNER_ID"), + } + + +def extract_jenkins(env): + # type: (MutableMapping[str, str]) -> Dict[str, Optional[str]] + """Extract CI tags from Jenkins environ.""" + branch = env.get("GIT_BRANCH", "") + name = env.get("JOB_NAME") + if name and branch: + name = re.sub("/{0}".format(git.normalize_ref(branch)), "", name) + if name: + name = "/".join((v for v in name.split("/") if v and "=" not in v)) + node_labels_list = [] # type: List[str] + node_labels_env = env.get("NODE_LABELS") # type: Optional[str] + if node_labels_env: + node_labels_list = node_labels_env.split() + return { + git.BRANCH: env.get("GIT_BRANCH"), + git.COMMIT_SHA: env.get("GIT_COMMIT"), + git.REPOSITORY_URL: env.get("GIT_URL", env.get("GIT_URL_1")), + PIPELINE_ID: env.get("BUILD_TAG"), + PIPELINE_NAME: name, + PIPELINE_NUMBER: env.get("BUILD_NUMBER"), + PIPELINE_URL: env.get("BUILD_URL"), + PROVIDER_NAME: "jenkins", + WORKSPACE_PATH: env.get("WORKSPACE"), + _CI_ENV_VARS: json.dumps( + { + "DD_CUSTOM_TRACE_ID": env.get("DD_CUSTOM_TRACE_ID"), + }, + separators=(",", ":"), + ), + NODE_LABELS: json.dumps(node_labels_list, separators=(",", ":")), + NODE_NAME: env.get("NODE_NAME"), + } + + +def extract_teamcity(env): + # type: (MutableMapping[str, str]) -> Dict[str, Optional[str]] + """Extract CI tags from Teamcity environ.""" + return { + JOB_URL: env.get("BUILD_URL"), + JOB_NAME: env.get("TEAMCITY_BUILDCONF_NAME"), + PROVIDER_NAME: "teamcity", + } + + +def extract_travis(env): + # type: (MutableMapping[str, str]) -> Dict[str, Optional[str]] + """Extract CI tags from Travis environ.""" + return { + git.BRANCH: env.get("TRAVIS_PULL_REQUEST_BRANCH") or env.get("TRAVIS_BRANCH"), + git.COMMIT_SHA: env.get("TRAVIS_COMMIT"), + git.REPOSITORY_URL: "https://github.com/{0}.git".format(env.get("TRAVIS_REPO_SLUG")), + git.TAG: env.get("TRAVIS_TAG"), + JOB_URL: env.get("TRAVIS_JOB_WEB_URL"), + PIPELINE_ID: env.get("TRAVIS_BUILD_ID"), + PIPELINE_NAME: env.get("TRAVIS_REPO_SLUG"), + PIPELINE_NUMBER: env.get("TRAVIS_BUILD_NUMBER"), + PIPELINE_URL: env.get("TRAVIS_BUILD_WEB_URL"), + PROVIDER_NAME: "travisci", + WORKSPACE_PATH: env.get("TRAVIS_BUILD_DIR"), + git.COMMIT_MESSAGE: env.get("TRAVIS_COMMIT_MESSAGE"), + } + + +def extract_bitrise(env): + # type: (MutableMapping[str, str]) -> Dict[str, Optional[str]] + """Extract CI tags from Bitrise environ.""" + commit = env.get("BITRISE_GIT_COMMIT") or env.get("GIT_CLONE_COMMIT_HASH") + branch = env.get("BITRISEIO_GIT_BRANCH_DEST") or env.get("BITRISE_GIT_BRANCH") + if env.get("BITRISE_GIT_MESSAGE"): + message = env.get("BITRISE_GIT_MESSAGE") # type: Optional[str] + elif env.get("GIT_CLONE_COMMIT_MESSAGE_SUBJECT") or env.get("GIT_CLONE_COMMIT_MESSAGE_BODY"): + message = "{0}:\n{1}".format( + env.get("GIT_CLONE_COMMIT_MESSAGE_SUBJECT"), env.get("GIT_CLONE_COMMIT_MESSAGE_BODY") + ) + else: + message = None + + return { + PROVIDER_NAME: "bitrise", + PIPELINE_ID: env.get("BITRISE_BUILD_SLUG"), + PIPELINE_NAME: env.get("BITRISE_TRIGGERED_WORKFLOW_ID"), + PIPELINE_NUMBER: env.get("BITRISE_BUILD_NUMBER"), + PIPELINE_URL: env.get("BITRISE_BUILD_URL"), + WORKSPACE_PATH: env.get("BITRISE_SOURCE_DIR"), + git.REPOSITORY_URL: env.get("GIT_REPOSITORY_URL"), + git.COMMIT_SHA: commit, + git.BRANCH: branch, + git.TAG: env.get("BITRISE_GIT_TAG"), + git.COMMIT_MESSAGE: message, + git.COMMIT_AUTHOR_NAME: env.get("GIT_CLONE_COMMIT_AUTHOR_NAME"), + git.COMMIT_AUTHOR_EMAIL: env.get("GIT_CLONE_COMMIT_AUTHOR_EMAIL"), + git.COMMIT_COMMITTER_NAME: env.get("GIT_CLONE_COMMIT_COMMITER_NAME"), + git.COMMIT_COMMITTER_EMAIL: env.get("GIT_CLONE_COMMIT_COMMITER_NAME"), + } + + +def extract_buddy(env): + # type: (MutableMapping[str, str]) -> Dict[str, Optional[str]] + """Extract CI tags from Buddy environ.""" + return { + PROVIDER_NAME: "buddy", + PIPELINE_ID: "{0}/{1}".format(env.get("BUDDY_PIPELINE_ID"), env.get("BUDDY_EXECUTION_ID")), + PIPELINE_NAME: env.get("BUDDY_PIPELINE_NAME"), + PIPELINE_NUMBER: env.get("BUDDY_EXECUTION_ID"), + PIPELINE_URL: env.get("BUDDY_EXECUTION_URL"), + git.REPOSITORY_URL: env.get("BUDDY_SCM_URL"), + git.COMMIT_SHA: env.get("BUDDY_EXECUTION_REVISION"), + git.BRANCH: env.get("BUDDY_EXECUTION_BRANCH"), + git.TAG: env.get("BUDDY_EXECUTION_TAG"), + git.COMMIT_MESSAGE: env.get("BUDDY_EXECUTION_REVISION_MESSAGE"), + git.COMMIT_COMMITTER_NAME: env.get("BUDDY_EXECUTION_REVISION_COMMITTER_NAME"), + git.COMMIT_COMMITTER_EMAIL: env.get("BUDDY_EXECUTION_REVISION_COMMITTER_EMAIL"), + } + + +def extract_codebuild(env): + # type: (MutableMapping[str, str]) -> Dict[str, Optional[str]] + """Extract CI tags from codebuild environments.""" + + tags = {} + + # AWS Codepipeline + if "CODEBUILD_INITIATOR" in env: + codebuild_initiator = env.get("CODEBUILD_INITIATOR") + if codebuild_initiator and codebuild_initiator.startswith("codepipeline"): + tags.update( + { + PROVIDER_NAME: "awscodepipeline", + PIPELINE_ID: env.get("DD_PIPELINE_EXECUTION_ID"), + _CI_ENV_VARS: json.dumps( + { + "CODEBUILD_BUILD_ARN": env.get("CODEBUILD_BUILD_ARN"), + "DD_PIPELINE_EXECUTION_ID": env.get("DD_PIPELINE_EXECUTION_ID"), + "DD_ACTION_EXECUTION_ID": env.get("DD_ACTION_EXECUTION_ID"), + }, + separators=(",", ":"), + ), + } + ) + + return tags + + +PROVIDERS = ( + ("APPVEYOR", extract_appveyor), + ("TF_BUILD", extract_azure_pipelines), + ("BITBUCKET_COMMIT", extract_bitbucket), + ("BUILDKITE", extract_buildkite), + ("CIRCLECI", extract_circle_ci), + ("CF_BUILD_ID", extract_codefresh), + ("GITHUB_SHA", extract_github_actions), + ("GITLAB_CI", extract_gitlab), + ("JENKINS_URL", extract_jenkins), + ("TEAMCITY_VERSION", extract_teamcity), + ("TRAVIS", extract_travis), + ("BITRISE_BUILD_SLUG", extract_bitrise), + ("BUDDY", extract_buddy), + ("CODEBUILD_INITIATOR", extract_codebuild), +) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/consul.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/consul.py new file mode 100644 index 0000000..72b9986 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/consul.py @@ -0,0 +1,4 @@ +APP = "consul" +SERVICE = "consul" +CMD = "consul.command" +KEY = "consul.key" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/db.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/db.py new file mode 100644 index 0000000..9223f5a --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/db.py @@ -0,0 +1,6 @@ +# tags +NAME = "db.name" # the database name (eg: dbname for pgsql) +USER = "db.user" # the user connecting to the db +SYSTEM = "db.system" # the database's DBMS name (e.g. postgresql for pgsql) +ROWCOUNT = "db.row_count" # the rowcount of a query +SYSTEM = "db.system" # the database's DBMS name (e.g. postgresql for pgsql) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/elasticsearch.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/elasticsearch.py new file mode 100644 index 0000000..ca42c0b --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/elasticsearch.py @@ -0,0 +1,9 @@ +SERVICE = "elasticsearch" +APP = "elasticsearch" + +# standard tags +URL = "elasticsearch.url" +METHOD = "elasticsearch.method" +TOOK = "elasticsearch.took" +PARAMS = "elasticsearch.params" +BODY = "elasticsearch.body" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/git.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/git.py new file mode 100644 index 0000000..92e4328 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/git.py @@ -0,0 +1,402 @@ +""" +tags for common git attributes +""" +import contextlib +import logging +import os +import random +import re +import subprocess +from typing import Dict # noqa:F401 +from typing import Generator # noqa:F401 +from typing import List # noqa:F401 +from typing import MutableMapping # noqa:F401 +from typing import NamedTuple # noqa:F401 +from typing import Optional # noqa:F401 +from typing import Tuple # noqa:F401 +from typing import Union # noqa:F401 + +from ddtrace.internal import compat +from ddtrace.internal.logger import get_logger +from ddtrace.internal.utils.time import StopWatch + + +GitNotFoundError = FileNotFoundError + +# Git Branch +BRANCH = "git.branch" + +# Git Commit SHA +COMMIT_SHA = "git.commit.sha" + +# Git Repository URL +REPOSITORY_URL = "git.repository_url" + +# Git Tag +TAG = "git.tag" + +# Git Commit Author Name +COMMIT_AUTHOR_NAME = "git.commit.author.name" + +# Git Commit Author Email +COMMIT_AUTHOR_EMAIL = "git.commit.author.email" + +# Git Commit Author Date (UTC) +COMMIT_AUTHOR_DATE = "git.commit.author.date" + +# Git Commit Committer Name +COMMIT_COMMITTER_NAME = "git.commit.committer.name" + +# Git Commit Committer Email +COMMIT_COMMITTER_EMAIL = "git.commit.committer.email" + +# Git Commit Committer Date (UTC) +COMMIT_COMMITTER_DATE = "git.commit.committer.date" + +# Git Commit Message +COMMIT_MESSAGE = "git.commit.message" + +# Python main package +MAIN_PACKAGE = "python_main_package" + +_RE_REFS = re.compile(r"^refs/(heads/)?") +_RE_ORIGIN = re.compile(r"^origin/") +_RE_TAGS = re.compile(r"^tags/") + +log = get_logger(__name__) + +_GitSubprocessDetails = NamedTuple( + "_GitSubprocessDetails", [("stdout", str), ("stderr", str), ("duration", float), ("returncode", int)] +) + + +def normalize_ref(name): + # type: (Optional[str]) -> Optional[str] + return _RE_TAGS.sub("", _RE_ORIGIN.sub("", _RE_REFS.sub("", name))) if name is not None else None + + +def is_ref_a_tag(ref): + # type: (Optional[str]) -> bool + return "tags/" in ref if ref else False + + +def _git_subprocess_cmd_with_details(*cmd, cwd=None, std_in=None): + # type: (str, Optional[str], Optional[bytes]) -> _GitSubprocessDetails + """Helper for invoking the git CLI binary + + Returns a tuple containing: + - a str representation of stdout + - a str representation of stderr + - the time it took to execute the command, in milliseconds + - the exit code + """ + git_cmd = ["git"] + git_cmd.extend(cmd) + + log.debug("Executing git command: %s", git_cmd) + + with StopWatch() as stopwatch: + process = subprocess.Popen( + git_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, cwd=cwd + ) + stdout, stderr = process.communicate(input=std_in) + + return _GitSubprocessDetails( + compat.ensure_text(stdout).strip(), + compat.ensure_text(stderr).strip(), + stopwatch.elapsed() * 1000, # StopWatch measures elapsed time in seconds + process.returncode, + ) + + +def _git_subprocess_cmd(cmd, cwd=None, std_in=None): + # type: (Union[str, list[str]], Optional[str], Optional[bytes]) -> str + """Helper for invoking the git CLI binary.""" + if isinstance(cmd, str): + cmd = cmd.split(" ") + + stdout, stderr, _, returncode = _git_subprocess_cmd_with_details(*cmd, cwd=cwd, std_in=None) + + if returncode == 0: + return stdout + raise ValueError(stderr) + + +def _set_safe_directory(): + try: + _git_subprocess_cmd("config --global --add safe.directory *") + except GitNotFoundError: + log.error("Git executable not found, cannot extract git metadata.") + except ValueError: + log.error("Error setting safe directory") + + +def _extract_clone_defaultremotename_with_details(cwd): + # type: (Optional[str]) -> _GitSubprocessDetails + return _git_subprocess_cmd_with_details( + "config", "--default", "origin", "--get", "clone.defaultRemoteName", cwd=cwd + ) + + +def _extract_upstream_sha(cwd=None): + # type: (Optional[str]) -> str + output = _git_subprocess_cmd("rev-parse @{upstream}", cwd=cwd) + return output + + +def _is_shallow_repository_with_details(cwd=None): + # type: (Optional[str]) -> Tuple[bool, float, int] + stdout, _, duration, returncode = _git_subprocess_cmd_with_details("rev-parse", "--is-shallow-repository", cwd=cwd) + is_shallow = stdout.strip() == "true" + return (is_shallow, duration, returncode) + + +def _get_device_for_path(path): + # type: (str) -> int + return os.stat(path).st_dev + + +def _unshallow_repository_with_details(cwd=None, repo=None, refspec=None): + # type (Optional[str], Optional[str], Optional[str]) -> _GitSubprocessDetails + cmd = [ + "fetch", + '--shallow-since="1 month ago"', + "--update-shallow", + "--filter=blob:none", + "--recurse-submodules=no", + ] + if repo is not None: + cmd.append(repo) + if refspec is not None: + cmd.append(refspec) + + return _git_subprocess_cmd_with_details(*cmd, cwd=cwd) + + +def _unshallow_repository(cwd=None, repo=None, refspec=None): + # type (Optional[str], Optional[str], Optional[str]) -> None + _unshallow_repository_with_details(cwd, repo, refspec) + + +def extract_user_info(cwd=None): + # type: (Optional[str]) -> Dict[str, Tuple[str, str, str]] + """Extract commit author info from the git repository in the current directory or one specified by ``cwd``.""" + # Note: `git show -s --format... --date...` is supported since git 2.1.4 onwards + stdout = _git_subprocess_cmd("show -s --format=%an,%ae,%ad,%cn,%ce,%cd --date=format:%Y-%m-%dT%H:%M:%S%z", cwd=cwd) + author_name, author_email, author_date, committer_name, committer_email, committer_date = stdout.split(",") + return { + "author": (author_name, author_email, author_date), + "committer": (committer_name, committer_email, committer_date), + } + + +def extract_git_version(cwd=None): + output = _git_subprocess_cmd("--version") + try: + version_info = tuple([int(part) for part in output.split()[2].split(".")]) + except ValueError: + log.error("Git version not found, it is not following the desired version format: %s", output) + return 0, 0, 0 + return version_info + + +def _extract_remote_url_with_details(cwd=None): + # type: (Optional[str]) -> _GitSubprocessDetails + return _git_subprocess_cmd_with_details("config", "--get", "remote.origin.url", cwd=cwd) + + +def extract_remote_url(cwd=None): + remote_url, error, _, returncode = _extract_remote_url_with_details(cwd=cwd) + if returncode == 0: + return remote_url + raise ValueError(error) + + +def _extract_latest_commits_with_details(cwd=None): + # type: (Optional[str]) -> _GitSubprocessDetails + return _git_subprocess_cmd_with_details("log", "--format=%H", "-n", "1000", '--since="1 month ago"', cwd=cwd) + + +def extract_latest_commits(cwd=None): + # type: (Optional[str]) -> List[str] + latest_commits, error, _, returncode = _extract_latest_commits_with_details(cwd=cwd) + if returncode == 0: + return latest_commits.split("\n") if latest_commits else [] + raise ValueError(error) + + +def get_rev_list_excluding_commits(commit_shas, cwd=None): + return _get_rev_list_with_details(excluded_commit_shas=commit_shas, cwd=cwd)[0] + + +def _get_rev_list_with_details(excluded_commit_shas=None, included_commit_shas=None, cwd=None): + # type: (Optional[list[str]], Optional[list[str]], Optional[str]) -> _GitSubprocessDetails + command = ["rev-list", "--objects", "--filter=blob:none"] + if extract_git_version(cwd=cwd) >= (2, 23, 0): + command.append('--since="1 month ago"') + command.append("--no-object-names") + command.append("HEAD") + if excluded_commit_shas: + exclusions = ["^%s" % sha for sha in excluded_commit_shas] + command.extend(exclusions) + if included_commit_shas: + inclusions = ["%s" % sha for sha in included_commit_shas] + command.extend(inclusions) + return _git_subprocess_cmd_with_details(*command, cwd=cwd) + + +def _get_rev_list(excluded_commit_shas=None, included_commit_shas=None, cwd=None): + # type: (Optional[list[str]], Optional[list[str]], Optional[str]) -> str + return _get_rev_list_with_details( + excluded_commit_shas=excluded_commit_shas, included_commit_shas=included_commit_shas, cwd=cwd + )[0] + + +def _extract_repository_url_with_details(cwd=None): + # type: (Optional[str]) -> _GitSubprocessDetails + """Extract the repository url from the git repository in the current directory or one specified by ``cwd``.""" + + return _git_subprocess_cmd_with_details("ls-remote", "--get-url", cwd=cwd) + + +def extract_repository_url(cwd=None): + # type: (Optional[str]) -> str + """Extract the repository url from the git repository in the current directory or one specified by ``cwd``.""" + stdout, stderr, _, returncode = _extract_repository_url_with_details(cwd=cwd) + if returncode == 0: + return stdout + raise ValueError(stderr) + + +def extract_commit_message(cwd=None): + # type: (Optional[str]) -> str + """Extract git commit message from the git repository in the current directory or one specified by ``cwd``.""" + # Note: `git show -s --format... --date...` is supported since git 2.1.4 onwards + commit_message = _git_subprocess_cmd("show -s --format=%s", cwd=cwd) + return commit_message + + +def extract_workspace_path(cwd=None): + # type: (Optional[str]) -> str + """Extract the root directory path from the git repository in the current directory or one specified by ``cwd``.""" + workspace_path = _git_subprocess_cmd("rev-parse --show-toplevel", cwd=cwd) + return workspace_path + + +def extract_branch(cwd=None): + # type: (Optional[str]) -> str + """Extract git branch from the git repository in the current directory or one specified by ``cwd``.""" + branch = _git_subprocess_cmd("rev-parse --abbrev-ref HEAD", cwd=cwd) + return branch + + +def extract_commit_sha(cwd=None): + # type: (Optional[str]) -> str + """Extract git commit SHA from the git repository in the current directory or one specified by ``cwd``.""" + commit_sha = _git_subprocess_cmd("rev-parse HEAD", cwd=cwd) + return commit_sha + + +def extract_git_metadata(cwd=None): + # type: (Optional[str]) -> Dict[str, Optional[str]] + """Extract git commit metadata.""" + tags = {} # type: Dict[str, Optional[str]] + _set_safe_directory() + try: + tags[REPOSITORY_URL] = extract_repository_url(cwd=cwd) + tags[COMMIT_MESSAGE] = extract_commit_message(cwd=cwd) + users = extract_user_info(cwd=cwd) + tags[COMMIT_AUTHOR_NAME] = users["author"][0] + tags[COMMIT_AUTHOR_EMAIL] = users["author"][1] + tags[COMMIT_AUTHOR_DATE] = users["author"][2] + tags[COMMIT_COMMITTER_NAME] = users["committer"][0] + tags[COMMIT_COMMITTER_EMAIL] = users["committer"][1] + tags[COMMIT_COMMITTER_DATE] = users["committer"][2] + tags[BRANCH] = extract_branch(cwd=cwd) + tags[COMMIT_SHA] = extract_commit_sha(cwd=cwd) + except GitNotFoundError: + log.error("Git executable not found, cannot extract git metadata.") + except ValueError as e: + debug_mode = log.isEnabledFor(logging.DEBUG) + stderr = str(e) + log.error("Error extracting git metadata: %s", stderr, exc_info=debug_mode) + + return tags + + +def extract_user_git_metadata(env=None): + # type: (Optional[MutableMapping[str, str]]) -> Dict[str, Optional[str]] + """Extract git commit metadata from user-provided env vars.""" + env = os.environ if env is None else env + + branch = normalize_ref(env.get("DD_GIT_BRANCH")) + tag = normalize_ref(env.get("DD_GIT_TAG")) + + # if DD_GIT_BRANCH is a tag, we associate its value to TAG instead of BRANCH + if is_ref_a_tag(env.get("DD_GIT_BRANCH")): + tag = branch + branch = None + + tags = {} + tags[REPOSITORY_URL] = env.get("DD_GIT_REPOSITORY_URL") + tags[COMMIT_SHA] = env.get("DD_GIT_COMMIT_SHA") + tags[BRANCH] = branch + tags[TAG] = tag + tags[COMMIT_MESSAGE] = env.get("DD_GIT_COMMIT_MESSAGE") + tags[COMMIT_AUTHOR_DATE] = env.get("DD_GIT_COMMIT_AUTHOR_DATE") + tags[COMMIT_AUTHOR_EMAIL] = env.get("DD_GIT_COMMIT_AUTHOR_EMAIL") + tags[COMMIT_AUTHOR_NAME] = env.get("DD_GIT_COMMIT_AUTHOR_NAME") + tags[COMMIT_COMMITTER_DATE] = env.get("DD_GIT_COMMIT_COMMITTER_DATE") + tags[COMMIT_COMMITTER_EMAIL] = env.get("DD_GIT_COMMIT_COMMITTER_EMAIL") + tags[COMMIT_COMMITTER_NAME] = env.get("DD_GIT_COMMIT_COMMITTER_NAME") + + return tags + + +@contextlib.contextmanager +def _build_git_packfiles_with_details(revisions, cwd=None, use_tempdir=True): + # type: (str, Optional[str], bool) -> Generator + basename = str(random.randint(1, 1000000)) + + # check that the tempdir and cwd are on the same filesystem, otherwise git pack-objects will fail + cwd = cwd if cwd else os.getcwd() + tempdir = compat.TemporaryDirectory() + if _get_device_for_path(cwd) == _get_device_for_path(tempdir.name): + basepath = tempdir.name + else: + log.debug("tempdir %s and cwd %s are on different filesystems, using cwd", tempdir.name, cwd) + basepath = cwd + + prefix = "{basepath}/{basename}".format(basepath=basepath, basename=basename) + + log.debug("Building packfiles in prefix path: %s", prefix) + + try: + process_details = _git_subprocess_cmd_with_details( + "pack-objects", + "--compression=9", + "--max-pack-size=3m", + prefix, + cwd=cwd, + std_in=revisions.encode("utf-8"), + ) + yield prefix, process_details + finally: + if isinstance(tempdir, compat.TemporaryDirectory): + log.debug("Cleaning up temporary directory: %s", basepath) + tempdir.cleanup() + + +@contextlib.contextmanager +def build_git_packfiles(revisions, cwd=None): + # type: (str, Optional[str]) -> Generator + with _build_git_packfiles_with_details(revisions, cwd=cwd) as (prefix, process_details): + if process_details.returncode == 0: + yield prefix + return + log.debug( + "Failed to pack objects, command return code: %s, error: %s", + process_details.returncode, + process_details.stderr, + ) + raise ValueError(process_details.stderr) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/http.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/http.py new file mode 100644 index 0000000..31a8286 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/http.py @@ -0,0 +1,22 @@ +""" +Standard http tags. + +For example: + +span.set_tag(URL, '/user/home') +span.set_tag(STATUS_CODE, 404) +""" +# tags +URL = "http.url" +METHOD = "http.method" +STATUS_CODE = "http.status_code" +USER_AGENT = "http.useragent" +STATUS_MSG = "http.status_msg" +QUERY_STRING = "http.query.string" +RETRIES_REMAIN = "http.retries_remain" +VERSION = "http.version" +CLIENT_IP = "http.client_ip" +ROUTE = "http.route" + +# template render span type +TEMPLATE = "template" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/kafka.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/kafka.py new file mode 100644 index 0000000..5b13c33 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/kafka.py @@ -0,0 +1,14 @@ +SERVICE = "kafka" + +TOPIC = "kafka.topic" +PARTITION = "kafka.partition" +MESSAGE_KEY = "kafka.message_key" +MESSAGE_OFFSET = "kafka.message_offset" +GROUP_ID = "kafka.group_id" +TOMBSTONE = "kafka.tombstone" +RECEIVED_MESSAGE = "kafka.received_message" + +HOST_LIST = "messaging.kafka.bootstrap.servers" + +PRODUCE = "kafka.produce" +CONSUME = "kafka.consume" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/kombu.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/kombu.py new file mode 100644 index 0000000..44a25f5 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/kombu.py @@ -0,0 +1,12 @@ +SERVICE = "kombu" + +# net extension +VHOST = "out.vhost" + +# standard tags +EXCHANGE = "kombu.exchange" +BODY_LEN = "kombu.body_length" +ROUTING_KEY = "kombu.routing_key" + +PUBLISH_NAME = "kombu.publish" +RECEIVE_NAME = "kombu.receive" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/memcached.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/memcached.py new file mode 100644 index 0000000..9b24051 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/memcached.py @@ -0,0 +1,4 @@ +CMD = "memcached.command" +DBMS_NAME = "memcached" +SERVICE = "memcached" +QUERY = "memcached.query" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/mongo.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/mongo.py new file mode 100644 index 0000000..d27d97d --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/mongo.py @@ -0,0 +1,4 @@ +SERVICE = "mongodb" +COLLECTION = "mongodb.collection" +DB = "mongodb.db" +QUERY = "mongodb.query" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/net.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/net.py new file mode 100644 index 0000000..4e3ff4a --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/net.py @@ -0,0 +1,12 @@ +""" +Standard network tags. +""" + +# request targets +TARGET_HOST = "out.host" +TARGET_PORT = "network.destination.port" +TARGET_IP = "network.destination.ip" + +PEER_HOSTNAME = "peer.hostname" + +BYTES_OUT = "net.out.bytes" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/redis.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/redis.py new file mode 100644 index 0000000..32f1d96 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/redis.py @@ -0,0 +1,14 @@ +# defaults +APP = "redis" +DEFAULT_SERVICE = "redis" + +# net extension +DB = "out.redis_db" + +# standard tags +RAWCMD = "redis.raw_command" +CMD = "redis.command" +ARGS_LEN = "redis.args_length" +PIPELINE_LEN = "redis.pipeline_length" +PIPELINE_AGE = "redis.pipeline_age" +CLIENT_NAME = "redis.client_name" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/sql.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/sql.py new file mode 100644 index 0000000..a8965dc --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/sql.py @@ -0,0 +1,75 @@ +from typing import Dict # noqa:F401 + +from ddtrace.internal.logger import get_logger +from ddtrace.internal.module import ModuleWatchdog + + +log = get_logger(__name__) + +# tags +DB = "sql.db" # the name of the database + + +def normalize_vendor(vendor): + # type: (str) -> str + """Return a canonical name for a type of database.""" + if not vendor: + return "db" # should this ever happen? + elif "sqlite" in vendor: + return "sqlite" + elif "postgres" in vendor or vendor == "psycopg2": + return "postgres" + else: + return vendor + + +def _dd_parse_pg_dsn(dsn): + # type: (str) -> Dict[str, str] + """ + Return a dictionary of the components of a postgres DSN. + >>> parse_pg_dsn('user=dog port=1543 dbname=dogdata') + {'user':'dog', 'port':'1543', 'dbname':'dogdata'} + """ + dsn_dict = dict() + try: + # Provides a default implementation for parsing DSN strings. + # The following is an example of a valid DSN string that fails to be parsed: + # "db=moon user=ears options='-c statement_timeout=1000 -c lock_timeout=250'" + dsn_dict = dict(_.split("=", 1) for _ in dsn.split()) + except Exception: + log.debug("Failed to parse postgres dsn connection", exc_info=True) + return dsn_dict + + +# Do not import from psycopg directly! This reference will be updated at runtime to use +# a better implementation that is provided by the psycopg library. +# This is done to avoid circular imports. +parse_pg_dsn = _dd_parse_pg_dsn + + +@ModuleWatchdog.after_module_imported("psycopg2") +def use_psycopg2_parse_dsn(psycopg_module): + """Replaces parse_pg_dsn with the helper function defined in psycopg2""" + global parse_pg_dsn + + try: + from psycopg2.extensions import parse_dsn + + parse_pg_dsn = parse_dsn + except ImportError: + # Best effort, we'll use our own parser: _dd_parse_pg_dsn + pass + + +@ModuleWatchdog.after_module_imported("psycopg") +def use_psycopg3_parse_dsn(psycopg_module): + """Replaces parse_pg_dsn with the helper function defined in psycopg3""" + global parse_pg_dsn + + try: + from psycopg.conninfo import conninfo_to_dict + + parse_pg_dsn = conninfo_to_dict + except ImportError: + # Best effort, we'll use our own parser: _dd_parse_pg_dsn + pass diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/test.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/test.py new file mode 100644 index 0000000..0829f6e --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/test.py @@ -0,0 +1,93 @@ +""" +tags for common test attributes +""" + +from enum import Enum + + +# Test Arguments +ARGUMENTS = TEST_ARGUMENTS = "test.arguments" + +# Test Framework +FRAMEWORK = TEST_FRAMEWORK = "test.framework" + +# Test Framework Version +FRAMEWORK_VERSION = TEST_FRAMEWORK_VERSION = "test.framework_version" + +# Test Command +COMMAND = "test.command" + +# Test Module +MODULE = "test.module" + +# Test Module Path +MODULE_PATH = "test.module_path" + +# Test Suite +SUITE = TEST_SUITE = "test.suite" + +# Test Name +NAME = TEST_NAME = "test.name" + +# Test Parameters +PARAMETERS = "test.parameters" + +# Test Result (XFail, XPass) +RESULT = TEST_RESULT = "test.result" + +# Skip Reason +SKIP_REASON = TEST_SKIP_REASON = "test.skip_reason" + +# Test Status +STATUS = TEST_STATUS = "test.status" + +# Traits +TRAITS = TEST_TRAITS = "test.traits" + +# Test Type +TYPE = TEST_TYPE = "test.type" + +# Test File +# Use when test implementation file is different from test suite name. +FILE = TEST_FILE = "test.file" + +# Test Source File +SOURCE_FILE = TEST_SOURCE_FILE = "test.source.file" + +# Test Source Start +SOURCE_START = TEST_SOURCE_START = "test.source.start" + +# Test Source End +SOURCE_END = TEST_SOURCE_END = "test.source.end" + +# Test Code Coverage Total Lines Percentage +LINES_PCT = TEST_LINES_PCT = "test.code_coverage.lines_pct" + +# Test Class Hierarchy +CLASS_HIERARCHY = "test.class_hierarchy" + +# Test Codeowners +CODEOWNERS = TEST_CODEOWNERS = "test.codeowners" + +# ITR +ITR_SKIPPED = "test.skipped_by_itr" + +# Test session-level ITR and coverage: +ITR_DD_CI_ITR_TESTS_SKIPPED = "_dd.ci.itr.tests_skipped" +ITR_TEST_SKIPPING_ENABLED = "test.itr.tests_skipping.enabled" +ITR_TEST_SKIPPING_TESTS_SKIPPED = "test.itr.tests_skipping.tests_skipped" +ITR_TEST_SKIPPING_TYPE = "test.itr.tests_skipping.type" +ITR_TEST_SKIPPING_COUNT = "test.itr.tests_skipping.count" +ITR_TEST_CODE_COVERAGE_ENABLED = "test.code_coverage.enabled" + +# ITR: unskippable tests +ITR_UNSKIPPABLE = "test.itr.unskippable" +ITR_FORCED_RUN = "test.itr.forced_run" + + +class Status(Enum): + PASS = "pass" + FAIL = "fail" + SKIP = "skip" + XFAIL = "xfail" + XPASS = "xpass" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/user.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/user.py new file mode 100644 index 0000000..3c90474 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/ext/user.py @@ -0,0 +1,8 @@ +# tags +ID = "usr.id" +NAME = "usr.name" +EMAIL = "usr.email" +ROLE = "usr.role" +SCOPE = "usr.scope" +SESSION_ID = "usr.session_id" +EXISTS = "usr.exists" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/filters.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/filters.py new file mode 100644 index 0000000..edbfb11 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/filters.py @@ -0,0 +1,72 @@ +import abc +import re +from typing import TYPE_CHECKING # noqa:F401 +from typing import List # noqa:F401 +from typing import Optional # noqa:F401 +from typing import Union # noqa:F401 + +from ddtrace.ext import http +from ddtrace.internal.processor.trace import TraceProcessor + + +if TYPE_CHECKING: # pragma: no cover + from ddtrace import Span # noqa:F401 + + +class TraceFilter(TraceProcessor): + @abc.abstractmethod + def process_trace(self, trace): + # type: (List[Span]) -> Optional[List[Span]] + """Processes a trace. + + None can be returned to prevent the trace from being exported. + """ + pass + + +class FilterRequestsOnUrl(TraceFilter): + r"""Filter out traces from incoming http requests based on the request's url. + + This class takes as argument a list of regular expression patterns + representing the urls to be excluded from tracing. A trace will be excluded + if its root span contains a ``http.url`` tag and if this tag matches any of + the provided regular expression using the standard python regexp match + semantic (https://docs.python.org/3/library/re.html#re.match). + + :param list regexps: a list of regular expressions (or a single string) defining + the urls that should be filtered out. + + Examples: + To filter out http calls to domain api.example.com:: + + FilterRequestsOnUrl(r'http://api\\.example\\.com') + + To filter out http calls to all first level subdomains from example.com:: + + FilterRequestOnUrl(r'http://.*+\\.example\\.com') + + To filter out calls to both http://test.example.com and http://example.com/healthcheck:: + + FilterRequestOnUrl([r'http://test\\.example\\.com', r'http://example\\.com/healthcheck']) + """ + + def __init__(self, regexps: Union[str, List[str]]): + if isinstance(regexps, str): + regexps = [regexps] + self._regexps = [re.compile(regexp) for regexp in regexps] + + def process_trace(self, trace): + # type: (List[Span]) -> Optional[List[Span]] + """ + When the filter is registered in the tracer, process_trace is called by + on each trace before it is sent to the agent, the returned value will + be fed to the next filter in the list. If process_trace returns None, + the whole trace is discarded. + """ + for span in trace: + url = span.get_tag(http.URL) + if span.parent_id is None and url is not None: + for regexp in self._regexps: + if regexp.match(url): + return None + return trace diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/README.md b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/README.md new file mode 100644 index 0000000..5cb3808 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/README.md @@ -0,0 +1,7 @@ +# Internal +This internal module is used to define and document an internal only API for `ddtrace`. + +These modules are not intended to be used outside of `ddtrace`. + +The APIs found within `ddtrace.internal` are subject to breaking changes at any time +and do not follow the semver versioning scheme of the `ddtrace` package. diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/_encoding.cpython-311-x86_64-linux-gnu.so b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/_encoding.cpython-311-x86_64-linux-gnu.so new file mode 100755 index 0000000000000000000000000000000000000000..48ae8d1a03c0f9ade6e03e796236a45c8d83dec7 GIT binary patch literal 357568 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%0Ad)_#1#ZJ*vRp)0NC2=~^xMn)Lal1^%9!UIEHK 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�UInWv=bz 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 int: ... + +class BufferFull(Exception): + pass + +class BufferItemTooLarge(Exception): + pass + +class BufferedEncoder(object): + max_size: int + max_item_size: int + def __init__(self, max_size: int, max_item_size: int) -> None: ... + def __len__(self) -> int: ... + def put(self, item: Any) -> None: ... + def encode(self) -> Optional[bytes]: ... + @property + def size(self) -> int: ... + +class ListBufferedEncoder(BufferedEncoder): + def get(self) -> List[bytes]: ... + def encode_item(self, item: Any) -> bytes: ... + +class MsgpackEncoderBase(BufferedEncoder): + content_type: str + def get_bytes(self) -> bytes: ... + def _decode(self, data: Union[str, bytes]) -> Any: ... + +class MsgpackEncoderV03(MsgpackEncoderBase): ... +class MsgpackEncoderV05(MsgpackEncoderBase): ... + +def packb(o: Any, **kwargs) -> bytes: ... diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/_rand.cpython-311-x86_64-linux-gnu.so b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/_rand.cpython-311-x86_64-linux-gnu.so new file mode 100755 index 0000000000000000000000000000000000000000..2bcdfc42768ca47c8ddf395c41f4fbcb2cfd5526 GIT binary patch literal 64144 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 Cq8XzA literal 0 HcmV?d00001 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/_rand.pyi b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/_rand.pyi new file mode 100644 index 0000000..f4e4450 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/_rand.pyi @@ -0,0 +1,3 @@ +def seed() -> None: ... +def rand64bits(check_pid: bool = True) -> int: ... +def rand128bits(check_pid: bool = True) -> int: ... diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/_tagset.cpython-311-x86_64-linux-gnu.so b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/_tagset.cpython-311-x86_64-linux-gnu.so new file mode 100755 index 0000000000000000000000000000000000000000..a4c8ffa3175aa80bccd66c616510d6f6187e44e3 GIT binary patch literal 118400 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

ty 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 literal 0 HcmV?d00001 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/_tagset.pyi b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/_tagset.pyi new file mode 100644 index 0000000..fd95131 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/_tagset.pyi @@ -0,0 +1,18 @@ +from typing import Dict + +class TagsetDecodeError(ValueError): ... +class TagsetEncodeError(ValueError): ... + +class TagsetMaxSizeEncodeError(TagsetEncodeError): + values: Dict[str, str] + max_size: int + current_results: str + def __init__(self, values: Dict[str, str], max_size: int, current_results: str): ... + +class TagsetMaxSizeDecodeError(TagsetDecodeError): + value: Dict[str, str] + max_size: int + def __init__(self, value: Dict[str, str], max_size: int): ... + +def decode_tagset_string(tagset: str) -> Dict[str, str]: ... +def encode_tagset_values(values: Dict[str, str], max_size: int = 512) -> str: ... diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/_utils.pxd b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/_utils.pxd new file mode 100644 index 0000000..9b79b95 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/_utils.pxd @@ -0,0 +1,2 @@ +cdef extern from "_utils.h": + cdef inline int PyBytesLike_Check(object o) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/agent.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/agent.py new file mode 100644 index 0000000..151852c --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/agent.py @@ -0,0 +1,106 @@ +import json +import os +import socket +from typing import TypeVar +from typing import Union + +from ddtrace.internal.logger import get_logger +from ddtrace.settings import _config as ddconfig + +from .http import HTTPConnection +from .http import HTTPSConnection +from .uds import UDSHTTPConnection +from .utils.http import get_connection + + +DEFAULT_HOSTNAME = "localhost" +DEFAULT_TRACE_PORT = 8126 +DEFAULT_UNIX_TRACE_PATH = "/var/run/datadog/apm.socket" +DEFAULT_UNIX_DSD_PATH = "/var/run/datadog/dsd.socket" +DEFAULT_STATS_PORT = 8125 + +ConnectionType = Union[HTTPSConnection, HTTPConnection, UDSHTTPConnection] + +T = TypeVar("T") + +log = get_logger(__name__) + + +# This method returns if a hostname is an IPv6 address +def is_ipv6_hostname(hostname): + # type: (Union[T, str]) -> bool + if not isinstance(hostname, str): + return False + try: + socket.inet_pton(socket.AF_INET6, hostname) + return True + except socket.error: # not a valid address + return False + + +def get_trace_url(): + # type: () -> str + """Return the Agent URL computed from the environment. + + Raises a ``ValueError`` if the URL is not supported by the Agent. + """ + user_supplied_host = ddconfig._trace_agent_hostname is not None + user_supplied_port = ddconfig._trace_agent_port is not None + + url = ddconfig._trace_agent_url + + if not url: + if user_supplied_host or user_supplied_port: + host = ddconfig._trace_agent_hostname or DEFAULT_HOSTNAME + port = ddconfig._trace_agent_port or DEFAULT_TRACE_PORT + if is_ipv6_hostname(host): + host = "[{}]".format(host) + url = "http://%s:%s" % (host, port) + elif os.path.exists("/var/run/datadog/apm.socket"): + url = "unix://%s" % (DEFAULT_UNIX_TRACE_PATH) + else: + url = "http://{}:{}".format(DEFAULT_HOSTNAME, DEFAULT_TRACE_PORT) + + return url + + +def get_stats_url(): + # type: () -> str + user_supplied_host = ddconfig._stats_agent_hostname is not None + user_supplied_port = ddconfig._stats_agent_port is not None + + url = ddconfig._stats_agent_url + + if not url: + if user_supplied_host or user_supplied_port: + port = ddconfig._stats_agent_port or DEFAULT_STATS_PORT + host = ddconfig._stats_agent_hostname or DEFAULT_HOSTNAME + if is_ipv6_hostname(host): + host = "[{}]".format(host) + url = "udp://{}:{}".format(host, port) + elif os.path.exists("/var/run/datadog/dsd.socket"): + url = "unix://%s" % (DEFAULT_UNIX_DSD_PATH) + else: + url = "udp://{}:{}".format(DEFAULT_HOSTNAME, DEFAULT_STATS_PORT) + return url + + +def info(): + agent_url = get_trace_url() + _conn = get_connection(agent_url, timeout=ddconfig._agent_timeout_seconds) + try: + _conn.request("GET", "info", headers={"content-type": "application/json"}) + resp = _conn.getresponse() + data = resp.read() + finally: + _conn.close() + + if resp.status == 404: + # Remote configuration is not enabled or unsupported by the agent + return None + + if resp.status < 200 or resp.status >= 300: + log.warning("Unexpected error: HTTP error status %s, reason %s", resp.status, resp.reason) + return None + + return json.loads(data) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/assembly.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/assembly.py new file mode 100644 index 0000000..9d50299 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/assembly.py @@ -0,0 +1,274 @@ +# Grammar: +# +# ident ::= [a-zA-Z_][a-zA-Z0-9_]* +# number ::= [0-9]+ +# label ::= ident ":" +# label_ref ::= "@" ident +# string_ref ::= "$" ident +# try_block_begin ::= "try" label_ref ["lasti"]? +# try_block_end ::= "tried" +# opcode ::= [A-Z][A-Z0-9_]* +# bind_opcode_arg ::= "{" ident "}" +# opcode_arg ::= label_ref | string | number | bind_opcode_arg | +# instruction ::= opcode [opcode_arg]? +# line ::= label | try_block_begin | try_block_end | instruction + +import dis +import sys +from types import CodeType +import typing as t + +import bytecode as bc + + +if sys.version_info >= (3, 11): + ParsedLine = t.Union[bc.Instr, bc.Label, bc.TryBegin, bc.TryEnd] +else: + ParsedLine = t.Union[bc.Instr, bc.Label] + + +def relocate(instrs: bc.Bytecode, lineno: int) -> bc.Bytecode: + new_instrs = bc.Bytecode() + for i in instrs: + if isinstance(i, bc.Instr): + new_i = i.copy() + new_i.lineno = lineno + new_instrs.append(new_i) + else: + new_instrs.append(i) + return new_instrs + + +def transform_instruction(opcode: str, arg: t.Any) -> t.Tuple[str, t.Any]: + # Handle pseudo-instructions + if sys.version_info >= (3, 12): + if opcode.upper() == "LOAD_METHOD": + opcode = "LOAD_ATTR" + arg = (True, arg) + elif opcode.upper() == "LOAD_ATTR" and not isinstance(arg, tuple): + arg = (False, arg) + + return opcode, arg + + +class BindOpArg(bc.Label): + # We cannot have arbitrary objects in Bytecode, so we subclass Label + def __init__(self, name: str, arg: str, lineno: t.Optional[int] = None) -> None: + self.name = name + self.arg = arg + self.lineno = lineno + + def __call__(self, bind_args: t.Dict[str, t.Any], lineno: t.Optional[int] = None) -> bc.Instr: + return bc.Instr(self.name, bind_args[self.arg], lineno=lineno if lineno is not None else self.lineno) + + +class Assembly: + def __init__( + self, name: t.Optional[str] = None, filename: t.Optional[str] = None, lineno: t.Optional[int] = None + ) -> None: + self._labels: t.Dict[str, bc.Label] = {} + self._ref_labels: t.Dict[str, bc.Label] = {} + self._tb: t.Optional[bc.TryBegin] = None + self._instrs = bc.Bytecode() + self._instrs.name = name or "" + self._instrs.filename = filename or __file__ + self._lineno = lineno + self._bind_opargs: t.Dict[int, BindOpArg] = {} + + def parse_ident(self, text: str) -> str: + if not text.isidentifier(): + raise ValueError("invalid identifier %s" % text) + + return text + + def parse_number(self, text: str) -> t.Optional[int]: + try: + return int(text) + except ValueError: + return None + + def parse_label(self, line: str) -> t.Optional[bc.Label]: + if not line.endswith(":"): + return None + + label_ident = self.parse_ident(line[:-1]) + if label_ident in self._labels: + raise ValueError("label %s already defined" % label_ident) + + label = self._labels[label_ident] = self._ref_labels.pop(label_ident, None) or bc.Label() + + return label + + def parse_label_ref(self, text: str) -> t.Optional[bc.Label]: + if not text.startswith("@"): + return None + + label_ident = self.parse_ident(text[1:]) + + try: + return self._labels[label_ident] + except KeyError: + try: + return self._ref_labels[label_ident] + except KeyError: + label = self._ref_labels[label_ident] = bc.Label() + return label + + def parse_string_ref(self, text: str) -> t.Optional[str]: + if not text.startswith("$"): + return None + + return self.parse_ident(text[1:]) + + if sys.version_info >= (3, 11): + + def parse_try_begin(self, line: str) -> t.Optional[bc.TryBegin]: + try: + head, label_ref, *lasti = line.split(maxsplit=2) + except ValueError: + return None + + if head != "try": + return None + + if self._tb is not None: + raise ValueError("cannot start try block while another is open") + + label = self.parse_label_ref(label_ref) + if label is None: + raise ValueError("invalid label reference for try block") + + tb = self._tb = bc.TryBegin(label, push_lasti=bool(lasti)) + + return tb + + def parse_try_end(self, line: str) -> t.Optional[bc.TryEnd]: + if line != "tried": + return None + + if self._tb is None: + raise ValueError("cannot end try block while none is open") + + end = bc.TryEnd(self._tb) + + self._tb = None + + return end + + def parse_opcode(self, text: str) -> str: + opcode = text.upper() + if opcode not in dis.opmap: + raise ValueError("unknown opcode %s" % opcode) + + return opcode + + def parse_expr(self, text: str) -> t.Any: + frame = sys._getframe(1) + + _globals = frame.f_globals.copy() + _globals["asm"] = bc + + return eval(text, _globals, frame.f_locals) # nosec + + def parse_opcode_arg(self, text: str) -> t.Union[bc.Label, str, int, t.Any]: + if not text: + return bc.UNSET + + return ( + self.parse_label_ref(text) + or self.parse_string_ref(text) + or self.parse_number(text) + or self.parse_expr(text) + ) + + def parse_bind_opcode_arg(self, text: str) -> t.Optional[str]: + if not text.startswith("{") or not text.endswith("}"): + return None + + return text[1:-1] + + def parse_instruction(self, line: str) -> t.Optional[t.Union[bc.Instr, BindOpArg]]: + opcode, *args = line.split(maxsplit=1) + + arg = "" + if args: + (arg,) = args + bind_arg = self.parse_bind_opcode_arg(arg) + if bind_arg is not None: + return BindOpArg(self.parse_opcode(opcode), bind_arg, lineno=self._lineno) + + return bc.Instr( + *transform_instruction(self.parse_opcode(opcode), self.parse_opcode_arg(arg)), lineno=self._lineno + ) + + def parse_line(self, line: str) -> ParsedLine: + if sys.version_info >= (3, 11): + entry = ( + self.parse_label(line) + or self.parse_try_begin(line) + or self.parse_try_end(line) + or self.parse_instruction(line) + ) + else: + entry = self.parse_label(line) or self.parse_instruction(line) + + if entry is None: + raise ValueError("invalid line %s" % line) + + return entry + + def _validate(self) -> None: + if self._ref_labels: + raise ValueError("undefined labels: %s" % ", ".join(self._ref_labels)) + + def parse(self, asm: str) -> None: + for line in (_.strip() for _ in asm.splitlines()): + if not line or line.startswith("#"): + continue + + entry = self.parse_line(line) + if isinstance(entry, BindOpArg): + self._bind_opargs[len(self._instrs)] = entry + + self._instrs.append(entry) + + self._validate() + + def bind(self, bind_args: t.Optional[t.Dict[str, t.Any]] = None, lineno: t.Optional[int] = None) -> bc.Bytecode: + if not self._bind_opargs: + if lineno is not None: + return relocate(self._instrs, lineno) + return self._instrs + + if bind_args is None: + raise ValueError("missing bind arguments") + + # If we have bind opargs, the bytecode we parsed has some + # BindOpArg placeholders that need to be resolved. Therefore, we + # make a copy of the parsed bytecode and replace the BindOpArg + # placeholders with the resolved values. + instrs = bc.Bytecode(self._instrs) + for i, arg in self._bind_opargs.items(): + instrs[i] = arg(bind_args, lineno=lineno) + + return relocate(instrs, lineno) if lineno is not None else instrs + + def compile(self, bind_args: t.Optional[t.Dict[str, t.Any]] = None, lineno: t.Optional[int] = None) -> CodeType: + return self.bind(bind_args, lineno=lineno).to_code() + + def _label_ident(self, label: bc.Label) -> str: + return next(ident for ident, l in self._labels.items() if l is label) # noqa: E741 + + def dis(self) -> None: + for entry in self._instrs: + if isinstance(entry, bc.Instr): + print(f" {entry.name:<32}{entry.arg if entry.arg is not None else ''}") + elif isinstance(entry, BindOpArg): + print(f" {entry.name:<32}{{{entry.arg}}}") + elif isinstance(entry, bc.Label): + print(f"{self._label_ident(entry)}:") + elif isinstance(entry, bc.TryBegin): + print(f"try @{self._label_ident(entry.target)} (lasti={entry.push_lasti})") + + def __iter__(self) -> t.Iterator[bc.Instr]: + return iter(self._instrs) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/atexit.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/atexit.py new file mode 100644 index 0000000..0d778e1 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/atexit.py @@ -0,0 +1,79 @@ +# -*- encoding: utf-8 -*- +""" +An API to provide atexit functionalities +""" +from __future__ import absolute_import + +import atexit +import logging +import signal +import threading +import typing # noqa:F401 + +from ddtrace.internal.utils import signals + + +log = logging.getLogger(__name__) + + +if hasattr(atexit, "unregister"): + register = atexit.register + unregister = atexit.unregister +else: + # Hello Python 2! + _registry = [] # type: typing.List[typing.Tuple[typing.Callable[..., None], typing.Tuple, typing.Dict]] + + def _ddtrace_atexit(): + # type: (...) -> None + """Wrapper function that calls all registered function on normal program termination""" + global _registry + + # DEV: we make a copy of the registry to prevent hook execution from + # introducing new hooks, potentially causing an infinite loop. + for hook, args, kwargs in list(_registry): + try: + hook(*args, **kwargs) + except Exception: + # Mimic the behaviour of Python's atexit hooks. + log.exception("Error in atexit hook %r", hook) + + def register( + func, # type: typing.Callable[..., typing.Any] + *args, # type: typing.Any + **kwargs, # type: typing.Any + ): + # type: (...) -> typing.Callable[..., typing.Any] + """Register a function to be executed upon normal program termination""" + + _registry.append((func, args, kwargs)) + return func + + def unregister(func): + # type: (typing.Callable[..., typing.Any]) -> None + """ + Unregister an exit function which was previously registered using + atexit.register. + If the function was not registered, it is ignored. + """ + global _registry + + _registry = [(f, args, kwargs) for f, args, kwargs in _registry if f != func] + + atexit.register(_ddtrace_atexit) + + +# registers a function to be called when an exit signal (TERM or INT) or received. +def register_on_exit_signal(f): + def handle_exit(sig, frame): + f() + + if threading.current_thread() is threading.main_thread(): + try: + signals.handle_signal(signal.SIGTERM, handle_exit) + signals.handle_signal(signal.SIGINT, handle_exit) + except Exception: + # We catch a general exception here because we don't know + # what might go wrong, but we don't want to stop + # normal program execution based upon failing to register + # a signal handler. + log.debug("Encountered an exception while registering a signal", exc_info=True) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/__init__.py new file mode 100644 index 0000000..f7f0cf9 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/__init__.py @@ -0,0 +1,11 @@ +""" +CI Visibility Service. +This is normally started automatically by including ``ddtrace=1`` or ``--ddtrace`` in the pytest run command. +To start the service manually, invoke the ``enable`` method:: + from ddtrace.internal.ci_visibility import CIVisibility + CIVisibility.enable() +""" +from .recorder import CIVisibility + + +__all__ = ["CIVisibility"] diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/constants.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/constants.py new file mode 100644 index 0000000..8a33f59 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/constants.py @@ -0,0 +1,66 @@ +from enum import IntEnum + + +SUITE = "suite" +TEST = "test" + +EVENT_TYPE = "type" + + +# Test Session ID +SESSION_ID = "test_session_id" + +# Test Module ID +MODULE_ID = "test_module_id" + +# Test Suite ID +SUITE_ID = "test_suite_id" + +# Event type signals for CI Visibility +SESSION_TYPE = "test_session_end" + +MODULE_TYPE = "test_module_end" + +SUITE_TYPE = "test_suite_end" + +# Agentless and EVP-specific constants +COVERAGE_TAG_NAME = "test.coverage" + +EVP_PROXY_AGENT_BASE_PATH = "/evp_proxy/v2" +EVP_PROXY_AGENT_ENDPOINT = "{}/api/v2/citestcycle".format(EVP_PROXY_AGENT_BASE_PATH) +AGENTLESS_ENDPOINT = "api/v2/citestcycle" +AGENTLESS_COVERAGE_ENDPOINT = "api/v2/citestcov" +AGENTLESS_API_KEY_HEADER_NAME = "dd-api-key" +AGENTLESS_APP_KEY_HEADER_NAME = "dd-application-key" +EVP_NEEDS_APP_KEY_HEADER_NAME = "X-Datadog-NeedsAppKey" +EVP_NEEDS_APP_KEY_HEADER_VALUE = "true" +EVP_PROXY_COVERAGE_ENDPOINT = "{}/{}".format(EVP_PROXY_AGENT_BASE_PATH, AGENTLESS_COVERAGE_ENDPOINT) +EVP_SUBDOMAIN_HEADER_API_VALUE = "api" +EVP_SUBDOMAIN_HEADER_COVERAGE_VALUE = "citestcov-intake" +EVP_SUBDOMAIN_HEADER_EVENT_VALUE = "citestcycle-intake" +EVP_SUBDOMAIN_HEADER_NAME = "X-Datadog-EVP-Subdomain" +AGENTLESS_BASE_URL = "https://citestcycle-intake" +AGENTLESS_COVERAGE_BASE_URL = "https://citestcov-intake" +AGENTLESS_DEFAULT_SITE = "datadoghq.com" +GIT_API_BASE_PATH = "/api/v2/git" +SETTING_ENDPOINT = "/api/v2/libraries/tests/services/setting" +SKIPPABLE_ENDPOINT = "/api/v2/ci/tests/skippable" + +# Intelligent Test Runner constants +ITR_UNSKIPPABLE_REASON = "datadog_itr_unskippable" +SKIPPED_BY_ITR_REASON = "Skipped by Datadog Intelligent Test Runner" + +# Tracer configuration defaults: +TRACER_PARTIAL_FLUSH_MIN_SPANS = 1 + + +class REQUESTS_MODE(IntEnum): + AGENTLESS_EVENTS = 0 + EVP_PROXY_EVENTS = 1 + TRACES = 2 + + +# Miscellaneous constants +CUSTOM_CONFIGURATIONS_PREFIX = "test.configuration" + +CIVISIBILITY_LOG_FILTER_RE = r"^ddtrace\.(contrib\.(coverage|pytest|unittest)|internal\.ci_visibility|ext\.git)" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/coverage.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/coverage.py new file mode 100644 index 0000000..e72e767 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/coverage.py @@ -0,0 +1,163 @@ +from itertools import groupby +import json +from typing import Dict # noqa:F401 +from typing import Iterable # noqa:F401 +from typing import List # noqa:F401 +from typing import Optional # noqa:F401 +from typing import Tuple # noqa:F401 + +import ddtrace +from ddtrace.internal.ci_visibility.constants import COVERAGE_TAG_NAME +from ddtrace.internal.ci_visibility.utils import get_relative_or_absolute_path_for_path +from ddtrace.internal.logger import get_logger + + +log = get_logger(__name__) +_global_relative_file_paths_for_cov: Dict[str, Dict[str, str]] = {} + +try: + from coverage import Coverage + from coverage import version_info as coverage_version + + # this public attribute became private after coverage==6.3 + EXECUTE_ATTR = "_execute" if coverage_version > (6, 3) else "execute" +except ImportError: + Coverage = None # type: ignore[misc,assignment] + EXECUTE_ATTR = "" + + +def is_coverage_available(): + return Coverage is not None + + +def _initialize_coverage(root_dir): + coverage_kwargs = { + "data_file": None, + "source": [root_dir], + "config_file": False, + "omit": [ + "*/site-packages/*", + ], + } + cov_object = Coverage(**coverage_kwargs) + cov_object.set_option("run:parallel", True) + return cov_object + + +def _start_coverage(root_dir: str): + coverage = _initialize_coverage(root_dir) + coverage.start() + return coverage + + +def _stop_coverage(module): + if _module_has_dd_coverage_enabled(module): + module._dd_coverage.stop() + module._dd_coverage.erase() + del module._dd_coverage + + +def _module_has_dd_coverage_enabled(module, silent_mode: bool = False) -> bool: + if not hasattr(module, "_dd_coverage"): + if not silent_mode: + log.warning("Datadog Coverage has not been initiated") + return False + return True + + +def _coverage_has_valid_data(coverage_data: Coverage, silent_mode: bool = False) -> bool: + if not coverage_data._collector or len(coverage_data._collector.data) == 0: + if not silent_mode: + log.warning("No coverage collector or data found for item") + return False + return True + + +def _switch_coverage_context(coverage_data: Coverage, unique_test_name: str): + if not _coverage_has_valid_data(coverage_data, silent_mode=True): + return + coverage_data._collector.data.clear() # type: ignore[union-attr] + try: + coverage_data.switch_context(unique_test_name) + except RuntimeError as err: + log.warning(err) + + +def _report_coverage_to_span(coverage_data: Coverage, span: ddtrace.Span, root_dir: str): + span_id = str(span.trace_id) + if not _coverage_has_valid_data(coverage_data): + return + span.set_tag_str( + COVERAGE_TAG_NAME, + build_payload(coverage_data, root_dir, span_id), + ) + coverage_data._collector.data.clear() # type: ignore[union-attr] + + +def segments(lines: Iterable[int]) -> List[Tuple[int, int, int, int, int]]: + """Extract the relevant report data for a single file.""" + _segments = [] + for _key, g in groupby(enumerate(sorted(lines)), lambda x: x[1] - x[0]): + group = list(g) + start = group[0][1] + end = group[-1][1] + _segments.append((start, 0, end, 0, -1)) + + return _segments + + +def _lines(coverage: Coverage, context: Optional[str]) -> Dict[str, List[Tuple[int, int, int, int, int]]]: + if not coverage._collector or not coverage._collector.data: + return {} + + return { + k: segments(v.keys()) if isinstance(v, dict) else segments(v) # type: ignore + for k, v in list(coverage._collector.data.items()) + } + + +def build_payload(coverage: Coverage, root_dir: str, test_id: Optional[str] = None) -> str: + """ + Generate a CI Visibility coverage payload, formatted as follows: + + "files": [ + { + "filename": , + "segments": [ + [Int, Int, Int, Int, Int], # noqa:F401 + ] + }, + ... + ] + + For each segment of code for which there is coverage, there are always five integer values: + The first number indicates the start line of the code block (index starting in 1) + The second number indicates the start column of the code block (index starting in 1). Use value -1 if the + column is unknown. + The third number indicates the end line of the code block + The fourth number indicates the end column of the code block + The fifth number indicates the number of executions of the block + If the number is >0 then it indicates the number of executions + If the number is -1 then it indicates that the number of executions are unknown + + :param coverage: Coverage object containing coverage data + :param root_dir: the directory relative to which paths to covered files should be resolved + :param test_id: a unique identifier for the current test run + """ + root_dir_str = str(root_dir) + if root_dir_str not in _global_relative_file_paths_for_cov: + _global_relative_file_paths_for_cov[root_dir_str] = {} + files_data = [] + for filename, lines in _lines(coverage, test_id).items(): + if filename not in _global_relative_file_paths_for_cov[root_dir_str]: + _global_relative_file_paths_for_cov[root_dir_str][filename] = get_relative_or_absolute_path_for_path( + filename, root_dir_str + ) + if lines: + files_data.append( + {"filename": _global_relative_file_paths_for_cov[root_dir_str][filename], "segments": lines} + ) + else: + files_data.append({"filename": _global_relative_file_paths_for_cov[root_dir_str][filename]}) + + return json.dumps({"files": files_data}) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/encoder.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/encoder.py new file mode 100644 index 0000000..ad8fbfc --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/encoder.py @@ -0,0 +1,208 @@ +import json +import threading +from typing import TYPE_CHECKING # noqa:F401 +from uuid import uuid4 + +from ddtrace.ext import SpanTypes +from ddtrace.internal._encoding import BufferedEncoder +from ddtrace.internal._encoding import packb as msgpack_packb +from ddtrace.internal.ci_visibility.constants import COVERAGE_TAG_NAME +from ddtrace.internal.ci_visibility.constants import EVENT_TYPE +from ddtrace.internal.ci_visibility.constants import MODULE_ID +from ddtrace.internal.ci_visibility.constants import MODULE_TYPE +from ddtrace.internal.ci_visibility.constants import SESSION_ID +from ddtrace.internal.ci_visibility.constants import SESSION_TYPE +from ddtrace.internal.ci_visibility.constants import SUITE_ID +from ddtrace.internal.ci_visibility.constants import SUITE_TYPE +from ddtrace.internal.encoding import JSONEncoderV2 +from ddtrace.internal.writer.writer import NoEncodableSpansError + + +if TYPE_CHECKING: # pragma: no cover + from typing import Any # noqa:F401 + from typing import Dict # noqa:F401 + from typing import List # noqa:F401 + from typing import Optional # noqa:F401 + + from ..span import Span # noqa:F401 + + +class CIVisibilityEncoderV01(BufferedEncoder): + content_type = "application/msgpack" + ALLOWED_METADATA_KEYS = ("language", "library_version", "runtime-id", "env") + PAYLOAD_FORMAT_VERSION = 1 + TEST_SUITE_EVENT_VERSION = 1 + TEST_EVENT_VERSION = 2 + + def __init__(self, *args): + super(CIVisibilityEncoderV01, self).__init__() + self._lock = threading.RLock() + self._metadata = {} + self._init_buffer() + self._metadata = {} + + def __len__(self): + with self._lock: + return len(self.buffer) + + def set_metadata(self, metadata): + self._metadata.update(metadata) + + def _init_buffer(self): + with self._lock: + self.buffer = [] + + def put(self, spans): + with self._lock: + self.buffer.append(spans) + + def encode_traces(self, traces): + return self._build_payload(traces=traces) + + def encode(self): + with self._lock: + payload = self._build_payload(self.buffer) + self._init_buffer() + return payload + + def _build_payload(self, traces): + normalized_spans = [self._convert_span(span, trace[0].context.dd_origin) for trace in traces for span in trace] + self._metadata = {k: v for k, v in self._metadata.items() if k in self.ALLOWED_METADATA_KEYS} + # TODO: Split the events in several payloads as needed to avoid hitting the intake's maximum payload size. + return CIVisibilityEncoderV01._pack_payload( + {"version": self.PAYLOAD_FORMAT_VERSION, "metadata": {"*": self._metadata}, "events": normalized_spans} + ) + + @staticmethod + def _pack_payload(payload): + return msgpack_packb(payload) + + def _convert_span(self, span, dd_origin): + # type: (Span, str) -> Dict[str, Any] + sp = JSONEncoderV2._span_to_dict(span) + sp = JSONEncoderV2._normalize_span(sp) + sp["type"] = span.get_tag(EVENT_TYPE) or span.span_type + sp["duration"] = span.duration_ns + sp["meta"] = dict(sorted(span._meta.items())) + sp["metrics"] = dict(sorted(span._metrics.items())) + if dd_origin is not None: + sp["meta"].update({"_dd.origin": dd_origin}) + + sp = CIVisibilityEncoderV01._filter_ids(sp) + + version = CIVisibilityEncoderV01.TEST_SUITE_EVENT_VERSION + if span.get_tag(EVENT_TYPE) == "test": + version = CIVisibilityEncoderV01.TEST_EVENT_VERSION + + if span.span_type == "test": + event_type = span.get_tag(EVENT_TYPE) + else: + event_type = "span" + + return {"version": version, "type": event_type, "content": sp} + + @staticmethod + def _filter_ids(sp): + """ + Remove trace/span/parent IDs if non-test event, move session/module/suite IDs from meta to outer content layer. + """ + if sp["meta"].get(EVENT_TYPE) in [SESSION_TYPE, MODULE_TYPE, SUITE_TYPE]: + del sp["trace_id"] + del sp["span_id"] + del sp["parent_id"] + else: + sp["trace_id"] = int(sp.get("trace_id") or "1") + sp["parent_id"] = int(sp.get("parent_id") or "1") + sp["span_id"] = int(sp.get("span_id") or "1") + if sp["meta"].get(EVENT_TYPE) in [SESSION_TYPE, MODULE_TYPE, SUITE_TYPE, SpanTypes.TEST]: + test_session_id = sp["meta"].get(SESSION_ID) + if test_session_id: + sp[SESSION_ID] = int(test_session_id) + del sp["meta"][SESSION_ID] + if sp["meta"].get(EVENT_TYPE) in [MODULE_TYPE, SUITE_TYPE, SpanTypes.TEST]: + test_module_id = sp["meta"].get(MODULE_ID) + if test_module_id: + sp[MODULE_ID] = int(test_module_id) + del sp["meta"][MODULE_ID] + if sp["meta"].get(EVENT_TYPE) in [SUITE_TYPE, SpanTypes.TEST]: + test_suite_id = sp["meta"].get(SUITE_ID) + if test_suite_id: + sp[SUITE_ID] = int(test_suite_id) + del sp["meta"][SUITE_ID] + if COVERAGE_TAG_NAME in sp["meta"]: + del sp["meta"][COVERAGE_TAG_NAME] + return sp + + +class CIVisibilityCoverageEncoderV02(CIVisibilityEncoderV01): + PAYLOAD_FORMAT_VERSION = 2 + boundary = uuid4().hex + content_type = "multipart/form-data; boundary=%s" % boundary + itr_suite_skipping_mode = False + + def _set_itr_suite_skipping_mode(self, new_value): + self.itr_suite_skipping_mode = new_value + + def put(self, spans): + spans_with_coverage = [span for span in spans if COVERAGE_TAG_NAME in span.get_tags()] + if not spans_with_coverage: + raise NoEncodableSpansError() + return super(CIVisibilityCoverageEncoderV02, self).put(spans_with_coverage) + + def _build_coverage_attachment(self, data): + # type: (bytes) -> List[bytes] + return [ + b"--%s" % self.boundary.encode("utf-8"), + b'Content-Disposition: form-data; name="coverage1"; filename="coverage1.msgpack"', + b"Content-Type: application/msgpack", + b"", + data, + ] + + def _build_event_json_attachment(self): + # type: () -> List[bytes] + return [ + b"--%s" % self.boundary.encode("utf-8"), + b'Content-Disposition: form-data; name="event"; filename="event.json"', + b"Content-Type: application/json", + b"", + b'{"dummy":true}', + ] + + def _build_body(self, data): + # type: (bytes) -> List[bytes] + return ( + self._build_coverage_attachment(data) + + self._build_event_json_attachment() + + [b"--%s--" % self.boundary.encode("utf-8")] + ) + + def _build_data(self, traces): + # type: (List[List[Span]]) -> Optional[bytes] + normalized_covs = [ + self._convert_span(span, "") for trace in traces for span in trace if COVERAGE_TAG_NAME in span.get_tags() + ] + if not normalized_covs: + return None + # TODO: Split the events in several payloads as needed to avoid hitting the intake's maximum payload size. + return msgpack_packb({"version": self.PAYLOAD_FORMAT_VERSION, "coverages": normalized_covs}) + + def _build_payload(self, traces): + # type: (List[List[Span]]) -> Optional[bytes] + data = self._build_data(traces) + if not data: + return None + return b"\r\n".join(self._build_body(data)) + + def _convert_span(self, span, dd_origin): + # type: (Span, str) -> Dict[str, Any] + converted_span = { + "test_session_id": int(span.get_tag(SESSION_ID) or "1"), + "test_suite_id": int(span.get_tag(SUITE_ID) or "1"), + "files": json.loads(span.get_tag(COVERAGE_TAG_NAME))["files"], + } + + if not self.itr_suite_skipping_mode: + converted_span["span_id"] = span.span_id + + return converted_span diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/filters.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/filters.py new file mode 100644 index 0000000..a6e4db8 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/filters.py @@ -0,0 +1,40 @@ +from typing import TYPE_CHECKING # noqa:F401 +from typing import Dict # noqa:F401 +from typing import List # noqa:F401 +from typing import Optional # noqa:F401 +from typing import Union # noqa:F401 + +import ddtrace +from ddtrace.constants import AUTO_KEEP +from ddtrace.ext import SpanTypes +from ddtrace.ext import ci +from ddtrace.filters import TraceFilter + + +if TYPE_CHECKING: + from ddtrace import Span # noqa:F401 + + +class TraceCiVisibilityFilter(TraceFilter): + def __init__(self, tags, service): + # type: (Dict[Union[str, bytes], str], str) -> None + self._tags = tags + self._service = service + + def process_trace(self, trace): + # type: (List[Span]) -> Optional[List[Span]] + if not trace: + return trace + + local_root = trace[0]._local_root + if not local_root or local_root.span_type != SpanTypes.TEST: + return None + + local_root.context.dd_origin = ci.CI_APP_TEST_ORIGIN + local_root.context.sampling_priority = AUTO_KEEP + for span in trace: + span.set_tags(self._tags) + span.service = self._service + span.set_tag_str(ci.LIBRARY_VERSION, ddtrace.__version__) + + return trace diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/git_client.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/git_client.py new file mode 100644 index 0000000..e2eb4e8 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/git_client.py @@ -0,0 +1,481 @@ +from ctypes import c_int +from enum import IntEnum +import json +from multiprocessing import Process +from multiprocessing import Value +import os +import time +from typing import Dict # noqa:F401 +from typing import List # noqa:F401 +from typing import Optional # noqa:F401 +from typing import Tuple # noqa:F401 + +from ddtrace.ext import ci +from ddtrace.ext.git import _build_git_packfiles_with_details +from ddtrace.ext.git import _extract_clone_defaultremotename_with_details +from ddtrace.ext.git import _extract_latest_commits_with_details +from ddtrace.ext.git import _extract_upstream_sha +from ddtrace.ext.git import _get_rev_list_with_details +from ddtrace.ext.git import _is_shallow_repository_with_details +from ddtrace.ext.git import _unshallow_repository +from ddtrace.ext.git import _unshallow_repository_with_details +from ddtrace.ext.git import extract_commit_sha +from ddtrace.ext.git import extract_git_version +from ddtrace.ext.git import extract_remote_url +from ddtrace.internal.agent import get_trace_url +from ddtrace.internal.compat import JSONDecodeError +from ddtrace.internal.logger import get_logger +from ddtrace.internal.utils.retry import fibonacci_backoff_with_jitter + +from .. import compat +from .. import telemetry +from ..utils.http import Response +from ..utils.http import get_connection +from ..utils.http import verify_url +from ..utils.time import StopWatch +from .constants import AGENTLESS_API_KEY_HEADER_NAME +from .constants import AGENTLESS_DEFAULT_SITE +from .constants import EVP_PROXY_AGENT_BASE_PATH +from .constants import EVP_SUBDOMAIN_HEADER_API_VALUE +from .constants import EVP_SUBDOMAIN_HEADER_NAME +from .constants import GIT_API_BASE_PATH +from .constants import REQUESTS_MODE +from .telemetry.constants import ERROR_TYPES +from .telemetry.constants import GIT_TELEMETRY_COMMANDS +from .telemetry.git import record_git_command +from .telemetry.git import record_objects_pack_data +from .telemetry.git import record_objects_pack_request +from .telemetry.git import record_search_commits + + +log = get_logger(__name__) + +# this exists only for the purpose of mocking in tests +RESPONSE = None + +# we're only interested in uploading .pack files +PACK_EXTENSION = ".pack" + +DEFAULT_TIMEOUT = 20 + +DEFAULT_METADATA_UPLOAD_TIMEOUT = 120 + + +class METADATA_UPLOAD_STATUS(IntEnum): + PENDING = 0 + IN_PROCESS = 1 + SUCCESS = 2 + FAILED = 3 + UNNECESSARY = 4 + + +FINISHED_METADATA_UPLOAD_STATUSES = [ + METADATA_UPLOAD_STATUS.FAILED, + METADATA_UPLOAD_STATUS.SUCCESS, + METADATA_UPLOAD_STATUS.UNNECESSARY, +] + + +class CIVisibilityGitClient(object): + def __init__( + self, + api_key, + requests_mode=REQUESTS_MODE.AGENTLESS_EVENTS, + base_url="", + ): + # type: (str, int, str) -> None + self._serializer = CIVisibilityGitClientSerializerV1(api_key) + self._worker = None # type: Optional[Process] + self._response = RESPONSE + self._requests_mode = requests_mode + self._metadata_upload_status = Value(c_int, METADATA_UPLOAD_STATUS.PENDING, lock=True) + if self._requests_mode == REQUESTS_MODE.EVP_PROXY_EVENTS: + self._base_url = get_trace_url() + EVP_PROXY_AGENT_BASE_PATH + GIT_API_BASE_PATH + elif self._requests_mode == REQUESTS_MODE.AGENTLESS_EVENTS: + self._base_url = "https://api.{}{}".format(os.getenv("DD_SITE", AGENTLESS_DEFAULT_SITE), GIT_API_BASE_PATH) + + def upload_git_metadata(self, cwd=None): + # type: (Optional[str]) -> None + self._tags = ci.tags(cwd=cwd) + if self._worker is None: + self._worker = Process( + target=CIVisibilityGitClient._run_protocol, + args=(self._serializer, self._requests_mode, self._base_url, self._metadata_upload_status), + kwargs={"_tags": self._tags, "_response": self._response, "cwd": cwd, "log_level": log.level}, + ) + self._worker.start() + log.debug("Upload git metadata to URL %s started with PID %s", self._base_url, self._worker.pid) + else: + log.debug("git metadata upload worker already started: %s", self._worker) + + def metadata_upload_finished(self): + return self._metadata_upload_status.value in FINISHED_METADATA_UPLOAD_STATUSES + + def _wait_for_metadata_upload(self, timeout=DEFAULT_METADATA_UPLOAD_TIMEOUT): + log.debug("Waiting up to %s seconds for git metadata upload to finish", timeout) + with StopWatch() as stopwatch: + while not self.metadata_upload_finished(): + log.debug("Waited %s so far, status is %s", stopwatch.elapsed(), self._metadata_upload_status.value) + if stopwatch.elapsed() >= timeout: + raise TimeoutError( + "Timed out waiting for git metadata upload to complete after %s seconds" % timeout + ) + + if self._worker.exitcode is not None: + log.debug( + "git metadata process exited with exitcode %s but upload status is %s", + self._worker.exitcode, + self._metadata_upload_status.value, + ) + raise ValueError("git metadata process exited with exitcode %s", self._worker.exitcode) + + self._worker.join(timeout=1) + time.sleep(1) + log.debug("git metadata upload completed, waited %s seconds", stopwatch.elapsed()) + + def wait_for_metadata_upload_status(self): + # type: () -> METADATA_UPLOAD_STATUS + self._wait_for_metadata_upload() + return self._metadata_upload_status.value # type: ignore + + @classmethod + def _run_protocol( + cls, + serializer, # CIVisibilityGitClientSerializerV1 + requests_mode, # int + base_url, # str + _metadata_upload_status, # METADATA_UPLOAD_STATUS + _tags=None, # Optional[Dict[str, str]] + _response=None, # Optional[Response] + cwd=None, # Optional[str] + log_level=0, # int + ): + # type: (...) -> None + log.setLevel(log_level) + telemetry.telemetry_writer.enable() + _metadata_upload_status.value = METADATA_UPLOAD_STATUS.IN_PROCESS + try: + if _tags is None: + _tags = {} + repo_url = cls._get_repository_url(tags=_tags, cwd=cwd) + + # If all latest commits are known to our gitdb backend, assume that unshallowing is unnecessary + latest_commits = cls._get_latest_commits(cwd=cwd) + backend_commits = cls._search_commits( + requests_mode, base_url, repo_url, latest_commits, serializer, _response + ) + + if backend_commits is None: + log.debug("No initial backend commits found, returning early.") + _metadata_upload_status.value = METADATA_UPLOAD_STATUS.FAILED + return + + commits_not_in_backend = list(set(latest_commits) - set(backend_commits)) + + if len(commits_not_in_backend) == 0: + log.debug("All latest commits found in backend, skipping metadata upload") + _metadata_upload_status.value = METADATA_UPLOAD_STATUS.UNNECESSARY + return + + if cls._is_shallow_repository(cwd=cwd) and extract_git_version(cwd=cwd) >= (2, 27, 0): + log.debug("Shallow repository detected on git > 2.27 detected, unshallowing") + try: + cls._unshallow_repository(cwd=cwd) + except ValueError: + log.warning("Failed to unshallow repository, continuing to send pack data", exc_info=True) + + latest_commits = cls._get_latest_commits(cwd=cwd) + backend_commits = cls._search_commits( + requests_mode, base_url, repo_url, latest_commits, serializer, _response + ) + if backend_commits is None: + log.debug("No backend commits found, returning early.") + _metadata_upload_status.value = METADATA_UPLOAD_STATUS.FAILED + return + + commits_not_in_backend = list(set(latest_commits) - set(backend_commits)) + + rev_list = cls._get_filtered_revisions( + excluded_commits=backend_commits, included_commits=commits_not_in_backend, cwd=cwd + ) + if rev_list: + log.debug("Building and uploading packfiles for revision list: %s", rev_list) + with _build_git_packfiles_with_details(rev_list, cwd=cwd) as (packfiles_prefix, packfiles_details): + record_git_command( + GIT_TELEMETRY_COMMANDS.PACK_OBJECTS, packfiles_details.duration, packfiles_details.returncode + ) + if packfiles_details.returncode == 0: + if cls._upload_packfiles( + requests_mode, base_url, repo_url, packfiles_prefix, serializer, _response, cwd=cwd + ): + _metadata_upload_status.value = METADATA_UPLOAD_STATUS.SUCCESS + return + _metadata_upload_status.value = METADATA_UPLOAD_STATUS.FAILED + log.warning("Failed to upload git metadata packfiles") + else: + log.debug("Revision list empty, no packfiles to build and upload") + _metadata_upload_status.value = METADATA_UPLOAD_STATUS.SUCCESS + record_objects_pack_data(0, 0) + finally: + telemetry.telemetry_writer.periodic(force_flush=True) + + @classmethod + def _get_repository_url(cls, tags=None, cwd=None): + # type: (Optional[Dict[str, str]], Optional[str]) -> str + if tags is None: + tags = {} + result = tags.get(ci.git.REPOSITORY_URL, "") + if not result: + result = extract_remote_url(cwd=cwd) + return result + + @classmethod + def _get_latest_commits(cls, cwd=None): + # type: (Optional[str]) -> List[str] + latest_commits, stderr, duration, returncode = _extract_latest_commits_with_details(cwd=cwd) + record_git_command(GIT_TELEMETRY_COMMANDS.GET_LOCAL_COMMITS, duration, returncode) + if returncode == 0: + return latest_commits.split("\n") if latest_commits else [] + raise ValueError(stderr) + + @classmethod + def _search_commits(cls, requests_mode, base_url, repo_url, latest_commits, serializer, _response): + # type: (int, str, str, List[str], CIVisibilityGitClientSerializerV1, Optional[Response]) -> Optional[List[str]] + payload = serializer.search_commits_encode(repo_url, latest_commits) + request_error = None + with StopWatch() as stopwatch: + try: + try: + response = _response or cls._do_request( + requests_mode, base_url, "/search_commits", payload, serializer + ) + except TimeoutError: + request_error = ERROR_TYPES.TIMEOUT + log.warning("Timeout searching commits") + return None + + if response.status >= 400: + request_error = ERROR_TYPES.CODE_4XX if response.status < 500 else ERROR_TYPES.CODE_5XX + log.debug( + "Error searching commits, response status code: %s , response body: %s", + response.status, + response.body, + ) + log.debug("Response body: %s", response.body) + return None + + try: + result = serializer.search_commits_decode(response.body) + return result + except JSONDecodeError: + request_error = ERROR_TYPES.BAD_JSON + log.warning("Error searching commits, response not parsable: %s", response.body) + return None + finally: + record_search_commits(stopwatch.elapsed() * 1000, error=request_error) + + @classmethod + @fibonacci_backoff_with_jitter(attempts=5, until=lambda result: isinstance(result, Response)) + def _do_request(cls, requests_mode, base_url, endpoint, payload, serializer, headers=None, timeout=DEFAULT_TIMEOUT): + # type: (int, str, str, str, CIVisibilityGitClientSerializerV1, Optional[dict], int) -> Response + url = "{}/repository{}".format(base_url, endpoint) + _headers = { + AGENTLESS_API_KEY_HEADER_NAME: serializer.api_key, + } + if requests_mode == REQUESTS_MODE.EVP_PROXY_EVENTS: + _headers = { + EVP_SUBDOMAIN_HEADER_NAME: EVP_SUBDOMAIN_HEADER_API_VALUE, + } + if headers is not None: + _headers.update(headers) + try: + parsed_url = verify_url(url) + url_path = parsed_url.path + conn = get_connection(url, timeout=timeout) + log.debug("Sending request: %s %s %s %s", "POST", url_path, payload, _headers) + conn.request("POST", url_path, payload, _headers) + resp = compat.get_connection_response(conn) + log.debug("Response status: %s", resp.status) + result = Response.from_http_response(resp) + finally: + conn.close() + return result + + @classmethod + def _get_filtered_revisions(cls, excluded_commits, included_commits=None, cwd=None): + # type: (List[str], Optional[List[str]], Optional[str]) -> str + filtered_revisions, _, duration, returncode = _get_rev_list_with_details( + excluded_commits, included_commits, cwd=cwd + ) + record_git_command(GIT_TELEMETRY_COMMANDS.GET_OBJECTS, duration, returncode if returncode != 0 else None) + return filtered_revisions + + @classmethod + def _upload_packfiles(cls, requests_mode, base_url, repo_url, packfiles_prefix, serializer, _response, cwd=None): + # type: (int, str, str, str, CIVisibilityGitClientSerializerV1, Optional[Response], Optional[str]) -> bool + + sha = extract_commit_sha(cwd=cwd) + parts = packfiles_prefix.split("/") + directory = "/".join(parts[:-1]) + rand = parts[-1] + packfiles_uploaded_count = 0 + packfiles_uploaded_bytes = 0 + for filename in os.listdir(directory): + if not filename.startswith(rand) or not filename.endswith(PACK_EXTENSION): + continue + file_path = os.path.join(directory, filename) + content_type, payload = serializer.upload_packfile_encode(repo_url, sha, file_path) + headers = {"Content-Type": content_type} + with StopWatch() as stopwatch: + error_type = None + try: + response = _response or cls._do_request( + requests_mode, base_url, "/packfile", payload, serializer, headers=headers + ) + if response.status == 204: + packfiles_uploaded_count += 1 + packfiles_uploaded_bytes += len(payload) + elif response.status >= 400: + log.debug( + "Git packfile upload request for file %s (sizee: %s) failed with status: %s", + filename, + len(payload), + response.status, + ) + error_type = ERROR_TYPES.CODE_4XX if response.status < 500 else ERROR_TYPES.CODE_5XX + except ConnectionRefusedError: + error_type = ERROR_TYPES.NETWORK + log.debug("Git packfile upload request for file %s failed: connection refused", filename) + except TimeoutError: + error_type = ERROR_TYPES.TIMEOUT + log.debug("Git packfile upload request for file %s (size: %s) timed out", filename, len(payload)) + finally: + duration = stopwatch.elapsed() * 1000 # StopWatch is in seconds + record_objects_pack_request(duration, error_type) + + record_objects_pack_data(packfiles_uploaded_count, packfiles_uploaded_bytes) + log.debug( + "Git packfiles upload succeeded, file count: %s, total size: %s", + packfiles_uploaded_count, + packfiles_uploaded_bytes, + ) + + return response.status == 204 + + @classmethod + def _is_shallow_repository(cls, cwd=None): + # type () -> bool + is_shallow_repository, duration, returncode = _is_shallow_repository_with_details(cwd=cwd) + record_git_command(GIT_TELEMETRY_COMMANDS.CHECK_SHALLOW, duration, returncode if returncode != 0 else None) + return is_shallow_repository + + @classmethod + def _unshallow_repository(cls, cwd=None): + # type () -> None + with StopWatch() as stopwatch: + error_exit_code = None + try: + remote, stderr, _, exit_code = _extract_clone_defaultremotename_with_details(cwd=cwd) + if exit_code != 0: + error_exit_code = exit_code + log.debug("Failed to get default remote: %s", stderr) + return + + try: + CIVisibilityGitClient._unshallow_repository_to_local_head(remote, cwd=cwd) + return + except ValueError as e: + log.debug("Could not unshallow repository to local head: %s", e) + + try: + CIVisibilityGitClient._unshallow_repository_to_upstream(remote, cwd=cwd) + return + except ValueError as e: + log.debug("Could not unshallow to upstream: %s", e) + + log.debug("Unshallowing to default") + _, unshallow_error, _, exit_code = _unshallow_repository_with_details(cwd=cwd, repo=remote) + if exit_code == 0: + log.debug("Unshallowing to default successful") + return + log.debug("Unshallowing failed: %s", unshallow_error) + error_exit_code = exit_code + return + finally: + duration = stopwatch.elapsed() * 1000 # StopWatch measures elapsed time in seconds + record_git_command(GIT_TELEMETRY_COMMANDS.UNSHALLOW, duration, error_exit_code) + + @classmethod + def _unshallow_repository_to_local_head(cls, remote, cwd=None): + # type (str, Optional[str) -> None + head = extract_commit_sha(cwd=cwd) + log.debug("Unshallowing to local head %s", head) + _unshallow_repository(cwd=cwd, repo=remote, refspec=head) + log.debug("Unshallowing to local head successful") + + @classmethod + def _unshallow_repository_to_upstream(cls, remote, cwd=None): + # type (str, Optional[str) -> None + upstream = _extract_upstream_sha(cwd=cwd) + log.debug("Unshallowing to upstream %s", upstream) + _unshallow_repository(cwd=cwd, repo=remote, refspec=upstream) + log.debug("Unshallowing to upstream") + + +class CIVisibilityGitClientSerializerV1(object): + def __init__(self, api_key): + # type: (str) -> None + self.api_key = api_key + + def search_commits_encode(self, repo_url, latest_commits): + # type: (str, list[str]) -> str + return json.dumps( + {"meta": {"repository_url": repo_url}, "data": [{"id": sha, "type": "commit"} for sha in latest_commits]} + ) + + def search_commits_decode(self, payload): + # type: (str) -> List[str] + res = [] # type: List[str] + try: + if isinstance(payload, bytes): + parsed = json.loads(payload.decode()) + else: + parsed = json.loads(payload) + return [item["id"] for item in parsed["data"] if item["type"] == "commit"] + except KeyError: + log.warning("Expected information not found in search_commits response", exc_info=True) + except JSONDecodeError: + log.warning("Unexpected decode error in search_commits response", exc_info=True) + + return res + + def upload_packfile_encode(self, repo_url, sha, file_path): + # type: (str, str, str) -> Tuple[str, bytes] + BOUNDARY = b"----------boundary------" + CRLF = b"\r\n" + body = [] + metadata = {"data": {"id": sha, "type": "commit"}, "meta": {"repository_url": repo_url}} + body.extend( + [ + b"--" + BOUNDARY, + b'Content-Disposition: form-data; name="pushedSha"', + b"Content-Type: application/json", + b"", + json.dumps(metadata).encode("utf-8"), + ] + ) + file_name = os.path.basename(file_path) + f = open(file_path, "rb") + file_content = f.read() + f.close() + body.extend( + [ + b"--" + BOUNDARY, + b'Content-Disposition: form-data; name="packfile"; filename="%s"' % file_name.encode("utf-8"), + b"Content-Type: application/octet-stream", + b"", + file_content, + ] + ) + body.extend([b"--" + BOUNDARY + b"--", b""]) + return "multipart/form-data; boundary=%s" % BOUNDARY.decode("utf-8"), CRLF.join(body) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/recorder.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/recorder.py new file mode 100644 index 0000000..311ad29 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/recorder.py @@ -0,0 +1,600 @@ +from collections import defaultdict +import json +import os +import socket +from typing import TYPE_CHECKING # noqa:F401 +from typing import NamedTuple # noqa:F401 +from uuid import uuid4 + +from ddtrace import Tracer +from ddtrace import config as ddconfig +from ddtrace.contrib import trace_utils +from ddtrace.ext import ci +from ddtrace.ext import test +from ddtrace.internal import atexit +from ddtrace.internal import compat +from ddtrace.internal import telemetry +from ddtrace.internal.agent import get_connection +from ddtrace.internal.agent import get_trace_url +from ddtrace.internal.ci_visibility.coverage import is_coverage_available +from ddtrace.internal.ci_visibility.filters import TraceCiVisibilityFilter +from ddtrace.internal.compat import JSONDecodeError +from ddtrace.internal.compat import parse +from ddtrace.internal.logger import get_logger +from ddtrace.internal.service import Service +from ddtrace.internal.utils.formats import asbool +from ddtrace.internal.writer.writer import Response +from ddtrace.provider import CIContextProvider + +from .. import agent +from ..utils.http import verify_url +from ..utils.time import StopWatch +from .constants import AGENTLESS_API_KEY_HEADER_NAME +from .constants import AGENTLESS_DEFAULT_SITE +from .constants import CUSTOM_CONFIGURATIONS_PREFIX +from .constants import EVP_PROXY_AGENT_BASE_PATH +from .constants import EVP_SUBDOMAIN_HEADER_API_VALUE +from .constants import EVP_SUBDOMAIN_HEADER_EVENT_VALUE +from .constants import EVP_SUBDOMAIN_HEADER_NAME +from .constants import REQUESTS_MODE +from .constants import SETTING_ENDPOINT +from .constants import SKIPPABLE_ENDPOINT +from .constants import SUITE +from .constants import TEST +from .constants import TRACER_PARTIAL_FLUSH_MIN_SPANS +from .git_client import METADATA_UPLOAD_STATUS +from .git_client import CIVisibilityGitClient +from .telemetry.constants import ERROR_TYPES +from .telemetry.git import record_settings +from .writer import CIVisibilityWriter + + +if TYPE_CHECKING: # pragma: no cover + from typing import Any # noqa:F401 + from typing import DefaultDict # noqa:F401 + from typing import Dict # noqa:F401 + from typing import List # noqa:F401 + from typing import Optional # noqa:F401 + from typing import Tuple # noqa:F401 + + from ddtrace.settings import IntegrationConfig # noqa:F401 + +log = get_logger(__name__) + +DEFAULT_TIMEOUT = 15 + +_CIVisibilitySettings = NamedTuple( + "_CIVisibilitySettings", + [("coverage_enabled", bool), ("skipping_enabled", bool), ("require_git", bool), ("itr_enabled", bool)], +) + + +class CIVisibilityAuthenticationException(Exception): + pass + + +def _extract_repository_name_from_url(repository_url): + # type: (str) -> str + try: + return parse.urlparse(repository_url).path.rstrip(".git").rpartition("/")[-1] + except ValueError: + # In case of parsing error, default to repository url + log.warning("Repository name cannot be parsed from repository_url: %s", repository_url) + return repository_url + + +def _get_git_repo(): + # this exists only for the purpose of patching in tests + return None + + +def _get_custom_configurations(): + # type () -> dict + custom_configurations = {} + for tag, value in ddconfig.tags.items(): + if tag.startswith(CUSTOM_CONFIGURATIONS_PREFIX): + custom_configurations[tag.replace("%s." % CUSTOM_CONFIGURATIONS_PREFIX, "", 1)] = value + + return custom_configurations + + +def _do_request(method, url, payload, headers): + # type: (str, str, str, Dict) -> Response + try: + parsed_url = verify_url(url) + url_path = parsed_url.path + conn = get_connection(url, timeout=DEFAULT_TIMEOUT) + log.debug("Sending request: %s %s %s %s", method, url_path, payload, headers) + conn.request("POST", url_path, payload, headers) + resp = compat.get_connection_response(conn) + log.debug("Response status: %s", resp.status) + result = Response.from_http_response(resp) + finally: + conn.close() + return result + + +class CIVisibility(Service): + _instance = None # type: Optional[CIVisibility] + enabled = False + _test_suites_to_skip = None # type: Optional[List[str]] + _tests_to_skip = defaultdict(list) # type: DefaultDict[str, List[str]] + + def __init__(self, tracer=None, config=None, service=None): + # type: (Optional[Tracer], Optional[IntegrationConfig], Optional[str]) -> None + super(CIVisibility, self).__init__() + + telemetry.telemetry_writer.enable() + + if tracer: + self.tracer = tracer + else: + if asbool(os.getenv("_DD_CIVISIBILITY_USE_CI_CONTEXT_PROVIDER")): + # Create a new CI tracer + self.tracer = Tracer(context_provider=CIContextProvider()) + else: + self.tracer = Tracer() + + # Partial traces are required for ITR to work in suite-level skipping for long test sessions, but we + # assume that a tracer is already configured if it's been passed in. + self.tracer.configure(partial_flush_enabled=True, partial_flush_min_spans=TRACER_PARTIAL_FLUSH_MIN_SPANS) + + self._configurations = ci._get_runtime_and_os_metadata() + custom_configurations = _get_custom_configurations() + if custom_configurations: + self._configurations["custom"] = custom_configurations + + self._api_key = os.getenv("_CI_DD_API_KEY", os.getenv("DD_API_KEY")) + + self._dd_site = os.getenv("DD_SITE", AGENTLESS_DEFAULT_SITE) + self._suite_skipping_mode = asbool(os.getenv("_DD_CIVISIBILITY_ITR_SUITE_MODE", default=False)) + self.config = config # type: Optional[IntegrationConfig] + self._tags = ci.tags(cwd=_get_git_repo()) # type: Dict[str, str] + self._service = service + self._codeowners = None + self._root_dir = None + self._should_upload_git_metadata = True + + int_service = None + if self.config is not None: + int_service = trace_utils.int_service(None, self.config) + # check if repository URL detected from environment or .git, and service name unchanged + if self._tags.get(ci.git.REPOSITORY_URL, None) and self.config and int_service == self.config._default_service: + self._service = _extract_repository_name_from_url(self._tags[ci.git.REPOSITORY_URL]) + elif self._service is None and int_service is not None: + self._service = int_service + + if ddconfig._ci_visibility_agentless_enabled: + if not self._api_key: + raise EnvironmentError( + "DD_CIVISIBILITY_AGENTLESS_ENABLED is set, but DD_API_KEY is not set, so ddtrace " + "cannot be initialized." + ) + requests_mode_str = "agentless" + self._requests_mode = REQUESTS_MODE.AGENTLESS_EVENTS + elif self._agent_evp_proxy_is_available(): + requests_mode_str = "agent EVP proxy" + self._requests_mode = REQUESTS_MODE.EVP_PROXY_EVENTS + else: + requests_mode_str = "APM (some features will be disabled" + self._requests_mode = REQUESTS_MODE.TRACES + self._should_upload_git_metadata = False + + if self._should_upload_git_metadata: + self._git_client = CIVisibilityGitClient(api_key=self._api_key or "", requests_mode=self._requests_mode) + self._git_client.upload_git_metadata(cwd=_get_git_repo()) + + self._api_settings = self._check_enabled_features() + + self._collect_coverage_enabled = self._should_collect_coverage(self._api_settings.coverage_enabled) + + self._configure_writer(coverage_enabled=self._collect_coverage_enabled) + + log.info("Service: %s (env: %s)", self._service, ddconfig.env) + log.info("Requests mode: %s", requests_mode_str) + log.info("Git metadata upload enabled: %s", self._should_upload_git_metadata) + log.info("API-provided settings: coverage collection: %s", self._api_settings.coverage_enabled) + log.info( + "API-provided settings: Intelligent Test Runner: %s, test skipping: %s", + self._api_settings.itr_enabled, + self._api_settings.skipping_enabled, + ) + log.info("Detected configurations: %s", str(self._configurations)) + + try: + from ddtrace.internal.codeowners import Codeowners + + self._codeowners = Codeowners() + except ValueError: + log.warning("CODEOWNERS file is not available") + except Exception: + log.warning("Failed to load CODEOWNERS", exc_info=True) + + @staticmethod + def _should_collect_coverage(coverage_enabled_by_api): + if not coverage_enabled_by_api and not asbool( + os.getenv("_DD_CIVISIBILITY_ITR_FORCE_ENABLE_COVERAGE", default=False) + ): + return False + if not is_coverage_available(): + log.warning( + "CI Visibility code coverage tracking is enabled, but the `coverage` package is not installed." + "To use code coverage tracking, please install `coverage` from https://pypi.org/project/coverage/" + ) + return False + return True + + def _check_settings_api(self, url, headers): + # type: (str, Dict[str, str]) -> _CIVisibilitySettings + payload = { + "data": { + "id": str(uuid4()), + "type": "ci_app_test_service_libraries_settings", + "attributes": { + "test_level": SUITE if self._suite_skipping_mode else TEST, + "service": self._service, + "env": ddconfig.env, + "repository_url": self._tags.get(ci.git.REPOSITORY_URL), + "sha": self._tags.get(ci.git.COMMIT_SHA), + "branch": self._tags.get(ci.git.BRANCH), + "configurations": self._configurations, + }, + } + } + + sw = StopWatch() + sw.start() + try: + response = _do_request("POST", url, json.dumps(payload), headers) + except TimeoutError: + record_settings(sw.elapsed() * 1000, error=ERROR_TYPES.TIMEOUT) + raise + if response.status >= 400: + error_code = ERROR_TYPES.CODE_4XX if response.status < 500 else ERROR_TYPES.CODE_5XX + record_settings(sw.elapsed() * 1000, error=error_code) + if response.status == 403: + raise CIVisibilityAuthenticationException() + raise ValueError("API response status code: %d", response.status) + try: + if isinstance(response.body, bytes): + parsed = json.loads(response.body.decode()) + else: + parsed = json.loads(response.body) + except JSONDecodeError: + record_settings(sw.elapsed() * 1000, error=ERROR_TYPES.BAD_JSON) + raise + + if "errors" in parsed and parsed["errors"][0] == "Not found": + record_settings(sw.elapsed() * 1000, error=ERROR_TYPES.UNKNOWN) + raise ValueError("Settings response contained an error, disabling Intelligent Test Runner") + + log.debug("Parsed API response: %s", parsed) + + try: + attributes = parsed["data"]["attributes"] + coverage_enabled = attributes["code_coverage"] + skipping_enabled = attributes["tests_skipping"] + require_git = attributes["require_git"] + itr_enabled = attributes.get("itr_enabled", False) + except KeyError: + record_settings(sw.elapsed() * 1000, error=ERROR_TYPES.UNKNOWN) + raise + + record_settings(sw.elapsed() * 1000, coverage_enabled, skipping_enabled, require_git, itr_enabled) + + return _CIVisibilitySettings(coverage_enabled, skipping_enabled, require_git, itr_enabled) + + def _check_enabled_features(self): + # type: () -> _CIVisibilitySettings + # DEV: Remove this ``if`` once ITR is in GA + _error_return_value = _CIVisibilitySettings(False, False, False, False) + + if not ddconfig._ci_visibility_intelligent_testrunner_enabled: + return _error_return_value + + if self._requests_mode == REQUESTS_MODE.EVP_PROXY_EVENTS: + url = get_trace_url() + EVP_PROXY_AGENT_BASE_PATH + SETTING_ENDPOINT + _headers = { + EVP_SUBDOMAIN_HEADER_NAME: EVP_SUBDOMAIN_HEADER_API_VALUE, + } + log.debug("Making EVP request to agent: url=%s, headers=%s", url, _headers) + elif self._requests_mode == REQUESTS_MODE.AGENTLESS_EVENTS: + if not self._api_key: + log.debug("Cannot make request to setting endpoint if API key is not set") + return _error_return_value + url = "https://api." + self._dd_site + SETTING_ENDPOINT + _headers = { + AGENTLESS_API_KEY_HEADER_NAME: self._api_key, + "Content-Type": "application/json", + } + else: + log.warning("Cannot make requests to setting endpoint if mode is not agentless or evp proxy") + return _error_return_value + + try: + settings = self._check_settings_api(url, _headers) + except CIVisibilityAuthenticationException: + # Authentication exception is handled during enable() to prevent the service from being used + raise + except Exception: + log.warning( + "Error checking Intelligent Test Runner API, disabling coverage collection and test skipping", + exc_info=True, + ) + return _error_return_value + + if settings.require_git: + log.info("Settings API requires git metadata, waiting for git metadata upload to complete") + try: + try: + if self._git_client.wait_for_metadata_upload_status() == METADATA_UPLOAD_STATUS.FAILED: + log.warning("Metadata upload failed, test skipping will be best effort") + except ValueError: + log.warning( + "Error waiting for git metadata upload, test skipping will be best effort", exc_info=True + ) + except TimeoutError: + log.warning("Timeout waiting for metadata upload, test skipping will be best effort") + + # The most recent API response overrides the first one + try: + settings = self._check_settings_api(url, _headers) + except Exception: + log.warning( + "Error checking Intelligent Test Runner API after git metadata upload," + " disabling coverage and test skipping", + exc_info=True, + ) + return _error_return_value + if settings.require_git: + log.warning("git metadata upload did not complete in time, test skipping will be best effort") + + return settings + + def _configure_writer(self, coverage_enabled=False, requests_mode=None): + writer = None + if requests_mode is None: + requests_mode = self._requests_mode + + if requests_mode == REQUESTS_MODE.AGENTLESS_EVENTS: + headers = {"dd-api-key": self._api_key} + writer = CIVisibilityWriter( + headers=headers, + coverage_enabled=coverage_enabled, + itr_suite_skipping_mode=self._suite_skipping_mode, + ) + elif requests_mode == REQUESTS_MODE.EVP_PROXY_EVENTS: + writer = CIVisibilityWriter( + intake_url=agent.get_trace_url(), + headers={EVP_SUBDOMAIN_HEADER_NAME: EVP_SUBDOMAIN_HEADER_EVENT_VALUE}, + use_evp=True, + coverage_enabled=coverage_enabled, + itr_suite_skipping_mode=self._suite_skipping_mode, + ) + if writer is not None: + self.tracer.configure(writer=writer) + + def _agent_evp_proxy_is_available(self): + # type: () -> bool + try: + info = agent.info() + except Exception: + info = None + + if info: + endpoints = info.get("endpoints", []) + if endpoints and any(EVP_PROXY_AGENT_BASE_PATH in endpoint for endpoint in endpoints): + return True + return False + + @classmethod + def test_skipping_enabled(cls): + if not cls.enabled or asbool(os.getenv("_DD_CIVISIBILITY_ITR_PREVENT_TEST_SKIPPING", default=False)): + return False + return cls._instance and cls._instance._api_settings.skipping_enabled + + def _fetch_tests_to_skip(self, skipping_mode): + # Make sure git uploading has finished + # this will block the thread until that happens + try: + try: + metadata_upload_status = self._git_client.wait_for_metadata_upload_status() + if metadata_upload_status not in [METADATA_UPLOAD_STATUS.SUCCESS, METADATA_UPLOAD_STATUS.UNNECESSARY]: + log.warning("git metadata upload was not successful, some tests may not be skipped") + except ValueError: + log.warning( + "Error waiting for metadata upload to complete while fetching tests to skip" + ", some tests may not be skipped", + exc_info=True, + ) + except TimeoutError: + log.debug("Timed out waiting for git metadata upload, some tests may not be skipped") + + payload = { + "data": { + "type": "test_params", + "attributes": { + "service": self._service, + "env": ddconfig.env, + "repository_url": self._tags.get(ci.git.REPOSITORY_URL), + "sha": self._tags.get(ci.git.COMMIT_SHA), + "configurations": self._configurations, + "test_level": skipping_mode, + }, + } + } + + _headers = { + "dd-api-key": self._api_key, + "Content-Type": "application/json", + } + if self._requests_mode == REQUESTS_MODE.EVP_PROXY_EVENTS: + url = get_trace_url() + EVP_PROXY_AGENT_BASE_PATH + SKIPPABLE_ENDPOINT + _headers = { + EVP_SUBDOMAIN_HEADER_NAME: EVP_SUBDOMAIN_HEADER_API_VALUE, + } + elif self._requests_mode == REQUESTS_MODE.AGENTLESS_EVENTS: + url = "https://api." + self._dd_site + SKIPPABLE_ENDPOINT + else: + log.warning("Cannot make requests to skippable endpoint if mode is not agentless or evp proxy") + return + + try: + response = _do_request("POST", url, json.dumps(payload), _headers) + except (TimeoutError, socket.timeout): + log.warning("Request timeout while fetching skippable tests") + self._test_suites_to_skip = [] + return + + self._test_suites_to_skip = [] + + if response.status >= 400: + log.warning("Skippable tests request responded with status %d", response.status) + return + try: + if isinstance(response.body, bytes): + parsed = json.loads(response.body.decode()) + else: + parsed = json.loads(response.body) + except json.JSONDecodeError: + log.warning("Skippable tests request responded with invalid JSON '%s'", response.body) + return + + if "data" not in parsed: + log.warning("Skippable tests request missing data, no tests will be skipped") + return + + try: + for item in parsed["data"]: + if item["type"] == skipping_mode and "suite" in item["attributes"]: + module = item["attributes"].get("configurations", {}).get("test.bundle", "").replace(".", "/") + path = "/".join((module, item["attributes"]["suite"])) if module else item["attributes"]["suite"] + + if skipping_mode == SUITE: + self._test_suites_to_skip.append(path) + else: + self._tests_to_skip[path].append(item["attributes"]["name"]) + except Exception: + log.warning("Error processing skippable test data, no tests will be skipped", exc_info=True) + self._test_suites_to_skip = [] + self._tests_to_skip = defaultdict(list) + + def _should_skip_path(self, path, name, test_skipping_mode=None): + if test_skipping_mode is None: + _test_skipping_mode = SUITE if self._suite_skipping_mode else TEST + else: + _test_skipping_mode = test_skipping_mode + + if _test_skipping_mode == SUITE: + return os.path.relpath(path) in self._test_suites_to_skip + else: + return name in self._tests_to_skip[os.path.relpath(path)] + return False + + @classmethod + def enable(cls, tracer=None, config=None, service=None): + # type: (Optional[Tracer], Optional[Any], Optional[str]) -> None + log.debug("Enabling %s", cls.__name__) + + if ddconfig._ci_visibility_agentless_enabled: + if not os.getenv("_CI_DD_API_KEY", os.getenv("DD_API_KEY")): + log.critical( + "%s disabled: environment variable DD_CIVISIBILITY_AGENTLESS_ENABLED is true but" + " DD_API_KEY is not set", + cls.__name__, + ) + cls.enabled = False + return + + if cls._instance is not None: + log.debug("%s already enabled", cls.__name__) + return + + try: + cls._instance = cls(tracer=tracer, config=config, service=service) + except CIVisibilityAuthenticationException: + log.warning("Authentication error, disabling CI Visibility, please check Datadog API key") + cls.enabled = False + return + + cls.enabled = True + + cls._instance.start() + atexit.register(cls.disable) + + log.debug("%s enabled", cls.__name__) + log.info( + "Final settings: coverage collection: %s, test skipping: %s", + cls._instance._collect_coverage_enabled, + CIVisibility.test_skipping_enabled(), + ) + + @classmethod + def disable(cls): + # type: () -> None + if cls._instance is None: + log.debug("%s not enabled", cls.__name__) + return + log.debug("Disabling %s", cls.__name__) + atexit.unregister(cls.disable) + + cls._instance.stop() + cls._instance = None + cls.enabled = False + + telemetry.telemetry_writer.periodic(force_flush=True) + + log.debug("%s disabled", cls.__name__) + + def _start_service(self): + # type: () -> None + tracer_filters = self.tracer._filters + if not any(isinstance(tracer_filter, TraceCiVisibilityFilter) for tracer_filter in tracer_filters): + tracer_filters += [TraceCiVisibilityFilter(self._tags, self._service)] # type: ignore[arg-type] + self.tracer.configure(settings={"FILTERS": tracer_filters}) + + if self.test_skipping_enabled() and (not self._tests_to_skip and self._test_suites_to_skip is None): + skipping_level = SUITE if self._suite_skipping_mode else TEST + self._fetch_tests_to_skip(skipping_level) + if self._suite_skipping_mode: + if self._test_suites_to_skip is None: + skippable_items_count = 0 + log.warning("Suites to skip remains None after fetching tests") + else: + skippable_items_count = len(self._test_suites_to_skip) + else: + skippable_items_count = sum([len(skippable_tests) for skippable_tests in self._tests_to_skip.values()]) + log.info("Intelligent Test Runner skipping level: %s", skipping_level) + log.info("Skippable items fetched: %s", skippable_items_count) + + def _stop_service(self): + # type: () -> None + if self._should_upload_git_metadata and not self._git_client.metadata_upload_finished(): + log.debug("git metadata upload still in progress, waiting before shutting down") + try: + try: + self._git_client._wait_for_metadata_upload(timeout=self.tracer.SHUTDOWN_TIMEOUT) + except ValueError: + log.debug("Error waiting for metadata upload to complete during shutdown", exc_info=True) + except TimeoutError: + log.debug("Timed out waiting for metadata upload to complete during shutdown.") + try: + self.tracer.shutdown() + except Exception: + log.warning("Failed to shutdown tracer", exc_info=True) + + @classmethod + def set_codeowners_of(cls, location, span=None): + if not cls.enabled or cls._instance is None or cls._instance._codeowners is None or not location: + return + + span = span or cls._instance.tracer.current_span() + if span is None: + return + + try: + handles = cls._instance._codeowners.of(location) + if handles: + span.set_tag(test.CODEOWNERS, json.dumps(handles)) + except KeyError: + log.debug("no matching codeowners for %s", location) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/telemetry/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/telemetry/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/telemetry/constants.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/telemetry/constants.py new file mode 100644 index 0000000..bdf8796 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/telemetry/constants.py @@ -0,0 +1,44 @@ +from enum import Enum + + +CIVISIBILITY_TELEMETRY_NAMESPACE = "civisibility" + + +class ERROR_TYPES(str, Enum): + TIMEOUT = "timeout" + NETWORK = "network" + CODE_4XX = "status_code_4xx_response" + CODE_5XX = "status_code_5xx_response" + BAD_JSON = "bad_json" + UNKNOWN = "unknown" + + +class GIT_TELEMETRY_COMMANDS(str, Enum): + GET_REPOSITORY = "get_repository" + GET_BRANCH = "get_branch" + CHECK_SHALLOW = "check_shallow" + UNSHALLOW = "unshallow" + GET_LOCAL_COMMITS = "get_local_commits" + GET_OBJECTS = "get_objects" + PACK_OBJECTS = "pack_objects" + + +class GIT_TELEMETRY(str, Enum): + COMMAND_COUNT = "git.command" + COMMAND_MS = "git.command_ms" + COMMAND_ERRORS = "git.command_errors" + + SEARCH_COMMITS_COUNT = "git_requests.search_commits" + SEARCH_COMMITS_MS = "git_requests.search_commits_ms" + SEARCH_COMMITS_ERRORS = "git_requests.search_commits_errors" + + OBJECTS_PACK_COUNT = "git_requests.objects_pack" + OBJECTS_PACK_MS = "git_requests.objects_pack_ms" + OBJECTS_PACK_ERRORS = "git_requests.objects_pack_errors" + OBJECTS_PACK_FILES = "git_requests.objects_pack_files" + OBJECTS_PACK_BYTES = "git_requests.objects_pack_bytes" + + SETTINGS_COUNT = "git_requests.settings" + SETTINGS_MS = "git_requests.settings_ms" + SETTINGS_ERRORS = "git_requests.settings_errors" + SETTINGS_RESPONSE = "git_requests.settings_response" diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/telemetry/git.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/telemetry/git.py new file mode 100644 index 0000000..c1ac9b6 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/telemetry/git.py @@ -0,0 +1,90 @@ +from typing import Optional + +from ddtrace.internal.ci_visibility.telemetry.constants import CIVISIBILITY_TELEMETRY_NAMESPACE as _NAMESPACE +from ddtrace.internal.ci_visibility.telemetry.constants import ERROR_TYPES +from ddtrace.internal.ci_visibility.telemetry.constants import GIT_TELEMETRY +from ddtrace.internal.ci_visibility.telemetry.constants import GIT_TELEMETRY_COMMANDS +from ddtrace.internal.ci_visibility.telemetry.utils import skip_if_agentless +from ddtrace.internal.logger import get_logger +from ddtrace.internal.telemetry import telemetry_writer + + +log = get_logger(__name__) + + +@skip_if_agentless +def record_git_command(command: GIT_TELEMETRY_COMMANDS, duration: float, exit_code: Optional[int]) -> None: + log.debug("Recording git command telemetry: %s, %s, %s", command, duration, exit_code) + tags = (("command", command),) + telemetry_writer.add_count_metric(_NAMESPACE, GIT_TELEMETRY.COMMAND_COUNT, 1, tags) + telemetry_writer.add_distribution_metric(_NAMESPACE, GIT_TELEMETRY.COMMAND_MS, duration, tags) + if exit_code is not None and exit_code != 0: + error_tags = (("command", command), ("exit_code", str(exit_code))) + telemetry_writer.add_count_metric(_NAMESPACE, GIT_TELEMETRY.COMMAND_ERRORS, 1, error_tags) + + +@skip_if_agentless +def record_search_commits(duration: float, error: Optional[ERROR_TYPES] = None) -> None: + log.debug("Recording search commits telemetry: %s, %s", duration, error) + telemetry_writer.add_count_metric(_NAMESPACE, GIT_TELEMETRY.SEARCH_COMMITS_COUNT, 1) + telemetry_writer.add_distribution_metric(_NAMESPACE, GIT_TELEMETRY.SEARCH_COMMITS_MS, duration) + if error is not None: + error_tags = (("error_type", str(error)),) + telemetry_writer.add_count_metric(_NAMESPACE, GIT_TELEMETRY.SEARCH_COMMITS_ERRORS, 1, error_tags) + + +@skip_if_agentless +def record_objects_pack_request(duration: float, error: Optional[ERROR_TYPES] = None) -> None: + log.debug("Recording objects pack request telmetry: %s, %s", duration, error) + telemetry_writer.add_count_metric(_NAMESPACE, GIT_TELEMETRY.OBJECTS_PACK_COUNT, 1) + telemetry_writer.add_distribution_metric(_NAMESPACE, GIT_TELEMETRY.OBJECTS_PACK_MS, duration) + if error is not None: + error_tags = (("error", error),) + telemetry_writer.add_count_metric(_NAMESPACE, GIT_TELEMETRY.OBJECTS_PACK_ERRORS, 1, error_tags) + + +@skip_if_agentless +def record_objects_pack_data(num_files: int, num_bytes: int) -> None: + log.debug("Recording objects pack data telemetry: %s, %s", num_files, num_bytes) + telemetry_writer.add_distribution_metric(_NAMESPACE, GIT_TELEMETRY.OBJECTS_PACK_BYTES, num_bytes) + telemetry_writer.add_distribution_metric(_NAMESPACE, GIT_TELEMETRY.OBJECTS_PACK_FILES, num_files) + + +@skip_if_agentless +def record_settings( + duration: float, + coverage_enabled: Optional[bool] = False, + skipping_enabled: Optional[bool] = False, + require_git: Optional[bool] = False, + itr_enabled: Optional[bool] = False, + error: Optional[ERROR_TYPES] = None, +) -> None: + log.debug( + "Recording settings telemetry: %s, %s, %s, %s, %s, %s", + duration, + coverage_enabled, + skipping_enabled, + require_git, + itr_enabled, + error, + ) + # Telemetry "booleans" are true if they exist, otherwise false + response_tags = [] + if coverage_enabled: + response_tags.append(("coverage_enabled", "1")) + if skipping_enabled: + response_tags.append(("itrskip_enabled", "1")) + if require_git: + response_tags.append(("require_git", "1")) + if itr_enabled: + response_tags.append(("itrskip_enabled", "1")) + + telemetry_writer.add_count_metric(_NAMESPACE, GIT_TELEMETRY.SETTINGS_COUNT, 1) + telemetry_writer.add_distribution_metric(_NAMESPACE, GIT_TELEMETRY.SETTINGS_MS, duration) + + telemetry_writer.add_count_metric( + _NAMESPACE, GIT_TELEMETRY.SETTINGS_RESPONSE, 1, tuple(response_tags) if response_tags else None + ) + if error is not None: + error_tags = (("error", error),) + telemetry_writer.add_count_metric(_NAMESPACE, GIT_TELEMETRY.SETTINGS_ERRORS, 1, error_tags) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/telemetry/utils.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/telemetry/utils.py new file mode 100644 index 0000000..fd82e6c --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/telemetry/utils.py @@ -0,0 +1,17 @@ +from ddtrace import config as ddconfig +from ddtrace.internal.logger import get_logger + + +log = get_logger(__name__) + + +def skip_if_agentless(func): + """Deocrator to skip sending telemetry if we are in agentless mode as it is not currently supported.""" + + def wrapper(*args, **kwargs): + if ddconfig._ci_visibility_agentless_enabled: + log.debug("Running in agentless mode, skipping sending telemetry") + return + func(*args, **kwargs) + + return wrapper diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/utils.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/utils.py new file mode 100644 index 0000000..1944ec8 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/utils.py @@ -0,0 +1,141 @@ +import inspect +import logging +import os +import re +import typing + +import ddtrace +from ddtrace import config as ddconfig +from ddtrace.contrib.coverage.constants import PCT_COVERED_KEY +from ddtrace.ext import test +from ddtrace.internal.ci_visibility.constants import CIVISIBILITY_LOG_FILTER_RE +from ddtrace.internal.logger import get_logger + + +log = get_logger(__name__) + + +def get_relative_or_absolute_path_for_path(path: str, start_directory: str): + try: + relative_path = os.path.relpath(path, start=start_directory) + except ValueError: + log.debug( + "Tried to collect relative path but it is using different drive paths on Windows, " + "using absolute path instead", + ) + return os.path.abspath(path) + return relative_path + + +def get_source_file_path_for_test_method(test_method_object, repo_directory: str) -> typing.Union[str, None]: + try: + file_object = inspect.getfile(test_method_object) + except TypeError: + return "" + + return get_relative_or_absolute_path_for_path(file_object, repo_directory) + + +def get_source_lines_for_test_method( + test_method_object, +) -> typing.Union[typing.Tuple[int, int], typing.Tuple[None, None]]: + try: + source_lines_tuple = inspect.getsourcelines(test_method_object) + except (TypeError, OSError): + return None, None + start_line = source_lines_tuple[1] + end_line = start_line + len(source_lines_tuple[0]) + return start_line, end_line + + +def _add_start_end_source_file_path_data_to_span( + span: ddtrace.Span, test_method_object, test_name: str, repo_directory: str +): + if not test_method_object: + log.debug( + "Tried to collect source start/end lines for test method %s but test method object could not be found", + test_name, + ) + return + source_file_path = get_source_file_path_for_test_method(test_method_object, repo_directory) + if not source_file_path: + log.debug( + "Tried to collect file path for test %s but it is a built-in Python function", + test_name, + ) + return + start_line, end_line = get_source_lines_for_test_method(test_method_object) + if not start_line or not end_line: + log.debug("Tried to collect source start/end lines for test method %s but an exception was raised", test_name) + span.set_tag_str(test.SOURCE_FILE, source_file_path) + if start_line: + span.set_tag(test.SOURCE_START, start_line) + if end_line: + span.set_tag(test.SOURCE_END, end_line) + + +def _add_pct_covered_to_span(coverage_data: dict, span: ddtrace.Span): + if not coverage_data or PCT_COVERED_KEY not in coverage_data: + log.warning("Tried to add total covered percentage to session span but no data was found") + return + lines_pct_value = coverage_data[PCT_COVERED_KEY] + if type(lines_pct_value) != float: + log.warning("Tried to add total covered percentage to session span but the format was unexpected") + return + span.set_tag(test.TEST_LINES_PCT, lines_pct_value) + + +def _generate_fully_qualified_test_name(test_module_path: str, test_suite_name: str, test_name: str) -> str: + return "{}.{}.{}".format(test_module_path, test_suite_name, test_name) + + +def _generate_fully_qualified_module_name(test_module_path: str, test_suite_name: str) -> str: + return "{}.{}".format(test_module_path, test_suite_name) + + +def take_over_logger_stream_handler(remove_ddtrace_stream_handlers=True): + """Creates a handler with a filter for CIVisibility-specific messages. The also removes the existing + handlers on the DDTrace logger, to prevent double-logging. + + This is useful for testrunners (currently pytest) that have their own logger. + + NOTE: This should **only** be called from testrunner-level integrations (eg: pytest, unittest). + """ + if ddconfig._debug_mode: + log.debug("CIVisibility not taking over ddtrace logger handler because debug mode is enabled") + return + + level = ddconfig.ci_visibility_log_level + + if level.upper() == "NONE": + log.debug("CIVisibility not taking over ddtrace logger because level is set to: %s", level) + return + + root_logger = logging.getLogger() + + if remove_ddtrace_stream_handlers: + log.debug("CIVisibility removing DDTrace logger handler") + ddtrace_logger = logging.getLogger("ddtrace") + for handler in ddtrace_logger.handlers: + ddtrace_logger.removeHandler(handler) + else: + log.warning("Keeping DDTrace logger handler, double logging is likely") + + logger_name_re = re.compile(CIVISIBILITY_LOG_FILTER_RE) + + ci_visibility_handler = logging.StreamHandler() + ci_visibility_handler.addFilter(lambda record: bool(logger_name_re.match(record.name))) + ci_visibility_handler.setFormatter( + logging.Formatter("[Datadog CI Visibility] %(levelname)-8s %(name)s:%(filename)s:%(lineno)d %(message)s") + ) + + try: + ci_visibility_handler.setLevel(level.upper()) + except ValueError: + log.warning("Invalid log level: %s", level) + return + + root_logger.addHandler(ci_visibility_handler) + root_logger.setLevel(min(root_logger.level, ci_visibility_handler.level)) + + log.debug("logger setup complete") diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/writer.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/writer.py new file mode 100644 index 0000000..6746e3c --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/ci_visibility/writer.py @@ -0,0 +1,148 @@ +import os +from typing import TYPE_CHECKING # noqa:F401 +from typing import Optional # noqa:F401 + +import ddtrace +from ddtrace import config +from ddtrace.vendor.dogstatsd import DogStatsd # noqa:F401 + +from .. import agent +from .. import service +from ..runtime import get_runtime_id +from ..writer import HTTPWriter +from ..writer import WriterClientBase +from .constants import AGENTLESS_BASE_URL +from .constants import AGENTLESS_COVERAGE_BASE_URL +from .constants import AGENTLESS_COVERAGE_ENDPOINT +from .constants import AGENTLESS_DEFAULT_SITE +from .constants import AGENTLESS_ENDPOINT +from .constants import EVP_PROXY_AGENT_ENDPOINT +from .constants import EVP_PROXY_COVERAGE_ENDPOINT +from .constants import EVP_SUBDOMAIN_HEADER_COVERAGE_VALUE +from .constants import EVP_SUBDOMAIN_HEADER_NAME +from .encoder import CIVisibilityCoverageEncoderV02 +from .encoder import CIVisibilityEncoderV01 + + +if TYPE_CHECKING: # pragma: no cover + from typing import Dict # noqa:F401 + from typing import List # noqa:F401 + + +class CIVisibilityEventClient(WriterClientBase): + def __init__(self): + encoder = CIVisibilityEncoderV01(0, 0) + encoder.set_metadata( + { + "language": "python", + "env": config.env, + "runtime-id": get_runtime_id(), + "library_version": ddtrace.__version__, + } + ) + super(CIVisibilityEventClient, self).__init__(encoder) + + +class CIVisibilityCoverageClient(WriterClientBase): + def __init__(self, intake_url, headers=None, itr_suite_skipping_mode=False): + encoder = CIVisibilityCoverageEncoderV02(0, 0) + if itr_suite_skipping_mode: + encoder._set_itr_suite_skipping_mode(itr_suite_skipping_mode) + self._intake_url = intake_url + if headers: + self._headers = headers + super(CIVisibilityCoverageClient, self).__init__(encoder) + + +class CIVisibilityProxiedCoverageClient(CIVisibilityCoverageClient): + ENDPOINT = EVP_PROXY_COVERAGE_ENDPOINT + + +class CIVisibilityAgentlessCoverageClient(CIVisibilityCoverageClient): + ENDPOINT = AGENTLESS_COVERAGE_ENDPOINT + + +class CIVisibilityAgentlessEventClient(CIVisibilityEventClient): + ENDPOINT = AGENTLESS_ENDPOINT + + +class CIVisibilityProxiedEventClient(CIVisibilityEventClient): + ENDPOINT = EVP_PROXY_AGENT_ENDPOINT + + +class CIVisibilityWriter(HTTPWriter): + RETRY_ATTEMPTS = 5 + HTTP_METHOD = "POST" + STATSD_NAMESPACE = "civisibility.writer" + + def __init__( + self, + intake_url="", # type: str + processing_interval=None, # type: Optional[float] + timeout=None, # type: Optional[float] + dogstatsd=None, # type: Optional[DogStatsd] + sync_mode=False, # type: bool + report_metrics=False, # type: bool + api_version=None, # type: Optional[str] + reuse_connections=None, # type: Optional[bool] + headers=None, # type: Optional[Dict[str, str]] + use_evp=False, # type: bool + coverage_enabled=False, # type: bool + itr_suite_skipping_mode=False, # type: bool + ): + if processing_interval is None: + processing_interval = config._trace_writer_interval_seconds + if timeout is None: + timeout = config._agent_timeout_seconds + intake_cov_url = None + if use_evp: + intake_url = agent.get_trace_url() + intake_cov_url = agent.get_trace_url() + elif config._ci_visibility_agentless_url: + intake_url = config._ci_visibility_agentless_url + intake_cov_url = config._ci_visibility_agentless_url + if not intake_url: + intake_url = "%s.%s" % (AGENTLESS_BASE_URL, os.getenv("DD_SITE", AGENTLESS_DEFAULT_SITE)) + + clients = ( + [CIVisibilityProxiedEventClient()] if use_evp else [CIVisibilityAgentlessEventClient()] + ) # type: List[WriterClientBase] + if coverage_enabled: + if not intake_cov_url: + intake_cov_url = "%s.%s" % (AGENTLESS_COVERAGE_BASE_URL, os.getenv("DD_SITE", AGENTLESS_DEFAULT_SITE)) + clients.append( + CIVisibilityProxiedCoverageClient( + intake_url=intake_cov_url, + headers={EVP_SUBDOMAIN_HEADER_NAME: EVP_SUBDOMAIN_HEADER_COVERAGE_VALUE}, + itr_suite_skipping_mode=itr_suite_skipping_mode, + ) + if use_evp + else CIVisibilityAgentlessCoverageClient( + intake_url=intake_cov_url, itr_suite_skipping_mode=itr_suite_skipping_mode + ) + ) + + super(CIVisibilityWriter, self).__init__( + intake_url=intake_url, + clients=clients, + processing_interval=processing_interval, + timeout=timeout, + dogstatsd=dogstatsd, + sync_mode=sync_mode, + reuse_connections=reuse_connections, + headers=headers, + ) + + def stop(self, timeout=None): + if self.status != service.ServiceStatus.STOPPED: + super(CIVisibilityWriter, self).stop(timeout=timeout) + + def recreate(self): + # type: () -> HTTPWriter + return self.__class__( + intake_url=self.intake_url, + processing_interval=self._interval, + timeout=self._timeout, + dogstatsd=self.dogstatsd, + sync_mode=self._sync_mode, + ) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/codeowners.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/codeowners.py new file mode 100644 index 0000000..06c88ed --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/codeowners.py @@ -0,0 +1,195 @@ +import os +import re +from typing import List # noqa:F401 +from typing import Optional # noqa:F401 +from typing import Tuple # noqa:F401 + + +def path_to_regex(pattern): + # type: (str) -> re.Pattern + """ + source https://github.com/sbdchd/codeowners/blob/c95e13d384ac09cfa1c23be1a8601987f41968ea/codeowners/__init__.py + + Copyright (c) 2019-2020 Steve Dignam + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + ported from https://github.com/hmarr/codeowners/blob/d0452091447bd2a29ee508eebc5a79874fb5d4ff/match.go#L33 + + MIT License + + Copyright (c) 2020 Harry Marr + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + """ + regex = "" + + slash_pos = pattern.find("/") + anchored = slash_pos > -1 and slash_pos != len(pattern) - 1 + + regex += r"\A" if anchored else r"(?:\A|/)" + + matches_dir = pattern[-1] == "/" + pattern_trimmed = pattern.strip("/") + + in_char_class = False + escaped = False + + iterator = enumerate(pattern_trimmed) + for i, ch in iterator: + if escaped: + regex += re.escape(ch) + escaped = False + continue + + if ch == "\\": + escaped = True + elif ch == "*": + if i + 1 < len(pattern_trimmed) and pattern_trimmed[i + 1] == "*": + left_anchored = i == 0 + leading_slash = i > 0 and pattern_trimmed[i - 1] == "/" + right_anchored = i + 2 == len(pattern_trimmed) + trailing_slash = i + 2 < len(pattern_trimmed) and pattern_trimmed[i + 2] == "/" + + if (left_anchored or leading_slash) and (right_anchored or trailing_slash): + regex += ".*" + + next(iterator, None) + next(iterator, None) + continue + regex += "[^/]*" + elif ch == "?": + regex += "[^/]" + elif ch == "[": + in_char_class = True + regex += ch + elif ch == "]": + if in_char_class: + regex += ch + in_char_class = False + else: + regex += re.escape(ch) + else: + regex += re.escape(ch) + + if in_char_class: + raise ValueError("unterminated character class in pattern {pattern}".format(pattern=pattern)) + + regex += "/" if matches_dir else r"(?:\Z|/)" + return re.compile(regex) + + +class Codeowners(object): + """Provide interface to parse CODEOWNERS file and match a given path against it.""" + + KNOWN_LOCATIONS = ( + "CODEOWNERS", + ".github/CODEOWNERS", + "docs/CODEOWNERS", + ".gitlab/CODEOWNERS", + ) + + def __init__(self, path=None, cwd=None): + # type: (Optional[str], Optional[str]) -> None + """Initialize Codeowners object. + + :param path: path to CODEOWNERS file otherwise try to use any from known locations + """ + path = path or self.location(cwd) + if path is not None: + self.path = path # type: str + self.patterns = [] # type: List[Tuple[re.Pattern, List[str]]] + self.parse() + + def location(self, cwd=None): + # type: (Optional[str]) -> Optional[str] + """Return the location of the CODEOWNERS file.""" + cwd = cwd or os.getcwd() + for location in self.KNOWN_LOCATIONS: + path = os.path.join(cwd, location) + if os.path.isfile(path): + return path + raise ValueError("CODEOWNERS file not found") + + def parse(self): + # type: () -> None + """Parse CODEOWNERS file and store the lines and regexes.""" + with open(self.path) as f: + patterns = [] + for line in f.readlines(): + line = line.strip() + if line == "": + continue + # Lines starting with '#' are comments. + if line.startswith("#"): + continue + if line.startswith("[") and line.endswith("]"): + # found a code owners section + continue + if line.startswith("^[") and line.endswith("]"): + # found an optional code owners section + continue + + elements = line.split() + if len(elements) < 2: + continue + + path = elements[0] + if path is None: + continue + + try: + pattern = path_to_regex(path) + except (ValueError, IndexError): + continue + + owners = [owner for owner in elements[1:] if owner] + + if not owners: + continue + patterns.append((pattern, owners)) + # Order is important. The last matching pattern has the most precedence. + patterns.reverse() + self.patterns = patterns + + def of(self, path): + # type: (str) -> List[str] + """Return code owners for a given path. + + :param path: path to check + :return: list of file code owners identified by the given path + """ + for pattern, owners in self.patterns: + if pattern.search(path): + return owners + raise KeyError("no code owners found for {path}".format(path=path)) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/compat.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/compat.py new file mode 100644 index 0000000..5372764 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/compat.py @@ -0,0 +1,458 @@ +from collections import Counter +from collections import OrderedDict +from collections import defaultdict +from collections import deque +import functools +from inspect import iscoroutinefunction +from inspect import isgeneratorfunction +import ipaddress +import os +import platform +import re +import sys +from tempfile import mkdtemp +import threading +from types import BuiltinFunctionType +from types import BuiltinMethodType +from types import FunctionType +from types import MethodType +from types import TracebackType +from typing import Any # noqa:F401 +from typing import AnyStr # noqa:F401 +from typing import Optional # noqa:F401 +from typing import Text # noqa:F401 +from typing import Tuple # noqa:F401 +from typing import Type # noqa:F401 +from typing import Union # noqa:F401 +import warnings + +from ddtrace.vendor.wrapt.wrappers import BoundFunctionWrapper +from ddtrace.vendor.wrapt.wrappers import FunctionWrapper + + +__all__ = [ + "httplib", + "iscoroutinefunction", + "Queue", + "StringIO", + "urlencode", + "parse", + "maybe_stringify", +] + +PYTHON_VERSION_INFO = sys.version_info + +# Infos about python passed to the trace agent through the header +PYTHON_VERSION = platform.python_version() +PYTHON_INTERPRETER = platform.python_implementation() + +import http.client as httplib # noqa: E402 +from io import StringIO # noqa: E402 +from queue import Queue # noqa: E402 +import urllib.parse as parse # noqa: E402 +from urllib.parse import urlencode # noqa: E402 + + +def ensure_text(s, encoding="utf-8", errors="ignore") -> str: + if isinstance(s, str): + return s + if isinstance(s, bytes): + return s.decode(encoding, errors) + raise TypeError("Expected str or bytes but received %r" % (s.__class__)) + + +def ensure_binary(s, encoding="utf-8", errors="ignore") -> bytes: + if isinstance(s, bytes): + return s + if not isinstance(s, str): + raise TypeError("Expected str or bytes but received %r" % (s.__class__)) + return s.encode(encoding, errors) + + +NumericType = Union[int, float] + +# Pattern class generated by `re.compile` +pattern_type = re.Pattern + +try: + from inspect import getfullargspec # noqa:F401 + + def is_not_void_function(f, argspec): + return ( + argspec.args + or argspec.varargs + or argspec.varkw + or argspec.defaults + or argspec.kwonlyargs + or argspec.kwonlydefaults + or isgeneratorfunction(f) + ) + +except ImportError: + from inspect import getargspec as getfullargspec # type: ignore[assignment] # noqa: F401 + + def is_not_void_function(f, argspec): + return argspec.args or argspec.varargs or argspec.keywords or argspec.defaults or isgeneratorfunction(f) + + +def is_integer(obj): + # type: (Any) -> bool + """Helper to determine if the provided ``obj`` is an integer type or not""" + # DEV: We have to make sure it is an integer and not a boolean + # >>> type(True) + # + # >>> isinstance(True, int) + # True + return isinstance(obj, int) and not isinstance(obj, bool) + + +try: + from time import time_ns +except ImportError: + from time import time as _time + + def time_ns(): + # type: () -> int + return int(_time() * 10e5) * 1000 + + +try: + from time import monotonic +except ImportError: + from ddtrace.vendor.monotonic import monotonic + + +try: + from time import monotonic_ns +except ImportError: + + def monotonic_ns(): + # type: () -> int + return int(monotonic() * 1e9) + + +try: + from time import process_time_ns +except ImportError: + from time import clock as _process_time # type: ignore[attr-defined] + + def process_time_ns(): + # type: () -> int + return int(_process_time() * 1e9) + + +main_thread = threading.main_thread() + + +def make_async_decorator(tracer, coro, *params, **kw_params): + """ + Decorator factory that creates an asynchronous wrapper that yields + a coroutine result. This factory is required to handle Python 2 + compatibilities. + + :param object tracer: the tracer instance that is used + :param function f: the coroutine that must be executed + :param tuple params: arguments given to the Tracer.trace() + :param dict kw_params: keyword arguments given to the Tracer.trace() + """ + + @functools.wraps(coro) + async def func_wrapper(*args, **kwargs): + with tracer.trace(*params, **kw_params): + result = await coro(*args, **kwargs) + return result + + return func_wrapper + + +# DEV: There is `six.u()` which does something similar, but doesn't have the guard around `hasattr(s, 'decode')` +def to_unicode(s): + # type: (AnyStr) -> Text + """Return a unicode string for the given bytes or string instance.""" + # No reason to decode if we already have the unicode compatible object we expect + # DEV: `six.text_type` will be a `str` for python 3 and `unicode` for python 2 + # DEV: Double decoding a `unicode` can cause a `UnicodeEncodeError` + # e.g. `'\xc3\xbf'.decode('utf-8').decode('utf-8')` + if isinstance(s, str): + return s + + # If the object has a `decode` method, then decode into `utf-8` + # e.g. Python 2 `str`, Python 2/3 `bytearray`, etc + if hasattr(s, "decode"): + return s.decode("utf-8", errors="ignore") + + # Always try to coerce the object into the `six.text_type` object we expect + # e.g. `to_unicode(1)`, `to_unicode(dict(key='value'))` + return str(s) + + +def get_connection_response( + conn, # type: httplib.HTTPConnection +): + # type: (...) -> httplib.HTTPResponse + """Returns the response for a connection. + + If using Python 2 enable buffering. + + Python 2 does not enable buffering by default resulting in many recv + syscalls. + + See: + https://bugs.python.org/issue4879 + https://github.com/python/cpython/commit/3c43fcba8b67ea0cec4a443c755ce5f25990a6cf + """ + return conn.getresponse() + + +CONTEXTVARS_IS_AVAILABLE = True + + +try: + from collections.abc import Iterable # noqa:F401 +except ImportError: + from collections import Iterable # type: ignore[no-redef, attr-defined] # noqa:F401 + + +def maybe_stringify(obj): + # type: (Any) -> Optional[str] + if obj is not None: + return str(obj) + return None + + +NoneType = type(None) + +BUILTIN_SIMPLE_TYPES = frozenset([int, float, str, bytes, bool, NoneType, type, complex]) +BUILTIN_MAPPNG_TYPES = frozenset([dict, defaultdict, Counter, OrderedDict]) +BUILTIN_SEQUENCE_TYPES = frozenset([list, tuple, set, frozenset, deque]) +BUILTIN_CONTAINER_TYPES = BUILTIN_MAPPNG_TYPES | BUILTIN_SEQUENCE_TYPES +BUILTIN_TYPES = BUILTIN_SIMPLE_TYPES | BUILTIN_CONTAINER_TYPES + + +try: + from types import MethodWrapperType + +except ImportError: + MethodWrapperType = object().__init__.__class__ # type: ignore[misc] + +CALLABLE_TYPES = ( + BuiltinMethodType, + BuiltinFunctionType, + FunctionType, + MethodType, + MethodWrapperType, + FunctionWrapper, + BoundFunctionWrapper, + property, + classmethod, + staticmethod, +) +BUILTIN = "builtins" + + +try: + from typing import Collection # noqa:F401 +except ImportError: + from typing import List # noqa:F401 + from typing import Set # noqa:F401 + from typing import Union # noqa:F401 + + Collection = Union[List, Set, Tuple] # type: ignore[misc,assignment] + +ExcInfoType = Union[Tuple[Type[BaseException], BaseException, Optional[TracebackType]], Tuple[None, None, None]] + + +try: + from json import JSONDecodeError +except ImportError: + JSONDecodeError = ValueError # type: ignore[misc,assignment] + + +def is_valid_ip(ip: str) -> bool: + try: + # try parsing the IP address + ipaddress.ip_address(str(ip)) + return True + except BaseException: + return False + + +def ip_is_global(ip): + # type: (str) -> bool + """ + is_global is Python 3+ only. This could raise a ValueError if the IP is not valid. + """ + parsed_ip = ipaddress.ip_address(str(ip)) + + return parsed_ip.is_global + + +# https://stackoverflow.com/a/19299884 +class TemporaryDirectory(object): + """Create and return a temporary directory. This has the same + behavior as mkdtemp but can be used as a context manager. For + example: + + with TemporaryDirectory() as tmpdir: + ... + + Upon exiting the context, the directory and everything contained + in it are removed. + """ + + def __init__(self, suffix="", prefix="tmp", _dir=None): + self._closed = False + self.name = None # Handle mkdtemp raising an exception + self.name = mkdtemp(suffix, prefix, _dir) + + def __repr__(self): + return "<{} {!r}>".format(self.__class__.__name__, self.name) + + def __enter__(self): + return self.name + + def cleanup(self, _warn=False): + if self.name and not self._closed: + try: + self._rmtree(self.name) + except (TypeError, AttributeError) as ex: + # Issue #10188: Emit a warning on stderr + # if the directory could not be cleaned + # up due to missing globals + if "None" not in str(ex): + raise + return + self._closed = True + if _warn: + self._warn("Implicitly cleaning up {!r}".format(self), ResourceWarning) + + def __exit__(self, exc, value, tb): + self.cleanup() + + def __del__(self): + # Issue a ResourceWarning if implicit cleanup needed + self.cleanup(_warn=True) + + # XXX (ncoghlan): The following code attempts to make + # this class tolerant of the module nulling out process + # that happens during CPython interpreter shutdown + # Alas, it doesn't actually manage it. See issue #10188 + _listdir = staticmethod(os.listdir) + _path_join = staticmethod(os.path.join) + _isdir = staticmethod(os.path.isdir) + _islink = staticmethod(os.path.islink) + _remove = staticmethod(os.remove) + _rmdir = staticmethod(os.rmdir) + _warn = warnings.warn + + def _rmtree(self, path): + # Essentially a stripped down version of shutil.rmtree. We can't + # use globals because they may be None'ed out at shutdown. + for name in self._listdir(path): + fullname = self._path_join(path, name) + try: + isdir = self._isdir(fullname) and not self._islink(fullname) + except OSError: + isdir = False + if isdir: + self._rmtree(fullname) + else: + try: + self._remove(fullname) + except OSError: + pass + try: + self._rmdir(path) + except OSError: + pass + + +try: + from shlex import quote as shquote +except ImportError: + import re + + _find_unsafe = re.compile(r"[^\w@%+=:,./-]").search + + def shquote(s): + # type: (str) -> str + """Return a shell-escaped version of the string *s*.""" + if not s: + return "''" + if _find_unsafe(s) is None: + return s + + # use single quotes, and put single quotes into double quotes + # the string $'b is then quoted as '$'"'"'b' + return "'" + s.replace("'", "'\"'\"'") + "'" + + +try: + from shlex import join as shjoin +except ImportError: + + def shjoin(args): # type: ignore[misc] + # type: (Iterable[str]) -> str + """Return a shell-escaped string from *args*.""" + return " ".join(shquote(arg) for arg in args) + + +try: + from contextlib import nullcontext +except ImportError: + from contextlib import contextmanager + + @contextmanager # type: ignore[no-redef] + def nullcontext(enter_result=None): + yield enter_result + + +if PYTHON_VERSION_INFO >= (3, 9): + from functools import singledispatchmethod +elif PYTHON_VERSION_INFO >= (3, 8): + # This fix was not backported to 3.8 + # https://github.com/python/cpython/issues/83860 + from functools import singledispatchmethod + + def _register(self, cls, method=None): + if hasattr(cls, "__func__"): + setattr(cls, "__annotations__", cls.__func__.__annotations__) + return self.dispatcher.register(cls, func=method) + + singledispatchmethod.register = _register # type: ignore[assignment] +else: + from functools import singledispatch + from functools import update_wrapper + + class singledispatchmethod: # type: ignore[no-redef] + """Single-dispatch generic method descriptor. + + Supports wrapping existing descriptors and handles non-descriptor + callables as instance methods. + """ + + def __init__(self, func): + if not callable(func) and not hasattr(func, "__get__"): + raise TypeError(f"{func!r} is not callable or a descriptor") + + self.dispatcher = singledispatch(func) + self.func = func + + def register(self, cls, method=None): + if hasattr(cls, "__func__"): + setattr(cls, "__annotations__", cls.__func__.__annotations__) + return self.dispatcher.register(cls, func=method) + + def __get__(self, obj, cls=None): + def _method(*args, **kwargs): + method = self.dispatcher.dispatch(args[0].__class__) + return method.__get__(obj, cls)(*args, **kwargs) + + _method.__isabstractmethod__ = self.__isabstractmethod__ + _method.register = self.register + update_wrapper(_method, self.func) + return _method + + @property + def __isabstractmethod__(self): + return getattr(self.func, "__isabstractmethod__", False) diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/constants.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/constants.py new file mode 100644 index 0000000..b3053e9 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/constants.py @@ -0,0 +1,102 @@ +from ddtrace.constants import AUTO_KEEP +from ddtrace.constants import AUTO_REJECT +from ddtrace.constants import USER_KEEP +from ddtrace.constants import USER_REJECT + + +PROPAGATION_STYLE_DATADOG = "datadog" +PROPAGATION_STYLE_B3_MULTI = "b3multi" +PROPAGATION_STYLE_B3_SINGLE = "b3" +_PROPAGATION_STYLE_W3C_TRACECONTEXT = "tracecontext" +_PROPAGATION_STYLE_NONE = "none" +_PROPAGATION_STYLE_DEFAULT = "datadog,tracecontext" +PROPAGATION_STYLE_ALL = ( + _PROPAGATION_STYLE_W3C_TRACECONTEXT, + PROPAGATION_STYLE_DATADOG, + PROPAGATION_STYLE_B3_MULTI, + PROPAGATION_STYLE_B3_SINGLE, + _PROPAGATION_STYLE_NONE, +) +W3C_TRACESTATE_KEY = "tracestate" +W3C_TRACEPARENT_KEY = "traceparent" +W3C_TRACESTATE_ORIGIN_KEY = "o" +W3C_TRACESTATE_SAMPLING_PRIORITY_KEY = "s" +DEFAULT_SAMPLING_RATE_LIMIT = 100 +SAMPLING_DECISION_TRACE_TAG_KEY = "_dd.p.dm" +DEFAULT_SERVICE_NAME = "unnamed-python-service" +# Used to set the name of an integration on a span +COMPONENT = "component" +HIGHER_ORDER_TRACE_ID_BITS = "_dd.p.tid" +MAX_UINT_64BITS = (1 << 64) - 1 +SPAN_LINKS_KEY = "_dd.span_links" +SPAN_API_DATADOG = "datadog" +SPAN_API_OTEL = "otel" +SPAN_API_OPENTRACING = "opentracing" +DEFAULT_BUFFER_SIZE = 20 << 20 # 20 MB +DEFAULT_MAX_PAYLOAD_SIZE = 20 << 20 # 20 MB +DEFAULT_PROCESSING_INTERVAL = 1.0 +DEFAULT_REUSE_CONNECTIONS = False +BLOCKED_RESPONSE_HTML = """ + You've been blocked + +

Sorry, you cannot access this page. Please contact the customer service team.

+
""" +BLOCKED_RESPONSE_JSON = ( + '{"errors": [{"title": "You\'ve been blocked", "detail": "Sorry, you cannot access ' + 'this page. Please contact the customer service team. Security provided by Datadog."}]}' +) +HTTP_REQUEST_BLOCKED = "http.request.blocked" +RESPONSE_HEADERS = "http.response.headers" +HTTP_REQUEST_QUERY = "http.request.query" +HTTP_REQUEST_COOKIE_VALUE = "http.request.cookie.value" +HTTP_REQUEST_COOKIE_NAME = "http.request.cookie.name" +HTTP_REQUEST_PATH = "http.request.path" +HTTP_REQUEST_HEADER_NAME = "http.request.header.name" +HTTP_REQUEST_HEADER = "http.request.header" +HTTP_REQUEST_PARAMETER = "http.request.parameter" +HTTP_REQUEST_BODY = "http.request.body" +HTTP_REQUEST_PATH_PARAMETER = "http.request.path.parameter" +REQUEST_PATH_PARAMS = "http.request.path_params" +STATUS_403_TYPE_AUTO = {"status_code": 403, "type": "auto"} + +MESSAGING_SYSTEM = "messaging.system" + +FLASK_ENDPOINT = "flask.endpoint" +FLASK_VIEW_ARGS = "flask.view_args" +FLASK_URL_RULE = "flask.url_rule" + +_HTTPLIB_NO_TRACE_REQUEST = "_dd_no_trace" +DEFAULT_TIMEOUT = 2.0 + + +class _PRIORITY_CATEGORY: + USER = "user" + RULE = "rule" + AUTO = "auto" + DEFAULT = "default" + + +# intermediate mapping of priority categories to actual priority values +# used to simplify code that selects sampling priority based on many factors +_CATEGORY_TO_PRIORITIES = { + _PRIORITY_CATEGORY.USER: (USER_KEEP, USER_REJECT), + _PRIORITY_CATEGORY.RULE: (USER_KEEP, USER_REJECT), + _PRIORITY_CATEGORY.AUTO: (AUTO_KEEP, AUTO_REJECT), + _PRIORITY_CATEGORY.DEFAULT: (AUTO_KEEP, AUTO_REJECT), +} +_KEEP_PRIORITY_INDEX = 0 +_REJECT_PRIORITY_INDEX = 1 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/core/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/core/__init__.py new file mode 100644 index 0000000..52a2982 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/core/__init__.py @@ -0,0 +1,322 @@ +""" +This file implements the Core API, the abstraction layer between Integrations and Product code. +The Core API enables two primary use cases: maintaining a tree of ``ExecutionContext`` objects +and dispatching events. + +When using the Core API, keep concerns separate between Products and Integrations. Integrations +should not contain any code that references Products (Tracing, AppSec, Spans, WAF, Debugging, et cetera) +and Product code should never reference the library being integrated with (for example by importing ``flask``). + +It's helpful to think of the context tree as a Trace with extra data on each Span. It's similar +to a tree of Spans in that it represents the parts of the execution state that Datadog products +care about. + +This example shows how ``core.context_with_data`` might be used to create a node in this context tree:: + + + import flask + + + def _patched_request(pin, wrapped, args, kwargs): + with core.context_with_data( + "flask._patched_request", + pin=pin, + flask_request=flask.request, + block_request_callable=_block_request_callable, + ) as ctx, ctx.get_item("flask_request_call"): + return wrapped(*args, **kwargs) + + +This example looks a bit like a span created by ``tracer.trace()``: it has a name, a ``Pin`` instance, and +``__enter__`` and ``__exit__`` functionality as a context manager. In fact, it's so similar to a span that +the Tracing code in ``ddtrace/tracing`` can create a span directly from it (that's what ``flask_request_call`` +is in this example). + +The ``ExecutionContext`` object in this example also holds some data that you wouldn't typically find on +spans, like ``flask_request`` and ``block_request_callable``. These illustrate the context's utility as a +generic container for data that Datadog products need related to the current execution. ``block_request_callable`` +happens to be used in ``ddtrace/appsec`` by the AppSec product code to make request-blocking decisions, and +``flask_request`` is a reference to a library-specific function that Tracing uses. + +The first argument to ``context_with_data`` is the unique name of the context. When choosing this name, +consider how to differentiate it from other similar contexts while making its purpose clear. An easy default +is to use the name of the function within which ``context_with_data`` is being called, prefixed with the +integration name and a dot, for example ``flask._patched_request``. + +The integration code finds all of the library-specific objects that products need, and puts them into +the context tree it's building via ``context_with_data``. Product code then accesses the data it needs +by calling ``ExecutionContext.get_item`` like this:: + + + pin = ctx.get_item("pin") + current_span = pin.tracer.current_span() + ctx.set_item("current_span", current_span) + flask_config = ctx.get_item("flask_config") + _set_request_tags(ctx.get_item("flask_request"), current_span, flask_config) + + +Integration code can also call ``get_item`` when necessary, for example when the Flask integration checks +the request blocking flag that may have been set on the context by AppSec code and then runs Flask-specific +response logic:: + + + if core.get_item(HTTP_REQUEST_BLOCKED): + result = start_response("403", [("content-type", "application/json")]) + + +In order for ``get_item`` calls in Product code like ``ddtrace/appsec`` to find what they're looking for, +they need to happen at the right time. That's the problem that the ``core.dispatch`` and ``core.on`` +functions solve. + +The common pattern is that integration code generates events by calling ``dispatch`` and product code +listens to those events by calling ``on``. This allows product code to react to integration code at the +appropriate moments while maintaining clear separation of concerns. + +For example, the Flask integration calls ``dispatch`` to indicate that a blocked response just started, +passing some data along with the event:: + + + call = tracer.trace("operation") + core.dispatch("flask.blocked_request_callable", call) + + +The AppSec code listens for this event and does some AppSec-specific stuff in the handler:: + + + def _on_flask_blocked_request(): + core.set_item(HTTP_REQUEST_BLOCKED, True) + core.on("flask.blocked_request_callable", _on_flask_blocked_request) + + +``ExecutionContexts`` also generate their own start and end events that Product code can respond to +like this:: + + + def _on_jsonify_context_started_flask(ctx): + span = ctx.get_item("pin").tracer.trace(ctx.get_item("name")) + ctx.set_item("flask_jsonify_call", span) + core.on("context.started.flask.jsonify", _on_jsonify_context_started_flask) + + +The names of these events follow the pattern ``context.[started|ended].``. +""" +from contextlib import contextmanager +import logging +import sys +from typing import TYPE_CHECKING # noqa:F401 +from typing import Any # noqa:F401 +from typing import Callable # noqa:F401 +from typing import Dict # noqa:F401 +from typing import List # noqa:F401 +from typing import Optional # noqa:F401 +from typing import Tuple # noqa:F401 + +from ddtrace.vendor.debtcollector import deprecate + +from ..utils.deprecations import DDTraceDeprecationWarning +from . import event_hub # noqa:F401 +from .event_hub import EventResultDict # noqa:F401 +from .event_hub import dispatch +from .event_hub import dispatch_with_results # noqa:F401 +from .event_hub import has_listeners # noqa:F401 +from .event_hub import on # noqa:F401 +from .event_hub import reset as reset_listeners # noqa:F401 + + +if TYPE_CHECKING: + from ddtrace.span import Span # noqa:F401 + + +try: + import contextvars +except ImportError: + import ddtrace.vendor.contextvars as contextvars # type: ignore + + +log = logging.getLogger(__name__) + + +_CURRENT_CONTEXT = None +ROOT_CONTEXT_ID = "__root" +SPAN_DEPRECATION_MESSAGE = ( + "The 'span' keyword argument on ExecutionContext methods is deprecated and will be removed in a future version." +) +SPAN_DEPRECATION_SUGGESTION = ( + "Please store contextual data on the ExecutionContext object using other kwargs and/or set_item()" +) + + +def _deprecate_span_kwarg(span): + if span is not None: + # https://github.com/tiangolo/fastapi/pull/10876 + if "fastapi" not in sys.modules and "fastapi.applications" not in sys.modules: + deprecate( + SPAN_DEPRECATION_MESSAGE, + message=SPAN_DEPRECATION_SUGGESTION, + category=DDTraceDeprecationWarning, + ) + + +class ExecutionContext: + __slots__ = ["identifier", "_data", "_parents", "_span", "_token"] + + def __init__(self, identifier, parent=None, span=None, **kwargs): + _deprecate_span_kwarg(span) + self.identifier = identifier + self._data = {} + self._parents = [] + self._span = span + if parent is not None: + self.addParent(parent) + self._data.update(kwargs) + if self._span is None and _CURRENT_CONTEXT is not None: + self._token = _CURRENT_CONTEXT.set(self) + dispatch("context.started.%s" % self.identifier, (self,)) + dispatch("context.started.start_span.%s" % self.identifier, (self,)) + + def __repr__(self): + return self.__class__.__name__ + " '" + self.identifier + "' @ " + str(id(self)) + + @property + def parents(self): + return self._parents + + @property + def parent(self): + return self._parents[0] if self._parents else None + + def end(self): + dispatch_result = dispatch_with_results("context.ended.%s" % self.identifier, (self,)) + if self._span is None: + try: + _CURRENT_CONTEXT.reset(self._token) + except ValueError: + log.debug( + "Encountered ValueError during core contextvar reset() call. " + "This can happen when a span holding an executioncontext is " + "finished in a Context other than the one that started it." + ) + except LookupError: + log.debug( + "Encountered LookupError during core contextvar reset() call. I don't know why this is possible." + ) + return dispatch_result + + def addParent(self, context): + if self.identifier == ROOT_CONTEXT_ID: + raise ValueError("Cannot add parent to root context") + self._parents.append(context) + + @classmethod + @contextmanager + def context_with_data(cls, identifier, parent=None, span=None, **kwargs): + new_context = cls(identifier, parent=parent, span=span, **kwargs) + try: + yield new_context + finally: + new_context.end() + + def get_item(self, data_key: str, default: Optional[Any] = None, traverse: Optional[bool] = True) -> Any: + # NB mimic the behavior of `ddtrace.internal._context` by doing lazy inheritance + current = self + while current is not None: + if data_key in current._data: + return current._data.get(data_key) + if not traverse: + break + current = current.parent + return default + + def __getitem__(self, key: str): + value = self.get_item(key) + if value is None and key not in self._data: + raise KeyError + return value + + def get_items(self, data_keys): + # type: (List[str]) -> Optional[Any] + return [self.get_item(key) for key in data_keys] + + def set_item(self, data_key, data_value): + # type: (str, Optional[Any]) -> None + self._data[data_key] = data_value + + def set_safe(self, data_key, data_value): + # type: (str, Optional[Any]) -> None + if data_key in self._data: + raise ValueError("Cannot overwrite ExecutionContext data key '%s'", data_key) + return self.set_item(data_key, data_value) + + def set_items(self, keys_values): + # type: (Dict[str, Optional[Any]]) -> None + for data_key, data_value in keys_values.items(): + self.set_item(data_key, data_value) + + def root(self): + if self.identifier == ROOT_CONTEXT_ID: + return self + current = self + while current.parent is not None: + current = current.parent + return current + + +def __getattr__(name): + if name == "root": + return _CURRENT_CONTEXT.get().root() + raise AttributeError + + +def _reset_context(): + global _CURRENT_CONTEXT + _CURRENT_CONTEXT = contextvars.ContextVar("ExecutionContext_var", default=ExecutionContext(ROOT_CONTEXT_ID)) + + +_reset_context() +_CONTEXT_CLASS = ExecutionContext + + +def context_with_data(identifier, parent=None, **kwargs): + return _CONTEXT_CLASS.context_with_data(identifier, parent=(parent or _CURRENT_CONTEXT.get()), **kwargs) + + +def get_item(data_key, span=None): + # type: (str, Optional[Span]) -> Optional[Any] + _deprecate_span_kwarg(span) + if span is not None and span._local_root is not None: + return span._local_root._get_ctx_item(data_key) + else: + return _CURRENT_CONTEXT.get().get_item(data_key) # type: ignore + + +def get_items(data_keys, span=None): + # type: (List[str], Optional[Span]) -> Optional[Any] + _deprecate_span_kwarg(span) + if span is not None and span._local_root is not None: + return [span._local_root._get_ctx_item(key) for key in data_keys] + else: + return _CURRENT_CONTEXT.get().get_items(data_keys) # type: ignore + + +def set_safe(data_key, data_value): + # type: (str, Optional[Any]) -> None + _CURRENT_CONTEXT.get().set_safe(data_key, data_value) # type: ignore + + +# NB Don't call these set_* functions from `ddtrace.contrib`, only from product code! +def set_item(data_key, data_value, span=None): + # type: (str, Optional[Any], Optional[Span]) -> None + _deprecate_span_kwarg(span) + if span is not None and span._local_root is not None: + span._local_root._set_ctx_item(data_key, data_value) + else: + _CURRENT_CONTEXT.get().set_item(data_key, data_value) # type: ignore + + +def set_items(keys_values, span=None): + # type: (Dict[str, Optional[Any]], Optional[Span]) -> None + _deprecate_span_kwarg(span) + if span is not None and span._local_root is not None: + span._local_root._set_ctx_items(keys_values) + else: + _CURRENT_CONTEXT.get().set_items(keys_values) # type: ignore diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/core/event_hub.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/core/event_hub.py new file mode 100644 index 0000000..ae2fa07 --- /dev/null +++ b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/core/event_hub.py @@ -0,0 +1,134 @@ +import dataclasses +import enum +from typing import Any +from typing import Callable +from typing import Dict +from typing import List +from typing import Optional +from typing import Tuple + +from ddtrace import config + + +_listeners: Dict[str, Dict[Any, Callable[..., Any]]] = {} +_all_listeners: List[Callable[[str, Tuple[Any, ...]], None]] = [] + + +class ResultType(enum.Enum): + RESULT_OK = 0 + RESULT_EXCEPTION = 1 + RESULT_UNDEFINED = -1 + + +@dataclasses.dataclass +class EventResult: + response_type: ResultType = ResultType.RESULT_UNDEFINED + value: Any = None + exception: Optional[BaseException] = None + + def __bool__(self): + "EventResult can easily be checked as a valid result" + return self.response_type == ResultType.RESULT_OK + + +_MissingEvent = EventResult() + + +class EventResultDict(Dict[str, EventResult]): + def __missing__(self, key: str): + return _MissingEvent + + def __getattr__(self, name: str): + return dict.__getitem__(self, name) + + +_MissingEventDict = EventResultDict() + + +def has_listeners(event_id: str) -> bool: + """Check if there are hooks registered for the provided event_id""" + global _listeners + return bool(_listeners.get(event_id)) + + +def on(event_id: str, callback: Callable[..., Any], name: Any = None) -> None: + """Register a listener for the provided event_id""" + global _listeners + if name is None: + name = id(callback) + if event_id not in _listeners: + _listeners[event_id] = {} + _listeners[event_id][name] = callback + + +def on_all(callback: Callable[..., Any]) -> None: + """Register a listener for all events emitted""" + global _all_listeners + if callback not in _all_listeners: + _all_listeners.insert(0, callback) + + +def reset(event_id: Optional[str] = None) -> None: + """Remove all registered listeners. If an event_id is provided, only clear those + event listeners. + """ + global _listeners + global _all_listeners + + if not event_id: + _listeners.clear() + _all_listeners.clear() + elif event_id in _listeners: + del _listeners[event_id] + + +def dispatch(event_id: str, args: Tuple[Any, ...] = ()) -> None: + """Call all hooks for the provided event_id with the provided args""" + global _all_listeners + global _listeners + + for hook in _all_listeners: + try: + hook(event_id, args) + except Exception: + if config._raise: + raise + + if event_id not in _listeners: + return + + for local_hook in _listeners[event_id].values(): + try: + local_hook(*args) + except Exception: + if config._raise: + raise + + +def dispatch_with_results(event_id: str, args: Tuple[Any, ...] = ()) -> EventResultDict: + """Call all hooks for the provided event_id with the provided args + returning the results and exceptions from the called hooks + """ + global _listeners + global _all_listeners + + for hook in _all_listeners: + try: + hook(event_id, args) + except Exception: + if config._raise: + raise + + if event_id not in _listeners: + return _MissingEventDict + + results = EventResultDict() + for name, hook in _listeners[event_id].items(): + try: + results[name] = EventResult(ResultType.RESULT_OK, hook(*args)) + except Exception as e: + if config._raise: + raise + results[name] = EventResult(ResultType.RESULT_EXCEPTION, None, e) + + return results diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/datadog/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/datadog/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/datadog/profiling/__init__.py b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/datadog/profiling/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/datadog/profiling/_ddup.cpython-311-x86_64-linux-gnu.so b/lambdas/aws-dd-forwarder-3.127.0/ddtrace/internal/datadog/profiling/_ddup.cpython-311-x86_64-linux-gnu.so new file mode 100755 index 0000000000000000000000000000000000000000..321b7cae8808c9f5d41e7cdbffb9812494dd339d GIT binary patch literal 3667048 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