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

Open
wants to merge 13 commits into
base: master
from

Conversation

Projects
None yet
4 participants
@tomberek
Copy link

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
'';
Show resolved Hide resolved nix/output.nix Outdated
Show resolved Hide resolved nix/output.nix Outdated
@Mic92
Copy link
Contributor

Mic92 left a comment

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")

This comment has been minimized.

@Mic92

Mic92 Nov 16, 2018

Contributor

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

This comment has been minimized.

@tomberek

tomberek Nov 20, 2018

revised with try-finally

This comment has been minimized.

@tomberek

tomberek Nov 20, 2018

also working type annotations ...

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.

This comment has been minimized.

@Mic92

Mic92 Nov 16, 2018

Contributor

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

This comment has been minimized.

Copy link

tomberek commented Nov 16, 2018

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

This comment has been minimized.

Copy link

tomberek commented Nov 16, 2018

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

This comment has been minimized.

Copy link
Contributor

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 tomberek force-pushed the tomberek:init_outputs branch 4 times, most recently from e9f62a5 to 92f0a9c Nov 20, 2018

@tomberek tomberek force-pushed the tomberek:init_outputs branch 3 times, most recently from 7de22ef to bfea718 Nov 20, 2018

@tomberek tomberek force-pushed the tomberek:init_outputs branch from bfea718 to 3bf176b Nov 20, 2018

tomberek added some commits Nov 2, 2018

Expose resources to deployment.keys
Re-evaluate info.machines in order to expose resources and publicIPv4 to
deployment.keys.

@tomberek tomberek force-pushed the tomberek:init_outputs branch from f445246 to b515685 Nov 23, 2018

@tomberek

This comment has been minimized.

Copy link

tomberek commented Nov 23, 2018

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
'';
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

tomberek added some commits Nov 23, 2018

@tomberek

This comment has been minimized.

Copy link

tomberek commented Nov 25, 2018

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;
       };

tomberek added some commits Dec 3, 2018

@nixos-discourse

This comment has been minimized.

Copy link

nixos-discourse commented Dec 20, 2018

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

@aszlig
Copy link
Member

aszlig left a comment

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.

Warning: This uses shell features and is potentially dangerous.
Environment variables:
$out is a temp directory available for use.
'';

This comment has been minimized.

@aszlig

aszlig Dec 23, 2018

Member

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

};
script = mkOption {
default = null;
type = types.nullOr (types.str);

This comment has been minimized.

@aszlig

aszlig Dec 23, 2018

Member

Parentheses not needed here.

{
options = {
name = mkOption {
default = "${name}";

This comment has been minimized.

@aszlig

aszlig Dec 23, 2018

Member

default = name; should be enough.

res = subprocess.check_output(
[defn.script],
env=env,
shell=True)

This comment has been minimized.

@aszlig

aszlig Dec 23, 2018

Member

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.

This comment has been minimized.

@tomberek

tomberek Dec 29, 2018

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment