Closed andersonnuss closed 3 months ago
Do you have the below policies ?
Allow group ShowSubscriptionGroup to inspect tenancies in tenancy
Allow group ShowSubscriptionGroup to inspect subscribed-services in tenancy
Allow group ShowSubscriptionGroup to inspect organizations-subscription in tenancy
or
inspect all-resources in tenancy
I'm using the tenant administrator. Even for the administrator, I apply the policies, but the request does not work. Is it possible that the Brazil East (São Paulo) region does not support the requests?
Hi, Can you try to go to the OCI Console -> Billing -> Subscription and check if you see data ?
Yes, the page is accessible. See the print below.
But some other pages, like Invoices and Payment History, are not supported in Brazil.
Hi, we will need to check with the service team if the REST API below is supported in Brazil:
https://csaap-e.oracle.com/oalapp/service/onesubs/proxy/20210501/organizationSubscription
What do you think about this? Do you have an easier way to check this information? My option here is to open a ticket with the OCI support team.
If you open support SR, please let me know the SR number and I will track it as well.
I opened an SR Ticket: 3-37685208281
Thank´s
Hi. Analyzing the return, I believe that we are working on different APIs to obtain the information.
The SR update: Please check: OCI Billing and Cost Management - How to Retrieve the Subscription Balance Using an API Request ( Doc ID 3005988.1 )
Thank you for your patience as I carefully review and attend to your request.
In response to your concern, I recommend reviewing the ListSubscriptions API Reference and Endpoints. This resource provides detailed information about subscriptions, including amounts and other relevant details. You can access it through the following link:
After reviewing the provided material, please inform me if it has provided the results you were seeking.
ListSubscription required subscription_id or plan number or buyer e-mail as input which I don't have from the tenant
I am using the below API: https://docs.oracle.com/en-us/iaas/api/#/en/usage-proxy/20190111/OrganizationSubscriptionSummary/ListOrganizationSubscriptions
It is also part of the Python SDK – Which is the only option I found how to list subscription with tenant_id as input https://docs.oracle.com/en-us/iaas/tools/python/2.131.0/api/onesubscription/client/oci.onesubscription.SubscriptionClient.html#oci.onesubscription.SubscriptionClient.list_subscriptions
Can you please update the SR, the API is working well in other tenants
I am tracking the support ticket and communicating with DevOps as well One question for you, can you please try to run it using cloud shell as below ?
wget https://raw.githubusercontent.com/oracle/oci-python-sdk/master/examples/showsubscription/showsubscription.py
python3 showsubscription.py -dt
I did change the endpoint as recommended by development Attached the new code, please try it, if works , i will deploy to Git.
# coding: utf-8
# Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved.
# This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
####################################################################################################################
# showsubscription.py
#
# @author: Adi Zohar, Mar 01 2024, Updated Aug 12 2024.
#
# Supports Python 3
####################################################################################################################
# Application Command line parameters
#
# -c config - OCI CLI Config
# -t profile - profile inside the config file
# -p proxy - Set Proxy (i.e. www-proxy-server.com:80)
# -ip - Use Instance Principals for Authentication
# -dt - Use Instance Principals with delegation token for cloud shell
# -all_services - Show all services (default Universal Credit only)
# -all_statuses - Show all Statuses (default ACTIVE only)
# -f FILE_NAME - Write to JSON file
#
####################################################################################################################
# Modules Included:
# - oci.onesubscription.OrganizationSubscriptionClient
# - oci.onesubscription.CommitmentClient
# - oci.onesubscription.SubscribedServiceClient
#
# APIs Used:
# - IdentityClient.get_tenancy - Policy TENANCY_INSPECT
# - IdentityClient.list_region_subscriptions - Policy TENANCY_INSPECT
# - OrganizationSubscriptionClient.list_organization_subscriptions - Policy ORGANIZATIONS_SUBSCRIPTION_INSPECT
# - SubscribedServiceClient.list_subscribed_services - Policy SUBSCRIBED_SERVICE_INSPECT
# - CommitmentClient.list_commitments - Policy SUBSCRIBED_SERVICE_INSPECT
#
# Policies:
# Allow group ShowSubscriptionGroup to inspect tenancies in tenancy
# Allow group ShowSubscriptionGroup to inspect subscribed-services in tenancy
# Allow group ShowSubscriptionGroup to inspect organizations-subscription in tenancy
####################################################################################################################
import sys
import argparse
import datetime
import oci
import os
import platform
import json
version = "2024.08.12"
##########################################################################
# Print header centered
##########################################################################
def print_header(name, category):
options = {0: 120, 1: 100, 2: 90, 3: 85}
chars = int(options[category])
print("")
print('#' * chars)
print("#" + name.center(chars - 2, " ") + "#")
print('#' * chars)
##########################################################################
# number_format
##########################################################################
def number_format(var):
if str(var).replace(".", "").isnumeric():
return str("{:,.0f}".format(float(var))).rjust(12)
return var
##########################################################################
# Create signer for Authentication
# Input - config_profile and is_instance_principals and is_delegation_token
# Output - config and signer objects
##########################################################################
def create_signer(config_file, config_profile, is_instance_principals, is_delegation_token):
# if instance principals authentications
if is_instance_principals:
try:
signer = oci.auth.signers.InstancePrincipalsSecurityTokenSigner()
config = {'region': signer.region, 'tenancy': signer.tenancy_id}
return config, signer
except Exception:
print_header("Error obtaining instance principals certificate, aborting", 0)
raise SystemExit
# -----------------------------
# Delegation Token
# -----------------------------
elif is_delegation_token:
try:
# check if env variables OCI_CONFIG_FILE, OCI_CONFIG_PROFILE exist and use them
env_config_file = os.environ.get('OCI_CONFIG_FILE')
env_config_section = os.environ.get('OCI_CONFIG_PROFILE')
# check if file exist
if env_config_file is None or env_config_section is None:
print("*** OCI_CONFIG_FILE and OCI_CONFIG_PROFILE env variables not found, abort. ***")
print("")
raise SystemExit
config = oci.config.from_file(env_config_file, env_config_section)
delegation_token_location = config["delegation_token_file"]
with open(delegation_token_location, 'r') as delegation_token_file:
delegation_token = delegation_token_file.read().strip()
# get signer from delegation token
signer = oci.auth.signers.InstancePrincipalsDelegationTokenSigner(delegation_token=delegation_token)
return config, signer
except KeyError:
print("* Key Error obtaining delegation_token_file")
raise SystemExit
except Exception:
raise
# -----------------------------
# config file authentication
# -----------------------------
else:
config = oci.config.from_file(
(config_file if config_file else oci.config.DEFAULT_LOCATION),
(config_profile if config_profile else oci.config.DEFAULT_PROFILE)
)
signer = oci.signer.Signer(
tenancy=config["tenancy"],
user=config["user"],
fingerprint=config["fingerprint"],
private_key_file_location=config.get("key_file"),
pass_phrase=oci.config.get_config_value_or_default(config, "pass_phrase"),
private_key_content=config.get("key_content")
)
return config, signer
##########################################################################
# Subscription Report
##########################################################################
def subscription_report(cmd, config, signer, tenancy):
data = {
'tenant_id': tenancy.id,
'tenant_name': tenancy.name,
'subscriptions': []
}
try:
# osub_org_client = oci.osub_organization_subscription.OrganizationSubscriptionClient(config, signer=signer)
osub_org_client = oci.onesubscription.OrganizationSubscriptionClient(config, signer=signer)
one_sub_client = oci.onesubscription.SubscribedServiceClient(config, signer=signer)
# osub_commit_client = oci.osub_subscription.CommitmentClient(config, signer=signer)
osub_commit_client = oci.onesubscription.CommitmentClient(config, signer=signer)
if cmd.proxy:
osub_org_client.base_client.session.proxies = {'https': cmd.proxy}
one_sub_client.base_client.session.proxies = {'https': cmd.proxy}
osub_commit_client.base_client.session.proxies = {'https': cmd.proxy}
############################
# Get Subscription list
############################
subscription_list = osub_org_client.list_organization_subscriptions(tenancy.id)
ucc_sub = [x for x in subscription_list.data if cmd.all_services or 'Universal' in x.service_name]
for sub in ucc_sub:
sub_info = {
'id': sub.id,
'service_name': str(sub.service_name),
'currency': sub.currency.iso_code if sub.currency else "",
'time_start': str(sub.time_start)[0:16],
'time_end': str(sub.time_end)[0:16],
'status': str(sub.status),
'total_value': str(sub.total_value),
'services': []
}
print_header(sub.service_name, 1)
print("")
print(f"Id : {sub_info['id']}")
print(f"Service Name : {sub_info['service_name']}")
print(f"Currency : {sub_info['currency']}")
print(f"Time Start : {sub_info['time_start']}")
print(f"Time End : {sub_info['time_end']}")
print(f"Status : {sub_info['status']}")
print(f"Total Value : {number_format(sub_info['total_value'])}")
############################
# Get Subscribed Service
############################
subscribed_services = one_sub_client.list_subscribed_services(tenancy.id, sub.id).data
for ss in subscribed_services:
if not cmd.all_statuses and ss.status != 'ACTIVE':
continue
small_sub = {
'status': ss.status,
'subscription_id': ss.subscription_id,
'id': ss.id,
'time_start': str(ss.time_start)[0:16],
'time_end': str(ss.time_end)[0:16],
'term_value': str(ss.term_value),
'admin_email': ss.admin_email,
'buyer_email': ss.buyer_email,
'agreement_id': ss.agreement_id,
'agreement_name': ss.agreement_name,
'time_agreement_end': str(ss.time_agreement_end)[0:16],
'bill_to_customer': ss.bill_to_customer.name if ss.bill_to_customer else "",
'end_user_customer': ss.end_user_customer.name if ss.end_user_customer else "",
'service_to_customer': ss.service_to_customer.name if ss.service_to_customer else "",
'billing_frequency': ss.billing_frequency,
'csi': ss.csi,
'operation_type': ss.operation_type,
'order_type': ss.order_type,
'order_number': ss.order_number,
'payment_method': ss.payment_method,
'payment_number': ss.payment_number,
'pricing_model': ss.pricing_model,
'product_number': ss.product.part_number if ss.product else "",
'product_name': ss.product.name if ss.product else "",
'is_payg': str(ss.is_payg) if ss.is_payg else "",
'is_having_usage': str(ss.is_having_usage),
'is_variable_commitment': str(ss.is_variable_commitment),
'original_promo_amount': "" if str(ss.original_promo_amount) == "null" else ss.original_promo_amount,
'funded_allocation_value': "" if str(ss.funded_allocation_value) == "null" else ss.funded_allocation_value,
'line_net_amount': ss.line_net_amount,
'total_value': ss.total_value,
'used_amount': ss.used_amount,
'available_amount': ss.available_amount,
'commits': []
}
print_header(small_sub['product_number'] + " - " + small_sub['product_name'], 3)
print("")
print(f"status : {small_sub['status']}")
print(f"subscription_id : {small_sub['subscription_id']}")
print(f"id : {small_sub['id']}")
print(f"time_start : {small_sub['time_start']}")
print(f"time_end : {small_sub['time_end']}")
print(f"term_value : {small_sub['term_value']}")
print(f"admin_email : {small_sub['admin_email']}")
print(f"buyer_email : {small_sub['buyer_email']}")
print(f"agreement_id : {small_sub['agreement_id']}")
print(f"agreement_name : {small_sub['agreement_name']}")
print(f"time_agreement_end : {small_sub['time_agreement_end']}")
print(f"bill_to_customer : {small_sub['bill_to_customer']}")
print(f"end_user_customer : {small_sub['end_user_customer']}")
print(f"service_to_customer : {small_sub['service_to_customer']}")
print(f"billing_frequency : {small_sub['billing_frequency']}")
print(f"csi : {small_sub['csi']}")
print(f"operation_type : {small_sub['operation_type']}")
print(f"order_type : {small_sub['order_type']}")
print(f"order_number : {small_sub['order_number']}")
print(f"payment_method : {small_sub['payment_method']}")
print(f"payment_number : {small_sub['payment_number']}")
print(f"pricing_model : {small_sub['pricing_model']}")
print(f"product_number : {small_sub['product_number']}")
print(f"product_name : {small_sub['product_name']}")
print(f"is_payg : {small_sub['is_payg']}")
print(f"is_having_usage : {small_sub['is_having_usage']}")
print(f"is_variable_commitment : {small_sub['is_variable_commitment']}")
print(f"original_promo_amount : {small_sub['original_promo_amount']}")
print(f"funded_allocation_value : {small_sub['funded_allocation_value']}")
print(f"line_net_amount : {number_format(small_sub['line_net_amount'])}")
print(f"total_value : {number_format(small_sub['total_value'])}")
print(f"used_amount : {number_format(small_sub['used_amount'])}")
print(f"available_amount : {number_format(small_sub['available_amount'])}")
# Add the commits
try:
commits = osub_commit_client.list_commitments(ss.id, compartment_id=tenancy.id).data
for commit in commits:
carr = {
'funded_allocation_value': "" if commit.funded_allocation_value == "null" else commit.funded_allocation_value,
'time_start': str(commit.time_start)[0:16],
'time_end': str(commit.time_end)[0:16],
'quantity': str(commit.quantity),
'used_amount': str(commit.used_amount),
'available_amount': str(commit.available_amount)
}
small_sub['commits'].append(carr)
print(
f"--> Commit : {carr['time_start'][0:10]} - {carr['time_end'][0:10]}, "
f"Total: {number_format(carr['quantity'])} "
f"Used: {number_format(carr['used_amount'])} "
f"Available: {number_format(carr['available_amount'])} "
f"Funded: {number_format(carr['funded_allocation_value'])}")
except oci.exceptions.ServiceError as e:
small_sub['commits_error'] = e.code
sub_info['services'].append(small_sub)
# add to data
data['subscriptions'].append(sub_info)
# return the data
return data
except oci.exceptions.ServiceError as e:
print("\nService Error at 'subscription_report' - " + str(e))
return data
except Exception as e:
print("\nException Error at 'subscription_report' - " + str(e))
return data
##########################################################################
# Main Process
##########################################################################
def main():
# Get Command Line Parser
parser = argparse.ArgumentParser(usage=argparse.SUPPRESS, formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=80, width=130))
parser.add_argument('-c', default="", dest='config_file', help='OCI CLI Config file')
parser.add_argument('-t', default="", dest='config_profile', help='Config Profile inside the config file')
parser.add_argument('-p', default="", dest='proxy', help='Set Proxy (i.e. www-proxy-server.com:80) ')
parser.add_argument('-ip', action='store_true', default=False, dest='is_instance_principals', help='Use Instance Principals for Authentication')
parser.add_argument('-dt', action='store_true', default=False, dest='is_delegation_token', help='Use Delegation Token for Authentication')
parser.add_argument('-all_services', action='store_true', default=False, dest='all_services', help='All Services (Default Universal Credit)')
parser.add_argument('-all_statuses', action='store_true', default=False, dest='all_statuses', help='All Subscription Statuses (Default ACTIVE)')
parser.add_argument('-f', type=argparse.FileType('w'), dest='file', help="Output to file (as json)")
cmd = parser.parse_args()
if len(sys.argv) < 2:
parser.print_help()
return
# Start print time info
print_header("Running Show Subscription", 0)
print("Author : Adi Zohar")
print("Disclaimer : This is not an official Oracle application, It does not supported by Oracle !")
print("Machine : " + platform.node() + " (" + platform.machine() + ")")
print("App Version : " + version)
print("OCI SDK Version : " + oci.version.__version__)
print("Python Version : " + platform.python_version())
if cmd.is_instance_principals:
print("Authentication : Instance Principals")
elif cmd.is_delegation_token:
print("Authentication : Instance Principals With Delegation Token")
else:
print("Authentication : Config File")
print("Date/Time : " + str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
print("Command Line : " + ' '.join(x for x in sys.argv[1:]))
print("Service Filter : " + ("ALL" if cmd.all_services else "UCC"))
print("Status Filter : " + ("ALL" if cmd.all_statuses else "ACTIVE"))
if cmd.file:
print("Writing to file..." + cmd.file.name)
############################################
# create signer
############################################
config, signer = create_signer(cmd.config_file, cmd.config_profile, cmd.is_instance_principals, cmd.is_delegation_token)
print_header("Fetching data", 0)
tenancy = None
try:
print("\nConnecting to Identity Service...\n")
identity = oci.identity.IdentityClient(config, signer=signer)
if cmd.proxy:
identity.base_client.session.proxies = {'https': cmd.proxy}
tenancy = identity.get_tenancy(config["tenancy"]).data
regions = identity.list_region_subscriptions(tenancy.id).data
# Set home region for connection
for reg in regions:
if reg.is_home_region:
tenancy_home_region = str(reg.region_name)
config['region'] = tenancy_home_region
signer.region = tenancy_home_region
print("Tenant Name : " + str(tenancy.name))
print("Tenant Id : " + tenancy.id)
print("Home Region : " + tenancy_home_region)
except Exception as e:
raise RuntimeError("\nError fetching tenant information - " + str(e))
############################################
# Connection to UsageAPI
############################################
try:
data = subscription_report(cmd, config, signer, tenancy)
# If print to json file
if cmd.file and data:
with open(cmd.file.name, 'w') as outfile:
json.dump(data, outfile, indent=4, sort_keys=False)
print("")
print("Exported to file " + cmd.file.name)
except Exception as e:
raise RuntimeError("\nError at main function - " + str(e))
##########################################################################
# Main Process
##########################################################################
main()
Very Nice. The code worked perfectly in my environment.
This is great, can you kindly close the SR and this ticket I will push this code to Git for next week Tuesday.
I'm trying to use the showsubscription.py script with a user that has all permissions over the tenant, but I receive a 401 Unauthorized error.
Is this a limitation for the Home Region, or could it be an issue?
Script Output:
Connecting to Identity Service...
Tenant Name : xxxxxx Tenant Id : xxxxxxxxxxxxxxxxxxxxx Home Region : sa-saopaulo-1
Service Error at 'subscription_report' - {'target_service': 'organization_subscription', 'status': 401, 'code': '401', 'opc-request-id': '47BE7E4765124B258144F708E2F87EEA/0FDD26D202B47BB8D26BA1FDF12FB8A1/D61E703426FF482C7B32E7A47305066B', 'message': 'HTTP 401 Unauthorized', 'operation_name': 'list_organization_subscriptions', 'timestamp': '2024-08-08T10:26:35.963324+00:00', 'client_version': 'Oracle-PythonSDK/2.93.1', 'request_endpoint': 'GET https://csaap-e.oracle.com/oalapp/service/onesubs/proxy/20210501/organizationSubscription', 'logging_tips': 'To get more info on the failing request, refer to https://docs.oracle.com/en-us/iaas/tools/python/latest/logging.html for ways to log the request/response details.', 'troubleshooting_tips': 'See https://docs.oracle.com/iaas/Content/API/References/apierrors.htm#apierrors_401__401_401 for more information about resolving this error. If you are unable to resolve this organization_subscription issue, please contact Oracle support and provide them this full error message.'}