Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

init: arbitrary output resources #1048

Merged
merged 5 commits into from Apr 29, 2019
Merged

Conversation

tomberek
Copy link
Contributor

@tomberek tomberek commented Nov 16, 2018

Using the ssh_keypair as a model, this allows one to create whatever JSON via a shell script in order to allow customized resources.

Example use:

resources.output.myKeys.func = ''
    tinc --batch -c $NIXOPS_OUTPUT_DIR generate-ed25519-keys > /dev/null
    jq '{pub:$pub,priv:$priv}' --null-input --rawfile pub $NIXOPS_OUTPUT_DIR/ed25519_key.pub --rawfile priv $NIXOPS_OUTPUT_DIR/ed25519_key.priv
'';

nix/output.nix Outdated Show resolved Hide resolved
nix/output.nix Outdated Show resolved Hide resolved
Copy link
Member

@Mic92 Mic92 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is definitely an interesting feature and I am looking forward to use this.

I started to add type annotation in https://github.com/NixOS/nixops/pull/1042/files
It would be great if you also could add them in this module.
This should help us to migrate in python3 and is general a good idea when integration
tests are hard to realize.

if self.value is None:
self.name = defn.name
self.state = self.UP
output_dir = tempfile.mkdtemp(prefix="nixops-output-tmp")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This leaks a temporary directory in case of an error (for example if subprocess.check_output fails). Unfortunately tempfile.TemporaryDirectory is only available in python3, leaving two alternatives here:

  • surround everything with a try-finally block
  • add a helper function using contextlib and call rmtree in the end

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revised with try-finally

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also working type annotations ...

nix/output.nix Outdated Show resolved Hide resolved
nix/output.nix Outdated
type = types.nullOr (types.str);
#type = types.nullOr (types.either types.str types.path);
description = ''
Text of a shell script which will produce a JSON value.
Copy link
Member

@Mic92 Mic92 Nov 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should limit this to shell. I could imagine with a high-level programming languages it would be easier to generate json in a safe way. Also one could leverage libraries.

@tomberek
Copy link
Contributor Author

Thanks for the feedback, I’ll incorporate the thoughts over the weekend sometime. I do expect that having resources available in phase 1 is going to be more important going forward: #959

Any thoughts on the direction of the design? At the moment I normally accomplish a similar thing by just using the file system and putting deployments into their own folder and use relative paths. With key/value store we can continue to use the “resource” paradigm when we have more backends. Another consideration is that it is sometimes cumbersome to output proper json from the command line.

The net effect of this design is that you can include any supporting scripts directly into nix and they are run for you automatically.

@tomberek
Copy link
Contributor Author

Another thought, use nix-shell in some way to manage the local dependencies that may be in the script? Or just allow people to write whatever they want?

@Mic92
Copy link
Member

Mic92 commented Nov 16, 2018

First of all I am not in the NixOps team, so I won't be able to merge this pull request in the end.
The design makes sense to me. I first thought those outputs could be build by nix derivations, but then we would end up with secrets in the nix store, which is not what we want.

Rather then a shell script I would change it to accept the path to an executable program. This could be a script or whatever the user wants to use.
The question is if it possible to have something like this:

resources.output.myKeys.command = pkgs.writeScript "foo.py" ''
#!${python}/bin/python
# do whatever
'';

resources.output.myKeys2.command = pkgs.writeScript "foo.sh" ''
#!${bash}/bin/bash
# do whatever
'';

Then one could use whatever fits the best to generate the output.
We already have many helpers in nixpkgs to generate programs on the fly and we might
have more in the future: NixOS/nixpkgs#49290

An alternative could be the following to allow additional dependencies
on the fly by using a nix-shell in the shebang:

resources.output.myKeys2.command = pkgs.writeScript "foo.sh" ''
#!/usr/bin/env nix-shell -p
#!nix-shell -p python3.pkgs.foobar -p python3 nix -i python3
'';

@tomberek
Copy link
Contributor Author

Wireguard example:

resources.output.wg_keys.script = ''
  wg genkey | tr -d '\n' | tee $out/privatekey | wg pubkey | tr -d '\n' > $out/publickey
  jq '{pub:$pub,priv:$priv}' --null-input --rawfile pub $out/publickey --rawfile priv $out/privatekey
'';

nixops/resources/output.py Outdated Show resolved Hide resolved
nixops/resources/output.py Outdated Show resolved Hide resolved
nixops/resources/output.py Outdated Show resolved Hide resolved
@tomberek
Copy link
Contributor Author

Also have tested using this to serialize (via tar and base 64) an output to re-serialize after garbage collection:

