crayon-cloudiq-sdk 1.0.0
SDK for Crayon's Cloud-IQ API
Requires Python: >=3.7

# **Crayon CloudIQ SDK for Python**

This project is an SDK for Crayon's CloudIQ API that can be used in Python
scripts and applications. Provides a simple interface to authenticate with 
the API using Oauth2. It can be used to create tenants, create licensing subscriptions,
and monitor billing. Anything that can be done in the Cloud-IQ portal can be automated 
using this package. 

Includes several pre-configured data schema and API methods.
Custom blocks of data can be posted to the API as Python dictionaries. REST methods: 
GET, POST, PATCH, PUT, and DELETE can be called with an API endpoint and data dictionary as arguments. 

## **Installation**

1. Install the crayon-cloudiq-sdk package with the following command:
	```
	pip install crayon-cloudiq-sdk
	```

## **Setup**
How to Create Cloud-IQ API Client Credentials

1. Login to Cloud IQ
2. Choose Manage -> API Management from the top menu
3. Press the + Add Client button
4. Choose a name of the client
5. Choose Resource Flow as the authentication type
6. Save the Client ID and the Client Secret 

## **Usage**

1. Create a new python script

2. Import the CloudIQ class
	```
	from cloudiq import CloudIQ
	```

3. Initialize an instance of the CloudIQ class with valid user credentials:
	```
	from cloudiq import CloudIQ

	CLIENT_ID = xxxxxxx-xxxx-xxxx-xxxx-xxxxxx
	CLIENT_SECRET = xxxxxxx-xxxx-xxxx-xxxx-xxxxxx
	USERNAME = "example@example.com"
	PASSWORD = "Password123456"

	crayon_api = CloudIQ(CLIENT_ID,CLIENT_SECRET,USERNAME,PASSWORD)
	```
	**The prefered way of importing credentials is through ENV variables.**
	```
	from os import getenv
	from cloudiq import CloudIQ

	CLIENT_ID = getenv('CLIENT_ID')
	CLIENT_SECRET = getenv('CLIENT_SECRET')
	USERNAME = getenv('CLOUDIQ_USER')
	PASSWORD = getenv('CLOUDIQ_PW')

	crayon_api = CloudIQ(CLIENT_ID,CLIENT_SECRET,USERNAME,PASSWORD)
	```
	ENV variables can be set using various methods including injection if using containers and pipelines or through a secrets manager such as Azure KeyVault. To set them on a local system using bash run the following commands:
	```
	export CLIENT_ID="xxxxxxx-xxxx-xxxx-xxxx-xxxxxx"
	export CLIENT_SECRET="xxxxxxx-xxxx-xxxx-xxxx-xxxxxx"
	export USERNAME="example@example.com"
	export PASSWORD="Password123456"
	```
	An alternative method is to use a config.ini file containing the credentials and retrieve them using the configparser module.
	```
	import configparser
	from cloudiq import CloudIQ

	# Parse configuration file
	try:
		config = configparser.ConfigParser()
		config.read('config.ini')
		ID = config['CRAYON_API']['ID']
		SECRET = config['CRAYON_API']['SECRET']
		USER = config['CRAYON_API']['USER']
		PASS = config['CRAYON_API']['PASS']
	except configparser.Error:
		print("Configuration Error...config.ini not found")
		exit()
	except KeyError:
		print("Configuration Error...configuration not found in config.ini")
		exit()

	crayon_api = CloudIQ(CLIENT_ID,CLIENT_SECRET,USERNAME,PASSWORD)
	```
	**See examples folder for authentication demos using configparser, ENV variables, and Azure DevOps Pipelines** 

## **Returned Data**

Data that is returned by the API is saved into a response object (except for getToken and validateToken). The response object contains values such as the status_code, headers, cookies, and the text returned by the API call.

* To return the json data from the response use response.json() class method

* To return the status code use the response.statuscode variable

* All successful API calls either return **200 OK**, **201 Created**, or **204 No Content**

* Most error responses also provide a detailed error message in JSON form

* If you receive a 500 error, the data Schema payload is most likely the issue. It may be formatted incorrectly or missing required fields.

**Remember to handle error statuses when writing automations**

### Error Handling Example using Return Status Code:

```
response = crayon_api.me()

if(int(response.status_code) == 200):
	# Handle JSON data
    print(response.json())
else:
	# Handle Error
	print(response.status_code)
	exit(1)
```

For a full explanation of the fields within a response object, please review the information in the following links:

* Official Requests.Response class documentation: https://docs.python-requests.org/en/latest/api/#requests.Response 

* W3 Schools: https://www.w3schools.com/PYTHON/ref_requests_response.asp

## **Example calls**

