Nearly every resource in Oracle Cloud Infrastructure can be tagged. Either at creation time or later. And you set/edit/remove those tags via the console, the CLI, or through the APIs.
Read about what/why/how here:
In my blog post above I mention that the bulk API isn't super convenient as it requires JSON inputs and only works on one compartment. This tool simplifies things quite a bit.
It lets you:
- operate on resources anywhere in the tenancy
- specify an update to be made to their tags
- see the changes that would be done (i.e. dry run)
- and optionally, actually make those changes
If you are running it on your laptop, have a config file in the default location, and have a [DEFAULT] section for your tenancy then the only arguments you need to provide are query, action, tag, and value.
$ ./ott.py query action tagnamespace.tagname value
Breaking that down:
- ./ott.py
- query - an OCI search query
- action - "set" or "remove"
- tagnamespace - a defined tag namespace. For example Oracle-Standard
- tagname - a defined tag name. For example OwnerEmail
- if you are setting a value the value
Example 1: add an Expires tag
To add a tag for Usage-Management.Expires to every resource in every region you could do:
$ ./ott.py 'query all resources' add Usage-Management.Expires 2026-07-01
Example 2: overwrite the existing Expires tag
The add action above won't overwrite any existing value, so any resource that already has that tag applied won't be changed to the new value. If you wanted to set the value of a tag, overwriting any existing value then you can use the set action:
$ ./ott.py 'query all resources' set Usage-Management.Expires 2026-07-01
Example 3: Use a more interesting query 1
Rather than inventing a new syntax OTT uses the existing OCI Search (sometimes called Resource Query Service or just RQS) syntax.
So anything you can use there works here too. Like the compartment ID:
$ ./ott.py -n 'query Instance resources where compartmentId="ocid1.compartment.oc1..XXX"' set Usage-Management.Owner christopher.johnson@oracle.com
NOTE: remember that the above will tag everything in the specified compartment but will NOT cascade down to compartments inside that compartment!
Example 4: Use a more interesting query 2
Search is very powerful and, in the right hands, you can do very clever things. For example if someone misspelled by email address you can use search to find the mistakes and have OTT fix them:
$ ./ott.py -n 'query all resources where (definedTags.namespace = "Usage-Management" && definedTags.key = "Owner" && definedTags.value = "chistopher.johnson@oracle.com")' set Usage-Management.Owner christopher.johnson@oracle.com
See below for additional useful and interesting queries
This tool uses similar command line arguments as many other OCI tools, so it should be familiar to many users.
▶ ./ott.py --help
2026-06-02 10:17:12,483 MainThread INFO ott:<module> -> Parsing command line and configuring...
usage: ott [-h] [-cf CONFIG_FILE] [-cp CONFIG_PROFILE] [-l LOG_FILE] [-d] [-n] [-w] [-rg REGIONS] query {set,add,delete} tag [tag ...]
The OCI Tagging Tool
positional arguments:
query
{set,add,delete}
tag
options:
-h, --help show this help message and exit
-cf CONFIG_FILE OCI Config file
-cp CONFIG_PROFILE Config Profile inside the config file
-l LOG_FILE, -log LOG_FILE
output log file
-d, -debug Enable debug
-n, --dry-run Dry run - do not actually make the specified changes
-w, --wait Wait for Work Requests to complete (success or failure)
-rg REGIONS, --region REGIONS
Comma list of regions separated (defaults to all subscribed regions)
Written by Chris Johnson | https://www.github.com/therealcmj | https://blogs.oracle.com/authors/christopher-johnson
| Option | Required | Meaning |
|---|---|---|
| -h | No | Show help |
| -cf CONFIGFILE | No | Specify the OCI config file |
| -cp PROFILE | No | Specify the Profile in the OCI config file |
| -l LOG_File | No | If you want a log file instead of just stdout |
| -d | No | Enable debug logging - useful for me. Maybe not for you |
| -w | No | Wait for the work requests to complete before exiting |
| -rg | No | OTT works one region at a time. If you want to work in multiple you may do that here |
| query | Yes | An OCI Resource Search query (see below) |
| action | Yes | The action to take - "add", "set" or "delete" |
| tag | Yes | the tag to update - specified as Namespace.Key |
| value | Yes | the value to set the key to |
Remember that whatever you specify for query needs to be quoted. And if "value" contains a string you need to quote that too. So it's probably best to just always quote both. That means something like:
./ott.py 'query Instance resources where compartmentId="ocid1.compartment.oc1..XXX"' set Chris.MyTag "The value I want"
On a Unix or Unix-like system (e.g. Linux, Cloud Shell, or on a Mac) what I showed above works. Or as in this example:
./ott.py 'query all resources where compartmentId="ocid1.compartment.oc1..XXX"' set Oracle-Standard.OwnerEmail christopher.johnson@oracle.com
Note how I use single quotes (AKA an apostrophe) to enclose the entire query. And then I can use regular quotes (i.e. ") inside there.
On Windows not so much. So you have to do something like this instead:
python ott.py "query all resources where compartmentId="""ocid1.compartment.oc1..XXX""" " set Oracle-Standard.OwnerEmail christopher.johnson@oracle.com
There's no way I could teach you the query syntax here. Check these docs for the full syntax reference. Or use the samples in Resource Explorer in the OCI console.
But here's a couple to get you started:
query instance resources where compartmentId="ocid1.compartment.oc1..XXX
query all resources where (definedTags.namespace = "Oracle-Standard" && definedTags.key = "OwnerEmail" && definedTags.value = "christopher.johnson@oracle.com"
e.g. so you can tag their owner
query all resources where compartmentId="ocid1.compartment.oc1..XXXX"
e.g. if you wanted to tag only one kinds of resource
query Compartment resources where compartmentId="ocid1.compartment.oc1..XXX"
query Vnic resources where additionalDetails.subnetId="ocid1.subnet.oc1.iad.XXX"
query IntegrationInstance resources return allAdditionalFields where integrationInstanceType="STANDARD"
query IntegrationInstance resources return allAdditionalFields where integrationInstanceType="STANDARDX"
e.g. so you can change the "OwnerEmail"
query all resources where (definedTags.namespace = "Oracle-Standard" && definedTags.key = "OwnerEmail" && definedTags.value != "christopher.johnson@oracle.com")
This tool was built for our own use but we find it quite useful. So I'm publishing it for others to benefit from.
These are the known issues
- OTT doesn't support on Freeform tags (because the Bulk Tagging API doesn't)
- We are working with engineering on roadmap for this
- If the bulk API won't include this in the near term I will likely enhance OTT
- some resource types that supposedly work don't
- if you find one please flag it for us and we'll get it fixed
- OTT relies on Search to have updated tag values
- Search is "eventually consistent" with the actual resources in OCI
- when making a large number of changes you should anticipate unnecessary updates or failures to update
As always, I'm happy to take contributions - just do the usual fork, branch, change, pull request, and I'll happily include them!
