Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(onboarding): early exit conditions in lib-injection #9323

Merged
merged 101 commits into from
Jun 11, 2024
Merged
Changes from 11 commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
b8ab509
add logic for checking packages and runtimes against allowlists
ZStriker19 May 20, 2024
40a4449
enhance telemetry event to be sent and modify for package incompatibi…
ZStriker19 May 20, 2024
9c28276
add rest of telemetry events needed
ZStriker19 May 21, 2024
4f47c1b
update telemetry payload to be easily sendable
ZStriker19 May 21, 2024
c3f9073
refactor and change payload spec
ZStriker19 May 21, 2024
69c2c23
modifications for updated metrics, make python 2.7 compatible
ZStriker19 May 23, 2024
0201430
implement new payload standard, sending multiple events in payload, m…
ZStriker19 May 23, 2024
5d4371c
fix override tags
ZStriker19 May 23, 2024
2b50e83
update metrics with new tags
ZStriker19 May 24, 2024
aaa5e0e
remove send_telemetry
ZStriker19 May 24, 2024
31a8812
Merge branch 'main' into zachg/guardrails_poc
emmettbutler May 28, 2024
4e0b323
Update lib-injection/sitecustomize.py
ZStriker19 May 28, 2024
5ade18a
check in requirements_script
emmettbutler May 28, 2024
705b1ce
add mode that imports riot;
emmettbutler May 29, 2024
5083060
remove ast mode
emmettbutler May 29, 2024
9491cdd
minimum requiremets in dockerfile
emmettbutler May 29, 2024
0c651ab
rename
emmettbutler May 29, 2024
ae4450b
commit generated file
emmettbutler May 29, 2024
9cab85e
check in requirements_script
emmettbutler May 28, 2024
b5c3d8b
add mode that imports riot;
emmettbutler May 29, 2024
dcba12b
remove ast mode
emmettbutler May 29, 2024
a22a5a5
Merge branch 'emmett.butler/min_versions' into zachg/guardrails_poc
emmettbutler May 29, 2024
c45d32a
include min versions file in build
emmettbutler May 29, 2024
5a1546c
proper riotfile import
emmettbutler May 30, 2024
857e45e
Merge branch 'emmett.butler/min_versions' into zachg/guardrails_poc
emmettbutler May 30, 2024
5ac0ec5
add a copy of the csv to lib-injection
emmettbutler May 30, 2024
aaa1604
add copy during OCI build
emmettbutler May 30, 2024
ff77089
try different path
emmettbutler May 30, 2024
8cf5032
more copying
emmettbutler May 30, 2024
f51bec0
wrong name
emmettbutler May 30, 2024
f73eb10
path
emmettbutler May 30, 2024
154a71b
verbose and listdir for debugging
emmettbutler May 30, 2024
b97ea06
dir?
emmettbutler May 30, 2024
dfc9c82
handle unset executable path
emmettbutler May 30, 2024
72fb22d
py version detection logic fix
emmettbutler May 30, 2024
e36b24c
use pkg_resources instead
emmettbutler May 30, 2024
582ed77
strs
emmettbutler May 30, 2024
d335d38
ignore setuptools
emmettbutler May 30, 2024
d005634
no more pkg_resources beyond 3.7
emmettbutler May 30, 2024
20dce57
no letters allowed
emmettbutler May 30, 2024
b7c9c09
allow forcing
emmettbutler May 30, 2024
21fcf75
copypasta
emmettbutler May 31, 2024
f3186b9
debug print
emmettbutler May 31, 2024
57192e4
debug print
emmettbutler May 31, 2024
c3fb4c8
do not install ddtrace because it causes logic under test to be skipped
emmettbutler May 31, 2024
5f0bfa1
no ddtracerun if it is not installed
emmettbutler May 31, 2024
2242ed0
remove prints
emmettbutler May 31, 2024
6b8406f
add a test for unsupported package in env
emmettbutler May 31, 2024
a72a7a6
try installing a different unsupported package that will not break th…
emmettbutler May 31, 2024
7cf4e73
reno
emmettbutler May 31, 2024
ac556ca
Merge branch 'main' into zachg/guardrails_poc
emmettbutler May 31, 2024
037cc0f
include specifier info in file
emmettbutler May 31, 2024
2add1fa
smarter version constraint handling
emmettbutler May 31, 2024
db3e4fc
tup not gen
emmettbutler May 31, 2024
cd9b818
oops
emmettbutler May 31, 2024
cd7f30a
oops
emmettbutler May 31, 2024
8372d75
dead file
emmettbutler May 31, 2024
fce97f8
regenerate
emmettbutler May 31, 2024
a792a32
lint
emmettbutler Jun 3, 2024
86cfecf
cover some more pieces of the test matrix
emmettbutler Jun 4, 2024
53e8643
Update lib-injection/sitecustomize.py
emmettbutler Jun 4, 2024
4010b64
review feedback
emmettbutler Jun 4, 2024
87c3517
update compatible versions
emmettbutler Jun 4, 2024
2e7b12c
fix apparent logic error in version check
emmettbutler Jun 4, 2024
c974688
change force test to exercise a setup that will actually generate traces
emmettbutler Jun 4, 2024
c0d4db4
disable instrumentation of unsupported package
emmettbutler Jun 4, 2024
4fae4f5
try with a different unsupported library that hopefully does not inte…
emmettbutler Jun 4, 2024
e6be3e4
syntax
emmettbutler Jun 4, 2024
6b62355
quiet
emmettbutler Jun 4, 2024
f079e93
wait longer???
emmettbutler Jun 4, 2024
26b8ec0
pretty sure this is supposed to work
emmettbutler Jun 4, 2024
930df59
change ddtrace version being checked for
emmettbutler Jun 4, 2024
efa8538
undefined name
emmettbutler Jun 4, 2024
279f12e
telemetry enabled envvar
emmettbutler Jun 4, 2024
25191af
Update lib-injection/sitecustomize.py
emmettbutler Jun 4, 2024
3b4fe03
Update lib-injection/sitecustomize.py
emmettbutler Jun 4, 2024
d2466ff
catch everything
emmettbutler Jun 4, 2024
e5c84da
Update releasenotes/notes/injection-guardrails-bde1d57db91f33d1.yaml
emmettbutler Jun 4, 2024
faba009
suitespec
emmettbutler Jun 6, 2024
9ebf5d8
checkpoint
emmettbutler Jun 10, 2024
1dfdb91
try platform pyversion again
emmettbutler Jun 10, 2024
b69ce20
strip minor version for site-packages path
emmettbutler Jun 10, 2024
09c6ca4
Merge branch 'main' into zachg/guardrails_poc
emmettbutler Jun 10, 2024
5e1e4ba
simpler path
emmettbutler Jun 10, 2024
0e31d1e
try a different path
emmettbutler Jun 10, 2024
b3332f8
multiple paths
emmettbutler Jun 10, 2024
ce22468
only use the first file found
emmettbutler Jun 10, 2024
c3f6936
Merge branch 'main' into zachg/guardrails_poc
emmettbutler Jun 10, 2024
8affa8f
Merge branch 'main' into zachg/guardrails_poc
emmettbutler Jun 10, 2024
b839f2a
Merge branch 'main' into zachg/guardrails_poc
emmettbutler Jun 10, 2024
24ec2b9
case insensitive package name comparisons
emmettbutler Jun 10, 2024
79e0d2a
missed a spot
emmettbutler Jun 10, 2024
5ab1346
Merge branch 'main' into zachg/guardrails_poc
emmettbutler Jun 10, 2024
8b08f59
missed a spot
emmettbutler Jun 10, 2024
a40cf90
Update lib-injection/sitecustomize.py
emmettbutler Jun 10, 2024
6b03ec4
Merge branch 'main' into zachg/guardrails_poc
emmettbutler Jun 10, 2024
362c030
Merge branch 'main' into zachg/guardrails_poc
emmettbutler Jun 10, 2024
2e80831
Merge branch 'main' into zachg/guardrails_poc
emmettbutler Jun 10, 2024
08cd07c
Update lib-injection/sitecustomize.py
emmettbutler Jun 10, 2024
8895a65
Update lib-injection/sitecustomize.py
emmettbutler Jun 10, 2024
43bdbc3
Merge branch 'main' into zachg/guardrails_poc
emmettbutler Jun 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
214 changes: 182 additions & 32 deletions lib-injection/sitecustomize.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,113 @@
This module when included on the PYTHONPATH will update the PYTHONPATH to point to a directory
containing the ddtrace package compatible with the current Python version and platform.
"""
from __future__ import print_function # noqa: E402

import json
import os
import platform
import subprocess
import sys
import time

import pkg_resources


# TODO: build list from riotfile instead of hardcoding
pkgs_allow_list = {
"flask": {"min": "1.0.0", "max": "3.3.0"},
}

runtimes_allow_list = {
"cpython": {"min": "3.7", "max": "3.12"},
}

allow_unsupported_integrations = os.environ.get("DD_TRACE_ALLOW_UNSUPPORTED_SSI_INTEGRATIONS", "").lower() in (
"true",
"1",
"t",
)
allow_unsupported_runtimes = os.environ.get("DD_TRACE_ALLOW_UNSUPPORTED_SSI_RUNTIMES", "").lower() in ("true", "1", "t")

installed_packages = pkg_resources.working_set
emmettbutler marked this conversation as resolved.
Show resolved Hide resolved
installed_packages = {pkg.key: pkg.version for pkg in installed_packages}

python_runtime = platform.python_implementation().lower()
python_version = ".".join(str(i) for i in sys.version_info[:2])


def create_count_metric(metric, tags=[]):
return {
"name": metric,
"tags": tags,
}


def gen_telemetry_payload(telemetry_events):
return {
"metadata": {
"language_name": "python",
"language_version": python_version,
"runtime_name": python_runtime,
"runtime_version": python_version,
"tracer_name": "python",
emmettbutler marked this conversation as resolved.
Show resolved Hide resolved
"tracer_version": installed_packages.get("ddtrace", "unknown"),
"pid": os.getpid(),
},
"points": telemetry_events,
}


def send_telemetry(event):
event_json = json.dumps(event)
print(event_json)
emmettbutler marked this conversation as resolved.
Show resolved Hide resolved
p = subprocess.Popen(
["/home/kyle_verhoog_datadoghq_com/dd_telemetry", str(os.getpid())],
ZStriker19 marked this conversation as resolved.
Show resolved Hide resolved
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
)
p.stdin.write(event_json)
p.stdin.close()


debug_mode = os.environ.get("DD_TRACE_DEBUG", "").lower() in ("true", "1", "t")
# Python versions that are supported by the current ddtrace release
installable_py_versions = ("3.7", "3.8", "3.9", "3.10", "3.11", "3.12")


def _get_clib():
"""Return the C library used by the system.

