Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ These are example scripts that support the [Oracle Cloud Migrations](https://doc
| [SetIP.py](SetIP/README.md) | Manage the private IP address of the target Assets in a migration plan | Python | 11 Nov 2024 |
| [cbt.py](ChangeBlockTracking/README.md) | Check and configure Change Block Tracking for multiple VMs in vCenter | Python | 9 December 2024 |
| [create_custom_image.py](create_custom_image/Readme.md) | Create zero byte custom image used for windows migration | Python | 3 October 2025 |
|[target\_asset\_report.py](target_asset_report/README.md)|Create target asset report across migration projects / plans within a compartment.|Python|8 April 2026|
4 changes: 4 additions & 0 deletions Scripts/target_asset_report/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.xlsx
*.xls
__pycache__/
*.py[cod]
36 changes: 36 additions & 0 deletions Scripts/target_asset_report/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
Copyright (c) 2020, 2024, Oracle and/or its affiliates.

The Universal Permissive License (UPL), Version 1.0

Subject to the condition set forth below, permission is hereby granted to any
person obtaining a copy of this software, associated documentation and/or data
(collectively the "Software"), free of charge and under any and all copyright
rights in the Software, and any and all patent rights owned or freely
licensable by each licensor hereunder covering either (i) the unmodified
Software as contributed to or provided by such licensor, or (ii) the Larger
Works (as defined below), to deal in both

(a) the Software, and
(b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
one is included with the Software (each a “Larger Work” to which the Software
is contributed by such licensors),

without restriction, including without limitation the rights to copy, create
derivative works of, display, perform, and distribute the Software and make,
use, sell, offer for sale, import, export, have made, and have sold the
Software and the Larger Work(s), and to sublicense the foregoing rights on
either these or other terms.

This license is subject to the following condition:

The above copyright notice and either this complete permission notice or at
a minimum a reference to the UPL must be included in all copies or
substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
37 changes: 37 additions & 0 deletions Scripts/target_asset_report/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# OCI Cloud Migrations Target Asset Reporting Tool

This script generates a migration reporting table for a given OCI compartment by walking through:

- Migration Projects (migrations)
- Migration Plans for each project
- Target Assets for each plan

For each target asset, it reports:

- Project and plan names
- Target asset name
- Target asset lifecycle state
- Excluded-from-execution flag
- `recommended_spec` JSON (non-null values only)
- `user_spec` JSON (non-null values only)

The report is printed as a Markdown table and also saved to an Excel file (`.xlsx`).

## Goal

Provide a single report that helps compare migration target assets and their non-null specification details across all projects/plans in a compartment.

## Parameters

- `-cp <profile>`: OCI config profile name (default: `DEFAULT`)
- `-ip`: Use Instance Principals authentication
- `-dt`: Use Delegation Token authentication (Auto selected when running in cloud shell)
- `-log [file]`: Also write output to a log file (default file when omitted: `log.txt`)
- `-c, --compartment-id <ocid>`: Compartment OCID to query (defaults to tenancy OCID when omitted)
- `--excel-file <filename.xlsx>`: Excel output file name (default: `migration_report.xlsx`)

## Usage

```bash
python main.py -cp DEFAULT -c <compartment_ocid> --excel-file migration_report.xlsx
```
208 changes: 208 additions & 0 deletions Scripts/target_asset_report/ocimodules/IAM.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import oci
import time

WaitRefresh = 10
MaxIDeleteTagIteration = 5


class OCICompartments:
fullpath = ""
level = 0
details = oci.identity.models.Compartment()


def GetCompartments(identity, rootID):
retry = True
while retry:
retry = False
try:
# print("Getting compartments for {}".format(rootID))
compartments = oci.pagination.list_call_get_all_results(identity.list_compartments, compartment_id=rootID, retry_strategy=oci.retry.DEFAULT_RETRY_STRATEGY).data
return compartments
except oci.exceptions.ServiceError as e:
if e.status == 429:
print("API busy.. retry", end="\r")
retry = True
time.sleep(WaitRefresh)
else:
print("bad error!: " + e.message)
return []


def GetCompartmentFullPath(compartments, ocid):
"""
Given a list of OCICompartments objects and an OCID,
returns the full path of the compartment that matches the given OCID.
If not found, returns None.
"""
for compartment in compartments:
if hasattr(compartment, "details") and getattr(compartment.details, "id", None) == ocid:
return getattr(compartment, "fullpath", None)
return None



#################################################
# Login #
#################################################
def Login(config, signer, startcomp, sso_user=False, get_compartments=False):
identity = oci.identity.IdentityClient(config, signer=signer)
if "user" in config:
try:
user = identity.get_user(config["user"]).data
print("Logged in as: {} @ {}".format(user.description, config["region"]))
except oci.exceptions.ServiceError as e:
if e.status == 404 and sso_user:
print("Warning: user not found — assuming SSO")
user = "IP-DT"
else:
raise e
else:
print("Logged in as: {} @ {}".format("InstancePrinciple/DelegationToken", config["region"]))
user = "IP-DT"

c = []
if get_compartments:
print ("Getting compartments...")
# Adding Start compartment
if "user" in config or ".tenancy." not in startcomp:
compartment = identity.get_compartment(compartment_id=startcomp, retry_strategy=oci.retry.DEFAULT_RETRY_STRATEGY).data
else:
# Bug fix - for working on root compartment using instance principle.
compartment = oci.identity.models.Compartment()
compartment.id = startcomp
compartment.name = "root compartment"
compartment.lifecycle_state = "ACTIVE"

newcomp = OCICompartments()
newcomp.details = compartment
if ".tenancy." in startcomp:
newcomp.fullpath = "/root"
newcomp.level = 0
else:
newcomp.level = 0
newcomp.fullpath = compartment.name
c.append(newcomp)

# Add first level subcompartments
compartments = GetCompartments(identity, startcomp)

# Add 2nd level subcompartments
fullpath = newcomp.fullpath + "/"
for compartment in compartments:
if compartment.lifecycle_state == "ACTIVE":
newcomp = OCICompartments()
newcomp.details = compartment
newcomp.fullpath = "{}{}".format(fullpath, compartment.name)
newcomp.level = 1
c.append(newcomp)
subcompartments = GetCompartments(identity, compartment.id)
subpath1 = compartment.name
for sub1 in subcompartments:
if sub1.lifecycle_state == "ACTIVE":
newcomp = OCICompartments()
newcomp.details = sub1
newcomp.fullpath = "{}{}/{}".format(fullpath, subpath1, sub1.name)
newcomp.level = 2
c.append(newcomp)

subcompartments2 = GetCompartments(identity, sub1.id)
subpath2 = sub1.name
for sub2 in subcompartments2:
if sub2.lifecycle_state == "ACTIVE":
newcomp = OCICompartments()
newcomp.details = sub2
newcomp.fullpath = "{}{}/{}/{}".format(fullpath, subpath1, subpath2, sub2.name)
newcomp.level = 3
c.append(newcomp)

subcompartments3 = GetCompartments(identity, sub2.id)
subpath3 = sub2.name
for sub3 in subcompartments3:
if sub3.lifecycle_state == "ACTIVE":
newcomp = OCICompartments()
newcomp.details = sub3
newcomp.fullpath = "{}{}/{}/{}/{}".format(fullpath, subpath1, subpath2, subpath3, sub3.name)
newcomp.level = 4
c.append(newcomp)

subcompartments4 = GetCompartments(identity, sub3.id)
subpath4 = sub3.name
for sub4 in subcompartments4:
if sub4.lifecycle_state == "ACTIVE":
newcomp = OCICompartments()
newcomp.details = sub4
newcomp.fullpath = "{}{}/{}/{}/{}/{}".format(fullpath, subpath1, subpath2,
subpath3, subpath4, sub4.name)
newcomp.level = 5
c.append(newcomp)

subcompartments5 = GetCompartments(identity, sub4.id)
subpath5 = sub4.name
for sub5 in subcompartments5:
if sub5.lifecycle_state == "ACTIVE":
newcomp = OCICompartments()
newcomp.details = sub5
newcomp.fullpath = "{}{}/{}/{}/{}/{}/{}".format(fullpath, subpath1, subpath2, subpath3, subpath4, subpath5, sub5.name)
newcomp.level = 6
c.append(newcomp)

subcompartments6 = GetCompartments(identity, sub5.id)
subpath6 = sub5.name
for sub6 in subcompartments6:
if sub6.lifecycle_state == "ACTIVE":
newcomp = OCICompartments()
newcomp.details = sub6
newcomp.fullpath = "{}{}/{}/{}/{}/{}/{}/{}".format(
fullpath,
subpath1,
subpath2,
subpath3,
subpath4,
subpath5, subpath6,
sub6.name)
newcomp.level = 7
c.append(newcomp)

return c


#################################################
# SubscribedRegions
#################################################
def SubscribedRegions(config, signer):
regions = []
identity = oci.identity.IdentityClient(config, signer=signer)
regionDetails = identity.list_region_subscriptions(tenancy_id=config["tenancy"]).data

# Add subscribed regions to list
for detail in regionDetails:
regions.append(detail.region_name)

return regions


#################################################
# GetHomeRegion
#################################################
def GetHomeRegion(config, signer):
home_region = ""
identity = oci.identity.IdentityClient(config, signer=signer)
regionDetails = identity.list_region_subscriptions(tenancy_id=config["tenancy"]).data

# Set home region for connection
for reg in regionDetails:
if reg.is_home_region:
home_region = str(reg.region_name)

return home_region


#################################################
# GetTenantName
#################################################
def GetTenantName(config, signer):
identity = oci.identity.IdentityClient(config, signer=signer)
tenancy = identity.get_tenancy(config['tenancy']).data
return tenancy.name

Loading