def packageInstaller(pkgs: Pkg) -> Derivation:
x = {
req: [pkgs.a, pkgs.b]
build:
...
}
x[req].append(pkgs.c)
return buildDerivation(x)
packageInstaller outputs a single package description, but you can also create something similar that easily outputs an entire configuration for the OS. You just need to keep mutation scoped within the language. The configuration that is outputted is immutable and whatever thing that's processing that configuration is not within user control.Keep in mind that mutability and immutability share a certain isomorphism. Within the nix expression language you often do something similar to mutation just as any other functional language. For example:
listVersion3 = listVersion1 ++ AdditionalStuff
It's just that in functional languages you're forced to keep versions of each mutation.How nix is architectured is exactly why it achieves purity and reproducibility, not the configuration language. It isn't even a goal of ansible in the same sense, per design.
The building blocks of nix is simple, of course it can be implemented in something imperative.
The fundamental design of Ansible is that you do imperative steps to reach an end state. The fundamental design of NixOS is that you describe the end state and the compiler derives the intermediate steps.
Yes, normally things work out with Ansible, but the devil is in the details and can involve a lot of fighting configurations that are non-cromulent.
And the problems with ansible would not be any better off if they had used something like the nix-language instead.
No, the fundamental design of configuration management systems (Ansible, Puppet, Chef, Salt, CFEngine) is that they are declarative. You say "this software should be installed, this user should exist, these files should have these contents and permissions, etc" and they figure out the right steps to take on your OS to move from the current state to your desired end state.
You are right though that they can produce unexpected results since they do not describe the entire system from the ground up.
You are right that imperative definition is not a problem for reproduceability, but it does make things harder to reason about since you have to mentally keep track of state between steps.
Chef doesn't allow you to specify the end state. Instead it allows you to specify a list of actions to be taken during deployment. The end result is affected by bunch of external inputs sent from the Chef server and every single bit of state the target server is able to observe. It is therefore impossible to reliably determine the end state of the target server before deployment. Our team has been burnt by this more than once.
Other similar systems are more or less the same. They have every hallmarks of being an imperative system.