I have started working on a collection for OCI (Oracle CLoud) platform.
During the design and planning I tackled an interesting point and I would be happy to consult this topic in this forum.
OCI doesn’t maintain unique resource names which means that a resource can be created over and over again with the same name and there is no issue with that. When a resource is created a resource/-id is being returned to the user. This ID can be used in order to update or delete that resource.
So full lifecycle scenario can be something like:
Create a resource using a name → fetch ID → call a task to update the resource using the resource id → delete the resource using the id.
This is reasonable and works fine. The issue I have with that and would be happy to discuss is that this approach breaks idempotency. A user calling the same creation task twice will result in the creation of 2 resources.
Is this approach makes sense as long as I document this behavior or do we expect it to be handled differently in such case?
You can always require the name to be unique from your side. In other words, you should test if the resource with the particular name already exists and use the existing resource to manage it instead of creating a new resource. You can even let the user choose the behavior.
In any case, documenting the behavior is essential.
I personally would go down the route of making the module/role idempotent even if it means workarounding the limitations of the OCI API or enforcing the uniqueness of names or other identifiers from your side. In the end, Ansible is all about the idempotency.
I thought I dealt with a similar issue awhile ago but I cant find it.
Generally I assume people will not read/miss the documentation. If a behavior seems really disruptive or risky, I would try to head it off in the module code and allow a user to “opt in” rather than “opt out”.
So id say, maybe raise an error if another resource with the same name existed and the ID was provided. And have a parameter so the user can ignore the error and create the object anyway?
I agree that is sensible approach. The problem for me here is that the user can still do things in the layer of the OCI UI or API and the ansible will not be synced with this behavior. There are info modules that can make it easier to handle but still, assuming one thing on ansible level while user can do something else on practical level is something I would want to avoid.
I think this is reasonable. It still breaks Idempotency on creation level but user will be aware of that.
So I think something about if user tries to create a resource by sending only a name (no ID) and resource already exists with this name, he will get a a warning and nothing will be created, if he will want to create it anyways I would expose an extra variable he can trigger and it will let him create multiple resources with the same name. I think its better having a warning rather then failing. Imagine a case which user has a playbook set. On the first run stuff will work but when he runs this playbook again it will fail because name already exists, it feels annoying to have states in the playbook itself. UX feels not natural, warning solves that.
Its better than letting him by mistake creating the resource with the same name especially in cases he will create compute instances and will have to pay for extra usage by mistake (or exhust his resources).
Add resource Id as idempotency key to create /update /delete module - if it is filled in (from info module) when create /change/ delete module behaves idempotently, if not when it is user’s decision.
If the user does supply the name but not the ID, you could simply act as follows:
If no object exists, create one (or say it’s already deleted on state=absent);
If one object exists, don’t create it (or delete it if state=absent);
If more than one object exists, fail and tell the user they need to provide the ID to distinguish them.
You probably want to make the behavior for 2. with state=present configurable though, so that users can also create more objects of the same name if they insist. But for 1. and 3. I think what I wrote above is the only thing that really makes sense.
Generally I think that for such kind of objects, OpenTOFU’s / Terraform’s approach is likely the better solution to manage them, i.e. to have a state file which stores IDs. That doesn’t really work well with Ansible’s general approach of not having a separate state file, though.
You can get it with info module, if it is empty, just use omit to omit the parameter
- name: Get resource id by name
oci.get_resource_id:
name: 'resource_name'
register: resource_id
- name: Create resource (idempotent)
oci.create_resource:
name: 'resource_name'
idempotency_key: "{{ resource_id.resource_id | default(omit) }}"
resource_params: ...
Ansible is capable of getting current state from actual infra, which is much more valuable from my point of view. One can always use inventory as a state file.
Yes this is what I’m planning to implement. But we are trying to cover the area in which resource is not there yet and we can’t use id. User will call the task with only a name. Now what happens when he calls the same playbook again? this is what I’m trying to resolve.
I think the approach that felix suggested makes sense and covers the edge cases. I will stick with this one
It’s only helpful if you can somehow uniquely identify things. Most cloud infrastructures don’t fit well into how this is traditionally done with Ansible.
Obviously, one can. But then you need to somehow update the inventory in case you create new objects. Ansible isn’t well equipped for that.
Then there’s obviously dynamic inventory plugins, for kind of this use-case, but also there you end up with the same problems as discussed in this thread.
I don’t think there is the best approach to this problem, but for cloud infra I think OpenTOFU’s approach is slightly better than the ones I’ve seen being used with Ansible.
(Combining names with labels and requiruing that a combination of these is unique might be one of the better approaches that work well with Ansible, but also that doesn’t help you in case someone creates two objects with the same set of name and labels - might it be due to manually using another tool to create these objects, or due to a bug/race condition in some Ansible module. In the end you have a situation where you have to resolve something manually. Obviously this can also happen with OpenTOFU’s state files…)
I am not familiar with GCP and OCI, but on Azure - group name and resource name is unique combination. On AWS ARN (Amazon Resource Name) is unuque globally, resource Id is unique for account and region and service scope.
At least on AWS, I don’t think you can create a new object with specified ARN. So there is no way to use the same identifier for creation and unique identification.