diff --git a/modules.nix b/modules.nix index 0158dd5..f5a914f 100644 --- a/modules.nix +++ b/modules.nix @@ -42,6 +42,12 @@ let default = false; description = "Verbose output during module activation (for debugging)"; }; + stateDir = lib.mkOption + { + type = str; + default = "/var/lib/nixvirt"; + description = "State dir"; + }; swtpm.enable = lib.mkOption { type = bool; @@ -122,7 +128,7 @@ let jsonFile = packages.writeText "nixvirt module script" (builtins.toJSON opts); verboseFlag = if cfg.verbose then "-v" else ""; in - "${moduleHelperFile} ${verboseFlag} --connect ${connection} ${jsonFile}\n"; + "${moduleHelperFile} ${verboseFlag} --statedir ${cfg.stateDir} --connect ${connection} ${jsonFile}\n"; extraPackages = [ packages.qemu-utils ] ++ (if cfg.swtpm.enable then [ packages.swtpm ] else [ ]); extraPaths = concatStrMap (p: "${p}/bin:") extraPackages; diff --git a/tool/nixvirt-module-helper b/tool/nixvirt-module-helper index f5c8d25..d6ffff6 100644 --- a/tool/nixvirt-module-helper +++ b/tool/nixvirt-module-helper @@ -1,19 +1,49 @@ #!/usr/bin/python3 -import sys, argparse, uuid, lxml.etree, json, libvirt, nixvirt +import sys, argparse, uuid, lxml.etree, json, libvirt, nixvirt, os parser = argparse.ArgumentParser(prog='nixvirt-module-helper',description='Define and control a collection of libvirt objects idempotently.') parser.add_argument('-v', '--verbose', action='store_true', help='report actions to stderr') parser.add_argument('--connect', action='store', required=True, metavar='URI', help='connection URI (e.g. qemu:///session)') +parser.add_argument('--statedir', action='store', required=True, metavar='PATH', help='state dir (e.g. /var/lib/nixvirt)') parser.add_argument('settingspath', action='store', metavar='PATH', help='path to JSON file of settings') args = parser.parse_args() +types = [("network","networks"),("pool","pools"),("domain","domains")] + +if not os.path.exists(args.statedir): + os.makedirs(args.statedir) with open(args.settingspath,"r") as f: - settings = json.load(f) + newSettings = json.load(f) + +# save newSettings as json since we will edit it +newSettingsStr = json.dumps(newSettings) + +oldSettingsPath = os.path.join(args.statedir, str(hash(args.connect))) +if os.path.isfile(oldSettingsPath): + with open(oldSettingsPath, "r") as f: + oldSettings = json.load(f) +else: + oldSettings = {} + +settings = {} + +for (type,key) in types: + newItemlist = newSettings.get(key) + oldItemlist = oldSettings.get(key) + if newItemlist is None or oldItemlist is None: + continue + else: + for newItem in newItemlist: + # tag it with "unchanged" + if oldItemlist.__contains__(newItem): + newItem["unchanged"] = True + settings[key] = newItemlist + class TypeSpec: def __init__(self,session,type,itemlist): self.oc = nixvirt.getObjectConnection(session,type) - self.specList = [nixvirt.ObjectSpec.fromDefinitionFile(self.oc,item["definition"],item.get("active"),item) for item in itemlist] + self.specList = [nixvirt.ObjectSpec.fromDefinitionFile(self.oc,item["definition"],item.get("active"),item.get("unchanged"),item) for item in itemlist] def deleteOld(self): allObjects = self.oc.getAll() @@ -39,8 +69,6 @@ class TypeSpec: try: session = nixvirt.Session(args.connect,args.verbose) - types = [("network","networks"),("pool","pools"),("domain","domains")] - session.vreport("Examining settings") typeSpecs = [] for (type,key) in types: @@ -68,6 +96,10 @@ try: for typeSpec in typeSpecs: typeSpec.setActive() + # save the new Settings + with open(oldSettingsPath, "w") as f: + f.write(newSettingsStr) + session.vreport("Done") except nixvirt.NixVirtError as err: diff --git a/tool/nixvirt.py b/tool/nixvirt.py index 89b40cf..1692f23 100644 --- a/tool/nixvirt.py +++ b/tool/nixvirt.py @@ -309,7 +309,7 @@ def defineExtra(self,extra): # what we want for an object class ObjectSpec: - def __init__(self,oc,specUUID = None,specName = None,specDefXML = None,active = None,extra = None): + def __init__(self,oc,specUUID = None,specName = None,specDefXML = None,active = None, unchanged = None, extra = None): if specUUID is not None: self.subject = oc.fromUUIDOrNone(specUUID) elif specName is not None: @@ -327,6 +327,7 @@ def __init__(self,oc,specUUID = None,specName = None,specDefXML = None,active = self.specName = specName self.specUUID = specUUID self.active = active + self.unchanged = unchanged self.extra = extra def vreport(self,msg): @@ -338,20 +339,22 @@ def fromUUID(oc,specUUID,active): def fromName(oc,specName,active): return ObjectSpec(oc,specName = specName,active = active) - def fromDefinition(oc,specDefXML,active,extra = None): + def fromDefinition(oc,specDefXML,active,unchanged,extra = None): specDefETree = xmlToETree(specDefXML) specUUID = uuid.UUID(specDefETree.find("uuid").text).bytes specName = specDefETree.find("name").text fixedDefETree = oc._fixDefinitionETree(specUUID,specDefETree) if fixedDefETree is not None: specDefXML = eTreeToXML(fixedDefETree) - return ObjectSpec(oc,specUUID = specUUID,specName = specName,specDefXML = specDefXML,active = active, extra = extra) + return ObjectSpec(oc,specUUID = specUUID,specName = specName,specDefXML = specDefXML,active = active, unchanged = unchanged, extra = extra) - def fromDefinitionFile(oc,path,active,extra = None): + def fromDefinitionFile(oc,path,active,unchanged,extra = None): specDefXML = oc.getFile(path) - return ObjectSpec.fromDefinition(oc,specDefXML,active, extra = extra) + return ObjectSpec.fromDefinition(oc,specDefXML,active,unchanged,extra = extra) def define(self): + if self.unchanged: + return if self.specDefXML is not None: if self.subject is not None: oldDefETree = self.subject.descriptionETree() @@ -377,8 +380,12 @@ def define(self): self.subject = self.oc._fromXML(self.specDefXML) def defineExtra(self): + if self.unchanged: + return if self.subject is not None: self.subject.defineExtra(self.extra) def setActive(self): + if self.unchanged: + return self.subject.setActive(self.active)