I’m interested in the design of the CM tools as well as their practical application. As a software architect I can’t help analyzing their design. One element of their design I find especially interesting is their programming paradigm choice. The tools seem to fall into two camps: declarative or imperative. Roughly speaking, Ansible and Puppet use a declarative format, while Chef uses more of an imperative one.
I think both have their merits and I find it difficult to decide which one I prefer. Ideally, I would prefer a declarative approach that provided a comprehensive specification of the system’s state. I want that specification to be based on a closed world assumption (by which I mean that if it isn’t in the specification it isn’t on the system.) A CM tool built on this assumption would not require statements like this from Ansible:
# Remove the user 'johnd' - user: name=johnd state=absent remove=yes
Puppet has similar statements. Having actions in your language means it is no longer stateful.The declarative CM tools therefore are quasi-stateful: sometimes you can use state expressions and sometimes you have to use imperative ones. Idemopotency wouldn’t be needed in a stateful specification, it’s only needed because these tools also rely on imperative statements.
Another problem is that a truly stateful declarative language would encompass the operating system and all applications, making it very large. This specification language would have to cover operating system objects like user accounts and low level application details such as DNS MX records. Ansible tries to augment its core language with modules, Puppet with additional resources. These additions, however, while often very well done (see their mySQL modules for example) seldom allow you to manage all an application’s details.
The alternative to primarily declarative approach is to follow a more imperative one. This is basically what Chef does. Chef uses a mix of normal Ruby and a custom syntax (Chef’s “DSL”). An imperative approach is nice because it is conceptually simple and it gives users the power to handle complex tasks. Good, but why use a DSL? As I learn Chef, I can’t see the point of a DSL. Why not just use plain old object oriented programming (OOP)?
The Chef interface could be a library of system objects similar to other Ruby libraries. For example:
Not perfect examples I realize but I hope they illustrate my point. Normal OOP techniques seem perfectly suited to the task of systems administration. The method calls could still be idempotent using some internal logic. Perhaps it will become obvious why a DSL was needed as I understand Chef better.
I suspect, though, that the main reason was because they thought system administrators would find the DSL syntax easier to learn than standard Ruby. I think that may underestimate administrators but Chef could be right. In the long run, though, administrators will have to become programmers anyway so the DSL only serves a transitional purpose.