We’re happy to announce the first public release of the official Python bindings for the Exoscale API. This library allows Exoscale users to perform all operations that they can currently do with other official tools and integrations (our web portal, the exo CLI, our Terraform provider etc.) with a nice, high-level Pythonic API available starting today using the pip command.

The official Python bindings for the Exoscale API

The new official Exoscale Python bindings allow for full and extended access to all Exoscale products and functionalities, and we look forward to extend them alongside the expansion of our cloud platform and API.

They allow for a better and smoother integration in scripts, tools and libraries, will be maintained in parallel to our existing Go bindings and CLI functionalities, and are definitively the way to go if you are looking forward to integrate Exoscale to a Python-based project.

For a simple usage, you can start off by passing API credentials via environment variables:

$ pip install exoscale
...
$ export EXOSCALE_API_KEY="EXOxxxxxxxxxxxxxxxxx" EXOSCALE_API_SECRET="xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$ python
Python 3.7.4 (default, Jul  9 2019, 18:13:23)
...

In order to demonstrate some of the functionalities of the library, here is a simple yet realistic scenario: we’ll create an SOS bucket, upload a template file to it and then create a Compute instance running a web server that will download our file and serve it customized with its hostname via HTTP via a DNS record in a domain name of ours.

The content of the file to be served is a template which will be customized by the web server:

$ echo "Hello from __ME__" > /tmp/index.html.tpl

Let’s start by initializing the Exoscale Python API client:

>>> import exoscale
>>> exo = exoscale.Exoscale()

We first need to create the Object Storage bucket and upload our template file, so that the instance can download it at boot time:

>>> bucket = exo.storage.create_bucket("bucky", zone="ch-gva-2")
>>> print(bucket.name)
bucky
>>> file_index = bucket.put_file("/tmp/index.html.tpl")

By default files uploaded to a bucket are only accessible by the bucket owner, and we need our template file to be accessible by our future Compute instance so we’ll have to adjust the file ACL accordingly:

>>> print(file_index.acl.read)
None
>>> file_index.set_acl('public-read')
>>> print(file_index.acl.read)
ALL_USERS

Our Object Storage part is now done, we can move on to the Compute part. Let’s first obtain a zone object representing the Exoscale zone in which we’ll create Compute resources:

>>> zone_gva2 = exo.compute.get_zone("ch-gva-2")

We create a firewall Security Group named web to which we add rules allowing ingress web traffic (HTTP and HTTPS):

>>> security_group_web = exo.compute.create_security_group("web")
>>> for rule in [
...     exoscale.api.compute.SecurityGroupRule.ingress(
...         description="HTTP",
...         network_cidr="0.0.0.0/0",
...         port="80",
...         protocol="tcp",
...     ),
...     exoscale.api.compute.SecurityGroupRule.ingress(
...         description="HTTPS",
...         network_cidr="0.0.0.0/0",
...         port="443",
...         protocol="tcp",
...     ),
... ]:
...     security_group_web.add_rule(rule)
...

We then provision an Elastic IP address that we’ll later attach to a Compute instance:

>>> elastic_ip = exo.compute.create_elastic_ip(zone_gva2)
>>> print(elastic_ip.address)
159.100.243.104
>>> instance = exo.compute.create_instance(
...     name="web1",
...     zone=zone_gva2,
...     type=exo.compute.get_instance_type("medium"),
...     template=list(
...         exo.compute.list_instance_templates(
...             zone_gva2,
...             "Linux Ubuntu 18.04 LTS 64-bit"))[0],
...     volume_size=50,
...     security_groups=[security_group_web],
...     user_data="""#cloud-config
... package_upgrade: true
... packages:
... - nginx
... write_files:
... - path: /etc/netplan/51-eip.yaml
...   content: |
...     network:
...       version: 2
...       renderer: networkd
...       ethernets:
...         lo:
...           match:
...             name: lo
...           addresses:
...             - {eip}/32
... runcmd:
... - curl {template_url} | sed -re "s/__ME__/$(hostname)/" > /var/www/html/index.html
... """.format(
      eip=elastic_ip.address,
      template_url=file_index.url),
... )

Let’s take a moment to look up some Compute instance object attributes:

>>> print(instance.id)
2d0c81ef-4f26-49a1-a04a-7cb91d484ddb
>>> print(instance.name)
web1
>>> print(instance.type.name)
Medium
>>> print(instance.template.name)
Linux Ubuntu 18.04 LTS 64-bit
>>> print(instance.ipv4_address)
159.100.242.43
>>> print(instance.zone.name)
ch-gva-2

We can now attach the EIP provisioned earlier to our new instance:

>>> instance.attach_elastic_ip(elastic_ip)
>>> for eip in instance.elastic_ips:
...     print(eip.address)
...
159.100.243.104

The final stage of our scenario is to create a DNS record pointing to our web server instance; in order to simplify the demo we’ll use an existing domain name example.net already configured to be served from the Exoscale DNS service, but it is also possible to set up everything from scratch using the library:

>>> domain = exo.dns.get_domain("example.net")
>>>
>>> record_web1 = domain.add_record(instance.name + "." + domain.name, "A", elastic_ip.address)
>>> for rec in (r for r in domain.records if r.type not in {"SOA", "NS"}):
...     print(rec.name, rec.type, rec.content)
...
web1.example.net A 159.100.243.104

A short while after, we can now test the whole set up using the Requests library:

>>> import requests
>>> res = requests.get("http://web1.example.net")
>>> print(res.text)
Hello from web1

This concludes a little tour of our new Exoscale Python library. The documentation is available here, and should you find bugs or have questions on how to use it feel free to open an issue at GitHub. Happy Pythoning!