1. Make an unauthenticated test ping to the API
	```
	response = crayon_api.ping()
	print(response,json())
	```

2. Get information about the currently authenticated user
	```
	response = crayon_api.me()
	print(response.json())
	```

3. Make a raw GET request:
	```
	# retrieves all products in the Azure Active Directory product family within Org 123456
	params = {
		'OrganizationId': 123456,
		'Include.ProductFamilyNames': 'Azure Active Directory'
	}
	# make a GET request to https://api.crayon.com/api/v1/AgreementProducts
	response = crayon_api.get("https://api.crayon.com/api/v1/AgreementProducts",params)
	print(response.json())
	```
	**Data can be sent to the API as a standard Python dictionary object**

4. Retrieve a valid authorization token:
	```
	response = crayon_api.getToken()
	print(response)
	```

5. Create a tenant using the CustomerTenantDetailed schema:
	```
	# Set Unique Tenant Variables
	tenant_name = "tenant_name"
	domain_prefix = "domain_prefix"

	# Initialize Tenant and Agreement objects
	tenant = crayon_api.CustomerTenantDetailed(
		tenant_name=tenant_name,
		domain_prefix=domain_prefix,
		org_id=111111,
		org_name="Fake Org",
		invoice_profile_id=80408, # Default
		contact_firstname="First",
		contact_lastname="Last",
		contact_email="email@example.com",
		contact_phone="5555555555",
		address_firstname="First",
		address_lastname="Last",
		address_address="75 NoWhere Lane",
		address_city="Boston",
		address_countrycode="US",
		address_region="MA",
		address_zipcode="02109"
	)

	agreement = crayon_api.CustomerTenantAgreement(
		firstname="First",
		lastname="Last",
		phone_number="5555555555",
		email="email@example.com"
	)

	#Create New Tenant
	new_tenant = crayon_api.createTenant(tenant.tenant)
	print(new_tenant.json())

	# Agree to Microsoft Customer Agreement
	tenant_id = new_tenant["Tenant"]["Id"]  
	agreement = crayon_api.createTenantAgreement(tenant_id,agreement.agreement)
	print(agreement.json())
	```

6. Buy a Microsoft license for a tenant using the SubscriptionDetailed schema:
	```
	tenant_id=123456

	# Create Subscription objects
	azure_subscription = crayon_api.SubscriptionDetailed(
		name="Azure P2 Subscription",
		tenant_id=tenant_id,
		part_number="CFQ7TTC0LFK5:0001",
		quantity=1,
		billing_cycle=1,
		duration="P1M"
	)

	# Create Azure P2 Subscription
	subscription = crayon_api.createSubscription(azure_subscription.subscription)
	print(subscription.json)
	```

##  **Docstring**

```
from cloudiq import CloudIQ
help(CloudIQ)
```

## **API Documentation**

1. Crayon API Documentation: https://apidocs.crayon.com/
2. Swagger UI (includes all valid schemas): https://api.crayon.com/docs/index.html

## **Schema currently implemented in CloudIQ class**

1. CustomerTenantDetailed
2. CustomerTenantAgreement
3. SubscriptionDetailed

## **Methods currently implemented in CloudIQ class**

1. get
2. ping
3. me
4. getToken
5. validateToken
6. getOrganizations
7. getOrganization
8. getOrganizationSalesContact
9. getAgreementProducts
10. getActivityLogs
11. getOrganizationHasAccess
12. getAddresses
13. getAddress
14. getSupportedBillingCycles
15. getAgreements
16. getAgreementReports
17. getCustomerTenants
18. getCustomerTenant
19. getCustomerTenantDetails
20. getCustomerTenantAzurePlan
21. getCustomerTenantAgreements
22. getBillingCycles
23. getProductVariantBillingCycles
24. getBillingCyclesNameDictionary
25. getBillingStatements
26. getGroupedBillingStatements
27. getBillingStatementExcel
28. getBillingStatementCSV
29. getBillingStatementJSON
30. getBlogItems
31. getClients
32. getClient
33. getConsumers
34. getConsumer
35. getCrayonAccounts
36. getCrayonAccount
37. getGroupings
38. getGrouping
39. getInvoiceProfiles
40. getInvoiceProfile
41. getProductContainers
42. getProductContainer
43. getProductContainerRowIssues
44. getProductContainerShoppingCart
45. getPrograms
46. getProgram
47. getPublishers
48. getPublisher
49. getRegions
50. getRegionByCode
51. getUsers
52. getUser
53. getUsername
54. getUsageCost
55. delete
56. patch 
57. post
58. put
59. createClient
60. deleteClient
61. createTenant
62. createSubscription
63. createTenantAgreement