nixos/taskserver: Handle declarative conf via JSON
We now no longer have the stupid --service-helper option, which silences messages about already existing organisations, users or groups. Instead of that option, we now have a new subcommand called "process-json", which accepts a JSON file directly from the specified NixOS module options and creates/deletes the users accordingly. Note that this still has a two issues left to solve in this area: * Deletion is not supported yet. * If a user is created imperatively, the next run of process-json will delete it once deletion is supported. So we need to implement deletion and a way to mark organisations, users and groups as "imperatively managed". Signed-off-by: aszlig <aszlig@redmoonstudios.org>
This commit is contained in:
parent
cf0501600a
commit
6e10705754
@ -142,8 +142,6 @@ let
|
|||||||
propagatedBuildInputs = [ pkgs.pythonPackages.click ];
|
propagatedBuildInputs = [ pkgs.pythonPackages.click ];
|
||||||
};
|
};
|
||||||
|
|
||||||
ctlcmd = "${nixos-taskserver}/bin/nixos-taskserver --service-helper";
|
|
||||||
|
|
||||||
withMeta = meta: defs: mkMerge [ defs { inherit meta; } ];
|
withMeta = meta: defs: mkMerge [ defs { inherit meta; } ];
|
||||||
|
|
||||||
in {
|
in {
|
||||||
@ -432,20 +430,10 @@ in {
|
|||||||
|
|
||||||
environment.TASKDDATA = cfg.dataDir;
|
environment.TASKDDATA = cfg.dataDir;
|
||||||
|
|
||||||
preStart = ''
|
preStart = let
|
||||||
${concatStrings (mapAttrsToList (orgName: attrs: ''
|
jsonOrgs = builtins.toJSON cfg.organisations;
|
||||||
${ctlcmd} add-org ${mkShellStr orgName}
|
jsonFile = pkgs.writeText "orgs.json" jsonOrgs;
|
||||||
|
in "${nixos-taskserver}/bin/nixos-taskserver process-json '${jsonFile}'";
|
||||||
${concatMapStrings (user: ''
|
|
||||||
echo Creating ${user} >&2
|
|
||||||
${ctlcmd} add-user ${mkShellStr orgName} ${mkShellStr user}
|
|
||||||
'') attrs.users}
|
|
||||||
|
|
||||||
${concatMapStrings (group: ''
|
|
||||||
${ctlcmd} add-group ${mkShellStr orgName} ${mkShellStr user}
|
|
||||||
'') attrs.groups}
|
|
||||||
'') cfg.organisations)}
|
|
||||||
'';
|
|
||||||
|
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
ExecStart = "@${taskd} taskd server";
|
ExecStart = "@${taskd} taskd server";
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import grp
|
import grp
|
||||||
|
import json
|
||||||
import pwd
|
import pwd
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@ -210,6 +211,13 @@ class Organisation(object):
|
|||||||
return newuser
|
return newuser
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def del_user(self, name):
|
||||||
|
"""
|
||||||
|
Delete a user and revoke its keys.
|
||||||
|
"""
|
||||||
|
sys.stderr.write("Delete user {}.".format(name))
|
||||||
|
# TODO: deletion!
|
||||||
|
|
||||||
def add_group(self, name):
|
def add_group(self, name):
|
||||||
"""
|
"""
|
||||||
Create a new group.
|
Create a new group.
|
||||||
@ -223,6 +231,13 @@ class Organisation(object):
|
|||||||
return newgroup
|
return newgroup
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def del_group(self, name):
|
||||||
|
"""
|
||||||
|
Delete a group.
|
||||||
|
"""
|
||||||
|
sys.stderr.write("Delete group {}.".format(name))
|
||||||
|
# TODO: deletion!
|
||||||
|
|
||||||
def get_user(self, name):
|
def get_user(self, name):
|
||||||
return self.users.get(name)
|
return self.users.get(name)
|
||||||
|
|
||||||
@ -261,6 +276,14 @@ class Manager(object):
|
|||||||
return neworg
|
return neworg
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def del_org(self, name):
|
||||||
|
"""
|
||||||
|
Delete and revoke keys of an organisation with all its users and
|
||||||
|
groups.
|
||||||
|
"""
|
||||||
|
sys.stderr.write("Delete org {}.".format(name))
|
||||||
|
# TODO: deletion!
|
||||||
|
|
||||||
def get_org(self, name):
|
def get_org(self, name):
|
||||||
return self.orgs.get(name)
|
return self.orgs.get(name)
|
||||||
|
|
||||||
@ -285,13 +308,11 @@ ORGANISATION = OrganisationType()
|
|||||||
|
|
||||||
|
|
||||||
@click.group()
|
@click.group()
|
||||||
@click.option('--service-helper', is_flag=True)
|
def cli():
|
||||||
@click.pass_context
|
|
||||||
def cli(ctx, service_helper):
|
|
||||||
"""
|
"""
|
||||||
Manage Taskserver users and certificates
|
Manage Taskserver users and certificates
|
||||||
"""
|
"""
|
||||||
ctx.obj = {'is_service_helper': service_helper}
|
pass
|
||||||
|
|
||||||
|
|
||||||
@cli.command("list-users")
|
@cli.command("list-users")
|
||||||
@ -351,14 +372,11 @@ def export_user(organisation, user):
|
|||||||
|
|
||||||
@cli.command("add-org")
|
@cli.command("add-org")
|
||||||
@click.argument("name")
|
@click.argument("name")
|
||||||
@click.pass_obj
|
def add_org(name):
|
||||||
def add_org(obj, name):
|
|
||||||
"""
|
"""
|
||||||
Create an organisation with the specified name.
|
Create an organisation with the specified name.
|
||||||
"""
|
"""
|
||||||
if os.path.exists(mkpath(name)):
|
if os.path.exists(mkpath(name)):
|
||||||
if obj['is_service_helper']:
|
|
||||||
return
|
|
||||||
msg = "Organisation with name {} already exists."
|
msg = "Organisation with name {} already exists."
|
||||||
sys.exit(msg.format(name))
|
sys.exit(msg.format(name))
|
||||||
|
|
||||||
@ -368,8 +386,7 @@ def add_org(obj, name):
|
|||||||
@cli.command("add-user")
|
@cli.command("add-user")
|
||||||
@click.argument("organisation", type=ORGANISATION)
|
@click.argument("organisation", type=ORGANISATION)
|
||||||
@click.argument("user")
|
@click.argument("user")
|
||||||
@click.pass_obj
|
def add_user(organisation, user):
|
||||||
def add_user(obj, organisation, user):
|
|
||||||
"""
|
"""
|
||||||
Create a user for the given organisation along with a client certificate
|
Create a user for the given organisation along with a client certificate
|
||||||
and print the key of the new user.
|
and print the key of the new user.
|
||||||
@ -379,8 +396,6 @@ def add_user(obj, organisation, user):
|
|||||||
"""
|
"""
|
||||||
userobj = organisation.add_user(user)
|
userobj = organisation.add_user(user)
|
||||||
if userobj is None:
|
if userobj is None:
|
||||||
if obj['is_service_helper']:
|
|
||||||
return
|
|
||||||
msg = "User {} already exists in organisation {}."
|
msg = "User {} already exists in organisation {}."
|
||||||
sys.exit(msg.format(user, organisation))
|
sys.exit(msg.format(user, organisation))
|
||||||
|
|
||||||
@ -388,18 +403,60 @@ def add_user(obj, organisation, user):
|
|||||||
@cli.command("add-group")
|
@cli.command("add-group")
|
||||||
@click.argument("organisation", type=ORGANISATION)
|
@click.argument("organisation", type=ORGANISATION)
|
||||||
@click.argument("group")
|
@click.argument("group")
|
||||||
@click.pass_obj
|
def add_group(organisation, group):
|
||||||
def add_group(obj, organisation, group):
|
|
||||||
"""
|
"""
|
||||||
Create a group for the given organisation.
|
Create a group for the given organisation.
|
||||||
"""
|
"""
|
||||||
userobj = organisation.add_group(group)
|
userobj = organisation.add_group(group)
|
||||||
if userobj is None:
|
if userobj is None:
|
||||||
if obj['is_service_helper']:
|
|
||||||
return
|
|
||||||
msg = "Group {} already exists in organisation {}."
|
msg = "Group {} already exists in organisation {}."
|
||||||
sys.exit(msg.format(group, organisation))
|
sys.exit(msg.format(group, organisation))
|
||||||
|
|
||||||
|
|
||||||
|
def add_or_delete(old, new, add_fun, del_fun):
|
||||||
|
"""
|
||||||
|
Given an 'old' and 'new' list, figure out the intersections and invoke
|
||||||
|
'add_fun' against every element that is not in the 'old' list and 'del_fun'
|
||||||
|
against every element that is not in the 'new' list.
|
||||||
|
|
||||||
|
Returns a tuple where the first element is the list of elements that were
|
||||||
|
added and the second element consisting of elements that were deleted.
|
||||||
|
"""
|
||||||
|
old_set = set(old)
|
||||||
|
new_set = set(new)
|
||||||
|
to_delete = old_set - new_set
|
||||||
|
to_add = new_set - old_set
|
||||||
|
for elem in to_delete:
|
||||||
|
del_fun(elem)
|
||||||
|
for elem in to_add:
|
||||||
|
add_fun(elem)
|
||||||
|
return to_add, to_delete
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command("process-json")
|
||||||
|
@click.argument('json-file', type=click.File('rb'))
|
||||||
|
def process_json(json_file):
|
||||||
|
"""
|
||||||
|
Create and delete users, groups and organisations based on a JSON file.
|
||||||
|
|
||||||
|
The structure of this file is exactly the same as the
|
||||||
|
'services.taskserver.organisations' option of the NixOS module and is used
|
||||||
|
for declaratively adding and deleting users.
|
||||||
|
|
||||||
|
Hence this subcommand is not recommended outside of the scope of the NixOS
|
||||||
|
module.
|
||||||
|
"""
|
||||||
|
data = json.load(json_file)
|
||||||
|
|
||||||
|
mgr = Manager()
|
||||||
|
add_or_delete(mgr.orgs.keys(), data.keys(), mgr.add_org, mgr.del_org)
|
||||||
|
|
||||||
|
for org in mgr.orgs.values():
|
||||||
|
add_or_delete(org.users.keys(), data[org.name]['users'],
|
||||||
|
org.add_user, org.del_user)
|
||||||
|
add_or_delete(org.groups.keys(), data[org.name]['groups'],
|
||||||
|
org.add_group, org.del_group)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
cli()
|
cli()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user