Background: What are Tags and How Do I Set and Edit Them
Tags in OCI are a powerful way to organize and find resources, provide access control, and are just generally neato. If you haven't used tags before the docs provide a few good examples of how you might use them. And there are a bunch of default / suggested tags to help get you started.
If you forget to tag your resouces when you create them or if you need to change them later it's easy enough to do it one at time. To change the tags on something you simply navigate to the object, click on the Tags tab, and then click on the pencil. Or to add a tag just click on More Actions and click Add Tag.
For example here's a compute instance:

Similarly from the CLI you can edit the tags on a compute instance via "oci compute instance update", though it's not quite as easy as using the web.
Those are fine for making what I call "onesy twosey" changes to a small number of resources. But if you want to update the tags on a large number of resources clicking around in a UI to do it is too time consuming and error prone for my liking.
And since it's something that users need to do (at least sometimes) OCI includes an IAM API called BulkEditTags for that purpose.
This is exposed in the OCI Console within Tenancy Explorer - just locate the objects you want to update, click actions, and then Manage Tags.

If using the UI isn't convenient and you prefer a CLI there's a command built in for you - "oci iam tag bulk-edit"
Introducing "oci iam tag bulk-edit"

The command shows you the required and missing arguments:
- compartment ID
the API only operates on resources in a single compartment so this is how you specify that here
- resources
this is a list of resources including their OCID and their type (more on that later), in JSON format
- bulk-edit-operations
this is literally just the list of changes you want to make (additions, replacements, deletions, etc), again in JSON format
As always with the OCI CLI, adding the "--help" argument gets you more detailed help:
Copied to Clipboard
Error: Could not Copy
Copied to Clipboard
Error: Could not Copy
DESCRIPTION
Edits the specified list of tag key definitions for the selected resources. This operation triggers a process
that edits the tags on all selected resources. The possible actions are:
• Add a defined tag when the tag does not already exist on the resource. * Update the value for a defined
tag when the tag is present on the resource. * Add a defined tag when it does not already exist on the
resource or update the value for a defined tag when the tag is present on the resource. * Remove a defined
tag from a resource. The tag is removed from the resource regardless of the tag value.
See BulkEditOperationDetails
<https://docs.cloud.oracle.com/api/#/en/identity/latest/datatypes/BulkEditOperationDetails> for more information.
The edits can include a combination of operations and tag sets. However, multiple operations cannot apply to one
key definition in the same request. For example, if one request adds tag set-1 to a resource and sets a tag value
to tag set-2, tag set-1 and tag set-2 cannot have any common tag definitions.
USAGE
oci iam tag bulk-edit [OPTIONS]
Perfect!
What's missing in these docs is a concrete example and step by step directions you can copy/paste to go get your work done.
Hence this post
The Inputs
Putting the compartment ID aside, the two inputs to the command are expected to be JSON. The CLI happily gives you example inputs - though you'll probably need a bit of explaining to understand the examples.
bulk-edit-operations
bulk-edit-operations tells OCI what changes you want made.
Copied to Clipboard Error: Could not Copy Copied to Clipboard Error: Could not Copy $ oci iam tag bulk-edit --compartment-id $MYOCICOMPARTMENT --generate-param-json-input bulk-edit-operations
[
{
"definedTags": {
"tagNamespace1": {
"tagKey1": "tagValue1",
"tagKey2": "tagValue2"
},
"tagNamespace2": {
"tagKey1": "tagValue1",
"tagKey2": "tagValue2"
}
},
"operationType": "string"
},
{
"definedTags": {
"tagNamespace1": {
"tagKey1": "tagValue1",
"tagKey2": "tagValue2"
},
"tagNamespace2": {
"tagKey1": "tagValue1",
"tagKey2": "tagValue2"
}
},
"operationType": "string"
}
]
|
Basically you say "make this change to these defined tags". And you can include multiple changes in a single input.
I don't love the docs on this (they were definitely written by a programmer) so here's a table I made instead:
I want to |
operationType to use |
What doc says |
Delete any existing value |
REMOVE |
removes the defined tag from the resource. The tag is removed from the resource regardless of the tag value |
Set the value
no matter what is or isn't there already |
ADD_OR_SET |
...add a defined tag if it does not already exist on the resource or update the value for a defined tag only if the tag is present on the resource. |
Add a value
but don't overwrite anything already there |
ADD_WHERE_ABSENT |
adds a defined tag only if the tag does not already exist on the resource |
Replace any existing value
but don't add it if there isn't one there |
SET_WHERE_PRESENT |
updates the value for a defined tag only if the tag is present on the resource |
I suspect that 99% of the time you'll want the first two (REMOVE or ADD_OR_SET). The other two are nice to have but in my experience are definitely less frequently used.
For the purposes of this example let's throw together one change - setting the Oracle-Standard.OwnerEmail tag to contain my email address. And let's put it in a file called operations.json so we can reuse it later if we want
That would look like this:
Copied to Clipboard Error: Could not Copy Copied to Clipboard Error: Could not Copy [
{
"defined-tags": {
"Oracle-Standard": {
"OwnerEmail": "christopher.johnson@oracle.com"
}
},
"operationType": "ADD_OR_SET"
}
]
|
resources
Resources is a little harder to explain. First here is the example from the command line help:
Copied to Clipboard Error: Could not Copy Copied to Clipboard Error: Could not Copy $ oci iam tag bulk-edit --compartment-id $MYOCICOMPARTMENT --generate-param-json-input resources
[
{
"id": "string",
"metadata": {
"string1": "string",
"string2": "string"
},
"resourceType": "string"
},
{
"id": "string",
"metadata": {
"string1": "string",
"string2": "string"
},
"resourceType": "string"
}
]
|
The "id" is fairly straightforward - it's just the OCID of the resource.
The resourceType is a CamelCase string identifying the type of the resource. You can figure this out by hand by looking through the list of supported resource types (via "oci iam tag bulk-edit-tags-resource-type list"). OR you can cheat like I do and use Resource Search to find it for you.
For example when I want to tag all of my compute instancs I use search to find one:
Copied to Clipboard
Error: Could not Copy
Copied to Clipboard
Error: Could not Copy
$ oci search resource structured-search --query-text 'query all resources where identifier="ocid1.instance.oc1.iad.XXX"'
{
"data": {
"items": [
{
"additional-details": {},
...
"identifier": "ocid1.instance.oc1.iad.XXX",
"identity-context": {},
"lifecycle-state": "STOPPED",
"resource-type": "Instance",
...
}
]
}
}
The "resource-type" "Instance" is what I need. If I want to tag my compute instances I construct the JSON like so:
Copied to Clipboard Error: Could not Copy Copied to Clipboard Error: Could not Copy [
{
"id": "ocid1.instance.oc1.iad.XXX",
"resourceType": "Instance"
},
{
"id": "ocid1.instance.oc1.iad.YYY",
"resourceType": "Instance"
},
{
"id": "ocid1.instance.oc1.iad.ZZZ",
"resourceType": "Instance"
}
]
|
You can actually make this (and put it in a file) with a little clever use of the OCI CLI and jq... like so:
Copied to Clipboard
Error: Could not Copy
Copied to Clipboard
Error: Could not Copy
oci search resource structured-search --query-text 'query Instance resources where compartmentID="ocid1.compartment.oc1..XXXXX"' | jq '[.data.items[]|{"id":.identifier,"resourceType":."resource-type"}]' > instances.json
NOTE: Remember that a bulk update can only operate on resources in a single compartment. So you can't use this sort of thing to update all of the compute instances in your tenancy. At least not directly.
I should probably break that down in detail, but basically I'm using the CLI to search for all compute instances in a specific compartment. Then I use JQ to take the output from that, remap the fields to the ones the API/CLI needs, and spit them out in a JSON array. And finally I dump the entire thing in a file.

In this case I'm just doing Instances, but you can update multiple kinds of resources in the same action. Up to 100 resources of any supported type can be done in a single request.
Actually, Finally Running the Command
Now that we have the 3 things we can actually run the command and pass it those inputs.
To do that we run the same command as above and provide:
- the compartment ID as a direct argument
- the resources via a file - by saying
--resources file://./instances.json
where instances.json is the file I created above
- the operations similarly
--bulk-edit-operations file://./operations.json
Like so:
Copied to Clipboard Error: Could not Copy Copied to Clipboard Error: Could not Copy $ oci iam tag bulk-edit --compartment-id $MYOCICOMPARTMENT --resources file://./instances.json --bulk-edit-operations file://./operations.json
{
"opc-work-request-id": "ocid1.taggingworkrequest.oc1..aaaaaaaa5wnbfmglr63rzlfbxpn3vrml2erh6zpixsgcxyxgtor6ziivixla"
}
|
The output is a work request which is processed asynchronously. We can see how it's going by running "bulk oci iam tagging-work-request get --work-request-id" with that work request ID.
If you do it right away you'll see status = ACCEPTED:
Copied to Clipboard Error: Could not Copy Copied to Clipboard Error: Could not Copy $ oci iam tagging-work-request get --work-request-id ocid1.taggingworkrequest.oc1..aaaaaaaa5wnbfmglr63rzlfbxpn3vrml2erh6zpixsgcxyxgtor6ziivixla
{
"data": {
"compartment-id": "ocid1.compartment.oc1..aaaaaaaaxwhjumhezttjkkrcxzg4x3a2gzuaa5ebbnyuu6zhqijty4arv7ma",
"id": "ocid1.taggingworkrequest.oc1..aaaaaaaa5wnbfmglr63rzlfbxpn3vrml2erh6zpixsgcxyxgtor6ziivixla",
"operation-type": "BULK_EDIT_OF_TAGS",
"percent-complete": 0.0,
"resources": [],
"status": "ACCEPTED",
"time-accepted": "2024-06-18T15:28:52.634000+00:00",
"time-finished": null,
"time-started": null
}
}
|
If you wait a bit you can see the changes in progress and then later the end result:
Copied to Clipboard Error: Could not Copy Copied to Clipboard Error: Could not Copy $ oci iam tagging-work-request get --work-request-id ocid1.taggingworkrequest.oc1..aaaaaaaa5wnbfmglr63rzlfbxpn3vrml2erh6zpixsgcxyxgtor6ziivixla
{
"data": {
"compartment-id": "ocid1.compartment.oc1..aaaaaaaaxwhjumhezttjkkrcxzg4x3a2gzuaa5ebbnyuu6zhqijty4arv7ma",
"id": "ocid1.taggingworkrequest.oc1..aaaaaaaa5wnbfmglr63rzlfbxpn3vrml2erh6zpixsgcxyxgtor6ziivixla",
"operation-type": "BULK_EDIT_OF_TAGS",
"percent-complete": 100.0,
"resources": [
{
"action-type": "UPDATED",
"entity-type": "instances",
"entity-uri": "https://iaas.us-ashburn-1.oraclecloud.com/20160918/instances/ocid1.instance.oc1.iad.XXX",
"identifier": "ocid1.instance.oc1.iad.XXX"
},
{
"action-type": "UPDATED",
"entity-type": "instances",
"entity-uri": "https://iaas.us-ashburn-1.oraclecloud.com/20160918/instances/ocid1.instance.oc1.iad.YYY",
"identifier": "ocid1.instance.oc1.iad.YYY"
},
{
"action-type": "UPDATED",
"entity-type": "instances",
"entity-uri": "https://iaas.us-ashburn-1.oraclecloud.com/20160918/instances/ocid1.instance.oc1.iad.ZZZ",
"identifier": "ocid1.instance.oc1.iad.ZZZ"
}
],
"status": "SUCCEEDED",
"time-accepted": "2024-06-18T15:28:52.634000+00:00",
"time-finished": "2024-06-18T15:29:48.317000+00:00",
"time-started": "2024-06-18T15:29:48.272000+00:00"
}
}
|
As they say - Easy Peasy Lemon Squeezy!