Acronis Cyber Platform API with Python Quick Start

We are actively opening Acronis Cyber Platform API for partners and developers and it’s very important not only to provide documentation, but also to support programmers with clear code examples to complete real-life development tasks. So we are opening our fully functional Acronis Cyber Cloud integration for WHMCS on PHP and base examples of the platform’s API usage with Python. Let’s look at the examples and understand how to use it as a base for your API evaluation.

If you have any question regarding Acronis Cyber Platform API samples or usage, please use either Acronis Cyber Platform Forum or Support Form on Acronis Developer Network portal .

Prerequisites

First, you need to have Python version 3.6 or above to run the samples. To use those samples in your environment, you should have prerequisites installed.

pytest>=5.1.0
requests>=2.22.0
argparse>=1.4.0
jsonschema>=3.0.2

To install those requirements, you can use provided requirements.txt

pip install -r requirements.txt

Samples configuration and Acronis Cyber Platform client_id and client_secret

All samples use a Client object defines in client.py in the root directory of the repository. The Client uses config.json to initialize a Client instance. You should provide information in config.json file like shown below.

{
  "login": "<your_login>",
  "password": "<your_password>",
  "router_url": "https://beta-cloud.acronis.com/api/1/accounts",
  "client_id": "<client_id>",
  "client_secret": "<client_secret>"
}
Parameter Meaning Required
login The login of the administrator account Yes
password The password of the administrator account Yes
router_url A router service url to receive actual base url for API calls Yes
client_id The registered client_id for your application Yes
client_secret The secret for the registered application Yes

A real application must securely store the client’s secret, login and password values. Exposing these values may allow an attacker to perform malicious or destructive operations within the tenant that this application manages.

To receive client_id and client_secret you need to create a new client representing your application in the platform. It will be bounded to the specified tenant, and assigned the same rights as the specified account.

Below you can find well commented code example how to obtain client_id and client_secret.

credentials = '<your_login>', '<your_password>'
tenant_id = '<your_tenant_guid>'
base_url = '<base_url_for_api_calls>'

client = {
    'type': 'agent',
    'tenant_id': tenant_id,
    'token_endpoint_auth_method': 'client_secret_basic',
    'data': {'name': '<your_app_name>'},
}

# Register the application as a client in the cloud platform by sending the POST request to the /clients  
# endpoint. The request should use the Basic authentication scheme and contain the account credentials.

# The requests module will automatically encode the credentials into Base64, construct the Authorization 
# header with the encoded credentials, and add this header to the request. The module will also convert
# the client object to a valid JSON object.
response = requests.post(f'{base_url}/clients', auth=credentials, json=client)

# The 201 code means that a new client representing the application has been created in the platform,
# bound to the specified tenant, and assigned the same rights as the specified account.
# A different code means that an error has occurred.
if response.status_code == 201:
    data = response.json()
    client_id = data['client_id']
    client_secret = data['client_secret']

Client initialization

Let’s look into the client initialization. For clarity all error and exception code omitted.

We need to exchange the client’s credentials for an access token by sending the POST request to the /idp/token endpoint. The request should use the basic authentication scheme and contain the client credentials.

At first, we request router server to obtain login specific url for following REST request. self._router_url and self._logins are read from config.json file we described above.

#  Send a request to the router url to get the login-specific
#  server url (for production environment
response = requests.get(
    self._router_url,
    verify=not use_grpm_config,
    params=dict(login=self._login),
)

And then we put the server_url we are received from the router to the internal _base_url field to use in following requests and available through base_url class property.

#  Form base_url based on received server_url
self._base_url = response.json().get("server_url")

Currently, the client supports two types of grant password and client_credentials. In all API calls samples, a client_credentials type is required. Thus, let’s focus on the client_credentials type.

We start to fill a dictionary for JSON request. self._grant_type.name provides current grant type.

payload = dict(grant_type=self._grant_type.name, scope='openid')

Create a basic authentication header for the following request.

auth = HTTPBasicAuth(self._client_id, self._client_secret)

And, finally we request a token with the previously received base_url and previously initialized payload dictionary.

response = requests.post(
    f'{self.base_url}/api/2/idp/token',
    verify=not use_grpm_config,
    auth=auth,
    headers={'Content-Type': 'application/x-www-form-urlencoded'},
    data=payload,
)