resources_ssl= {
     output."ssl_test".script =''
       temp=$(nix-store -r $(nix-instantiate --quiet -E "(with import <nixpkgs>{}; custom-certs \"$RANDOM\")") | tr -d '\n')
       tar -C $temp -cf $out/temp.tar . >&2
       cat $out/temp.tar | base64
     '';
   };

This set's up a new set of SSL CA's and certificates. The result is serialized to a value stored in the statefile. The following in deployment.keys will extract it, re-create the original output from the statefile during deployment:

"test2" = let a = resources.output.ssl_test.value;
                     b = builtins.toFile "test2" a;
                     c = builtins.trace b (pkgs.runCommandCC "thing" {} ''
                       mkdir $out
                       pushd $out
                       cat ${b} | base64 -d | tar x
                       '');
         in
         if a == null then {} else
         {
         text = some-function c;
       };

@nixos-discourse
Copy link

This pull request has been mentioned on Nix community. There might be relevant details there:

https://discourse.nixos.org/t/prs-ready-for-review-december/1711/5

Copy link
Member

@aszlig aszlig left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the pull request, this is quite useful for quite a few things I do even in my own deployments.

However I don't like the name output, because it's not very descriptive of what this is doing. I'd rather prefer something like commandOutput, customOutput or even just custom.

nix/output.nix Outdated
Warning: This uses shell features and is potentially dangerous.
Environment variables:
$out is a temp directory available for use.
'';
Copy link
Member

@aszlig aszlig Dec 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Indentation , also please note that descriptions are written in Docbook, so maybe use <warning/> and probably <envar/> here.

