Is very interesting though, I think I'm gonna try it myself.
One of them is handling "if-this-then-that-else-that". Being purely declarative, Ansible is horrible at that.
Pyinfra can be used in imperative mode, am I right? This would make the use of if-else a breeze, which would be a really good reason for me to to switch.
In puppet and saltstack you can declare that a folder is empty and declare a specific file in this folder. The system's smart enough to delete all the files except the one.
To achieve such feat in ansible is hard. Easiest way is to have two tasks, one deletes everything and second recreates your file. Doesn't feel very declarative
Unrelated thing, they don't even try to be declarative in ansible E.g you can have a file with state "touch". It not a state if it updates each playbook run!
ansible modules (built-in and community) are a different matter. generally, quality will vary.
I'm fairly sure that the way to make Puppet do what you suggest is the same in Puppet and Ansible. The difference is that Puppet is smart enough to not actually remove you file during every run (I think). On the other hand, Ansible will normally not be configured to run every 30 minutes like Puppet, so it's much less of an issue.
Both tools are great but they work some what differently. How you think about using them is much the same though, you need to tell the computer what to do. In Puppet this is often talked about is if you describe the state, I suppose that's partly true, but in the end you have a series of actions the computer will need to take to achieve this state.
Pulumi for Terraform and Dagger for Docker are two examples I use
I like CUE as a language to replace my Yaml that has some of the typical language constructs but maintains the declarative approach
As you craft your "Why this and not Ansible" content, you might actually state clearly what you already noted on the Performance page, namely: "One of the reasons pyinfra was started was performance of agent-less tools at the time." If I read that, it'd instantly make me want to stick around and read some more, play with pyinfra, etc. BTW, i will be playing with it anyway, but just wanted to point out that you likely won;t need to start from scratch for copy (on a comparison or answering "Why this and not Ansible" content). Cheers!
I use ansible for creating machine images or initial provisioning. (I don't run the ansible, someone racks the host, sets it's build state to install, and boots the host and it joins the appropriate cluster and people do container things. I don't necessarily know when my ansible runs against a host.
I also have a pretty good stack of ansible playbooks that I use manually day to day for hardware validation for new server models and one off type stuff. But again, I never really know what I'm running against or have pet servers.
A good chunk of hardware validation runs automatically if the boot target is set to hw-validate, but the whole point is that you are gonna find stuff that doesn't work with your standard process and either pass on it or adjust.
I do run tf to provision cloud infra so its transparent to the devs, and, honestly, not sure how ansible is dated and tf is not, they are pretty much the same thing in a different coat.
And honestly, generating thousands of lines of conflicting generic yaml isn't really much of an improvement over writing it once and running it automatically on 1000s of boxes.
If so, nice, shout about it more - it's my number one requirement of such a tool, why I think Terraform (or OpenTofu) is great and mostly everything else sucks, and I think it should be everyone's. It's just obviously (at least, once someone makes it available!) the correct paradigm for managing stateful resources and coping with drift.
- state definitions, "ensure this apt package is installed" (apt.packages: https://docs.pyinfra.com/en/next/operations/apt.html#operati...) - stateless, "run these shell commands" (server.shell: https://docs.pyinfra.com/en/next/operations/server.html#oper...)
Most operations are state definitions and much preferred, the stateless ones exist to satisfy edge cases where either the state-ful version isn't implemented or simply isn't possible.
In Terraform CDK, you use a language like python to compute the set of resources and such you want to have, and then hand that over to the terraform core, which does the usual terraform song and dance to make it happen.
This is actually interesting to me, because we struggle with even the simplest data transformations in ansible so much. Like, as soon as you start thinking about doing a simple list comprehension in python in jinja templating, lots and lots of pain starts. From there, we're really starting to think about templating the inventories in some way because it would be less painful.
Totally agree on templating which is why inventories have always been python code just as operations, giving maximum flexibility (with some complexity/type confusion drawbacks).
https://docs.ansible.com/ansible/latest/reference_appendices...
The problem is to track deletions you either have to constantly have a view of global state (i.e. do you want to put `linux-kernel` in your package list?) or you need to store specific state about that machine (i.e. `redis` was installed by playbook redis-server.yml, task "install redis") - because the packages absence in that list doesn't necessarily mean "uninstall it" if something else in another playbook or task will later declare it should be present.
As soon as you're trying to do deletions, you're making assumptions that the view of the state you have is complete and total and that is usually not the case - and even if it is within the scope of your system, is it the case on the system you're interacting with? Do you know every package that should be installed because it comes out of the box in the distro? Do you want to (aka: do you have the time, resourcing and effort to do this for the almost zero gain it will get you in the short term unless you can point to business outcomes which are fulfilled by the activity?)
terraform does this, which is why it tracks the its own representation of the prior global state. So when you remove a declared resource the diff against the prior state is interpreted as a delete. Note this does introduce the problem of "drift" when you have resources that are not captured in the scope of the state.
> i.e. do you want to put `linux-kernel` in your package list?
Yes. At least I want to put something like "core-packages" or "default" or similar as part of setting my explicit intent.
Given that ansible has neither it can't be much better then what it is. I disagree that that is the right choice though. As it is I see not much more value in ansible than in some sort of SSH over xargs contraption combined with a list of servers. The guarantees they give are the same.
> Do you know every package that should be installed because it comes out of the box in the distro? Do you want to [...]?
No, I don't want to. Thankfully, with NixOS I don't need to, since the pre-installed packages are automatically part of the declared state of my NixOS systems (i.e. I declare the wanted state in the same way in which the defaults are also declared, which makes it easy to merge both).
You can't be declarative all the way down because reality is not declarative.
You can have all modules being declarative but if you need orchestration, it's not declarative anymore unless you create a new abstraction on top of it.
So people keep arguing about declarative vs imperative and fail to specify at which abstraction level they want things to be either.
Since apparently (I try to avoid ansible, so I might be missing something) playbooks are the go-to approach of using ansible this means that most uses of ansible are imperative (in the context of configuring a system), unless you only ever give a system a singular role and then you are probably defining your role in imperative steps.
A system like NixOS on the other hand presents the entirety of a system configuration in a single declarative interface that is applied in one go, while applying such a configuration to a system can be a thought of as an imperative step (although it is usually a singular, unconditional step). So it is declarative at a higher abstraction level.
Declarative structure is at the heart of functional programming. Declarative is not the right choice everywhere, but when it makes sense, it can significantly raise the quality of the code.
Declarative scripts make it easy to manage a fleet.
for i in range(100):
ip = cidrhost(subnet, i)
if exists := get_server(ip):
continue
create_server(ip=ip)
and so on. I don't like it, but because it's procedural/imperative, not because it's particularly more 'petty' than the Terraform (or equivalent) would be.For me it's more about what I'm doing, conceptually. I want a server to exist, it to have access to this S3 bucket, etc. - the logic of how to interface with the APIs to make that happen, to manage their lifecycle and check current state etc. isn't what I'm thinking about. (In Terraform terms, that belongs in the provider.) When I write the above I'm just thinking I want 100 servers, so:
resource "cloud_server" "my_servers" {
count = 100
ip = cidrhost(subnet, count.index)
# and so on
}
comes much more naturally.The difference between just using some Python vs Terraform is idempotency. TF isn’t going to touch the nodes the script succeeded on; if you have to start your for-loop script it will, which may not be desirable.
Frankly these days configuration management is a bit dated…
You’re much better off in most cases using a tool like Packer with whatever system you want to bake an image, then use a simple user-data script for customization.
It’s very hard to scale continuous config management to thousands of servers.
Packer and Terraform do different jobs (they're both by Hashicorp!) - you can bake an immutable image all you like, you still need to get a server, put the image on it, give it that S3 bucket it needs, IAM, etc.
But there’s always edge cases and situations that doesn’t work which is why pyinfra supports both and they can combine any way you like.
I definitely think it'd be easier to explain a python-like declarative language to someone who asks what programming is than actual python. 'It's just describing the way things should be' vs. 'it's like a series of instructions for how to compute ...'
Certainly not more clever IMO, if anything the opposite. Like I said above or elsewhere in this thread, when I'm managing infrastructure with Terraform I don't want to (and don't have to) be thinking about how to interface with the API, check whether things exist already, their current state, how to move from that to what I want, etc. I just know the way I want it being, I declare that, and the procedure for figuring it out and making it so is the provider's job. That's not smarter! The smart's in the provider! (But ok if you're going to make me flex, I've written and contributed to providers too... But that's Go; not declarative.)
If a SQL compiler or `terraform plan` command can convert "the current state of the system" + "desired end-state" to a series of steps that constitute "how to get there from here", then I can usually just move forward to declaring more desired states after that, or debugging something else, etc. Let the computer do the routine calculations.
When using a path-finding / route-finding tool, having the map and some basic pathfinding algorithms already programmed in means we no longer need to "pop a candidate route-segment off the list of candidates and evaluate the new route cost"... I simply observe that I am "probably here" and I wish to get to "there"; propose a route and if it's good enough I'll instruct the machine to do that.
If I can declare that I want the final system to contain only the folder "/stuff/config.yaml" with permissions 700 -- I don't care what the contents of stuff were previously, and if it had a million temp files in it from an install going sideways or the wrong permissions or a thousand nested folders in it, well, it would be great if the silly computer had a branching workflow that detected and fixed that for me, rather than me having to write yet another one-off script to clean up yet another silly mis-configured system that Bob left as a dumping ground that I have to write yet more brittle bizarre-situation-handling code for.
Same for SQL and data. "Look, Mr. Database, I don't actually know what's in the table today, and I don't know why the previous user dumped a million unrelated rows in the table.... Can you answer my query about if my package has shipped, or not?"
Obviously you can create declarative idioms in Python
This looks like infinity times better than Ansible in some cases and somewhat worse in others (python.call every time I'd need to access a previous operation's result feels clunky, though I certainly understand why it works that way).
Do you think it would be possible to use Ansible modules as pyinfra operations? As in, for example:
- name: install foo
apt:
pkg: foo
state: present
could be available as: from pyinfra import ansible
ansible(name='install foo').apt(pkg='foo', state='present')
where the `ansible` function itself would know nothing about apt, just forward everything to the Ansible module.Note 1: I know pyinfra has a way to interface with apt, this is just an example :) Note 2: It's just my curiosity, my sysadmin days are long gone now.
Alternatively you could just yield ansible cli and execute from the local machine using the @local connector.
DISCOVERING the available ansible actions is the JFC since, like all good things python, it depends on what's currently on the PYTHONPATH and what makes writing or using any such language-server some onoz
And this wasn't what you asked, but ansible has a dedicated library for exec, since the normal `ansible` and `ansible-playbook` CLIs are really, really oriented toward interactive use: https://github.com/ansible/ansible-runner#readme
The way TF and Pulumi traditionally think about this problem would be to use cloud-init/ignition/Cloudformation Hooks to cause the machine to execute scripts upon itself. Ansible also has an approach do that via "ansible-pull" which one would use in a circumstance where the machine has no sshd nor SSM agent upon it but you still want some complex configuration management applied post-boot (or, actually even if they do have sshd/ssm but there are literally a hundred of them, since the machines doing the same operation to themselves is going to be much less error prone than trying to connect to each one of them and executing the same operations, regardless of the concurrency of any such CM tool)
> pyinfra now executes operations at runtime, rather than pre-generating commands. Although the change isn't noticeable this fixes an entire class of bugs and confusion. See the limitations section in the v2 docs. All of those issues are now a thing of the past.
https://github.com/pyinfra-dev/pyinfra/blob/3.x/CHANGELOG.md
I also hang out on the Matrix room: https://matrix.to/#/#pyinfra:matrix.org
Another thing: the GH repo points at currently in beta v3 and the docs for this are here: https://docs.pyinfra.com/en/next (highly recommend starting with v3, I just haven't had any time recently to wrap up the release, but it's stable).