In success cases, we should receive an access token and put it into the _auth_header class variable, accessible through auth_header property for future use.

access_token = response.json().get('access_token')

#  Generate authorization header for further use in the requests
self._auth_header = dict(Authorization=f'Bearer {access_token}')

Here we have an initialized client ready to help serve requests to Acronis Cyber Platform API.

Basic scenario: create a tenant, set a quota, create a user and send an activation e-mail

Most common scenario is the following: 1) create a tenant, 2) set a quota, 3) create a user and 4) send an activation e-mail. Tenants samples are located in ManagementAPI/ManagementTenant directory. Quotas code can be found in ManagementAPI/ManagementServices directory and users routines locate in ManagementAPI/ManagementUser.

Creating a tenant is quite simple. The only required parameters to create a tenant are name and kind. And you may pack a bunch of additional specific tenant info and post it to /tenants endpoint using basic authentication header form an instantiated client.

"""Creates a new tenant via the `/tenants` endpoint.
To create tenant in trial mode use kind=customer

:param client: Client object
:param tenant_data: Tenant information
:return: A dictionary with tenant information
"""
payload = {
    'parent_id': tenant_data.get('parent_id', client.tenant_id),
    'name': tenant_data.get('name'),
    'kind': tenant_data.get('kind'),
    'contact': {
        'address1': tenant_data.get('address1'),
        'address2': tenant_data.get('address2'),
        'city': tenant_data.get('city'),
        'country': tenant_data.get('country'),
        'email': tenant_data.get('email'),
        'firstname': tenant_data.get('firstname'),
        'lastname': tenant_data.get('lastname'),
        'phone': tenant_data.get('phone'),
        'state': tenant_data.get('state'),
        'zipcode': tenant_data.get('zipcode'),
    },
    'language': tenant_data.get('language', 'en'),
    'customer-id': tenant_data.get('customer-id'),
    'internal-tag': tenant_data.get('internal-tag'),
}

response = requests.post(
    f'{client.base_url}/api/2/tenants',
    headers=client.auth_header,
    json=payload,
)

return response.json()

A returned dictionary contains all required information regarding created tenant including tenant_id, which can be used if following API calls to perform additional task.

To handle a tenant’s quota or any other available updatable parameters at first, we need to receive offered items. Offering items retrieved for a specific tenant. It can be done by directly providing it to the script or use tenant id from an instantiated client. It’s put in tenant_id property which is part of the current implementation.

#  OPTIONAL: Create and store more properties
#  of the client here if necessary
response = requests.get(
     f'{self.base_url}/api/2/users/me',
     verify=not use_grpm_config,
     headers=self._auth_header,
     )

self._tenant_id = response.json().get('tenant_id')

And finally, just make a GET call to specific endpoint /tenants/{tenant_id}/offering_items with tenant id included and basic authentication header form an instantiated client.

tenant_id = tenant_id or client.tenant_id

response = requests.get(
   f'{client.base_url}/api/2/tenants/{tenant_id}/offering_items',
   headers=client.auth_header,
)

return response.json()['items']

And check if application_id and service name available for the specified tenant or client.

"""Change the quotas of service for tenant

:param client: Client object
:param tenant_id: The ID of the tenant
:param application_id: The ID of application
:param service_name: The name of service
:param value: Soft quota value
:param overage: The hard quota value
:return: Information about updated items
"""

# Get offered items the way we described above
items = get_offered_items(client, tenant_id)

# Filter received items by application_id and service name  
offering_items = [item for item in items if
                  item['application_id'] == application_id and
                  item['name'] == service_name]

# Create base dictionary filled with application and service found   
payload = dict(offering_items=offering_items)

# Check if any offering exists
if not payload['offering_items']:
      raise Exception('Provided `application_id` and '
                     '`name` of service not available')

# Check if found offering enabled
if payload['offering_items'][0]['status'] == 0:
      raise Exception('Service is disabled on the tenant')

# Set values for quota it the payload dictionary will be used for API call
payload['offering_items'][0]['quota']['value'] = value
payload['offering_items'][0]['quota']['overage'] = overage

And now we ready to update quotas using PUT method using the same URI as for previous GET method are used to retrieve offering. payload dictionary is from the previous step and basic authentication header form an instantiated client.