If GNU is not detected then returns MUSL.
"""
import platform

libc, version = platform.libc_ver()
if libc == "glibc":
return "gnu"
return "musl"


def _log(msg, *args, level="info"):
def _log(msg, *args, **kwargs):
"""Log a message to stderr.

This function is provided instead of built-in Python logging since we can't rely on any logger
being configured.
"""
level = kwargs.get("level", "info")
if debug_mode:
asctime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
msg = "[%s] [%s] datadog.autoinstrumentation(pid: %d): " % (asctime, level.upper(), os.getpid()) + msg % args
print(msg, file=sys.stderr)


def _inject():
telemetry_data = []
integration_incomp = False
runtime_incomp = False
try:
import ddtrace
except ModuleNotFoundError:
except ImportError:
_log("user-installed ddtrace not found, configuring application to use injection site-packages")

platform = "manylinux2014" if _get_clib() == "gnu" else "musllinux_1_1"
Expand All @@ -51,13 +119,74 @@ def _inject():
_log("ddtrace_pkgs path is %r" % pkgs_path, level="debug")
_log("ddtrace_pkgs contents: %r" % os.listdir(pkgs_path), level="debug")

python_version = ".".join(str(i) for i in sys.version_info[:2])
if python_version not in installable_py_versions:
_log(
f"This version of ddtrace does not support single step instrumentation with python {python_version} "
f"(supported versions: {installable_py_versions}), aborting",
level="error",
# check installed packages against allow list
incompatible_packages = {}
for package_name, package_version in installed_packages.items():
if package_name in pkgs_allow_list:
# TODO: this checking code will have to be more intelligent in the future
if (
package_version < pkgs_allow_list[package_name]["min"]
or package_version > pkgs_allow_list[package_name]["max"]
):
incompatible_packages[package_name] = package_version

if incompatible_packages:
_log("Found incompatible packages: %s." % incompatible_packages, level="debug")
integration_incomp = True
if not allow_unsupported_integrations:
_log("Aborting dd-trace-py instrumentation.", level="debug")

for key, value in incompatible_packages.items():
telemetry_data.append(
create_count_metric(
"library_entrypoint.abort.integration",
[
"integration_name:" + key,
emmettbutler marked this conversation as resolved.
Show resolved Hide resolved
"integration_version:" + value,
"min_supported_version:" + pkgs_allow_list[key]["min"],
"max_supported_version:" + pkgs_allow_list[key]["max"],
],
)
)

else:
_log(
"DD_TRACE_ALLOW_UNSUPPORTED_SSI_INTEGRATIONS set to True, allowing unsupported integrations.",
level="debug",
)
if python_version not in runtimes_allow_list.get(python_runtime, []):
_log("Found incompatible runtime: %s %s." % (python_runtime, python_version), level="debug")
emmettbutler marked this conversation as resolved.
Show resolved Hide resolved
runtime_incomp = True
if not allow_unsupported_runtimes:
_log("Aborting dd-trace-py instrumentation.", level="debug")

telemetry_data.append(
create_count_metric(
"library_entrypoint.abort.runtime",
[
"min_supported_version:"
emmettbutler marked this conversation as resolved.
Show resolved Hide resolved
+ runtimes_allow_list.get(python_runtime, {}).get("min", "unknown"),
"max_supported_version:"
+ runtimes_allow_list.get(python_runtime, {}).get("max", "unknown"),
],
)
)
else:
_log(
"DD_TRACE_ALLOW_UNSUPPORTED_SSI_RUNTIMES set to True, allowing unsupported runtimes.",
level="debug",
)
if telemetry_data:
telemetry_data.append(
create_count_metric(
"library_entrypoint.abort",
[
"reason:integration" if integration_incomp else "reason:incompatible_runtime",
],
)
)
telemetry_event = gen_telemetry_payload(telemetry_data)
send_telemetry(telemetry_event)
return

site_pkgs_path = os.path.join(pkgs_path, "site-packages-ddtrace-py%s-%s" % (python_version, platform))
Expand All @@ -69,7 +198,6 @@ def _inject():
# Add the custom site-packages directory to the Python path to load the ddtrace package.
sys.path.insert(0, site_pkgs_path)
_log("sys.path %s" % sys.path, level="debug")

try:
import ddtrace # noqa: F401

Expand All @@ -79,29 +207,51 @@ def _inject():
else:
# In injected environments, the profiler needs to know that it is only allowed to use the native exporter
os.environ["DD_PROFILING_EXPORT_LIBDD_REQUIRED"] = "true"

# We should wrap this import at the very least in a try except block
emmettbutler marked this conversation as resolved.
Show resolved Hide resolved
# This import has the same effect as ddtrace-run for the current process (auto-instrument all libraries).
import ddtrace.bootstrap.sitecustomize

# Modify the PYTHONPATH for any subprocesses that might be spawned:
# - Remove the PYTHONPATH entry used to bootstrap this installation as it's no longer necessary
# now that the package is installed.
# - Add the custom site-packages directory to PYTHONPATH to ensure the ddtrace package can be loaded
# - Add the ddtrace bootstrap dir to the PYTHONPATH to achieve the same effect as ddtrace-run.
python_path = os.getenv("PYTHONPATH", "").split(os.pathsep)
if script_dir in python_path:
python_path.remove(script_dir)
python_path.insert(0, site_pkgs_path)
bootstrap_dir = os.path.abspath(os.path.dirname(ddtrace.bootstrap.sitecustomize.__file__))
python_path.insert(0, bootstrap_dir)
python_path = os.pathsep.join(python_path)
os.environ["PYTHONPATH"] = python_path

# Also insert the bootstrap dir in the path of the current python process.
sys.path.insert(0, bootstrap_dir)
_log("successfully configured ddtrace package, python path is %r" % os.environ["PYTHONPATH"])
try:
import ddtrace.bootstrap.sitecustomize

# Modify the PYTHONPATH for any subprocesses that might be spawned:
# - Remove the PYTHONPATH entry used to bootstrap this installation as it's no longer necessary
# now that the package is installed.
# - Add the custom site-packages directory to PYTHONPATH to ensure the ddtrace package can be loaded
# - Add the ddtrace bootstrap dir to the PYTHONPATH to achieve the same effect as ddtrace-run.
python_path = os.getenv("PYTHONPATH", "").split(os.pathsep)
if script_dir in python_path:
python_path.remove(script_dir)
python_path.insert(0, site_pkgs_path)
bootstrap_dir = os.path.abspath(os.path.dirname(ddtrace.bootstrap.sitecustomize.__file__))
python_path.insert(0, bootstrap_dir)
python_path = os.pathsep.join(python_path)
os.environ["PYTHONPATH"] = python_path

# Also insert the bootstrap dir in the path of the current python process.
sys.path.insert(0, bootstrap_dir)
_log("successfully configured ddtrace package, python path is %r" % os.environ["PYTHONPATH"])
event = gen_telemetry_payload(
[
create_count_metric(
"library_entrypoint.complete",
[
"injection_forced:" + str(runtime_incomp or integration_incomp).lower(),
],
)
]
)
send_telemetry(event)
except Exception as e:
# maybe switch errortype since error will have too high over cardinality
emmettbutler marked this conversation as resolved.
Show resolved Hide resolved
event = gen_telemetry_payload(
[create_count_metric("library_entrypoint.error", ["error:" + type(e).__name__.lower()])]
emmettbutler marked this conversation as resolved.
Show resolved Hide resolved
)
send_telemetry(event)
_log("failed to load ddtrace.bootstrap.sitecustomize: %s" % e, level="error")
return
else:
_log(f"user-installed ddtrace found: {ddtrace.__version__}, aborting site-packages injection", level="warning")
_log(
"user-installed ddtrace found: %s, aborting site-packages injection" % ddtrace.__version__, level="warning"
)


_inject()
Loading