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