feat: `impure` option for passing `--impure` to Nix commands
Adds a new impure configuration option that allows users to pass --impure to appropriate Nix commands. Useful for cases like including runtime metadata, outside of the flake, in a system derivation.
Hey @nlewo 👋 thanks for creating and sharing comin with the world! Hoping this is a feature you'd be happy to accept.
I needed impure evaluation for how I'm approaching the provisioning of my systems: I wanted a way of passing through metadata from my provisioning source (Pulumi) to my NixOS configuration. In my case, I template EC2 userdata, creating an /etc/nixos/metadata.json file with interpolated values from the Pulumi program. This then gets read in by a NixOS module using builtins.fromJSON (builtins.readFile "/etc/nixos/metadata.json") so i can then use it throughout configuration, referring to values like config.metadata.foo.
Of course, since /etc/nixos/metadata.json is outside my flake, I needed the --impure flag. I've been using this over the past week and it works nicely :) Let me know what you think
Hello @cmacrae,
I actually don't really like to add this nix derivation-show option explicitly. Instead, I would actually prefer to add an option such as nixDerivationShowExtraArgs which would take a list of strings. Thanks to this option, you could then provide the --impure flag to the evaluation.
What do you think about this?
I'm wondering what are the kind of values you want to get at deploy time. Using impure is generally an anti pattern and maybe you could find another approach. For instance, maybe it would be possible to let pulumi injecting these values into your machine at deploy time. In this case, you could have these values in your pulumi repository, which could be an input of your Nix flake, to avoid this impure flag.
Sure, totally understand and respect your standpoint. I don't think any self-respecting user of Nix ever feels too comfortable passing --impure 😅
So, I had actually originally implemented this as a nixArgs option, where I'd added support for passing arbitrary flags/options/arguments, but it turned out a little ugly as there was some logic i had to introduce to figure out where, in the various Nix invokations, to inject the configured arguments. I have since force-pushed over that... which I am now regretting 🙃
As for your question concerning runtime config from Pulumi, I'll do my best to demonstrate what I've cooked up. Certainly eager to hear if there's an alternative approach that I haven't thought of/I'm not aware of.
So, I have a private Pulumi Component Resource library which I developed to deploy NixOS EC2 instances. As part of its parameters, you point it at a flake config, which simply gets switched to in the userdata, once switched to, comin takes over.
The necessity for --impure came from me needing to be able to pass arbitrary configuration information through from Pulumi to Nix. A very simple example of this would be setting the hostname of a system according to the Pulumi stack that's being deployed; as part of the component resource, it automatically writes a metadata file to /etc/nixos/metadata.json like so:
set -euo pipefail
echo "=== Bootstrapping {config_name} ==="
echo "Writing metadata..."
cat > /etc/nixos/metadata.json <<'EOF'
{metadata_json}
EOF
chmod 644 /etc/nixos/metadata.json
here, {metadata_json} has been constructed like this:
return Output.all(**all_outputs).apply(
lambda resolved: json.dumps(
{
"configName": self.config_name,
"stack": self.stack,
"region": self.region,
"project": self.project,
"secretArn": resolved.get("secretArn", self.secret_arn),
"parameters": {
**static_params,
**{k: resolved[k] for k in param_outputs},
},
},
indent=2,
)
)
As you can see, this includes some standard metadata: flake config name, stack, region, project, etc., as well as arbitrary parameters that specific instances may need to expose.
I then have a very simple NixOS module that flake-housed configs load to be able to then reference these in their configuration, like so:
networking.hostName = lib.mkOverride 100 "${config.metadata.configName}-${config.metadata.stack}";
This allows for composing NixOS instances based on the deployment scenario. For example, differing configuration between stacks representing environments. Or when dynamically composing many machines that are largely the same, but have slight differences, you only need one correlating nixosConfiguration in your flake outputs, if that makes sense?
I understand I could generate such metadata and have it get pushed to git, then use that as an input, but it doesn't really fit my architecture and I would personally find that a little clunky.
What do you think? Is there some other, obvious solution that I'm missing? I'd be very happy to be shown another way :)
I have since force-pushed over that... which I am now regretting
I guess you should still be able to find it in your local git reflog (excepting if you git gc).
"secretArn": resolved.get("secretArn", self.secret_arn),
I'm wondering if this secret is written to your Nix store, which is world readable.
then use that as an input, but it doesn't really fit my architecture and I would personally find that a little clunky.
Ok, I totally understand you want to preserve your exiting architecture! (But i still think the end goal would be to either have your metadata in a repository, or as values consumed by your services (such as secrets for instance))
I guess you should still be able to find it in your local git reflog (excepting if you git gc).
Good idea 👍 If I were to resurrect that, does it sound like an implementation you'd be more inclined to accept than the current impure implementation in this PR?
I'm wondering if this secret is written to your Nix store, which is world readable.
It's not, I'm aware of the implications of secrets management in Nix. This is just the ARN of an AWS SecretsManager secret that each instance (if it has secrets defined) gets created. There's then a materialise-secrets service that gets the secret value (a JSON dict) and writes each k/v pair to files in /run/secrets - the instance has access to fetch its secret value via an IAM instance profile
Ok, I totally understand you want to preserve your exiting architecture! (But i still think the end goal would be to either have your metadata in a repository, or as values consumed by your services (such as secrets for instance))
🙏 Yeah, I definitely see what you're getting at. It's perhaps something I'll revisit in the future, but this is working very nicely for me for now :)
If I were to resurrect that, does it sound like an implementation you'd be more inclined to accept than the current impure implementation in this PR?
Yes, having a list of options provided to the Nix evaluation would be nice. (And if required later, the same could be achieved for the building part.)