Skip to content

Commit a79ff08

Browse files
authored
fix OpenResty crash (#7)
* chmod the built .so (not sure if it matters) * avoid use of ngx_core_conf_t
1 parent 6e25f1b commit a79ff08

14 files changed

+136
-65
lines changed

.circleci/config.yml

+38-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ jobs:
4141
destination: nginx-tag
4242

4343
test:
44+
parameters:
45+
# I'm hacking in testing support for OpenResty.
46+
# If this parameter is not empty, then the runner (below) will override
47+
# the docker image used inside of the tests, so it's an OpenResty image
48+
# instead of an nginx image.
49+
openresty-image:
50+
type: string
51+
default: ""
4452
docker:
4553
# "cimg" means "convenience image." Helpful images provided by CircleCI.
4654
# This one contains Python, docker-compose, and a bunch of other things.
@@ -67,7 +75,13 @@ jobs:
6775
# TODO: This sucks, though. Find a better way. The output of a failed test
6876
# runner should be red in the CI interface.
6977
- run: |
70-
if test/bin/run --verbose; then
78+
openresty_image=<< parameters.openresty-image >>
79+
if [ "$openresty_image" != '' ]; then
80+
export NGINX_MODULES_DIRECTORY=/usr/local/openresty/nginx/modules
81+
export NGINX_IMAGE=$openresty_image
82+
export NGINX_CONF_PATH=/usr/local/openresty/nginx/conf/nginx.conf
83+
fi
84+
if test/bin/run --verbose --failfast; then
7185
touch .tests-succeeded
7286
fi
7387
- store_artifacts:
@@ -83,6 +97,29 @@ jobs:
8397
workflows:
8498
build-and-test-all:
8599
jobs:
100+
# In addition to the jobs generated by `../bin/generate_jobs_yaml.sh`,
101+
# I hard-code the latest OpenResty equivalent, just for testing.
102+
- test:
103+
name: "test 1.21.4-alpine OpenResty"
104+
openresty-image: "openresty/openresty:1.21.4.1-alpine"
105+
requires:
106+
- "build 1.21.4-alpine"
107+
filters:
108+
tags:
109+
only: /^v[0-9]+\.[0-9]+\.[0-9]+/
110+
# I'm commenting out the Debian-based version of OpenResty because, as of
111+
# this writing, their nginx is built without --with-compat, which means
112+
# that our module fails the module signature check.
113+
# - test:
114+
# name: "test 1.21.4 OpenResty"
115+
# openresty-image: "openresty/openresty:1.21.4.1-bullseye"
116+
# requires:
117+
# - "build 1.21.4"
118+
# filters:
119+
# tags:
120+
# only: /^v[0-9]+\.[0-9]+\.[0-9]+/
121+
122+
# The following are generated by `../bin/generate_jobs_yaml.sh`.
86123
- build:
87124
name: "build 1.23.2-alpine"
88125
build-image: "datadog/docker-library:nginx-datadog-build-1.23.2-alpine"

.dockerignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.build/
2+
.docker-build/

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ MAKE_JOB_COUNT ?= $(shell nproc)
99
.PHONY: build
1010
build: build-deps nginx/objs/Makefile sources
1111
mkdir -p $(BUILD_DIR) && cd $(BUILD_DIR) && cmake -DBUILD_TESTING=OFF .. && make -j $(MAKE_JOB_COUNT) VERBOSE=1
12+
chmod 755 $(BUILD_DIR)/libngx_http_datadog_module.so
1213
@echo 'build successful 👍'
1314

1415
.PHONY: sources

src/datadog_conf.h

+15-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ extern "C" {
1313
#include <opentracing/span.h>
1414

1515
#include <string>
16+
#include <vector>
1617

1718
namespace datadog {
1819
namespace nginx {
@@ -22,12 +23,17 @@ struct datadog_tag_t {
2223
NgxScript value_script;
2324
};
2425

25-
struct conf_directive_source_location {
26+
struct conf_directive_source_location_t {
2627
ngx_str_t file_name; // e.g. "nginx.conf"
2728
ngx_uint_t line; // line number within the file `file_name`
2829
ngx_str_t directive_name; // e.g. "proxy_pass"
2930
};
3031

32+
struct environment_variable_t {
33+
std::string name;
34+
std::string value;
35+
};
36+
3137
struct datadog_main_conf_t {
3238
ngx_array_t *tags;
3339
// `is_tracer_configured` is whether the tracer has been configured, either
@@ -48,7 +54,7 @@ struct datadog_main_conf_t {
4854
// > Configuration already loaded to default configuration by
4955
// > [[source location]]. Explicit configuration must appear before
5056
// > the first [[directive name]].
51-
conf_directive_source_location tracer_conf_source_location;
57+
conf_directive_source_location_t tracer_conf_source_location;
5258
// `are_log_formats_defined` is whether we have already injected `log_format`
5359
// directives into the configuration. The directives define Datadog-specific
5460
// access log formats; one of which will override nginx's default.
@@ -58,6 +64,13 @@ struct datadog_main_conf_t {
5864
// directive).
5965
bool are_log_formats_defined;
6066
ngx_array_t *span_context_keys;
67+
// This module automates the forwarding of the environment variables in
68+
// `TracingLibrary::environment_variable_names()`. Rather than injecting
69+
// `env` directives into the configuration, or mucking around with the core
70+
// module configuration, instead we grab the values from the environment
71+
// of the master process and apply them later in the worker processes after
72+
// `fork()`.
73+
std::vector<environment_variable_t> environment_variables;
6174
};
6275

6376
struct datadog_loc_conf_t {

src/datadog_directive.cpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,9 @@ char *set_tracer(const ngx_command_t *command, ngx_conf_t *conf, string_view tra
7676
main_conf->is_tracer_configured = true;
7777
main_conf->tracer_conf = to_ngx_str(conf->pool, tracer_conf);
7878
main_conf->tracer_conf_source_location =
79-
conf_directive_source_location{.file_name = conf->conf_file->file.name,
80-
.line = conf->conf_file->line,
81-
.directive_name = command->name};
79+
conf_directive_source_location_t{.file_name = conf->conf_file->file.name,
80+
.line = conf->conf_file->line,
81+
.directive_name = command->name};
8282

8383
// In order for span context propagation to work, the names of the HTTP
8484
// headers added to requests need to be known ahead of time.

src/ngx_http_datadog_module.cpp

+18-47
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <cstdlib>
66
#include <exception>
77
#include <iterator>
8+
#include <string>
89
#include <utility>
910

1011
#include "datadog_conf.h"
@@ -25,6 +26,7 @@ extern "C" {
2526
#include <ngx_config.h>
2627
#include <ngx_core.h>
2728
#include <ngx_http.h>
29+
#include <stdlib.h> // ::setenv
2830
}
2931

3032
// clang-format off
@@ -68,7 +70,6 @@ static ngx_uint_t anywhere =
6870
| NGX_HTTP_MAIN_CONF; // the toplevel configuration, e.g. where modules are loaded
6971

7072
static ngx_command_t datadog_commands[] = {
71-
7273
{ ngx_string("opentracing"),
7374
anywhere | NGX_CONF_TAKE1,
7475
toggle_opentracing,
@@ -213,7 +214,7 @@ static ngx_command_t datadog_commands[] = {
213214
NGX_HTTP_LOC_CONF_OFFSET,
214215
0,
215216
nullptr},
216-
217+
217218
ngx_null_command
218219
};
219220

@@ -247,52 +248,7 @@ ngx_module_t ngx_http_datadog_module = {
247248
};
248249
// clang-format on
249250

250-
// Configure nginx to set the environment variable as indicated by the
251-
// specified `entry` in the context of the specified `cycle`. `entry` is a
252-
// string in one of the following forms:
253-
//
254-
// 1. "FOO"
255-
// 2. "FOO=value"
256-
//
257-
// The environment variable name in this example is "FOO". In the case of the
258-
// first form, the value of the environment variable will be inherited from the
259-
// parent process. In the case of the second form, the value of the
260-
// environment variable will be as specified after the equal sign.
261-
//
262-
// Note that `ngx_set_env` is adapted from the function of the same name in
263-
// `nginx.c` within the nginx source code.
264-
static void *ngx_set_env(string_view entry, ngx_cycle_t *cycle) {
265-
ngx_core_conf_t *ccf = (ngx_core_conf_t *)ngx_get_conf(cycle->conf_ctx, ngx_core_module);
266-
267-
ngx_str_t *value, *var;
268-
ngx_uint_t i;
269-
270-
var = (ngx_str_t *)ngx_array_push(&ccf->env);
271-
if (var == NULL) {
272-
return NGX_CONF_ERROR;
273-
}
274-
275-
const ngx_str_t entry_str = to_ngx_str(entry);
276-
*var = entry_str;
277-
278-
for (i = 0; i < var->len; i++) {
279-
if (var->data[i] == '=') {
280-
var->len = i;
281-
return NGX_CONF_OK;
282-
}
283-
}
284-
285-
return NGX_CONF_OK;
286-
}
287-
288251
static ngx_int_t datadog_master_process_post_config(ngx_cycle_t *cycle) noexcept {
289-
// Forward tracer-specific environment variables to worker processes.
290-
for (const auto &env_var_name : TracingLibrary::environment_variable_names()) {
291-
if (const void *const error = ngx_set_env(env_var_name, cycle)) {
292-
return ngx_int_t(error);
293-
}
294-
}
295-
296252
// If tracing has not so far been configured, then give it a default
297253
// configuration. This means that the nginx configuration did not use the
298254
// `datadog` directive, and did not use any overridden directives, such as
@@ -304,6 +260,16 @@ static ngx_int_t datadog_master_process_post_config(ngx_cycle_t *cycle) noexcept
304260
main_conf->tracer_conf = ngx_string(""); // default config
305261
}
306262

263+
// Forward tracer-specific environment variables to worker processes.
264+
std::string name;
265+
for (const auto &env_var_name : TracingLibrary::environment_variable_names()) {
266+
name = env_var_name;
267+
if (const char *value = std::getenv(name.c_str())) {
268+
main_conf->environment_variables.push_back(
269+
environment_variable_t{.name = name, .value = value});
270+
}
271+
}
272+
307273
return NGX_OK;
308274
}
309275

@@ -343,6 +309,11 @@ static ngx_int_t datadog_init_worker(ngx_cycle_t *cycle) noexcept try {
343309
return NGX_OK;
344310
}
345311

312+
for (const auto &entry : main_conf->environment_variables) {
313+
const bool overwrite = false;
314+
::setenv(entry.name.c_str(), entry.value.c_str(), overwrite);
315+
}
316+
346317
std::shared_ptr<ot::Tracer> tracer = load_tracer(cycle->log, str(main_conf->tracer_conf));
347318
if (!tracer) {
348319
return NGX_ERROR;

test/bin/run

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ cd "$(dirname "$0")/../"
44

55
set -e
66

7-
export NGINX_IMAGE="nginx:$(cat ../nginx-tag)"
7+
export NGINX_IMAGE=${NGINX_IMAGE:-nginx:$(cat ../nginx-tag)}
8+
export NGINX_MODULES_DIRECTORY=${NGINX_MODULES_DIRECTORY:-/usr/lib/nginx/modules}
89

910
docker-compose build --parallel
1011
python3 -m unittest "$@"

test/bin/run_parallel

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ cd "$(dirname "$0")/../"
44

55
set -e
66

7-
export NGINX_IMAGE="nginx:$(cat ../nginx-tag)"
7+
export NGINX_IMAGE=${NGINX_IMAGE:-nginx:$(cat ../nginx-tag)}
8+
export NGINX_MODULES_DIRECTORY=${NGINX_MODULES_DIRECTORY:-/usr/lib/nginx/modules}
89
docker-compose build --parallel
910

1011
scratch=$(mktemp -d)

test/cases/environment_variables/conf/nginx.conf

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ error_log stderr;
22

33
load_module modules/ngx_http_datadog_module.so;
44

5+
# Make sure that if the user overrides the value of an environment variable,
6+
# that the override is honored.
7+
env DD_VERSION=overridden;
8+
59
events {
610
worker_connections 1024;
711
}

test/cases/environment_variables/test_environment_variables.py

+4
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ def test_environment_variables(self):
7373
# Env variables that are not on the allow list return "-".
7474
self.assertEqual('-', worker_env[variable_name],
7575
error_context)
76+
elif variable_name == 'DD_VERSION':
77+
# conf/nginx.conf has an "env" directive that overrides DD_VERSION.
78+
self.assertEqual('overridden', worker_env[variable_name],
79+
error_context)
7680
else:
7781
self.assertEqual(value, worker_env[variable_name],
7882
error_context)

test/cases/orchestration.py

+12-9
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@
3030
docker_compose_flags = []
3131
docker_flags = []
3232

33+
# Depending on which Docker image nginx is running in, the nginx config might be
34+
# in different locations. Ideally we could deduce the path to the config file
35+
# by parsing `nginx -V`, but that doesn't always work (e.g. OpenResty).
36+
nginx_conf_path = os.environ.get('NGINX_CONF_PATH', '/etc/nginx/nginx.conf')
37+
3338

3439
def docker_compose_command(*args):
3540
return [docker_compose_command_path, *docker_compose_flags, *args]
@@ -56,13 +61,9 @@ def child_env(parent_env=None):
5661
parent_env = os.environ
5762

5863
result = {}
59-
if 'NGINX_IMAGE' in parent_env:
60-
result['NGINX_IMAGE'] = parent_env['NGINX_IMAGE']
61-
else:
62-
# [repo]/test/cases/orchestration.py -> [repo]/nginx-tag
63-
version = (Path(__file__).resolve().parent.parent.parent /
64-
'nginx-tag').read_text()
65-
result['NGINX_IMAGE'] = f'nginx:{version}'
64+
for name in ('NGINX_IMAGE', 'NGINX_MODULES_DIRECTORY'):
65+
if name in parent_env:
66+
result[name] = parent_env[name]
6667

6768
# Forward DOCKER_HOST, DOCKER_TLS_VERIFY, etc.
6869
for name, value in parent_env.items():
@@ -258,7 +259,9 @@ def print_duration(of_what, output):
258259
before = time.monotonic()
259260
yield
260261
after = time.monotonic()
261-
print(f'{of_what} took {after - before} seconds.', file=output, flush=True)
262+
print(f'{of_what} took {after - before:.5} seconds.',
263+
file=output,
264+
flush=True)
262265

263266

264267
def docker_compose_services():
@@ -561,7 +564,7 @@ def nginx_replace_config(self, nginx_conf_text, file_name):
561564
return status, log_lines
562565

563566
script = f"""
564-
>/etc/nginx/nginx.conf cat <<'END_CONF'
567+
>{nginx_conf_path} cat <<'END_CONF'
565568
{nginx_conf_text}
566569
END_CONF
567570
"""

test/docker-compose.yml

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ services:
1010
dockerfile: ./Dockerfile
1111
args:
1212
NGINX_IMAGE: ${NGINX_IMAGE}
13+
NGINX_MODULES_DIRECTORY: ${NGINX_MODULES_DIRECTORY}
1314
environment:
1415
- DD_ENV=prod
1516
- DD_AGENT_HOST=agent

test/services/nginx/Dockerfile

+9-1
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,21 @@
22
ARG NGINX_IMAGE
33
FROM ${NGINX_IMAGE}
44

5+
# If NGINX_IMAGE is not a core nginx image but instead OpenResty, the file system layout
6+
# will differ from that expected in the tests' conf files.
7+
# Put nginx's default HTML in a known location to smooth things over.
8+
RUN mkdir -p /usr/share/nginx/html
9+
COPY index.html /usr/share/nginx/html
10+
511
# Install the `kill` command (`procps` package on Debian), so that one-off
612
# nginx instances that are started via `docker-compose exec` can be signaled to
713
# gracefully shutdown.
814
COPY ./install_tools.sh /tmp/
915
RUN /tmp/install_tools.sh
1016

11-
COPY ngx_http_datadog_module.so /usr/lib/nginx/modules
17+
ARG NGINX_MODULES_DIRECTORY
18+
RUN mkdir -p ${NGINX_MODULES_DIRECTORY}
19+
COPY ngx_http_datadog_module.so ${NGINX_MODULES_DIRECTORY}
1220

1321
ENTRYPOINT ["nginx"]
1422
CMD ["-g", "daemon off;"]

test/services/nginx/index.html

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Welcome to nginx!</title>
5+
<style>
6+
body {
7+
width: 35em;
8+
margin: 0 auto;
9+
font-family: Tahoma, Verdana, Arial, sans-serif;
10+
}
11+
</style>
12+
</head>
13+
<body>
14+
<h1>Welcome to nginx!</h1>
15+
<p>If you see this page, the nginx web server is successfully installed and
16+
working. Further configuration is required.</p>
17+
18+
<p>For online documentation and support please refer to
19+
<a href="http://nginx.org/">nginx.org</a>.<br/>
20+
Commercial support is available at
21+
<a href="http://nginx.com/">nginx.com</a>.</p>
22+
23+
<p><em>Thank you for using nginx.</em></p>
24+
</body>
25+
</html>

0 commit comments

Comments
 (0)