1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.services.datadog-agent;
9
10 ddConf = {
11 skip_ssl_validation = false;
12 confd_path = "/etc/datadog-agent/conf.d";
13 additional_checksd = "/etc/datadog-agent/checks.d";
14 use_dogstatsd = true;
15 }
16 // lib.optionalAttrs (cfg.logLevel != null) { log_level = cfg.logLevel; }
17 // lib.optionalAttrs (cfg.hostname != null) { inherit (cfg) hostname; }
18 // lib.optionalAttrs (cfg.ddUrl != null) { dd_url = cfg.ddUrl; }
19 // lib.optionalAttrs (cfg.site != null) { site = cfg.site; }
20 // lib.optionalAttrs (cfg.tags != null) { tags = lib.concatStringsSep ", " cfg.tags; }
21 // lib.optionalAttrs (cfg.enableLiveProcessCollection) {
22 process_config = {
23 dd_agent_bin = "${datadogPkg}/bin/agent";
24 process_collection.enabled = "true";
25 container_collection.enabled = "true";
26 };
27 }
28 // lib.optionalAttrs (cfg.enableTraceAgent) {
29 apm_config = {
30 enabled = true;
31 };
32 }
33 // cfg.extraConfig;
34
35 # Generate Datadog configuration files for each configured checks.
36 # This works because check configurations have predictable paths,
37 # and because JSON is a valid subset of YAML.
38 makeCheckConfigs =
39 entries:
40 lib.mapAttrs' (name: conf: {
41 name = "datadog-agent/conf.d/${name}.d/conf.yaml";
42 value.source = pkgs.writeText "${name}-check-conf.yaml" (builtins.toJSON conf);
43 }) entries;
44
45 defaultChecks = {
46 disk = cfg.diskCheck;
47 network = cfg.networkCheck;
48 };
49
50 # Assemble all check configurations and the top-level agent
51 # configuration.
52 etcfiles =
53 with pkgs;
54 with builtins;
55 {
56 "datadog-agent/datadog.yaml" = {
57 source = writeText "datadog.yaml" (toJSON ddConf);
58 };
59 }
60 // makeCheckConfigs (cfg.checks // defaultChecks);
61
62 # Apply the configured extraIntegrations to the provided agent
63 # package. See the documentation of `dd-agent/integrations-core.nix`
64 # for detailed information on this.
65 datadogPkg = cfg.package.override {
66 pythonPackages = pkgs.datadog-integrations-core cfg.extraIntegrations;
67 };
68in
69{
70 options.services.datadog-agent = {
71 enable = lib.mkEnableOption "Datadog-agent v7 monitoring service";
72
73 package = lib.mkPackageOption pkgs "datadog-agent" {
74 extraDescription = ''
75 ::: {.note}
76 The provided package is expected to have an overridable `pythonPackages`-attribute
77 which configures the Python environment with the Datadog checks.
78 :::
79 '';
80 };
81
82 apiKeyFile = lib.mkOption {
83 description = ''
84 Path to a file containing the Datadog API key to associate the
85 agent with your account.
86 '';
87 example = "/run/keys/datadog_api_key";
88 type = lib.types.path;
89 };
90
91 ddUrl = lib.mkOption {
92 description = ''
93 Custom dd_url to configure the agent with. Useful if traffic to datadog
94 needs to go through a proxy.
95 Don't use this to point to another datadog site (EU) - use site instead.
96 '';
97 default = null;
98 example = "http://haproxy.example.com:3834";
99 type = lib.types.nullOr lib.types.str;
100 };
101
102 site = lib.mkOption {
103 description = ''
104 The datadog site to point the agent towards.
105 Set to datadoghq.eu to point it to their EU site.
106 '';
107 default = null;
108 example = "datadoghq.eu";
109 type = lib.types.nullOr lib.types.str;
110 };
111
112 tags = lib.mkOption {
113 description = "The tags to mark this Datadog agent";
114 example = [
115 "test"
116 "service"
117 ];
118 default = null;
119 type = lib.types.nullOr (lib.types.listOf lib.types.str);
120 };
121
122 hostname = lib.mkOption {
123 description = "The hostname to show in the Datadog dashboard (optional)";
124 default = null;
125 example = "mymachine.mydomain";
126 type = lib.types.nullOr lib.types.str;
127 };
128
129 logLevel = lib.mkOption {
130 description = "Logging verbosity.";
131 default = null;
132 type = lib.types.nullOr (
133 lib.types.enum [
134 "DEBUG"
135 "INFO"
136 "WARN"
137 "ERROR"
138 ]
139 );
140 };
141
142 extraIntegrations = lib.mkOption {
143 default = { };
144 type = lib.types.attrs;
145
146 description = ''
147 Extra integrations from the Datadog core-integrations
148 repository that should be built and included.
149
150 By default the included integrations are disk, mongo, network,
151 nginx and postgres.
152
153 To include additional integrations the name of the derivation
154 and a function to filter its dependencies from the Python
155 package set must be provided.
156 '';
157
158 example = lib.literalExpression ''
159 {
160 ntp = pythonPackages: [ pythonPackages.ntplib ];
161 }
162 '';
163 };
164
165 extraConfig = lib.mkOption {
166 default = { };
167 type = lib.types.attrs;
168 description = ''
169 Extra configuration options that will be merged into the
170 main config file {file}`datadog.yaml`.
171 '';
172 };
173
174 enableLiveProcessCollection = lib.mkOption {
175 description = ''
176 Whether to enable the live process collection agent.
177 '';
178 default = false;
179 type = lib.types.bool;
180 };
181
182 processAgentPackage = lib.mkOption {
183 default = pkgs.datadog-process-agent;
184 defaultText = lib.literalExpression "pkgs.datadog-process-agent";
185 description = ''
186 Which DataDog v7 agent package to use. Note that the provided
187 package is expected to have an overridable `pythonPackages`-attribute
188 which configures the Python environment with the Datadog
189 checks.
190 '';
191 type = lib.types.package;
192 };
193
194 enableTraceAgent = lib.mkOption {
195 description = ''
196 Whether to enable the trace agent.
197 '';
198 default = false;
199 type = lib.types.bool;
200 };
201
202 checks = lib.mkOption {
203 description = ''
204 Configuration for all Datadog checks. Keys of this attribute
205 set will be used as the name of the check to create the
206 appropriate configuration in `conf.d/$check.d/conf.yaml`.
207
208 The configuration is converted into JSON from the plain Nix
209 language configuration, meaning that you should write
210 configuration adhering to Datadog's documentation - but in Nix
211 language.
212
213 Refer to the implementation of this module (specifically the
214 definition of `defaultChecks`) for an example.
215
216 Note: The 'disk' and 'network' check are configured in
217 separate options because they exist by default. Attempting to
218 override their configuration here will have no effect.
219 '';
220
221 example = {
222 http_check = {
223 init_config = null; # sic!
224 instances = [
225 {
226 name = "some-service";
227 url = "http://localhost:1337/healthz";
228 tags = [ "some-service" ];
229 }
230 ];
231 };
232 };
233
234 default = { };
235
236 # sic! The structure of the values is up to the check, so we can
237 # not usefully constrain the type further.
238 type = with lib.types; attrsOf attrs;
239 };
240
241 diskCheck = lib.mkOption {
242 description = "Disk check config";
243 type = lib.types.attrs;
244 default = {
245 init_config = { };
246 instances = [ { use_mount = "false"; } ];
247 };
248 };
249
250 networkCheck = lib.mkOption {
251 description = "Network check config";
252 type = lib.types.attrs;
253 default = {
254 init_config = { };
255 # Network check only supports one configured instance
256 instances = [
257 {
258 collect_connection_state = false;
259 excluded_interfaces = [
260 "lo"
261 "lo0"
262 ];
263 }
264 ];
265 };
266 };
267 };
268 config = lib.mkIf cfg.enable {
269 environment.systemPackages = [
270 datadogPkg
271 pkgs.sysstat
272 pkgs.procps
273 pkgs.iproute2
274 ];
275
276 users.users.datadog = {
277 description = "Datadog Agent User";
278 uid = config.ids.uids.datadog;
279 group = "datadog";
280 home = "/var/log/datadog/";
281 createHome = true;
282 };
283
284 users.groups.datadog.gid = config.ids.gids.datadog;
285
286 systemd.services =
287 let
288 makeService =
289 attrs:
290 lib.recursiveUpdate {
291 path = [
292 datadogPkg
293 pkgs.sysstat
294 pkgs.procps
295 pkgs.iproute2
296 ];
297 wantedBy = [ "multi-user.target" ];
298 serviceConfig = {
299 User = "datadog";
300 Group = "datadog";
301 Restart = "always";
302 RestartSec = 2;
303 };
304 restartTriggers = [ datadogPkg ] ++ map (x: x.source) (lib.attrValues etcfiles);
305 } attrs;
306 in
307 {
308 datadog-agent = makeService {
309 description = "Datadog agent monitor";
310 preStart = ''
311 chown -R datadog: /etc/datadog-agent
312 rm -f /etc/datadog-agent/auth_token
313 '';
314 script = ''
315 export DD_API_KEY=$(head -n 1 ${cfg.apiKeyFile})
316 exec ${datadogPkg}/bin/agent run -c /etc/datadog-agent/datadog.yaml
317 '';
318 serviceConfig.PermissionsStartOnly = true;
319 };
320
321 dd-jmxfetch = lib.mkIf (lib.hasAttr "jmx" cfg.checks) (makeService {
322 description = "Datadog JMX Fetcher";
323 path = [
324 datadogPkg
325 pkgs.python
326 pkgs.sysstat
327 pkgs.procps
328 pkgs.jdk
329 ];
330 serviceConfig.ExecStart = "${datadogPkg}/bin/dd-jmxfetch";
331 });
332
333 datadog-process-agent = lib.mkIf cfg.enableLiveProcessCollection (makeService {
334 description = "Datadog Live Process Agent";
335 path = [ ];
336 script = ''
337 export DD_API_KEY=$(head -n 1 ${cfg.apiKeyFile})
338 ${cfg.processAgentPackage}/bin/process-agent --config /etc/datadog-agent/datadog.yaml
339 '';
340 });
341
342 datadog-trace-agent = lib.mkIf cfg.enableTraceAgent (makeService {
343 description = "Datadog Trace Agent";
344 path = [ ];
345 script = ''
346 export DD_API_KEY=$(head -n 1 ${cfg.apiKeyFile})
347 ${datadogPkg}/bin/trace-agent --config /etc/datadog-agent/datadog.yaml
348 '';
349 });
350
351 };
352
353 environment.etc = etcfiles;
354 };
355}