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