"""Updates the list of services, quotas on other service parameters for
tenant via the `/tenants/{tenant_id}/offering_items` endpoint

:param client: Client object
:param tenant_id: The ID of the tenant
:param payload: Information about changing items
:return: Information about updated items
"""
response = requests.put(
    f'{client.base_url}/api/2/tenants/{tenant_id}/offering_items',
    headers=client.auth_header,
    json=payload,
)

return response.json()['items']

The same technics can be used to update any updatable service parameters, only you need is to change is payload dictionary.

As quotas successfully set, we can create a user. The required parameters to create a user are login and email. And the process starts with check a login availability. So, we send a GET request with a new login name to /users/check_login endpoint and basic authentication header form an instantiated client.

"""Checks if login is free via the `/users/check_login` endpoint.

:param client: Client object
:param login: login to check
:return: `True` if login is free, `False` otherwise
"""
response = requests.get(
    f'{client.base_url}/api/2/users/check_login',
    params={'username': login},
    headers=client.auth_header,
)
return response.status_code == 204

And if the new login name is available we can create a new user. We use /users endpoint, payload dictionary with all needed info and basic authentication header form an instantiated client.

"""Creates a new user via the `/users` endpoint.

:param client: Client object
:param user_data: User account information
:return: A dictionary with user account information
"""
payload = {
    'tenant_id': user_data.get('tenant_id', client.tenant_id),
    'login': user_data.get('login'),
    'contact': {
        'login': user_data.get('login'),
        'email': user_data.get('email'),
        'address1': user_data.get('address1'),
        'address2': user_data.get('address2'),
        'state': user_data.get('state'),
        'zipcode': user_data.get('zipcode'),
        'city': user_data.get('city'),
        'phone': user_data.get('phone'),
        'firstname': user_data.get('firstname'),
        'lastname': user_data.get('lastname'),
    },
}

response = requests.post(
    f'{client.base_url}/api/2/users',
    headers=client.auth_header,
    json=payload,
)

return response.json()

As soon as user successfully created we need to send an activation e-mail. To build the endpoint URL for the request we need to take the new user_id from previous step and POST request to users/{user_id}/send-activation-email endpoint.

"""Sends activation e-mail to user
via the `/users/{user_id}/send-activation-email` endpoint.

In case of customer tenant this function is available only for
users of child tenants

In case of partner tenant this function is available only for
users of child tenants with role 'partner_admin' (Administrator)

:param client: Client object
:param user_id: The ID of the user account
:return: `True` if succeeded, `False` otherwise
"""
response = requests.post(
    f'{client.base_url}/api/2/users/{user_id}/send-activation-email',
    headers=client.auth_header,
    json={},
)

return response.status_code == 204

Thus, now we have full scenario completed. The code quoted from the following samples:

  1. ManagementAPI\ManagementTenant\how_to_create_tenant.py
  2. ManagementAPI\ManagementServices\how_to_set_quotas.py
  3. ManagementAPI\ManagementUser\how_to_create_user.py
  4. ManagementAPI\ManagementUser\how_to_send_user_activation_email.py

How to run samples

To use samples, move to the directory which contains the sample you want to run. Then run appropriate the python script with all the needed parameters. For example,

cd ManagementAPI/ManagementServices

python how_to_retreive_offering_items.py

python how_to_set_quotas.py --tenant-id 4bbbbbbbb-f000-4000-8588-a00000000000 \
                            --application-id a9fd8016-0e00-4ade-949e-6efe8672dac0 \
                            --service-name vms \
                            --value 10 \
                            --overage 20

Most scripts just collect parameters and make a corresponding API call directly with an appropriate HTTP method, API endpoint URI and an authentication token received by the Client during initialization based on data in config.json file.

Summary

Now you know how to start to code your own solution using our Management API, how to run samples, and you understand base principles of Clients implementation and usage.

  • Start today, register on the Acronis Developer Portal and see the code samples available
  • Contact our team through the feedback/request form on the Acronis Developer Portal
  • Review solutions available in the Acronis Cyber Cloud Solutions Portal
Updated
Stas Pavlov
Technology Evangelist
Python code examples
Account management API
Acronis Cyber Platform