Role names in collections should be able to begin with a underscore

According to the Collection structure chapter of the docs:

Role names are now limited to contain only lowercase alphanumeric characters, plus _ and start with an alpha character.

However, I’ve observed that role names can start with an underscore _ character without causing any issues. I have tried this with Ansible versions 2.9, 2.10, 2.11, 2.12, 2.13, 2.14, 2,15, 2.16 and 2.17, and none of them had problems consuming a role with such a name. Ansible Galaxy also imports the role without any issues.

I understand that a role name like this might not be aesthetically pleasing, but there is a good reason for it. We recently added an Ansible role for common shared tasks to the prometheus ansible collection. The role is named _common (fqcn prometheus.prometheus._common) to distinguish it from the other roles in the collection, as it is an internal role that’s only supposed to be used by the other roles and not directly by a user.

Originally posted this as a issue at Role names should be able to begin with a underscore. · Issue #2035 · ansible/ansible-documentation · GitHub but posting it here as well by the suggestion of @samccann

1 Like

You’re not the only one using roles whose name starts with an underscore, community.sops has an internal role as well: community.sops/roles at main · ansible-collections/community.sops · GitHub

(Leading underscores used to indicate deprecated content in Ansible 2.9 and before, still do in ansible-core’s internal collection ansible.builtin I think, and for some time ansible-core’s tooling couldn’t decide what a leading underscore meant in a collection. (Now it’s just a regular part of the name.) I proposed at some point to use a leading underscore in collections for internal names that shouldn’t be exposed to the user, but that proposal was rejected, though it’s still often used as an indicator that something is private, since there is no other way except writing in the docs DON'T USE THIS! or something like that… But that all was for plugins, not roles; I think for roles the leading underscore was always OK, except maybe for ansible-doc, which might have also mishandled it for roles in collections. In any case, that should be fine now for some years, or so.)

1 Like

Personally I think private things should be put in a reasonably named subdirectory ( prometheus.prometheus.internal.common), instead of using an unintuitive convention.

3 Likes

@nitzmahone - as the current SC core rep - any opinions on this one? Should we change the docs to say _rolename is allowed and that some collections use this to indicate roles for internal-use only etc?

I’ve said before that Ansible feels more like a bizarre extension of Python than an application written in Python. The convention in Python is that leading underscores on identifiers indicate some unenforced local scoping. That convention seems to be exactly what we’re talking about for role names.

The document is demonstrably wrong. It’s a lot easier to change a document to match reality than it is to change code to match a document, even if the document reflected “the plan” at the time it was written. In practice, the recommendation is that all identifiers should be lower-case (although why that is has never been justified as far as I know), and that identifiers should not start with a digit. I haven’t tried making collection role names or any other identifiers that start with a digit, but I don’t expect it to work - mostly because the parser probably breaks. If so, that no-leading-digits thing is a de facto enforced prohibition, in contrast to the other bits.

If you think of “_” as the lowest of the lower-case r'\w' characters, all the guideline really says is that role names can’t start with a digit. I would be happy with the documentation making distinctions among recommendations, conventions, best practices, and prohibitions. If it’s the latter, and code doesn’t enforce it, that’s a bug.

The restriction in ansible-core is that content use valid Python identifiers (which is a technical limitation caused by how collection content is loaded) ; all other restrictions are arbitrary and enforced (if at all) at other points.

TASK [flowerysong.test._한글 : debug] ******************************************
ok: [localhost] => 
    msg: Hello world!

Galaxy will refuse to import some content that the engine itself is perfectly fine with, but I think that this documentation does not correctly reflect those restrictions either.

I think the documentation should reflect the behavior and include _ as a valid starting character, but not recommend the Pythonism.

We’ve made choices in the past to stick with documentation that doesn’t match the current implementation as the documentation reflects what is ‘supported’. I can’t think of the most recent case of this, but in general, just because it ‘works’ today, doesn’t mean it’s supported by the core team. Some future code change could start to enforce this. Which is why we’d need core approval to change the docs here.

Ansible is not a Python extension, it was never meant to be, yes it is coded in python and most plugins are also written in Python, but you should not need to know Python or it’s conventions to use Ansible (though knowing does help you a lot). For example, you can writes modules in any language (scripted or compiled) and they should 'just work’TM as long as they handle the expected outputs and inputs.

I don’t think it can be removed from ansible-core without a deprecation cycle. ansible-core tries to preserve backwards compatibility for lots of things that aren’t recommended per se. To me, this just seems like an oversight in the docs change documenting the feature. standalone role naming is totally different, so I see that bit of documentation as a way to help deter from some of those patterns that will no longer work (like having - in a role name).

I was a bit surprised that this actually works pretty well. ansible-playbook (and thus ansible-test), galaxy-importer, and ansible-lint are fine with this. ansible-doc isn’t, it will neither list such roles nor show docs if you provide the role’s FQCN. But for internal roles I guess that’s ok.

I personally don’t care whether private stuff has a leading underscore in its name or is stored in some subdirectory, as long as there is a proper way to mark it private that’s respected by the most important tooling. Which right now unfortunately does not exist.

1 Like

Just to clarify, although it would be helpful, I am not proposing that the leading underscore would mark the role as private/internal but simply to correct the information in the docs, which is clearly false.

It’s sufficient for me that I am able to implement a few measures in my role to prevent unintended or accidental direct use of the role, such as not having a main entry point and including assert tasks such as:

- name: "Validate invocation of _common role"
  ansible.builtin.assert:
    that:
      - "ansible_parent_role_names is defined"
      - "ansible_parent_role_names | default() | length > 0"
      - "ansible_collection_name == 'prometheus.prometheus'"
    fail_msg: "Error: The '_common' role is a internal role and cannot be invoked directly."
  tags:
    - always

Sure it’s not a guarantee that you won’t be able to invoke the role if you are determined to do so, but I’m ok with that.