diff --git a/nixops/__main__.py b/nixops/__main__.py index b845587ba..056b95062 100755 --- a/nixops/__main__.py +++ b/nixops/__main__.py @@ -164,6 +164,7 @@ help="activate unchanged configurations as well", ) add_common_deployment_options(subparser) +add_common_modify_options(subparser) subparser = add_subparser(subparsers, "send-keys", help="send encryption keys") subparser.set_defaults(op=op_send_keys) diff --git a/nixops/evaluation.py b/nixops/evaluation.py new file mode 100644 index 000000000..b94c3026f --- /dev/null +++ b/nixops/evaluation.py @@ -0,0 +1,44 @@ +from dataclasses import dataclass +import subprocess +import typing +import json + + +@dataclass +class NetworkEval: + + description: str = "Unnamed NixOps network" + enableRollback: bool = False + enableState: bool = True + + +def _eval_attr( + attr, nix_exprs: typing.List[str] +) -> typing.Dict[typing.Any, typing.Any]: + p = subprocess.run( + [ + "nix-instantiate", + "--eval-only", + "--json", + "--strict", + # Arg + "--arg", + "checkConfigurationOptions", + "false", + # Attr + "-A", + attr, + ] + + nix_exprs, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + if p.returncode != 0: + raise RuntimeError(p.stderr.decode()) + + return json.loads(p.stdout) + + +def eval_network(nix_exprs: typing.List[str]) -> NetworkEval: + result = _eval_attr("network", nix_exprs) + return NetworkEval(**result) diff --git a/nixops/script_defs.py b/nixops/script_defs.py index 9a263b40e..692ad6e8c 100644 --- a/nixops/script_defs.py +++ b/nixops/script_defs.py @@ -26,8 +26,10 @@ from datetime import datetime from pprint import pprint import importlib +from functools import lru_cache from nixops.plugins import get_plugin_manager +from nixops.evaluation import eval_network pm = get_plugin_manager() @@ -37,6 +39,11 @@ ] +@lru_cache() +def _create_state(state_file: str) -> nixops.statefile.StateFile: + return nixops.statefile.StateFile(state_file) + + def op_list_plugins(args): if args.verbose: tbl = create_table([("Installed Plugins", "c"), ("Plugin Reference", "c")]) @@ -68,14 +75,14 @@ def sort_deployments( # $NIXOPS_DEPLOYMENT. def one_or_all(args: Namespace) -> List[nixops.deployment.Deployment]: if args.all: - sf = nixops.statefile.StateFile(args.state_file) + sf = _create_state(args.state_file) return sf.get_all_deployments() else: return [open_deployment(args)] def op_list_deployments(args): - sf = nixops.statefile.StateFile(args.state_file) + sf = _create_state(args.state_file) tbl = create_table( [ ("UUID", "l"), @@ -99,7 +106,7 @@ def op_list_deployments(args): def open_deployment(args): - sf = nixops.statefile.StateFile(args.state_file) + sf = _create_state(args.state_file) depl = sf.open_deployment(uuid=args.deployment) depl.extra_nix_path = sum(args.nix_path or [], []) @@ -145,12 +152,19 @@ def modify_deployment(args, depl: nixops.deployment.Deployment): def op_create(args): - sf = nixops.statefile.StateFile(args.state_file) + sf = _create_state(args.state_file) depl = sf.create_deployment() sys.stderr.write("created deployment ‘{0}’\n".format(depl.uuid)) modify_deployment(args, depl) - if args.name or args.deployment: - set_name(depl, args.name or args.deployment) + + # When deployment is created without state "name" does not exist + name = args.deployment + if "name" in args: + name = args.name or args.deployment + + if name: + set_name(depl, name) + sys.stdout.write(depl.uuid + "\n") @@ -284,7 +298,7 @@ def name_to_key(name: str) -> Tuple[str, str, List[object]]: ) if args.all: - sf = nixops.statefile.StateFile(args.state_file) + sf = _create_state(args.state_file) if not args.plain: tbl = create_table([("Deployment", "l")] + table_headers) for depl in sort_deployments(sf.get_all_deployments()): @@ -553,7 +567,18 @@ def op_restore(args): def op_deploy(args): + + # If nix expressions are passed evaluate the network first so we can figure + # out if we need to create state or not + if args.nix_exprs: + network = eval_network(args.nix_exprs) + # If state is not enabled we need to create the deployment first in the in-memory sqlite db + if not network.enableState: + args.state_file = ":memory:" + op_create(args) + depl = open_deployment(args) + if args.confirm: depl.logger.set_autoresponse("y") if args.evaluate_only: @@ -710,7 +735,7 @@ def op_export(args): def op_import(args): - sf = nixops.statefile.StateFile(args.state_file) + sf = _create_state(args.state_file) existing = set(sf.query_deployments()) dump = json.loads(sys.stdin.read()) diff --git a/nixops/statefile.py b/nixops/statefile.py index d9792ca1d..1f00a3e74 100644 --- a/nixops/statefile.py +++ b/nixops/statefile.py @@ -82,7 +82,10 @@ class StateFile(object): def __init__(self, db_file: str) -> None: self.db_file: str = db_file - if os.path.splitext(db_file)[1] not in [".nixops", ".charon"]: + if db_file != ":memory:" and os.path.splitext(db_file)[1] not in [ + ".nixops", + ".charon", + ]: raise Exception( "state file ‘{0}’ should have extension ‘.nixops’".format(db_file) )