···199199200200- `programs.gnupg.agent.pinentryFlavor` is now set in `/etc/gnupg/gpg-agent.conf`, and will no longer take precedence over a `pinentry-program` set in `~/.gnupg/gpg-agent.conf`.
201201202202+- `services.influxdb2` now supports doing an automatic initial setup and provisioning of users, organizations, buckets and authentication tokens, see [#249502](https://github.com/NixOS/nixpkgs/pull/249502) for more details.
203203+202204- `wrapHelm` now exposes `passthru.pluginsDir` which can be passed to `helmfile`. For convenience, a top-level package `helmfile-wrapped` has been added, which inherits `passthru.pluginsDir` from `kubernetes-helm-wrapped`. See [#217768](https://github.com/NixOS/nixpkgs/issues/217768) for details.
203205204206- `boot.initrd.network.udhcp.enable` allows control over dhcp during stage 1 regardless of what `networking.useDHCP` is set to.
+386-66
nixos/modules/services/databases/influxdb2.nix
···33let
44 inherit
55 (lib)
66+ any
77+ attrNames
88+ attrValues
99+ count
610 escapeShellArg
1111+ filterAttrs
1212+ flatten
1313+ flip
1414+ getExe
715 hasAttr
1616+ hasInfix
1717+ listToAttrs
818 literalExpression
1919+ mapAttrsToList
2020+ mdDoc
921 mkEnableOption
1022 mkIf
1123 mkOption
2424+ nameValuePair
2525+ optional
2626+ subtractLists
1227 types
2828+ unique
1329 ;
14301531 format = pkgs.formats.json { };
1632 cfg = config.services.influxdb2;
1733 configFile = format.generate "config.json" cfg.settings;
3434+3535+ validPermissions = [
3636+ "authorizations"
3737+ "buckets"
3838+ "dashboards"
3939+ "orgs"
4040+ "tasks"
4141+ "telegrafs"
4242+ "users"
4343+ "variables"
4444+ "secrets"
4545+ "labels"
4646+ "views"
4747+ "documents"
4848+ "notificationRules"
4949+ "notificationEndpoints"
5050+ "checks"
5151+ "dbrp"
5252+ "annotations"
5353+ "sources"
5454+ "scrapers"
5555+ "notebooks"
5656+ "remotes"
5757+ "replications"
5858+ ];
5959+6060+ # Determines whether at least one active api token is defined
6161+ anyAuthDefined =
6262+ flip any (attrValues cfg.provision.organizations)
6363+ (o: o.present && flip any (attrValues o.auths)
6464+ (a: a.present && a.tokenFile != null));
6565+6666+ provisionState = pkgs.writeText "provision_state.json" (builtins.toJSON {
6767+ inherit (cfg.provision) organizations users;
6868+ });
6969+7070+ provisioningScript = pkgs.writeShellScript "post-start-provision" ''
7171+ set -euo pipefail
7272+ export INFLUX_HOST="http://"${escapeShellArg (
7373+ if ! hasAttr "http-bind-address" cfg.settings
7474+ || hasInfix "0.0.0.0" cfg.settings.http-bind-address
7575+ then "localhost:8086"
7676+ else cfg.settings.http-bind-address
7777+ )}
7878+7979+ # Wait for the influxdb server to come online
8080+ count=0
8181+ while ! influx ping &>/dev/null; do
8282+ if [ "$count" -eq 300 ]; then
8383+ echo "Tried for 30 seconds, giving up..."
8484+ exit 1
8585+ fi
8686+8787+ if ! kill -0 "$MAINPID"; then
8888+ echo "Main server died, giving up..."
8989+ exit 1
9090+ fi
9191+9292+ sleep 0.1
9393+ count=$((count++))
9494+ done
9595+9696+ # Do the initial database setup. Pass /dev/null as configs-path to
9797+ # avoid saving the token as the active config.
9898+ if test -e "$STATE_DIRECTORY/.first_startup"; then
9999+ influx setup \
100100+ --configs-path /dev/null \
101101+ --org ${escapeShellArg cfg.provision.initialSetup.organization} \
102102+ --bucket ${escapeShellArg cfg.provision.initialSetup.bucket} \
103103+ --username ${escapeShellArg cfg.provision.initialSetup.username} \
104104+ --password "$(< "$CREDENTIALS_DIRECTORY/admin-password")" \
105105+ --token "$(< "$CREDENTIALS_DIRECTORY/admin-token")" \
106106+ --retention ${toString cfg.provision.initialSetup.retention}s \
107107+ --force >/dev/null
108108+109109+ rm -f "$STATE_DIRECTORY/.first_startup"
110110+ fi
111111+112112+ provision_result=$(${getExe pkgs.influxdb2-provision} ${provisionState} "$INFLUX_HOST" "$(< "$CREDENTIALS_DIRECTORY/admin-token")")
113113+ if [[ "$(jq '[.auths[] | select(.action == "created")] | length' <<< "$provision_result")" -gt 0 ]]; then
114114+ echo "Created at least one new token, queueing service restart so we can manipulate secrets"
115115+ touch "$STATE_DIRECTORY/.needs_restart"
116116+ fi
117117+ '';
118118+119119+ restarterScript = pkgs.writeShellScript "post-start-restarter" ''
120120+ set -euo pipefail
121121+ if test -e "$STATE_DIRECTORY/.needs_restart"; then
122122+ rm -f "$STATE_DIRECTORY/.needs_restart"
123123+ /run/current-system/systemd/bin/systemctl restart influxdb2
124124+ fi
125125+ '';
126126+127127+ organizationSubmodule = types.submodule (organizationSubmod: let
128128+ org = organizationSubmod.config._module.args.name;
129129+ in {
130130+ options = {
131131+ present = mkOption {
132132+ description = mdDoc "Whether to ensure that this organization is present or absent.";
133133+ type = types.bool;
134134+ default = true;
135135+ };
136136+137137+ description = mkOption {
138138+ description = mdDoc "Optional description for the organization.";
139139+ default = null;
140140+ type = types.nullOr types.str;
141141+ };
142142+143143+ buckets = mkOption {
144144+ description = mdDoc "Buckets to provision in this organization.";
145145+ default = {};
146146+ type = types.attrsOf (types.submodule (bucketSubmod: let
147147+ bucket = bucketSubmod.config._module.args.name;
148148+ in {
149149+ options = {
150150+ present = mkOption {
151151+ description = mdDoc "Whether to ensure that this bucket is present or absent.";
152152+ type = types.bool;
153153+ default = true;
154154+ };
155155+156156+ description = mkOption {
157157+ description = mdDoc "Optional description for the bucket.";
158158+ default = null;
159159+ type = types.nullOr types.str;
160160+ };
161161+162162+ retention = mkOption {
163163+ type = types.ints.unsigned;
164164+ default = 0;
165165+ description = mdDoc "The duration in seconds for which the bucket will retain data (0 is infinite).";
166166+ };
167167+ };
168168+ }));
169169+ };
170170+171171+ auths = mkOption {
172172+ description = mdDoc "API tokens to provision for the user in this organization.";
173173+ default = {};
174174+ type = types.attrsOf (types.submodule (authSubmod: let
175175+ auth = authSubmod.config._module.args.name;
176176+ in {
177177+ options = {
178178+ id = mkOption {
179179+ description = mdDoc "A unique identifier for this authentication token. Since influx doesn't store names for tokens, this will be hashed and appended to the description to identify the token.";
180180+ readOnly = true;
181181+ default = builtins.substring 0 32 (builtins.hashString "sha256" "${org}:${auth}");
182182+ defaultText = "<a hash derived from org and name>";
183183+ type = types.str;
184184+ };
185185+186186+ present = mkOption {
187187+ description = mdDoc "Whether to ensure that this user is present or absent.";
188188+ type = types.bool;
189189+ default = true;
190190+ };
191191+192192+ description = mkOption {
193193+ description = ''
194194+ Optional description for the API token.
195195+ Note that the actual token will always be created with a descriptionregardless
196196+ of whether this is given or not. The name is always added plus a unique suffix
197197+ to later identify the token to track whether it has already been created.
198198+ '';
199199+ default = null;
200200+ type = types.nullOr types.str;
201201+ };
202202+203203+ tokenFile = mkOption {
204204+ type = types.nullOr types.path;
205205+ default = null;
206206+ description = mdDoc "The token value. If not given, influx will automatically generate one.";
207207+ };
208208+209209+ operator = mkOption {
210210+ description = mdDoc "Grants all permissions in all organizations.";
211211+ default = false;
212212+ type = types.bool;
213213+ };
214214+215215+ allAccess = mkOption {
216216+ description = mdDoc "Grants all permissions in the associated organization.";
217217+ default = false;
218218+ type = types.bool;
219219+ };
220220+221221+ readPermissions = mkOption {
222222+ description = mdDoc ''
223223+ The read permissions to include for this token. Access is usually granted only
224224+ for resources in the associated organization.
225225+226226+ Available permissions are `authorizations`, `buckets`, `dashboards`,
227227+ `orgs`, `tasks`, `telegrafs`, `users`, `variables`, `secrets`, `labels`, `views`,
228228+ `documents`, `notificationRules`, `notificationEndpoints`, `checks`, `dbrp`,
229229+ `annotations`, `sources`, `scrapers`, `notebooks`, `remotes`, `replications`.
230230+231231+ Refer to `influx auth create --help` for a full list with descriptions.
232232+233233+ `buckets` grants read access to all associated buckets. Use `readBuckets` to define
234234+ more granular access permissions.
235235+ '';
236236+ default = [];
237237+ type = types.listOf (types.enum validPermissions);
238238+ };
239239+240240+ writePermissions = mkOption {
241241+ description = mdDoc ''
242242+ The read permissions to include for this token. Access is usually granted only
243243+ for resources in the associated organization.
244244+245245+ Available permissions are `authorizations`, `buckets`, `dashboards`,
246246+ `orgs`, `tasks`, `telegrafs`, `users`, `variables`, `secrets`, `labels`, `views`,
247247+ `documents`, `notificationRules`, `notificationEndpoints`, `checks`, `dbrp`,
248248+ `annotations`, `sources`, `scrapers`, `notebooks`, `remotes`, `replications`.
249249+250250+ Refer to `influx auth create --help` for a full list with descriptions.
251251+252252+ `buckets` grants write access to all associated buckets. Use `writeBuckets` to define
253253+ more granular access permissions.
254254+ '';
255255+ default = [];
256256+ type = types.listOf (types.enum validPermissions);
257257+ };
258258+259259+ readBuckets = mkOption {
260260+ description = mdDoc "The organization's buckets which should be allowed to be read";
261261+ default = [];
262262+ type = types.listOf types.str;
263263+ };
264264+265265+ writeBuckets = mkOption {
266266+ description = mdDoc "The organization's buckets which should be allowed to be written";
267267+ default = [];
268268+ type = types.listOf types.str;
269269+ };
270270+ };
271271+ }));
272272+ };
273273+ };
274274+ });
18275in
19276{
20277 options = {
21278 services.influxdb2 = {
2222- enable = mkEnableOption (lib.mdDoc "the influxdb2 server");
279279+ enable = mkEnableOption (mdDoc "the influxdb2 server");
2328024281 package = mkOption {
25282 default = pkgs.influxdb2-server;
26283 defaultText = literalExpression "pkgs.influxdb2";
2727- description = lib.mdDoc "influxdb2 derivation to use.";
284284+ description = mdDoc "influxdb2 derivation to use.";
28285 type = types.package;
29286 };
3028731288 settings = mkOption {
32289 default = { };
3333- description = lib.mdDoc ''configuration options for influxdb2, see <https://docs.influxdata.com/influxdb/v2.0/reference/config-options> for details.'';
290290+ description = mdDoc ''configuration options for influxdb2, see <https://docs.influxdata.com/influxdb/v2.0/reference/config-options> for details.'';
34291 type = format.type;
35292 };
36293···41298 organization = mkOption {
42299 type = types.str;
43300 example = "main";
4444- description = "Primary organization name";
301301+ description = mdDoc "Primary organization name";
45302 };
4630347304 bucket = mkOption {
48305 type = types.str;
49306 example = "example";
5050- description = "Primary bucket name";
307307+ description = mdDoc "Primary bucket name";
51308 };
5230953310 username = mkOption {
54311 type = types.str;
55312 default = "admin";
5656- description = "Primary username";
313313+ description = mdDoc "Primary username";
57314 };
5831559316 retention = mkOption {
6060- type = types.str;
6161- default = "0";
6262- description = ''
6363- The duration for which the bucket will retain data (0 is infinite).
6464- Accepted units are `ns` (nanoseconds), `us` or `µs` (microseconds), `ms` (milliseconds),
6565- `s` (seconds), `m` (minutes), `h` (hours), `d` (days) and `w` (weeks).
6666- '';
317317+ type = types.ints.unsigned;
318318+ default = 0;
319319+ description = mdDoc "The duration in seconds for which the bucket will retain data (0 is infinite).";
67320 };
6832169322 passwordFile = mkOption {
70323 type = types.path;
7171- description = "Password for primary user. Don't use a file from the nix store!";
324324+ description = mdDoc "Password for primary user. Don't use a file from the nix store!";
72325 };
7332674327 tokenFile = mkOption {
75328 type = types.path;
7676- description = "API Token to set for the admin user. Don't use a file from the nix store!";
329329+ description = mdDoc "API Token to set for the admin user. Don't use a file from the nix store!";
77330 };
78331 };
332332+333333+ organizations = mkOption {
334334+ description = mdDoc "Organizations to provision.";
335335+ example = literalExpression ''
336336+ {
337337+ myorg = {
338338+ description = "My organization";
339339+ buckets.mybucket = {
340340+ description = "My bucket";
341341+ retention = 31536000; # 1 year
342342+ };
343343+ auths.mytoken = {
344344+ readBuckets = ["mybucket"];
345345+ tokenFile = "/run/secrets/mytoken";
346346+ };
347347+ };
348348+ }
349349+ '';
350350+ default = {};
351351+ type = types.attrsOf organizationSubmodule;
352352+ };
353353+354354+ users = mkOption {
355355+ description = mdDoc "Users to provision.";
356356+ default = {};
357357+ example = literalExpression ''
358358+ {
359359+ # admin = {}; /* The initialSetup.username will automatically be added. */
360360+ myuser.passwordFile = "/run/secrets/myuser_password";
361361+ }
362362+ '';
363363+ type = types.attrsOf (types.submodule (userSubmod: let
364364+ user = userSubmod.config._module.args.name;
365365+ org = userSubmod.config.org;
366366+ in {
367367+ options = {
368368+ present = mkOption {
369369+ description = mdDoc "Whether to ensure that this user is present or absent.";
370370+ type = types.bool;
371371+ default = true;
372372+ };
373373+374374+ passwordFile = mkOption {
375375+ description = mdDoc "Password for the user. If unset, the user will not be able to log in until a password is set by an operator! Don't use a file from the nix store!";
376376+ default = null;
377377+ type = types.nullOr types.path;
378378+ };
379379+ };
380380+ }));
381381+ };
79382 };
80383 };
81384 };
8238583386 config = mkIf cfg.enable {
8484- assertions = [
8585- {
8686- assertion = !(hasAttr "bolt-path" cfg.settings) && !(hasAttr "engine-path" cfg.settings);
8787- message = "services.influxdb2.config: bolt-path and engine-path should not be set as they are managed by systemd";
8888- }
8989- ];
387387+ assertions =
388388+ [
389389+ {
390390+ assertion = !(hasAttr "bolt-path" cfg.settings) && !(hasAttr "engine-path" cfg.settings);
391391+ message = "services.influxdb2.config: bolt-path and engine-path should not be set as they are managed by systemd";
392392+ }
393393+ ]
394394+ ++ flatten (flip mapAttrsToList cfg.provision.organizations (orgName: org:
395395+ flip mapAttrsToList org.auths (authName: auth:
396396+ [
397397+ {
398398+ assertion = 1 == count (x: x) [
399399+ auth.operator
400400+ auth.allAccess
401401+ (auth.readPermissions != []
402402+ || auth.writePermissions != []
403403+ || auth.readBuckets != []
404404+ || auth.writeBuckets != [])
405405+ ];
406406+ message = "influxdb2: provision.organizations.${orgName}.auths.${authName}: The `operator` and `allAccess` options are mutually exclusive with each other and the granular permission settings.";
407407+ }
408408+ (let unknownBuckets = subtractLists (attrNames org.buckets) auth.readBuckets; in {
409409+ assertion = unknownBuckets == [];
410410+ message = "influxdb2: provision.organizations.${orgName}.auths.${authName}: Refers to invalid buckets in readBuckets: ${toString unknownBuckets}";
411411+ })
412412+ (let unknownBuckets = subtractLists (attrNames org.buckets) auth.writeBuckets; in {
413413+ assertion = unknownBuckets == [];
414414+ message = "influxdb2: provision.organizations.${orgName}.auths.${authName}: Refers to invalid buckets in writeBuckets: ${toString unknownBuckets}";
415415+ })
416416+ ]
417417+ )
418418+ ));
419419+420420+ services.influxdb2.provision = mkIf cfg.provision.enable {
421421+ organizations.${cfg.provision.initialSetup.organization} = {
422422+ buckets.${cfg.provision.initialSetup.bucket} = {
423423+ inherit (cfg.provision.initialSetup) retention;
424424+ };
425425+ };
426426+ users.${cfg.provision.initialSetup.username} = {
427427+ inherit (cfg.provision.initialSetup) passwordFile;
428428+ };
429429+ };
9043091431 systemd.services.influxdb2 = {
92432 description = "InfluxDB is an open-source, distributed, time series database";
···111451 "admin-password:${cfg.provision.initialSetup.passwordFile}"
112452 "admin-token:${cfg.provision.initialSetup.tokenFile}"
113453 ];
454454+455455+ ExecStartPost = mkIf cfg.provision.enable (
456456+ [provisioningScript] ++
457457+ # Only the restarter runs with elevated privileges
458458+ optional anyAuthDefined "+${restarterScript}"
459459+ );
114460 };
115461116116- path = [pkgs.influxdb2-cli];
462462+ path = [
463463+ pkgs.influxdb2-cli
464464+ pkgs.jq
465465+ ];
117466118118- # Mark if this is the first startup so postStart can do the initial setup
119119- preStart = mkIf cfg.provision.enable ''
467467+ # Mark if this is the first startup so postStart can do the initial setup.
468468+ # Also extract any token secret mappings and apply them if this isn't the first start.
469469+ preStart = let
470470+ tokenPaths = listToAttrs (flatten
471471+ # For all organizations
472472+ (flip mapAttrsToList cfg.provision.organizations
473473+ # For each contained token that has a token file
474474+ (_: org: flip mapAttrsToList (filterAttrs (_: x: x.tokenFile != null) org.auths)
475475+ # Collect id -> tokenFile for the mapping
476476+ (_: auth: nameValuePair auth.id auth.tokenFile))));
477477+ tokenMappings = pkgs.writeText "token_mappings.json" (builtins.toJSON tokenPaths);
478478+ in mkIf cfg.provision.enable ''
120479 if ! test -e "$STATE_DIRECTORY/influxd.bolt"; then
121480 touch "$STATE_DIRECTORY/.first_startup"
481481+ else
482482+ # Manipulate provisioned api tokens if necessary
483483+ ${getExe pkgs.influxdb2-token-manipulator} "$STATE_DIRECTORY/influxd.bolt" ${tokenMappings}
122484 fi
123485 '';
124124-125125- postStart = let
126126- initCfg = cfg.provision.initialSetup;
127127- in mkIf cfg.provision.enable (
128128- ''
129129- set -euo pipefail
130130- export INFLUX_HOST="http://"${escapeShellArg (cfg.settings.http-bind-address or "localhost:8086")}
131131-132132- # Wait for the influxdb server to come online
133133- count=0
134134- while ! influx ping &>/dev/null; do
135135- if [ "$count" -eq 300 ]; then
136136- echo "Tried for 30 seconds, giving up..."
137137- exit 1
138138- fi
139139-140140- if ! kill -0 "$MAINPID"; then
141141- echo "Main server died, giving up..."
142142- exit 1
143143- fi
144144-145145- sleep 0.1
146146- count=$((count++))
147147- done
148148-149149- # Do the initial database setup. Pass /dev/null as configs-path to
150150- # avoid saving the token as the active config.
151151- if test -e "$STATE_DIRECTORY/.first_startup"; then
152152- influx setup \
153153- --configs-path /dev/null \
154154- --org ${escapeShellArg initCfg.organization} \
155155- --bucket ${escapeShellArg initCfg.bucket} \
156156- --username ${escapeShellArg initCfg.username} \
157157- --password "$(< "$CREDENTIALS_DIRECTORY/admin-password")" \
158158- --token "$(< "$CREDENTIALS_DIRECTORY/admin-token")" \
159159- --retention ${escapeShellArg initCfg.retention} \
160160- --force >/dev/null
161161-162162- rm -f "$STATE_DIRECTORY/.first_startup"
163163- fi
164164- ''
165165- );
166486 };
167487168488 users.extraUsers.influxdb2 = {
+191-2
nixos/tests/influxdb2.nix
···6677 nodes.machine = { lib, ... }: {
88 environment.systemPackages = [ pkgs.influxdb2-cli ];
99+ # Make sure that the service is restarted immediately if tokens need to be rewritten
1010+ # without relying on any Restart=on-failure behavior
1111+ systemd.services.influxdb2.serviceConfig.RestartSec = 6000;
912 services.influxdb2.enable = true;
1013 services.influxdb2.provision = {
1114 enable = true;
···1518 passwordFile = pkgs.writeText "admin-pw" "ExAmPl3PA55W0rD";
1619 tokenFile = pkgs.writeText "admin-token" "verysecureadmintoken";
1720 };
2121+ organizations.someorg = {
2222+ buckets.somebucket = {};
2323+ auths.sometoken = {
2424+ description = "some auth token";
2525+ readBuckets = ["somebucket"];
2626+ writeBuckets = ["somebucket"];
2727+ };
2828+ };
2929+ users.someuser.passwordFile = pkgs.writeText "tmp-pw" "abcgoiuhaoga";
3030+ };
3131+3232+ specialisation.withModifications.configuration = { ... }: {
3333+ services.influxdb2.provision = {
3434+ organizations.someorg.buckets.somebucket.present = false;
3535+ organizations.someorg.auths.sometoken.present = false;
3636+ users.someuser.present = false;
3737+3838+ organizations.myorg = {
3939+ description = "Myorg description";
4040+ buckets.mybucket = {
4141+ description = "Mybucket description";
4242+ };
4343+ auths.mytoken = {
4444+ operator = true;
4545+ description = "operator token";
4646+ tokenFile = pkgs.writeText "tmp-tok" "someusertoken";
4747+ };
4848+ };
4949+ users.myuser.passwordFile = pkgs.writeText "tmp-pw" "abcgoiuhaoga";
5050+ };
5151+ };
5252+5353+ specialisation.withParentDelete.configuration = { ... }: {
5454+ services.influxdb2.provision = {
5555+ organizations.someorg.present = false;
5656+ # Deleting the parent implies:
5757+ #organizations.someorg.buckets.somebucket.present = false;
5858+ #organizations.someorg.auths.sometoken.present = false;
5959+ };
6060+ };
6161+6262+ specialisation.withNewTokens.configuration = { ... }: {
6363+ services.influxdb2.provision = {
6464+ organizations.default = {
6565+ auths.operator = {
6666+ operator = true;
6767+ description = "new optoken";
6868+ tokenFile = pkgs.writeText "tmp-tok" "newoptoken";
6969+ };
7070+ auths.allaccess = {
7171+ operator = true;
7272+ description = "new allaccess";
7373+ tokenFile = pkgs.writeText "tmp-tok" "newallaccess";
7474+ };
7575+ auths.specifics = {
7676+ description = "new specifics";
7777+ readPermissions = ["users" "tasks"];
7878+ writePermissions = ["tasks"];
7979+ tokenFile = pkgs.writeText "tmp-tok" "newspecificstoken";
8080+ };
8181+ };
8282+ };
1883 };
1984 };
20852186 testScript = { nodes, ... }:
2287 let
8888+ specialisations = "${nodes.machine.system.build.toplevel}/specialisation";
2389 tokenArg = "--token verysecureadmintoken";
2490 in ''
9191+ def assert_contains(haystack, needle):
9292+ if needle not in haystack:
9393+ print("The haystack that will cause the following exception is:")
9494+ print("---")
9595+ print(haystack)
9696+ print("---")
9797+ raise Exception(f"Expected string '{needle}' was not found")
9898+9999+ def assert_lacks(haystack, needle):
100100+ if needle in haystack:
101101+ print("The haystack that will cause the following exception is:")
102102+ print("---")
103103+ print(haystack, end="")
104104+ print("---")
105105+ raise Exception(f"Unexpected string '{needle}' was found")
106106+25107 machine.wait_for_unit("influxdb2.service")
2610827109 machine.fail("curl --fail -X POST 'http://localhost:8086/api/v2/signin' -u admin:wrongpassword")
28110 machine.succeed("curl --fail -X POST 'http://localhost:8086/api/v2/signin' -u admin:ExAmPl3PA55W0rD")
2911130112 out = machine.succeed("influx org list ${tokenArg}")
3131- assert "default" in out
113113+ assert_contains(out, "default")
114114+ assert_lacks(out, "myorg")
115115+ assert_contains(out, "someorg")
3211633117 out = machine.succeed("influx bucket list ${tokenArg} --org default")
3434- assert "default" in out
118118+ assert_contains(out, "default")
119119+120120+ machine.fail("influx bucket list ${tokenArg} --org myorg")
121121+122122+ out = machine.succeed("influx bucket list ${tokenArg} --org someorg")
123123+ assert_contains(out, "somebucket")
124124+125125+ out = machine.succeed("influx user list ${tokenArg}")
126126+ assert_contains(out, "admin")
127127+ assert_lacks(out, "myuser")
128128+ assert_contains(out, "someuser")
129129+130130+ out = machine.succeed("influx auth list ${tokenArg}")
131131+ assert_lacks(out, "operator token")
132132+ assert_contains(out, "some auth token")
133133+134134+ with subtest("withModifications"):
135135+ machine.succeed('${specialisations}/withModifications/bin/switch-to-configuration test')
136136+ machine.wait_for_unit("influxdb2.service")
137137+138138+ out = machine.succeed("influx org list ${tokenArg}")
139139+ assert_contains(out, "default")
140140+ assert_contains(out, "myorg")
141141+ assert_contains(out, "someorg")
142142+143143+ out = machine.succeed("influx bucket list ${tokenArg} --org myorg")
144144+ assert_contains(out, "mybucket")
145145+146146+ out = machine.succeed("influx bucket list ${tokenArg} --org someorg")
147147+ assert_lacks(out, "somebucket")
148148+149149+ out = machine.succeed("influx user list ${tokenArg}")
150150+ assert_contains(out, "admin")
151151+ assert_contains(out, "myuser")
152152+ assert_lacks(out, "someuser")
153153+154154+ out = machine.succeed("influx auth list ${tokenArg}")
155155+ assert_contains(out, "operator token")
156156+ assert_lacks(out, "some auth token")
157157+158158+ # Make sure the user token is also usable
159159+ machine.succeed("influx auth list --token someusertoken")
160160+161161+ with subtest("keepsUnrelated"):
162162+ machine.succeed('${nodes.machine.system.build.toplevel}/bin/switch-to-configuration test')
163163+ machine.wait_for_unit("influxdb2.service")
164164+165165+ out = machine.succeed("influx org list ${tokenArg}")
166166+ assert_contains(out, "default")
167167+ assert_contains(out, "myorg")
168168+ assert_contains(out, "someorg")
169169+170170+ out = machine.succeed("influx bucket list ${tokenArg} --org default")
171171+ assert_contains(out, "default")
172172+173173+ out = machine.succeed("influx bucket list ${tokenArg} --org myorg")
174174+ assert_contains(out, "mybucket")
175175+176176+ out = machine.succeed("influx bucket list ${tokenArg} --org someorg")
177177+ assert_contains(out, "somebucket")
178178+179179+ out = machine.succeed("influx user list ${tokenArg}")
180180+ assert_contains(out, "admin")
181181+ assert_contains(out, "myuser")
182182+ assert_contains(out, "someuser")
183183+184184+ out = machine.succeed("influx auth list ${tokenArg}")
185185+ assert_contains(out, "operator token")
186186+ assert_contains(out, "some auth token")
187187+188188+ with subtest("withParentDelete"):
189189+ machine.succeed('${specialisations}/withParentDelete/bin/switch-to-configuration test')
190190+ machine.wait_for_unit("influxdb2.service")
191191+192192+ out = machine.succeed("influx org list ${tokenArg}")
193193+ assert_contains(out, "default")
194194+ assert_contains(out, "myorg")
195195+ assert_lacks(out, "someorg")
196196+197197+ out = machine.succeed("influx bucket list ${tokenArg} --org default")
198198+ assert_contains(out, "default")
199199+200200+ out = machine.succeed("influx bucket list ${tokenArg} --org myorg")
201201+ assert_contains(out, "mybucket")
202202+203203+ machine.fail("influx bucket list ${tokenArg} --org someorg")
204204+205205+ out = machine.succeed("influx user list ${tokenArg}")
206206+ assert_contains(out, "admin")
207207+ assert_contains(out, "myuser")
208208+ assert_contains(out, "someuser")
209209+210210+ out = machine.succeed("influx auth list ${tokenArg}")
211211+ assert_contains(out, "operator token")
212212+ assert_lacks(out, "some auth token")
213213+214214+ with subtest("withNewTokens"):
215215+ machine.succeed('${specialisations}/withNewTokens/bin/switch-to-configuration test')
216216+ machine.wait_for_unit("influxdb2.service")
217217+218218+ out = machine.succeed("influx auth list ${tokenArg}")
219219+ assert_contains(out, "operator token")
220220+ assert_contains(out, "some auth token")
221221+ assert_contains(out, "new optoken")
222222+ assert_contains(out, "new allaccess")
223223+ assert_contains(out, "new specifics")
35224 '';
36225})