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