Merge pull request #249502 from oddlama/feat-influxdb-provision-full
nixos/influxdb2: add provisioning and nixos tests
This commit is contained in:
commit
8d524e610b
6 changed files with 641 additions and 68 deletions
|
@ -203,6 +203,8 @@ The module update takes care of the new config syntax and the data itself (user
|
|||
|
||||
- `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`.
|
||||
|
||||
- `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.
|
||||
|
||||
- `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.
|
||||
|
||||
- `boot.initrd.network.udhcp.enable` allows control over dhcp during stage 1 regardless of what `networking.useDHCP` is set to.
|
||||
|
|
|
@ -3,34 +3,291 @@
|
|||
let
|
||||
inherit
|
||||
(lib)
|
||||
any
|
||||
attrNames
|
||||
attrValues
|
||||
count
|
||||
escapeShellArg
|
||||
filterAttrs
|
||||
flatten
|
||||
flip
|
||||
getExe
|
||||
hasAttr
|
||||
hasInfix
|
||||
listToAttrs
|
||||
literalExpression
|
||||
mapAttrsToList
|
||||
mdDoc
|
||||
mkEnableOption
|
||||
mkIf
|
||||
mkOption
|
||||
nameValuePair
|
||||
optional
|
||||
subtractLists
|
||||
types
|
||||
unique
|
||||
;
|
||||
|
||||
format = pkgs.formats.json { };
|
||||
cfg = config.services.influxdb2;
|
||||
configFile = format.generate "config.json" cfg.settings;
|
||||
|
||||
validPermissions = [
|
||||
"authorizations"
|
||||
"buckets"
|
||||
"dashboards"
|
||||
"orgs"
|
||||
"tasks"
|
||||
"telegrafs"
|
||||
"users"
|
||||
"variables"
|
||||
"secrets"
|
||||
"labels"
|
||||
"views"
|
||||
"documents"
|
||||
"notificationRules"
|
||||
"notificationEndpoints"
|
||||
"checks"
|
||||
"dbrp"
|
||||
"annotations"
|
||||
"sources"
|
||||
"scrapers"
|
||||
"notebooks"
|
||||
"remotes"
|
||||
"replications"
|
||||
];
|
||||
|
||||
# Determines whether at least one active api token is defined
|
||||
anyAuthDefined =
|
||||
flip any (attrValues cfg.provision.organizations)
|
||||
(o: o.present && flip any (attrValues o.auths)
|
||||
(a: a.present && a.tokenFile != null));
|
||||
|
||||
provisionState = pkgs.writeText "provision_state.json" (builtins.toJSON {
|
||||
inherit (cfg.provision) organizations users;
|
||||
});
|
||||
|
||||
provisioningScript = pkgs.writeShellScript "post-start-provision" ''
|
||||
set -euo pipefail
|
||||
export INFLUX_HOST="http://"${escapeShellArg (
|
||||
if ! hasAttr "http-bind-address" cfg.settings
|
||||
|| hasInfix "0.0.0.0" cfg.settings.http-bind-address
|
||||
then "localhost:8086"
|
||||
else cfg.settings.http-bind-address
|
||||
)}
|
||||
|
||||
# Wait for the influxdb server to come online
|
||||
count=0
|
||||
while ! influx ping &>/dev/null; do
|
||||
if [ "$count" -eq 300 ]; then
|
||||
echo "Tried for 30 seconds, giving up..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! kill -0 "$MAINPID"; then
|
||||
echo "Main server died, giving up..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sleep 0.1
|
||||
count=$((count++))
|
||||
done
|
||||
|
||||
# Do the initial database setup. Pass /dev/null as configs-path to
|
||||
# avoid saving the token as the active config.
|
||||
if test -e "$STATE_DIRECTORY/.first_startup"; then
|
||||
influx setup \
|
||||
--configs-path /dev/null \
|
||||
--org ${escapeShellArg cfg.provision.initialSetup.organization} \
|
||||
--bucket ${escapeShellArg cfg.provision.initialSetup.bucket} \
|
||||
--username ${escapeShellArg cfg.provision.initialSetup.username} \
|
||||
--password "$(< "$CREDENTIALS_DIRECTORY/admin-password")" \
|
||||
--token "$(< "$CREDENTIALS_DIRECTORY/admin-token")" \
|
||||
--retention ${toString cfg.provision.initialSetup.retention}s \
|
||||
--force >/dev/null
|
||||
|
||||
rm -f "$STATE_DIRECTORY/.first_startup"
|
||||
fi
|
||||
|
||||
provision_result=$(${getExe pkgs.influxdb2-provision} ${provisionState} "$INFLUX_HOST" "$(< "$CREDENTIALS_DIRECTORY/admin-token")")
|
||||
if [[ "$(jq '[.auths[] | select(.action == "created")] | length' <<< "$provision_result")" -gt 0 ]]; then
|
||||
echo "Created at least one new token, queueing service restart so we can manipulate secrets"
|
||||
touch "$STATE_DIRECTORY/.needs_restart"
|
||||
fi
|
||||
'';
|
||||
|
||||
restarterScript = pkgs.writeShellScript "post-start-restarter" ''
|
||||
set -euo pipefail
|
||||
if test -e "$STATE_DIRECTORY/.needs_restart"; then
|
||||
rm -f "$STATE_DIRECTORY/.needs_restart"
|
||||
/run/current-system/systemd/bin/systemctl restart influxdb2
|
||||
fi
|
||||
'';
|
||||
|
||||
organizationSubmodule = types.submodule (organizationSubmod: let
|
||||
org = organizationSubmod.config._module.args.name;
|
||||
in {
|
||||
options = {
|
||||
present = mkOption {
|
||||
description = mdDoc "Whether to ensure that this organization is present or absent.";
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
|
||||
description = mkOption {
|
||||
description = mdDoc "Optional description for the organization.";
|
||||
default = null;
|
||||
type = types.nullOr types.str;
|
||||
};
|
||||
|
||||
buckets = mkOption {
|
||||
description = mdDoc "Buckets to provision in this organization.";
|
||||
default = {};
|
||||
type = types.attrsOf (types.submodule (bucketSubmod: let
|
||||
bucket = bucketSubmod.config._module.args.name;
|
||||
in {
|
||||
options = {
|
||||
present = mkOption {
|
||||
description = mdDoc "Whether to ensure that this bucket is present or absent.";
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
|
||||
description = mkOption {
|
||||
description = mdDoc "Optional description for the bucket.";
|
||||
default = null;
|
||||
type = types.nullOr types.str;
|
||||
};
|
||||
|
||||
retention = mkOption {
|
||||
type = types.ints.unsigned;
|
||||
default = 0;
|
||||
description = mdDoc "The duration in seconds for which the bucket will retain data (0 is infinite).";
|
||||
};
|
||||
};
|
||||
}));
|
||||
};
|
||||
|
||||
auths = mkOption {
|
||||
description = mdDoc "API tokens to provision for the user in this organization.";
|
||||
default = {};
|
||||
type = types.attrsOf (types.submodule (authSubmod: let
|
||||
auth = authSubmod.config._module.args.name;
|
||||
in {
|
||||
options = {
|
||||
id = mkOption {
|
||||
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.";
|
||||
readOnly = true;
|
||||
default = builtins.substring 0 32 (builtins.hashString "sha256" "${org}:${auth}");
|
||||
defaultText = "<a hash derived from org and name>";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
present = mkOption {
|
||||
description = mdDoc "Whether to ensure that this user is present or absent.";
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
|
||||
description = mkOption {
|
||||
description = ''
|
||||
Optional description for the API token.
|
||||
Note that the actual token will always be created with a descriptionregardless
|
||||
of whether this is given or not. The name is always added plus a unique suffix
|
||||
to later identify the token to track whether it has already been created.
|
||||
'';
|
||||
default = null;
|
||||
type = types.nullOr types.str;
|
||||
};
|
||||
|
||||
tokenFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = mdDoc "The token value. If not given, influx will automatically generate one.";
|
||||
};
|
||||
|
||||
operator = mkOption {
|
||||
description = mdDoc "Grants all permissions in all organizations.";
|
||||
default = false;
|
||||
type = types.bool;
|
||||
};
|
||||
|
||||
allAccess = mkOption {
|
||||
description = mdDoc "Grants all permissions in the associated organization.";
|
||||
default = false;
|
||||
type = types.bool;
|
||||
};
|
||||
|
||||
readPermissions = mkOption {
|
||||
description = mdDoc ''
|
||||
The read permissions to include for this token. Access is usually granted only
|
||||
for resources in the associated organization.
|
||||
|
||||
Available permissions are `authorizations`, `buckets`, `dashboards`,
|
||||
`orgs`, `tasks`, `telegrafs`, `users`, `variables`, `secrets`, `labels`, `views`,
|
||||
`documents`, `notificationRules`, `notificationEndpoints`, `checks`, `dbrp`,
|
||||
`annotations`, `sources`, `scrapers`, `notebooks`, `remotes`, `replications`.
|
||||
|
||||
Refer to `influx auth create --help` for a full list with descriptions.
|
||||
|
||||
`buckets` grants read access to all associated buckets. Use `readBuckets` to define
|
||||
more granular access permissions.
|
||||
'';
|
||||
default = [];
|
||||
type = types.listOf (types.enum validPermissions);
|
||||
};
|
||||
|
||||
writePermissions = mkOption {
|
||||
description = mdDoc ''
|
||||
The read permissions to include for this token. Access is usually granted only
|
||||
for resources in the associated organization.
|
||||
|
||||
Available permissions are `authorizations`, `buckets`, `dashboards`,
|
||||
`orgs`, `tasks`, `telegrafs`, `users`, `variables`, `secrets`, `labels`, `views`,
|
||||
`documents`, `notificationRules`, `notificationEndpoints`, `checks`, `dbrp`,
|
||||
`annotations`, `sources`, `scrapers`, `notebooks`, `remotes`, `replications`.
|
||||
|
||||
Refer to `influx auth create --help` for a full list with descriptions.
|
||||
|
||||
`buckets` grants write access to all associated buckets. Use `writeBuckets` to define
|
||||
more granular access permissions.
|
||||
'';
|
||||
default = [];
|
||||
type = types.listOf (types.enum validPermissions);
|
||||
};
|
||||
|
||||
readBuckets = mkOption {
|
||||
description = mdDoc "The organization's buckets which should be allowed to be read";
|
||||
default = [];
|
||||
type = types.listOf types.str;
|
||||
};
|
||||
|
||||
writeBuckets = mkOption {
|
||||
description = mdDoc "The organization's buckets which should be allowed to be written";
|
||||
default = [];
|
||||
type = types.listOf types.str;
|
||||
};
|
||||
};
|
||||
}));
|
||||
};
|
||||
};
|
||||
});
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.influxdb2 = {
|
||||
enable = mkEnableOption (lib.mdDoc "the influxdb2 server");
|
||||
enable = mkEnableOption (mdDoc "the influxdb2 server");
|
||||
|
||||
package = mkOption {
|
||||
default = pkgs.influxdb2-server;
|
||||
defaultText = literalExpression "pkgs.influxdb2";
|
||||
description = lib.mdDoc "influxdb2 derivation to use.";
|
||||
description = mdDoc "influxdb2 derivation to use.";
|
||||
type = types.package;
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
default = { };
|
||||
description = lib.mdDoc ''configuration options for influxdb2, see <https://docs.influxdata.com/influxdb/v2.0/reference/config-options> for details.'';
|
||||
description = mdDoc ''configuration options for influxdb2, see <https://docs.influxdata.com/influxdb/v2.0/reference/config-options> for details.'';
|
||||
type = format.type;
|
||||
};
|
||||
|
||||
|
@ -41,52 +298,135 @@ in
|
|||
organization = mkOption {
|
||||
type = types.str;
|
||||
example = "main";
|
||||
description = "Primary organization name";
|
||||
description = mdDoc "Primary organization name";
|
||||
};
|
||||
|
||||
bucket = mkOption {
|
||||
type = types.str;
|
||||
example = "example";
|
||||
description = "Primary bucket name";
|
||||
description = mdDoc "Primary bucket name";
|
||||
};
|
||||
|
||||
username = mkOption {
|
||||
type = types.str;
|
||||
default = "admin";
|
||||
description = "Primary username";
|
||||
description = mdDoc "Primary username";
|
||||
};
|
||||
|
||||
retention = mkOption {
|
||||
type = types.str;
|
||||
default = "0";
|
||||
description = ''
|
||||
The duration for which the bucket will retain data (0 is infinite).
|
||||
Accepted units are `ns` (nanoseconds), `us` or `µs` (microseconds), `ms` (milliseconds),
|
||||
`s` (seconds), `m` (minutes), `h` (hours), `d` (days) and `w` (weeks).
|
||||
'';
|
||||
type = types.ints.unsigned;
|
||||
default = 0;
|
||||
description = mdDoc "The duration in seconds for which the bucket will retain data (0 is infinite).";
|
||||
};
|
||||
|
||||
passwordFile = mkOption {
|
||||
type = types.path;
|
||||
description = "Password for primary user. Don't use a file from the nix store!";
|
||||
description = mdDoc "Password for primary user. Don't use a file from the nix store!";
|
||||
};
|
||||
|
||||
tokenFile = mkOption {
|
||||
type = types.path;
|
||||
description = "API Token to set for the admin user. Don't use a file from the nix store!";
|
||||
description = mdDoc "API Token to set for the admin user. Don't use a file from the nix store!";
|
||||
};
|
||||
};
|
||||
|
||||
organizations = mkOption {
|
||||
description = mdDoc "Organizations to provision.";
|
||||
example = literalExpression ''
|
||||
{
|
||||
myorg = {
|
||||
description = "My organization";
|
||||
buckets.mybucket = {
|
||||
description = "My bucket";
|
||||
retention = 31536000; # 1 year
|
||||
};
|
||||
auths.mytoken = {
|
||||
readBuckets = ["mybucket"];
|
||||
tokenFile = "/run/secrets/mytoken";
|
||||
};
|
||||
};
|
||||
}
|
||||
'';
|
||||
default = {};
|
||||
type = types.attrsOf organizationSubmodule;
|
||||
};
|
||||
|
||||
users = mkOption {
|
||||
description = mdDoc "Users to provision.";
|
||||
default = {};
|
||||
example = literalExpression ''
|
||||
{
|
||||
# admin = {}; /* The initialSetup.username will automatically be added. */
|
||||
myuser.passwordFile = "/run/secrets/myuser_password";
|
||||
}
|
||||
'';
|
||||
type = types.attrsOf (types.submodule (userSubmod: let
|
||||
user = userSubmod.config._module.args.name;
|
||||
org = userSubmod.config.org;
|
||||
in {
|
||||
options = {
|
||||
present = mkOption {
|
||||
description = mdDoc "Whether to ensure that this user is present or absent.";
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
|
||||
passwordFile = mkOption {
|
||||
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!";
|
||||
default = null;
|
||||
type = types.nullOr types.path;
|
||||
};
|
||||
};
|
||||
}));
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = !(hasAttr "bolt-path" cfg.settings) && !(hasAttr "engine-path" cfg.settings);
|
||||
message = "services.influxdb2.config: bolt-path and engine-path should not be set as they are managed by systemd";
|
||||
}
|
||||
];
|
||||
assertions =
|
||||
[
|
||||
{
|
||||
assertion = !(hasAttr "bolt-path" cfg.settings) && !(hasAttr "engine-path" cfg.settings);
|
||||
message = "services.influxdb2.config: bolt-path and engine-path should not be set as they are managed by systemd";
|
||||
}
|
||||
]
|
||||
++ flatten (flip mapAttrsToList cfg.provision.organizations (orgName: org:
|
||||
flip mapAttrsToList org.auths (authName: auth:
|
||||
[
|
||||
{
|
||||
assertion = 1 == count (x: x) [
|
||||
auth.operator
|
||||
auth.allAccess
|
||||
(auth.readPermissions != []
|
||||
|| auth.writePermissions != []
|
||||
|| auth.readBuckets != []
|
||||
|| auth.writeBuckets != [])
|
||||
];
|
||||
message = "influxdb2: provision.organizations.${orgName}.auths.${authName}: The `operator` and `allAccess` options are mutually exclusive with each other and the granular permission settings.";
|
||||
}
|
||||
(let unknownBuckets = subtractLists (attrNames org.buckets) auth.readBuckets; in {
|
||||
assertion = unknownBuckets == [];
|
||||
message = "influxdb2: provision.organizations.${orgName}.auths.${authName}: Refers to invalid buckets in readBuckets: ${toString unknownBuckets}";
|
||||
})
|
||||
(let unknownBuckets = subtractLists (attrNames org.buckets) auth.writeBuckets; in {
|
||||
assertion = unknownBuckets == [];
|
||||
message = "influxdb2: provision.organizations.${orgName}.auths.${authName}: Refers to invalid buckets in writeBuckets: ${toString unknownBuckets}";
|
||||
})
|
||||
]
|
||||
)
|
||||
));
|
||||
|
||||
services.influxdb2.provision = mkIf cfg.provision.enable {
|
||||
organizations.${cfg.provision.initialSetup.organization} = {
|
||||
buckets.${cfg.provision.initialSetup.bucket} = {
|
||||
inherit (cfg.provision.initialSetup) retention;
|
||||
};
|
||||
};
|
||||
users.${cfg.provision.initialSetup.username} = {
|
||||
inherit (cfg.provision.initialSetup) passwordFile;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.influxdb2 = {
|
||||
description = "InfluxDB is an open-source, distributed, time series database";
|
||||
|
@ -111,58 +451,38 @@ in
|
|||
"admin-password:${cfg.provision.initialSetup.passwordFile}"
|
||||
"admin-token:${cfg.provision.initialSetup.tokenFile}"
|
||||
];
|
||||
|
||||
ExecStartPost = mkIf cfg.provision.enable (
|
||||
[provisioningScript] ++
|
||||
# Only the restarter runs with elevated privileges
|
||||
optional anyAuthDefined "+${restarterScript}"
|
||||
);
|
||||
};
|
||||
|
||||
path = [pkgs.influxdb2-cli];
|
||||
path = [
|
||||
pkgs.influxdb2-cli
|
||||
pkgs.jq
|
||||
];
|
||||
|
||||
# Mark if this is the first startup so postStart can do the initial setup
|
||||
preStart = mkIf cfg.provision.enable ''
|
||||
# Mark if this is the first startup so postStart can do the initial setup.
|
||||
# Also extract any token secret mappings and apply them if this isn't the first start.
|
||||
preStart = let
|
||||
tokenPaths = listToAttrs (flatten
|
||||
# For all organizations
|
||||
(flip mapAttrsToList cfg.provision.organizations
|
||||
# For each contained token that has a token file
|
||||
(_: org: flip mapAttrsToList (filterAttrs (_: x: x.tokenFile != null) org.auths)
|
||||
# Collect id -> tokenFile for the mapping
|
||||
(_: auth: nameValuePair auth.id auth.tokenFile))));
|
||||
tokenMappings = pkgs.writeText "token_mappings.json" (builtins.toJSON tokenPaths);
|
||||
in mkIf cfg.provision.enable ''
|
||||
if ! test -e "$STATE_DIRECTORY/influxd.bolt"; then
|
||||
touch "$STATE_DIRECTORY/.first_startup"
|
||||
else
|
||||
# Manipulate provisioned api tokens if necessary
|
||||
${getExe pkgs.influxdb2-token-manipulator} "$STATE_DIRECTORY/influxd.bolt" ${tokenMappings}
|
||||
fi
|
||||
'';
|
||||
|
||||
postStart = let
|
||||
initCfg = cfg.provision.initialSetup;
|
||||
in mkIf cfg.provision.enable (
|
||||
''
|
||||
set -euo pipefail
|
||||
export INFLUX_HOST="http://"${escapeShellArg (cfg.settings.http-bind-address or "localhost:8086")}
|
||||
|
||||
# Wait for the influxdb server to come online
|
||||
count=0
|
||||
while ! influx ping &>/dev/null; do
|
||||
if [ "$count" -eq 300 ]; then
|
||||
echo "Tried for 30 seconds, giving up..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! kill -0 "$MAINPID"; then
|
||||
echo "Main server died, giving up..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sleep 0.1
|
||||
count=$((count++))
|
||||
done
|
||||
|
||||
# Do the initial database setup. Pass /dev/null as configs-path to
|
||||
# avoid saving the token as the active config.
|
||||
if test -e "$STATE_DIRECTORY/.first_startup"; then
|
||||
influx setup \
|
||||
--configs-path /dev/null \
|
||||
--org ${escapeShellArg initCfg.organization} \
|
||||
--bucket ${escapeShellArg initCfg.bucket} \
|
||||
--username ${escapeShellArg initCfg.username} \
|
||||
--password "$(< "$CREDENTIALS_DIRECTORY/admin-password")" \
|
||||
--token "$(< "$CREDENTIALS_DIRECTORY/admin-token")" \
|
||||
--retention ${escapeShellArg initCfg.retention} \
|
||||
--force >/dev/null
|
||||
|
||||
rm -f "$STATE_DIRECTORY/.first_startup"
|
||||
fi
|
||||
''
|
||||
);
|
||||
};
|
||||
|
||||
users.extraUsers.influxdb2 = {
|
||||
|
|
|
@ -6,6 +6,9 @@ import ./make-test-python.nix ({ pkgs, ...} : {
|
|||
|
||||
nodes.machine = { lib, ... }: {
|
||||
environment.systemPackages = [ pkgs.influxdb2-cli ];
|
||||
# Make sure that the service is restarted immediately if tokens need to be rewritten
|
||||
# without relying on any Restart=on-failure behavior
|
||||
systemd.services.influxdb2.serviceConfig.RestartSec = 6000;
|
||||
services.influxdb2.enable = true;
|
||||
services.influxdb2.provision = {
|
||||
enable = true;
|
||||
|
@ -15,22 +18,208 @@ import ./make-test-python.nix ({ pkgs, ...} : {
|
|||
passwordFile = pkgs.writeText "admin-pw" "ExAmPl3PA55W0rD";
|
||||
tokenFile = pkgs.writeText "admin-token" "verysecureadmintoken";
|
||||
};
|
||||
organizations.someorg = {
|
||||
buckets.somebucket = {};
|
||||
auths.sometoken = {
|
||||
description = "some auth token";
|
||||
readBuckets = ["somebucket"];
|
||||
writeBuckets = ["somebucket"];
|
||||
};
|
||||
};
|
||||
users.someuser.passwordFile = pkgs.writeText "tmp-pw" "abcgoiuhaoga";
|
||||
};
|
||||
|
||||
specialisation.withModifications.configuration = { ... }: {
|
||||
services.influxdb2.provision = {
|
||||
organizations.someorg.buckets.somebucket.present = false;
|
||||
organizations.someorg.auths.sometoken.present = false;
|
||||
users.someuser.present = false;
|
||||
|
||||
organizations.myorg = {
|
||||
description = "Myorg description";
|
||||
buckets.mybucket = {
|
||||
description = "Mybucket description";
|
||||
};
|
||||
auths.mytoken = {
|
||||
operator = true;
|
||||
description = "operator token";
|
||||
tokenFile = pkgs.writeText "tmp-tok" "someusertoken";
|
||||
};
|
||||
};
|
||||
users.myuser.passwordFile = pkgs.writeText "tmp-pw" "abcgoiuhaoga";
|
||||
};
|
||||
};
|
||||
|
||||
specialisation.withParentDelete.configuration = { ... }: {
|
||||
services.influxdb2.provision = {
|
||||
organizations.someorg.present = false;
|
||||
# Deleting the parent implies:
|
||||
#organizations.someorg.buckets.somebucket.present = false;
|
||||
#organizations.someorg.auths.sometoken.present = false;
|
||||
};
|
||||
};
|
||||
|
||||
specialisation.withNewTokens.configuration = { ... }: {
|
||||
services.influxdb2.provision = {
|
||||
organizations.default = {
|
||||
auths.operator = {
|
||||
operator = true;
|
||||
description = "new optoken";
|
||||
tokenFile = pkgs.writeText "tmp-tok" "newoptoken";
|
||||
};
|
||||
auths.allaccess = {
|
||||
operator = true;
|
||||
description = "new allaccess";
|
||||
tokenFile = pkgs.writeText "tmp-tok" "newallaccess";
|
||||
};
|
||||
auths.specifics = {
|
||||
description = "new specifics";
|
||||
readPermissions = ["users" "tasks"];
|
||||
writePermissions = ["tasks"];
|
||||
tokenFile = pkgs.writeText "tmp-tok" "newspecificstoken";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = { nodes, ... }:
|
||||
let
|
||||
specialisations = "${nodes.machine.system.build.toplevel}/specialisation";
|
||||
tokenArg = "--token verysecureadmintoken";
|
||||
in ''
|
||||
def assert_contains(haystack, needle):
|
||||
if needle not in haystack:
|
||||
print("The haystack that will cause the following exception is:")
|
||||
print("---")
|
||||
print(haystack)
|
||||
print("---")
|
||||
raise Exception(f"Expected string '{needle}' was not found")
|
||||
|
||||
def assert_lacks(haystack, needle):
|
||||
if needle in haystack:
|
||||
print("The haystack that will cause the following exception is:")
|
||||
print("---")
|
||||
print(haystack, end="")
|
||||
print("---")
|
||||
raise Exception(f"Unexpected string '{needle}' was found")
|
||||
|
||||
machine.wait_for_unit("influxdb2.service")
|
||||
|
||||
machine.fail("curl --fail -X POST 'http://localhost:8086/api/v2/signin' -u admin:wrongpassword")
|
||||
machine.succeed("curl --fail -X POST 'http://localhost:8086/api/v2/signin' -u admin:ExAmPl3PA55W0rD")
|
||||
|
||||
out = machine.succeed("influx org list ${tokenArg}")
|
||||
assert "default" in out
|
||||
assert_contains(out, "default")
|
||||
assert_lacks(out, "myorg")
|
||||
assert_contains(out, "someorg")
|
||||
|
||||
out = machine.succeed("influx bucket list ${tokenArg} --org default")
|
||||
assert "default" in out
|
||||
assert_contains(out, "default")
|
||||
|
||||
machine.fail("influx bucket list ${tokenArg} --org myorg")
|
||||
|
||||
out = machine.succeed("influx bucket list ${tokenArg} --org someorg")
|
||||
assert_contains(out, "somebucket")
|
||||
|
||||
out = machine.succeed("influx user list ${tokenArg}")
|
||||
assert_contains(out, "admin")
|
||||
assert_lacks(out, "myuser")
|
||||
assert_contains(out, "someuser")
|
||||
|
||||
out = machine.succeed("influx auth list ${tokenArg}")
|
||||
assert_lacks(out, "operator token")
|
||||
assert_contains(out, "some auth token")
|
||||
|
||||
with subtest("withModifications"):
|
||||
machine.succeed('${specialisations}/withModifications/bin/switch-to-configuration test')
|
||||
machine.wait_for_unit("influxdb2.service")
|
||||
|
||||
out = machine.succeed("influx org list ${tokenArg}")
|
||||
assert_contains(out, "default")
|
||||
assert_contains(out, "myorg")
|
||||
assert_contains(out, "someorg")
|
||||
|
||||
out = machine.succeed("influx bucket list ${tokenArg} --org myorg")
|
||||
assert_contains(out, "mybucket")
|
||||
|
||||
out = machine.succeed("influx bucket list ${tokenArg} --org someorg")
|
||||
assert_lacks(out, "somebucket")
|
||||
|
||||
out = machine.succeed("influx user list ${tokenArg}")
|
||||
assert_contains(out, "admin")
|
||||
assert_contains(out, "myuser")
|
||||
assert_lacks(out, "someuser")
|
||||
|
||||
out = machine.succeed("influx auth list ${tokenArg}")
|
||||
assert_contains(out, "operator token")
|
||||
assert_lacks(out, "some auth token")
|
||||
|
||||
# Make sure the user token is also usable
|
||||
machine.succeed("influx auth list --token someusertoken")
|
||||
|
||||
with subtest("keepsUnrelated"):
|
||||
machine.succeed('${nodes.machine.system.build.toplevel}/bin/switch-to-configuration test')
|
||||
machine.wait_for_unit("influxdb2.service")
|
||||
|
||||
out = machine.succeed("influx org list ${tokenArg}")
|
||||
assert_contains(out, "default")
|
||||
assert_contains(out, "myorg")
|
||||
assert_contains(out, "someorg")
|
||||
|
||||
out = machine.succeed("influx bucket list ${tokenArg} --org default")
|
||||
assert_contains(out, "default")
|
||||
|
||||
out = machine.succeed("influx bucket list ${tokenArg} --org myorg")
|
||||
assert_contains(out, "mybucket")
|
||||
|
||||
out = machine.succeed("influx bucket list ${tokenArg} --org someorg")
|
||||
assert_contains(out, "somebucket")
|
||||
|
||||
out = machine.succeed("influx user list ${tokenArg}")
|
||||
assert_contains(out, "admin")
|
||||
assert_contains(out, "myuser")
|
||||
assert_contains(out, "someuser")
|
||||
|
||||
out = machine.succeed("influx auth list ${tokenArg}")
|
||||
assert_contains(out, "operator token")
|
||||
assert_contains(out, "some auth token")
|
||||
|
||||
with subtest("withParentDelete"):
|
||||
machine.succeed('${specialisations}/withParentDelete/bin/switch-to-configuration test')
|
||||
machine.wait_for_unit("influxdb2.service")
|
||||
|
||||
out = machine.succeed("influx org list ${tokenArg}")
|
||||
assert_contains(out, "default")
|
||||
assert_contains(out, "myorg")
|
||||
assert_lacks(out, "someorg")
|
||||
|
||||
out = machine.succeed("influx bucket list ${tokenArg} --org default")
|
||||
assert_contains(out, "default")
|
||||
|
||||
out = machine.succeed("influx bucket list ${tokenArg} --org myorg")
|
||||
assert_contains(out, "mybucket")
|
||||
|
||||
machine.fail("influx bucket list ${tokenArg} --org someorg")
|
||||
|
||||
out = machine.succeed("influx user list ${tokenArg}")
|
||||
assert_contains(out, "admin")
|
||||
assert_contains(out, "myuser")
|
||||
assert_contains(out, "someuser")
|
||||
|
||||
out = machine.succeed("influx auth list ${tokenArg}")
|
||||
assert_contains(out, "operator token")
|
||||
assert_lacks(out, "some auth token")
|
||||
|
||||
with subtest("withNewTokens"):
|
||||
machine.succeed('${specialisations}/withNewTokens/bin/switch-to-configuration test')
|
||||
machine.wait_for_unit("influxdb2.service")
|
||||
|
||||
out = machine.succeed("influx auth list ${tokenArg}")
|
||||
assert_contains(out, "operator token")
|
||||
assert_contains(out, "some auth token")
|
||||
assert_contains(out, "new optoken")
|
||||
assert_contains(out, "new allaccess")
|
||||
assert_contains(out, "new specifics")
|
||||
'';
|
||||
})
|
||||
|
|
34
pkgs/servers/nosql/influxdb2/provision.nix
Normal file
34
pkgs/servers/nosql/influxdb2/provision.nix
Normal file
|
@ -0,0 +1,34 @@
|
|||
{ lib
|
||||
, stdenv
|
||||
, fetchFromGitHub
|
||||
, python3Packages
|
||||
, makeWrapper
|
||||
}:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "influxdb2-provision";
|
||||
version = "1.0.0";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "oddlama";
|
||||
repo = "influxdb2-provision";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-kgpUtXmwy9buupNzQj/6AIeN8XG2x0XjIckK3WIFC+I=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ makeWrapper ];
|
||||
buildInputs = [ python3Packages.python python3Packages.influxdb-client ];
|
||||
|
||||
installPhase = ''
|
||||
install -Dm0555 influxdb2-provision.py $out/bin/influxdb2-provision
|
||||
wrapProgram $out/bin/influxdb2-provision --prefix PYTHONPATH : "$PYTHONPATH"
|
||||
'';
|
||||
|
||||
meta = with lib; {
|
||||
description = "A small utility to help provisioning influxdb2";
|
||||
homepage = "https://github.com/oddlama/influxdb2-provision";
|
||||
license = licenses.mit;
|
||||
maintainers = with maintainers; [oddlama];
|
||||
mainProgram = "influxdb2-provision";
|
||||
};
|
||||
}
|
26
pkgs/servers/nosql/influxdb2/token-manipulator.nix
Normal file
26
pkgs/servers/nosql/influxdb2/token-manipulator.nix
Normal file
|
@ -0,0 +1,26 @@
|
|||
{ buildGoModule
|
||||
, fetchFromGitHub
|
||||
, lib
|
||||
}:
|
||||
|
||||
buildGoModule rec {
|
||||
pname = "influxdb2-token-manipulator";
|
||||
version = "1.0.0";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "oddlama";
|
||||
repo = "influxdb2-token-manipulator";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-9glz+TdqvGJgSsbLm4J/fn7kzMC75z74/jxZrEZiooc=";
|
||||
};
|
||||
|
||||
vendorHash = "sha256-zBZk7JbNILX18g9+2ukiESnFtnIVWhdN/J/MBhIITh8=";
|
||||
|
||||
meta = with lib; {
|
||||
description = "Utility program to manipulate influxdb api tokens for declarative setups";
|
||||
homepage = "https://github.com/oddlama/influxdb2-token-manipulator";
|
||||
license = licenses.mit;
|
||||
maintainers = with maintainers; [oddlama];
|
||||
mainProgram = "influxdb2-token-manipulator";
|
||||
};
|
||||
}
|
|
@ -26995,6 +26995,8 @@ with pkgs;
|
|||
influxdb = callPackage ../servers/nosql/influxdb { };
|
||||
influxdb2-server = callPackage ../servers/nosql/influxdb2 { };
|
||||
influxdb2-cli = callPackage ../servers/nosql/influxdb2/cli.nix { };
|
||||
influxdb2-token-manipulator = callPackage ../servers/nosql/influxdb2/token-manipulator.nix { };
|
||||
influxdb2-provision = callPackage ../servers/nosql/influxdb2/provision.nix { };
|
||||
# For backwards compatibility with older versions of influxdb2,
|
||||
# which bundled the server and CLI into the same derivation. Will be
|
||||
# removed in a few releases.
|
||||
|
|
Loading…
Reference in a new issue