nix/output.nix Outdated Show resolved Hide resolved
nix/output.nix Outdated Show resolved Hide resolved
res = subprocess.check_output(
[defn.script],
env=env,
shell=True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please note that this will make the script dependent on the system's shell. For example if you're using NixOps on a Debian machine, you'll end up getting dash instead of bash. So I'd probably wrap the script with stdenv.shell or pkgs.bash so that if the script is using bashisms it will still work.

One possibility would be to add another option to the resource, like eg. executable, which defaults to using something like eg. pkgs.writeScript "somename" "#!${pkgs.stdenv.shell}\n${config.script}". That way you only need to run the executable here without the need to set shell=True.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorporated all the other feedback. I'm not sure how to do this one. Whenever I try to do this, it seems the writeScript gets evaluated, but not instantiated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you post an output of that with --debug?

@tomberek
Copy link
Contributor Author

This still functions on top of master. Also works well with #1054

TODO: I'd like to create a similar thing, but where the resource is created and stored on the remote machines. Send-keys would then retrieve things like public keys (pub/priv created on remote machine) and send public side to other machines that need it.

@nixos-discourse
Copy link

This pull request has been mentioned on Nix community. There might be relevant details there:

https://discourse.nixos.org/t/nixops-arbitrary-output/1406/2

@AmineChikhaoui
Copy link
Member

@tomberek is there anything left to do in this PR from your side ?
@aszlig are you ok with the state of the PR at this point ?

Copy link
Member

@aszlig aszlig left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The indentation nitpick still holds, but the more important question here is: Why did you disable the tests?

release.nix Outdated Show resolved Hide resolved
res = subprocess.check_output(
[defn.script],
env=env,
shell=True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you post an output of that with --debug?

@tomberek tomberek force-pushed the init_outputs branch 2 times, most recently from 5f65041 to 70453b6 Compare April 18, 2019 14:59
nixops/deployment.py Outdated Show resolved Hide resolved
nixops/backends/ec2.py Outdated Show resolved Hide resolved
nixops/resources/output.py Outdated Show resolved Hide resolved
nixops/resources/output.py Outdated Show resolved Hide resolved
nixops/resources/output.py Outdated Show resolved Hide resolved
@tomberek
Copy link
Contributor Author

Added a test. Runs with python tests.py tests.functional.test_output_creates -A vbox

machine = {resources, ...} : {
imports = [ <nixpkgs/nixos/modules/profiles/minimal.nix> ];
deployment = {
keys."secret.key".text = resources.output.thing.value;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This requires the 3 evaluations you added, so I suggest another example/test ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a new test.

nixops/resources/output.py Outdated Show resolved Hide resolved
nixops/resources/output.py Outdated Show resolved Hide resolved
Use content-hashed sha256 for resource_id
Improve typing
Expose resources to deployment.keys
Re-evaluate info.machines in order to expose resources and publicIPv4 to
deployment.keys.
Add test for output resource generation
Use SelfDeletingDir
New test for modification of output resource
Remove superfluous return
@AmineChikhaoui
Copy link
Member

@tomberek Btw didn't you have a version where it used pkgs.writeScriptBin ? That idea sounds better, I'm not sure how easy would it be to define the script as a "package" basically with all needed dependencies without relying on the system.
Did you have problems while trying such approach ?

Also maybe an overkill, but I wish we could do such scripts in isolation (inside nix builds ? in chroots?), the idea of allowing random scripts is a bit scary to me :) But that's probably more complicated.

@@ -309,13 +309,13 @@ def evaluate_args(self):
except subprocess.CalledProcessError:
raise NixEvalError

def evaluate_config(self, attr):
def evaluate_config(self, attr,extra_exprs=[]):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the 3 evals diff is still here even after the rebase

@tomberek
Copy link
Contributor Author

I never got the writeScriptBin approach to work. It had to do with the order of instantiation, so the drv got created, but never realized. I decided to leave that as a future improvement.

Regarding the three-phase: yes, most use cases include using an output resource in deployment.keys such that it’s most useful with it. I can remove it if you think it’s cleaner that way. (I’d have to modify the tests, but that’s ok)

Another approach is to redesign the whole thing in a better way, I’d appreciate any ideas. Especially if we include a method to generate the resources remotely.

@AmineChikhaoui
Copy link
Member

I never got the writeScriptBin approach to work. It had to do with the order of instantiation, so the drv got created, but never realized. I decided to leave that as a future improvement.

Can you share the nix/output.nix that had the issue ? I want to try it out as well.

Regarding the three-phase: yes, most use cases include using an output resource in deployment.keys such that it’s most useful with it. I can remove it if you think it’s cleaner that way. (I’d have to modify the tests, but that’s ok)

I'm just not sure if adding yet another eval is a good idea, it will make things slower in my opinion. I'd like to get more opinions on that (perhaps in a separate PR): @edolstra @rbvermaa @grahamc @aszlig @domenkozar (randomly pinging people :-) ), any opinions ?

@tomberek
Copy link
Contributor Author

@AmineChikhaoui : I've got some of the "executable" work here if you want to play around with it:
https://github.com/tomberek/nixops/blob/v2_outputs/nix/output.nix
https://github.com/tomberek/nixops/blob/v2_outputs/nixops/resources/output.py

Note: it's not in a working state.

@AmineChikhaoui
Copy link
Member

I gave it a try to use a generated executable (which uses pkgs.writeScriptBin) but I've hit some infinite recursions issues during eval. See gist: https://gist.github.com/AmineChikhaoui/65c93cb4c6934f186c0afc818b88b7c6.

Not sure yet why it would be very different from using a regular NixOS module. I know NixOps is a bit different in the evaluation but it still uses evalModules under the hood.
@aszlig Please let me know if you spot an obvious issue and see that we can still use a nix pkg for the script instead of just using the host environment which I would like to avoid if possible.

@aszlig
Copy link
Member

aszlig commented Apr 23, 2019

@AmineChikhaoui: This looks a lot like it's recursing into <nixpkgs/pkgs/top-level/all-packages.nix>. Can you post at which stage you get the infinite recursion?

@AmineChikhaoui
Copy link
Member

@aszlig Ok, I think this happens in Deployment::evaluate_args() which is in my network:

nix-instantiate --read-write-mode \
 --arg networkExprs '[ "/home/amine/src/nixops/output.nix" ]' \
 --arg args {} --argstr uuid 9ad8bea1-6215-11e9-a024-02420a47088d \
 --argstr deploymentName out nix/eval-machine-info.nix \
 --eval-only --xml --strict --arg checkConfigurationOptions false -A info

with @grahamc's nix patch NixOS/nix#2761, I get the following output (not sure if relevant for our case though):

/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/pkgs/stdenv/generic/check-meta.nix:22:5 3730 130 28.6923
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/pkgs/stdenv/generic/check-meta.nix:215:9 2145 65 33
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/lib/systems/doubles.nix:24:50 4768 18 264.889
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/lib/modules.nix:271:5 236 10 23.6
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/lib/systems/inspect.nix:58:45 539 24 22.4583
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/lib/modules.nix:98:6 23 1 23
/home/amine/src/nixops/nix/eval-machine-info.nix:350:21 158 5 31.6
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/lib/types.nix:348:23 15 1 15
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/lib/modules.nix:370:19 216 12 18
/home/amine/src/nixops/nix/eval-machine-info.nix:184:9 643 12 53.5833
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/pkgs/top-level/stage.nix:206:11 2042 6 340.333
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/pkgs/stdenv/generic/make-derivation.nix:108:95 2217 77 28.7922
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/pkgs/stdenv/generic/make-derivation.nix:106:14 188 6 31.3333
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/lib/modules.nix:399:7 94 5 18.8
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/pkgs/stdenv/linux/default.nix:350:9 49 1 49
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/lib/modules.nix:455:21 299 15 19.9333
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/lib/modules.nix:360:14 313 15 20.8667
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/lib/attrsets.nix:356:46 1077 52 20.7115
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/lib/strings.nix:59:16 50 1 50
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/lib/modules.nix:458:16 300 15 20
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/lib/modules.nix:192:8 19 1 19
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/lib/strings.nix:123:60 99 2 49.5
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/pkgs/stdenv/generic/check-meta.nix:214:9 118672 65 1825.72
/home/amine/src/nixops/nix/eval-machine-info.nix:192:9 18 1 18
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/pkgs/stdenv/generic/make-derivation.nix:108:33 3653 77 47.4416
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/pkgs/stdenv/generic/make-derivation.nix:102:36 158 6 26.3333
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/lib/systems/parse.nix:174:12 8819 19 464.158
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/lib/modules.nix:96:36 43 1 43
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/lib/modules.nix:243:23 299 12 24.9167
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/lib/modules.nix:218:9 184 7 26.2857
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/lib/attrsets.nix:125:18 107 3 35.6667
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/lib/options.nix:110:10 15 1 15
/nix/store/5kbkahsc6kz480syx8i063n5ys2k0pd0-nixexprs.tar.xz/lib/modules.nix:350:17 282 15 18.8

@AmineChikhaoui
Copy link
Member

Btw the first eval Deployment::evaluate_config() seems to be wrong as well as it doesn't produce anything basically:

[nix-shell:~/src/nixops]$ nixops deploy -d out --debug
nix-instantiate --read-write-mode -I nixops=/home/amine/src/nixops/nixops/../nix --arg networkExprs [ "/home/amine/src/nixops/output.nix" ] --arg args {} --argstr uuid 9ad8bea1-6215-11e9-a024-02420a47088d --argstr deploymentName out <nixops/eval-machine-info.nix> --eval-only --xml --strict --arg checkConfigurationOptions false -A info.network
XML output of nix-instantiate:
<?xml version='1.0' encoding='utf-8'?>
<expr>
  <attrs>
  </attrs>
</expr>

@edolstra
Copy link
Member

I agree with @aszlig, output is a pretty vague name. Seeing resources.output doesn't really give an intuitive idea of what kind of resource it is.

@tomberek
Copy link
Contributor Author

@edolstra I'm not committed to any particular name. Any suggestions?

  • resources.local (in anticipation of resources.remote)
  • resources.static

@aszlig recommendations:

  • resources.commandOutput
  • resources.customOutput
  • resources.custom

I'm thinking resources.custom would be good. It could later come in multiple types "local" and "remote", "vault", "s3", "url", etc. specifying where the resource is created and/or stored.

I don't want to bikeshed on this, so if there's a consensus, strong feeling, no response for a day, or a first response for one of these I will refactor/test/push a new version.

@AmineChikhaoui
Copy link
Member

AmineChikhaoui commented Apr 24, 2019

So @edolstra mentioned something about --xml vs --json when I asked about the issue but I thought it's not related then I tried that and it's actually the root cause of the infinite recursion issue I was seeing.
https://gist.github.com/AmineChikhaoui/a028d72187ff07370d41421281b8df20 🤷‍♂️
I've opened a nix issue to see if there is a workaround for that: NixOS/nix#2783

I wish we can nuke the xml parsing we have there and only use json but that will require an update to all the resources that do that in the ResourceDefinition classes.

@AmineChikhaoui
Copy link
Member

@aszlig recommendations:

* resources.commandOutput

* resources.customOutput

* resources.custom

Using resources.commandsOutput seems reasonable to me.

@AmineChikhaoui
Copy link
Member

Forgot to post an update here:
I gave up on the packaged script approach: even if the xml was generated properly nixops currently only builds the machines part, we can change that maybe but we're also blocked by the xml usage, in order to get rid of it there should be a major refactoring of the resources/backends using it.

So I guess we will go with the current approach.
@tomberek can you take care of the resource name change and also remove the 3rd evaluation, we can discuss adding it in the other PR that you have open.

@tomberek
Copy link
Contributor Author

@AmineChikhaoui : updated as requested. Thanks for working through this!

I think you ran into the same issue I did on the script.

nix/command-output.nix Outdated Show resolved Hide resolved
Text of a script which will produce a JSON value.
<warning>Warning: This uses shell features and is potentially dangerous.</warning>
Environment variables:
<envar>$out</envar> is a temp directory available for use.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's $out ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

out is available as an environment variable in the script as temp directory.
https://github.com/NixOS/nixops/pull/1048/files#diff-6d7517db2e330d537da7846c6ab7f4c5R69

@AmineChikhaoui AmineChikhaoui merged commit 5cd194e into NixOS:master Apr 29, 2019
@nixos-discourse
Copy link

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/nixops-and-openvpn/4896/3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants