Hi,
Once loaded, a playbook is completely static (includes etc. are evaluated while loading, not while executing). This has its advantages but also makes includes special, e.g.
* they don't handle tags or delegate_to
* not sure what they do with sudo/sudo_user from reading the code (silently ignored unless I'm missing something?)
* with_* handling is unintuitive (you can't say with_items: $groups.foo due to very eager evaluation of include/with_*)
At the very least, we'd like to extend the include directive to push down tags, sudo/sudo_user, delegate_to, notify to individual tasks in the file, but for a more flexible approach, how about we introduced some form of lazy evaluation/dynamic playbooks? ("we introduced" meaning "I'll submit a patch once we agree on a design")
An action plugin could optionally return a list of dicts describing additional tasks, just like extra facts right now. The tasks would be then fed back into the playbook loop by e.g. PlayBook._run_task calling itself recursively. Note that this has nothing to do with includes and YAML parsing and is very generic. Extra tasks could be loaded from a database, generated on the fly etc.
A major issue is security. This feature would open up ways to influence ansible-playbook's actions from outside the playbook YAML file. Given a vulnerable module _and_ a compromised host in inventory, the master could be tricked into executing arbitrary modules. I'd suggest this feature be available only to action_plugins using a class attribute, similar to BYPASS_HOST_LOOP ("normal" not being one of these whitelisted plugins).
To fully implement our lazy_includes we'd need some code from Play/PlayBook classes copied or factored out to a helper class but that could remain our in-house mess. We'd also need a way to notify the loader module what tags are white/blacklisted if we want tags in lazy-loaded plays to just work.
Yes, it's a major change in ansible's behaviour but right now I think that the static nature of playbooks lead to unexpected behaviour similar to e.g. nginx and its if{} blocks. They look like procedural config (evaluated top-down) but are strictly declarative and evaluated outside in (top level, then ifs, then nested ifs...). This led to endless confusion and "if" is now deprecated in nginx.
Or, if letting tasks return more tasks is too drastic (despite being 100% backwards-compatible), maybe pushing the loading process to plugins is a way to go?
Best regards,
Grzegorz Nosek