Hey all, I'm the creator/primary maintainer of pyinfra! Super excited (a little terrified) to see this on the frontpage, happy to answer any questions :)
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).
Is it declarative? Obviously python isn't, but since it's not executed as a script but rather the module passed to pyinfra, it could be, and looks like maybe it is just registering work to (potentially) do on module load?
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.
I never understood the desire to make things declarative. It seemed to me to always hide what is actually happening and it made it more difficult to understand. Is there a simple way to understand why declarative stuff is desirable to some people?
I think the main reason people like the declarative approach is that done right, it's idempotent. You also don't have to think about the current state of the system at all. You just need to describe what you want it to look like. Of course in practice it can be more nuanced than that, but thinking declaratively can make things much simpler in some scenarios.
These aren't as related as you think. Ansible is imperative and idempotent.
Ansibles' resources are declarative [0]. What part of Ansible is imperative?
https://docs.ansible.com/ansible/latest/reference_appendices...
Ansible playbooks are usually a list of steps to execute in order (imperative). Those steps may try to present a declarative interface to what they are supposed to do, but many fail to fulfill the definition of "declarative" that you have linked. E.g. with the built-in modules it is impossible to declare a desired set of installed packages, only a set of packages to be installed _in addition to all already installed packages_. This means it is impossible to remove an installed package again by removing it from the declaration, you have to specify a second step (imperative) that explicitly removes the package. This makes it impossible to declare a final state for "installed packages" with ansible.
This is debating state-management though, which Ansible makes the correct choice about: Ansible largely works the way a user expects when they transition to it from doing things on the command line, and guides them towards idempotency (which is a pre-requisite for declarative configuration).
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?)
Yes, this is debating state management. For full declarativity some form of state management for the parts of the system that should be under declarative control (like terraform) or a stateless but very holistic view of the system (like NixOS, I guess also Guix System) are needed.
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.
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).
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.
Yes. At least I want to put something like "core-packages" or "default" or similar as part of setting my explicit intent.
> Ansible playbooks are usually a list of steps to execute in order (imperative)
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.
I agree with you, your declarative abstraction has to have an imperative implementation underneath that will do all the dirty work. Ansible presents this declarative interface at the module level (if the module is implemented properly, most aren't), and a playbook is an imperative list of declarations to be applied. Roles also combine a list of imperative steps into a declarative interface.
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.
Ansible is only as idempotent as the module it calls though.
It’s the cattle not pets mindset. In most organizations the sysadmin team is really undersized. Not uncommon to have one admin per several hundred systems. In such places, there is no time to care for individual servers. If a server is misbehaving we blow it away and spin up a clean replacement.
Declarative scripts make it easy to manage a fleet.
I think that's.. perhaps not orthogonal, but has some orthogonal component - you could certainly have something like:
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:
comes much more naturally.This fails completely even at small scale when the script is interrupted before finishing.
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.
Eh, any way you do it could leave it in an unfinished state if interrupted, I'm not too bothered about that. (But it does sound like you think I was speaking in favour of doing it in a procedural python script sort of way? I was not.)
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.
They work together to produce immutable cattle. The alternative is managing a pool of servers where you are doing things like in-place patch upgrades, vs a teardown of the old infra and replacing it with the newly baked servers.
I'm well aware, I just don't see what 'use Packer' has to do with choice of programming paradigm for Terraform or other tool in that role.
It makes people feel that they're smarter than you. See also functional programming. That said sometimes it's useful as s way to auto generate imperative actions.
I really can't see how you could feel that way about it after spending even just a few minutes (which it sounds like you have) to understand what it means beyond just reacting to terminology, something having a name.
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.)
Because what most people want is actually something closer to "Goal Seeking." If the system works as intended (and as you point out with the need to debug is often does not!) then defining the desired end-state and letting the system figure out how to get there, is a simpler, higher order abstraction. And it can also often be clearer to just say "ensure these prerequisites are met" such that alternative implementations can achieve the same outcome. In practice, abstractions are leaky.
Have you been introduced to functional programming? It's excellent and mind-bending at first. Here's an overview: https://github.com/readme/guides/functional-programming-basi...
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.
It lets you separate "here is the final state of the system that I want" from "how to get there".
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?"
Conceptually I think it’s much nicer to define the state of the system rather than the steps to get there, and tool of choice figures it out.
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.
Yes... and no. It depends on the operation (the docs explicitly state if an operation is _not_ idempotent "stateless operation"). Operations are either:
- 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.
Ah, so this is similar to the Terraform CDK approach?
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.
Interesting, not heard of CDK before! Kind of similar? As long as the language is Python I suppose! Would be possible to integrate with other languages too I guess, not something I’ve ever looked into though.
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).
Relevant XKCD: https://xkcd.com/303/
Obviously you can create declarative idioms in Python
Sure, if you keep reading I described one that it looked like this might be doing.
As you can see here, the main question is what are the advantages over Ansible, a mature and the most popular agentless configuration management tool written in Python. So I propose putting this answer right to the landing page
I think I tried to shy away from specifically being "Ansible does this bad so pyinfra does this" and instead focus on the features that differentiate like "Instant debugging with realtime stdin/stdout/stderr output (-vvv).". But it seems like that isn't enough and the landing page needs to be more explicit in comparison. Ty for the feedback!
I like Ansible, but that doesn't mean thera are no pain points.
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.
Ansible is declarative?
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!
You're confusing declarative with idempotent. Ansible is both, it won't change anything if the state is already what you declared. The nitpicked case you chose, you want the file to have the latest timestamp, this is a valid state to declare.
The shell task breaks the declarative nature a bit, along with registering task results and then writing conditional whens based on them. Interpolating values based on the registered results does too imho
Plus, what your are declaring is often times not the state you desire but... action you want to take. Say you use `apt.name: [pkg1, pkg2]`, run it, then remove `pkg2` from the list. Running this again won't remove `pkg2` from your system. So it's declarative, but not necessarily on the optimal level all the times.
Ansible tries to be declarative and idempotent, but fails at being both.
ansible-core succeeds pretty well in being both.
ansible modules (built-in and community) are a different matter. generally, quality will vary.
Ansible is neither.
You can use Rsync to to a puppet created folder You’d need to have a source folder with the file which is fine
There's a lot of cases where Puppet fails on something similar. Packages is probably the easiest example. Puppet won't remove all package not explicitly declared, nor will it remove files it created, if you remove the Puppet code for managing that file.
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.
There's definitely a trend in this direction.
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
Is performance better than Ansible? I have used Ansible extensively and find it excruciatingly slow.
Excruciatingly slow is an understatement :)
Why being slow is a bad thing? Ansible gives me a legitimate excuse to have proper lunch. ;-)
You're supposed to be writing compilers during that time
Yes! https://docs.pyinfra.com/en/next/performance.html
Hi @Fizzadar and congrats on making this and getting it out the door; kudos!
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 applaud trying to be positive and focus on "this is what we do well," but yeah at least some explicit comparison would help. The copy right now is kind of assuming the reader already knows Ansible to compare against as a baseline. Which is probably fair for most people who find your project, but people who find your project are also probably not happy with Ansible and want to know if this addresses their pain points immediately.
Is very interesting though, I think I'm gonna try it myself.
That would be appreciated. I saw the homepage and my first thought was "Ansible is python. How are these things different?" Obviously pure python vs yaml is one thing. But beyond that it's not clear. Perhaps are specific use cases in your mind where one or the other is a better fit, and that would be helpful as well.
[yet another reference to Ansible, sorry! :)]
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:
could be available as: 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.
Definitely possible! Not familiar with the ansible Python API so partially guessing but the pyinfra op could yield a callback function that then calls ansible at execution time.
Alternatively you could just yield ansible cli and execute from the local machine using the @local connector.
FWIW, ansible modules (all of them, to the best of my knowledge) operate via a stdin/stdout contract since that's the one universal api for "do this thing over (ssh|docker|ssm|local)". That's also why it supports writing plugins in any language (shell, compiled, python, etc) since `subprocess.Popen().communicate(b'{"do_awesome":true}')` works great
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
What is different in v3? Didn't see it in the "Next" docs.
Mostly this from the 3.x changelog:
https://github.com/pyinfra-dev/pyinfra/blob/3.x/CHANGELOG.md
How does it compare with pulumi?
Almost exactly as it compares to terraform, since both TF and Pulumi only get down into the shell of any provsioned virtual machine via "connect and run some shell, good luck". I'd guess it would also be horrifically painful to even do that in circumstances such as Auto Scaling Groups, where even TF and Pulumi don't know the actual IP or InstanceIds
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)
How does it compare to Fabric? At first glance it looks quite similar. All our scripts are written in Fabric, but Fabric appears to be somewhat abandoned and the latest version never reached full parity with v1. I'd be looking to try something new next time.
Oh man this is really cool. I have also written a Python infrastructure-as-code project (https://pages.micahrl.com/progfiguration/), I really like the idea of using a programming language rather than a text document to define infrastructure. Yours looks very polished, and the built in support for testing in Docker is a brilliant idea.
dang this exploded. I came across the project this morning when I was looking at a blog on how to implement a generic programming language to become a configuration language and it mentioned pyinfra. Glad this project is getting some exposure. :)