Comment se connecter et utiliser l'API de Clever Cloud ?
Lecture 9 min. âą
Table des matiĂšres
Bonjour đ
Cela semble bĂȘte dis comme ça mais mĂȘme si je bosse chez Clever Cloud depuis plus dâun an. Je nâai jamais rĂ©ellement utilisĂ© les produits que jâaide Ă concevoir.
Sauf cette semaine oĂč je me suis mis en tĂȘte de crĂ©er des rĂŽles ansible et plus particuliĂšre des modules ansible qui nĂ©cessitent de dĂ©velopper en python.
Mais me diriez-vous : âQuel rapport cela a avec Clever-Cloud?â
Bonne question !
La rĂ©ponse est que je veux ĂȘtre capable au moyen dâune API trĂšs simplifiĂ© de faire des trucs du genre:
- name : Order a Postgres Database
noa.clevercloud.addon_register:
provider: postgres
organisation: orga_xxxx
auth:
consumer_key: xxxx
consumer_secret: xxxx
ressource_key: xxxx
ressource_secret: xxxx
details:
plan: xxs_sml
version: 15
register: result
- name: Display details
ansible.builtin.debug:
msg:
- "{ result.addon_id }"
- "{ result.env.POSTGRESQL_ADDON_URI }"
- name: Delete addon
noa.clevercloud.addon_remove:
addon: "{ result.addon_id }"
Si vous nâĂȘtes pas familier de la syntaxe ansible ce nâest pas trĂšs grave. Disons que pour simplifier chaque bloc faits des actions:
- Commande un addon et enregiste le retour dans $result$
- Affiche le contenu de result (son ID et lâURL de connexion)
- DĂ©truit lâaddon créé
Avant que lâon me le dise oui, il existe dĂ©jĂ une CLI, les clever-tools qui faire tout ce dont jâai besoin sauf une choses, rĂ©cupĂ©rer les credentials.
Je me connecte. Il vous faut un compte, ce nâest pas long Ă faire. ^^
clever login
Cela ouvre mon navigateur qui demande de mâauthentifier si cela nâest pas dĂ©jĂ le cas.
Je créé mon addon
clever addon create --plan xxs_sml postgresql-addon deleteme --addon-version 15
Mon addon est bien lĂ đ

Mais problĂšme, il nây a rien dans lâAPI des clever-tools qui me permettent dâaccĂ©der aux informations de connexions qui sont dans les variables dâenvironnement de lâaddon.

Cela se comprend, car les addons sont dans la philosophie de Clever censĂ©es fonctionner avec une ou plusieurs applications et de fait si lors de la crĂ©ation de lâaddon on le lie Ă une application en faisant
clever addon create --plan xxs_sml postgresql-addon deleteme --addon-version 15 --link app_xxxx
Lâenvironnement se retrouve alors injectĂ© dans lâapplication app_xxxx
. Ce qui résout le problÚme.
Mais moi, je ne suis pas dans ce cas de figure.
Moi, je veux hacker le systÚme pour créer mon rÎle ansible.

Et donc il me faut trouver un autre moyen. Un moyen me donnant accÚs au précieux environnement tant convoité.
Plan de bataille
Bien maintenant que les présentations sont faites, on commencer à élaborer notre stratégie.
DĂ©jĂ , il nous faut comprendre comment fonctionne lâAPI de Clever-Cloud.
Authentification
Toute lâAPI est protĂ©gĂ©e par de lâOAuth 1.0a, ça Ă©voluera bientĂŽt grĂące Ă merveilleux Biscuit, mais pour lâheure ce nâest pas encore le cas.
Donc, nous allons devoir nous authenfier Ă lâancienne.
NâĂ©tant pas le premier ni le dernier Ă mâĂȘtre fracassĂ© les dents sur le mur de OAuth 1.0. Une personne mâa prĂ©cĂ©dĂ© et Ă créé un projet oauth-consumer-server, qui a pour but dâaider les Ăąmes en peines comme moi dans le douleureux exercice de la danse de lâauthentification.
Une fois la danse réalisée, nous obtiendrons les valeurs pour remplir les xxxx
de la section auth.
auth:
consumer_key: xxxx
consumer_secret: xxxx
ressource_key: xxxx
ressource_secret: xxxx
Les plans
Ce paramĂštre bien que semblant insignifiant, mâa pris un temps assez considĂ©rable Ă comprendre. đ
plan: xxs_sml
Si lâon rĂ©alise lâingĂ©niĂ©rie inverse de la crĂ©ation dâun addon dans le navigateur depuis la console, on sâapperçoit que ce qui est envoyĂ© Ă lâapi ressemble Ă ceci:
{
"name": "deleteme",
"region": "par",
"providerId": "postgresql-addon",
"plan": "plan_c32d00fb-6c06-48a9-a0a3-9d808937ec68",
"options": {
"version": "14",
"encryption": "false"
}
}
Au travers de
POST https://api.clever-cloud.com/v2/self/addons
Et lĂ , ce fut mon KO technique.
Mais par les Flammes câest quoi ce plan_c32d00fb-6c06-48a9-a0a3-9d808937ec68
???
Câest le moment dâexplorer lâAPI !
Me vient alors une idĂ©e, jâai entendu maintes fois entendre dire Hubert SablonniĂšre que la page de pricing de Clever Ă©tait bĂątis sur des webcomponents. Et dedans il y a les plans. Peut-ĂȘtre aussi la signifiacation de ce hash.

Peut-ĂȘtre que les composants sont hydratĂ© Ă postĂ©riori du render de la page ?
Et bingo ! En inspectant les requĂȘtes qui passent, on dĂ©couvre celle-ci
GET https://api.clever-cloud.com/v2/products/addonproviders
Qui me renvoit:
[
{
"id" : "postgresql-addon",
"name": "PostgreSQL",
"plans" : [
{
"id" : "plan_c32d00fb-6c06-48a9-a0a3-9d808937ec68",
"name" : "XXS Small Space",
"slug" : "xxs_sml",
},
{
"id" : "plan_b972af97-96cb-4a5d-b4ff-7b3efb6ff44b",
"name" : "XL Huge Space",
"slug" : "xl_hug",
}
]
},
{
"id" : "redis-addon",
"name": "Redis",
"plans" : [
{
"id" : "plan_c62dd71e-15c3-483e-879d-75e4c836e21e",
"name" : "S",
"slug" : "s_mono",
},
{
"id" : "plan_3901ff93-bb24-411d-9a5c-be3c625e3dd9",
"name" : "3XL",
"slug" : "xxxl_mono",
}
]
},
]
Subarashi !! Nous avons tout ce que nous avons besoin. pour chaque addon chaque plan associĂ© ! đ
Version
Bien maintenant les versions de chaque addon !
Nous en avons besoin, car la requĂȘte de crĂ©ation la spĂ©cifie.
{
"providerId": "postgresql-addon",
"options": {
"version": "14",
}
}
Malheureusement, la page de pricing ne donne pas cette indication.
On va donc rĂ©cupĂ©rer notre pioche et aller creuser dans les requĂȘtes de la console.
Lorsque lâon est dans le processus de crĂ©ation dâun addon il arrive pour certain addon que la version de celui-ci soit demandĂ©. En mĂȘme temps que la region que lâon verra dans la partie suivante.
Lorsque lâon est authentifiĂ© par la danse et que lâon est en cours de crĂ©ation dâun addon PG la requĂȘte suivant part vers Clever.
GET https://api.clever-cloud.com/v4/addon-providers/postgresql-addon
Celle-ci nous renvoie :
{
"providerId": "postgresql-addon",
"clusters": [
],
"dedicated": {
"12": {
},
"15": {
},
"11": {
},
"13": {
},
"10": {
},
"14": {
}
},
}
Si câest du redit:
GET https://api.clever-cloud.com/v4/addon-providers/redis-addon
On aura:
{
"providerId": "redis-addon",
"clusters": [
],
"dedicated": {
"7.0.11": {
},
},
}
Etc, pour tous les addons versionnés.
Region
Clever nâest pas disponible quâen France il y a Ă©galement des rĂ©gions partout sur la planĂšte.
Pour déterminer les différentes informations qui sont nécessaire pour remplir la case region
{
"name": "deleteme",
"region": "par",
}
Pour cela, il y a encore un autre call API pour nous sauver ^^
GET https://api.clever-cloud.com/v4/products/zones
Plein dâinfos encore, câest super, mais nous câest le name
, city
, country
qui nous plaĂźt ^^
[
{
"name": "par",
"country": "France",
"city": "Paris",
},
{
"name": "mtl",
"country": "Canada",
"city": "Montreal",
},
{
"name": "syd",
"country": "Australia",
"city": "Sydney",
},
]
Bon, on a tout pour crĂ©er lâaddon mais on est pas beaucoup plus avancĂ© quâavec le clever-tools, on lâa juste fait de maniĂšre horriblement inefficace. đ
RĂ©cupĂ©rer lâenvironnement de lâaddon !
Câest lĂ quâen ressortant lâinspecteur dâĂ©lĂ©ments en navigant dans les âinformationsâ de lâaddon on sâaperçoit quâun requĂȘte API nous renvoie le beurre et lâargent du beurre. đ
GET https://api.clever-cloud.com/v2/self/addons/addon_xxxx/env
[
{
"name": "POSTGRESQL_ADDON_VERSION",
"value": "15"
},
{
"name": "POSTGRESQL_ADDON_USER",
"value": "upkdd3yyifvzo1fzo5nj"
},
{
"name": "POSTGRESQL_ADDON_PASSWORD",
"value": "xxxx"
},
{
"name": "POSTGRESQL_ADDON_DB",
"value": "bfi3miifvl16rgqjap1e"
},
{
"name": "POSTGRESQL_ADDON_HOST",
"value": "bfi3miifvl16rgqjap1e-postgresql.services.clever-cloud.com"
},
{
"name": "POSTGRESQL_ADDON_PORT",
"value": "6955"
},
{
"name": "POSTGRESQL_ADDON_URI",
"value": "postgresql://upkdd3yyifvzo1fzo5nj:xxxxx@bfi3miifvl16rgqjap1e-postgresql.services.clever-cloud.com:6955/bfi3miifvl16rgqjap1e"
}
]
Chaque addon a son propre mode set de variables, je donne ici en exemple pour une PG.
Note
la variable POSTGRESQL_ADDON_PASSWORD
sera en clair bien entendu, sinon ça ne sert Ă rien đ
Bon lĂ , on est prĂȘt non ?
Oui, je crois que oui.
Le langage
Ah non, il manque un petit truc mineur.
En quoi nous allons coder tout ce bazar ?
Vu que mon but est de crĂ©er un module ansible et ansible jusquâĂ ce que jet le remplace, câest du python, donc go python.
On ne va pas non plus se casser la tĂȘte avec lâenfer sur Terre quâest oauth car quelquâun a dĂ©jĂ fait le sale boulot pour nous.
Cette fois-ci nous sommes prĂȘts !
Pas de quartiers !!
Oauth
BonâŻ! type la plus complexe, sâauthentifier. đ
Diriger vous vers un dossier de travail.
git clone https://github.com/CleverCloud/oauth-consumer-server.git oauth-server
DĂ©ployez lâapp sur Clever
$ cd oauth-server
$ clever create --type maven "Mon app d'OAuth"
Your application has been successfully created!
Fantastique !
En faisant un coup de
clever domain
app-637919bd-94ab-4966-b8f7-b0556ebbc0c6.cleverapps.io
Vous récupérez le domaine sur laquelle tourne votre app, mémorisez le quelque part.
Puis dĂ©finissez une variable dâenvironnement
clever env set APP_URL https://app-637919bd-94ab-4966-b8f7-b0556ebbc0c6.cleverapps.io/
Attention
Bien mettre le â/â Ă la fin !!
Maintenant il nous faut quelque chose Ă consommer et câest lĂ quâintervient la seconde brique.
Pour cela, il faut se rendre dans la console de Clever et créer un oAuth consumer
Il va vous demander tout un tas dâinformation et presque toutes les rĂ©ponses sont le domaine mĂ©morisĂ© plus tĂŽt.

LĂ , libre Ă vous de lui tomate, salade oignons, moi, je lui mets le minimum syndical de droits.

Vous allez alors accéder à une page avec deux champs. Key
et Secret
, ce seront pour la suite, notre consumer_key
et consumer_secret
.

Que le spectacle commence !
clever deploy
AprĂšs quelque instants lâapplication va dĂ©marrer, vous allez voir des logs de builds passer.
JusquâĂ un mirifique
Deployment successful
Il est maintenant temps de nous rendre sur notre app.
clever open
Elle va faire la gueule

Mais est bien aimable de nous dire pourquoi.
Il manque la consumerKey
en query params, et ça on a.
https://app-637919bd-94ab-4966-b8f7-b0556ebbc0c6.cleverapps.io/?consumerKey={{consumer_key}}
Et de mĂȘme pour la consumerSecret
https://app-637919bd-94ab-4966-b8f7-b0556ebbc0c6.cleverapps.io/?consumerKey={{consumer_key}}&consumerSecret={{consumer_secret}}
Cela lance la danse !
Connexion Ă Clever

Authorisation de la délégation de resource.

Et finalement les tokens !
Your token : xxxxxx Your token secret : xxxxxxx
Nous nommerons respectivement
Your token
: ressource_key
Your token secret
: ressource_secret
API
Maintenant que nous avons des tokens, nous pouvons nous connecter Ă la partie protĂ©gĂ©e de lâAPI.
Il est temps dâĂ©crire du python ^^
Dans un dossier, on se crée un virtual env.
python -m venv
source ./venv/bin/activate
Puis, on installe les dépendances
pip install requests-oauthlib
pip install python-dotenv
Pour éviter que les credentials soient visibles dans le code nous allons les mettre dans un fichier .env
non versionné.
consumer_key=xxxxx
consumer_secret=xxxxxx
resource_key=xxxxx
resource_secret=xxxxx
Biensûr vous remplacez les xxxx
đ
Puis, on charge le tout dans lâenvironnement dans le fichier main.py
import os
from dotenv import load_dotenv
load_dotenv()
def main():
consumer_key = os.getenv("consumer_key")
consumer_secret = os.getenv("consumer_secret")
resource_key = os.getenv("resource_key")
resource_secret = os.getenv("resource_secret")
if __name__ == "__main__":
main()
Cool, on peut maintenant sâauthentifier
import os
from dotenv import load_dotenv
+ from requests_oauthlib import OAuth1Session
load_dotenv()
def main():
consumer_key = os.getenv("consumer_key")
consumer_secret = os.getenv("consumer_secret")
resource_key = os.getenv("resource_key")
resource_secret = os.getenv("resource_secret")
+ clever_api = OAuth1Session(
+ client_key=consumer_key,
+ client_secret=consumer_secret,
+ resource_owner_key=resource_key,
+ resource_owner_secret=resource_secret
+ )
if __name__ == "__main__":
main()
MĂȘme si ce call nâest pas protĂ©gĂ©, ça fait un bon test ^^
Les plans
def main():
// snip
response = clever_api.get(f"{api_endpoint}/v2/products/addonproviders").json()
print(response)
Et si le prĂ©cĂ©dant ne lâĂ©tait pas, celui-ci nâest pas accessible publiquement
def main():
// snip
response = clever_api.get(f"{api_endpoint}/v2/products/addonproviders/postgresql-addon").json()
print(response)
Et fonctionne tout aussi bien đ
Nous sommes connecté et authentifié !
Le deuxiÚme call est une version scopé du premier à un addon en particulier.
Et lĂ un peu de python magique đ§ââïž
def main():
// snip
response = clever_api.get(f"{api_endpoint}/v2/products/addonproviders/postgresql-addon").json()
plans = {x["slug"] : x["id"] for x in response["plan"]}
Et paf pistache, on a notre mapping đ
On peut le faire avec tous les addons
import pprint
def main():
response = clever_api.get(f"{api_endpoint}/v2/products/addonproviders").json()
providers = {addon["name"]: {
"id": addon["id"],
"plans": {x["slug"]: x["id"] for x in addon["plans"]}} for addon in response
}
pprint.pprint(providers)
Et pouf, on a toutCode python
{'Cellar S3 storage': {'id': 'cellar-addon',
'plans': {'S': 'plan_84c85ee3-5fdb-4aca-a727-298ddc14b766'}},
'Configuration provider': {'id': 'config-provider',
'plans': {'std': 'plan_5d8e9596-dd73-4b73-84d9-e165372c5324'}},
'Elastic Stack': {'id': 'es-addon',
'plans': {'4xl': 'plan_31ea6328-7df8-4208-a18d-137d2941f16d',
'5xl': 'plan_197dbf8a-c30b-49c9-a2ac-126a0b79efba',
'l': 'plan_33ad969a-2f37-4d47-8707-93315650fc0f',
'm': 'plan_bb93c360-d60c-4441-b18b-cb530a6b7b11',
's': 'plan_7675a239-057e-448e-85fb-77b5aa2ef47e',
'xl': 'plan_0e494649-d62a-45e7-ba81-61c1a5d8503a',
'xs': 'plan_0e0bc5ea-ba21-41e8-865b-1ed48e0163ca',
'xxl': 'plan_56265b48-d826-4484-9fd8-d3038c973027',
'xxxl': 'plan_a9565b70-7d5b-44b9-892f-de9bc6cade84'}},
'FS Buckets': {'id': 'fs-bucket',
'plans': {'s': 'plan_09345cf9-b8ed-4540-b4f7-80ec422fd27b'}},
'Jenkins': {'id': 'jenkins',
'plans': {'L': 'plan_f8ee2197-dc72-474e-b396-ee952648bb12',
'M': 'plan_57ed26d0-5143-49b1-9232-f6f97f1881f2',
'S': 'plan_36ec7fbb-5c1e-4639-b512-1bc864fe52c2',
'XL': 'plan_f407e34a-2370-4a67-940f-3ef8d1aec772',
'XS': 'plan_9436de3e-b4e6-48e7-8f5c-0bec0dc8b592'}},
'MailPace - Transactional Email': {'id': 'mailpace',
'plans': {'clever_scaling_10': 'plan_f57a8522-6b62-4928-84db-3ffbff7f9ce3',
'clever_scaling_100': 'plan_c7441676-003b-4c8d-848d-39c666d5e21a',
'clever_scaling_20': 'plan_0bb798b3-74c4-47bb-bc83-81bbae865b23',
'clever_scaling_30': 'plan_a5593c76-e933-4677-989e-e3a1c1af42eb',
'clever_scaling_40': 'plan_5bef85c2-84c1-4c18-8ac9-4aca8a7f9a79',
'clever_scaling_50': 'plan_dc730525-1a5e-487a-b657-4999b5806ad5',
'clever_scaling_70': 'plan_71875a57-74fc-4814-92fc-aa29b98ae1f8',
'clever_solo': 'plan_5a8310e0-0038-4ada-a482-84d761d17b11'}},
'Matomo Analytics': {'id': 'addon-matomo',
'plans': {'beta': 'plan_87283ba6-617c-420d-8e37-3350a2fcdd66'}},
'MongoDB': {'id': 'mongodb-addon',
'plans': {'dev': 'plan_847dd55f-0847-4497-9e53-7eef4281e068',
'l_big': 'plan_24bbde1f-e8c7-4a45-b8bf-1ab1e320049c',
'l_med': 'plan_66f4b19c-621a-4b39-892d-0e6f0bf62fed',
'l_sml': 'plan_513675e4-32c0-4b3a-991c-24fcff62b0cf',
'm_big': 'plan_e8036bc6-5341-4c6e-b843-f4c60d442198',
'm_hug': 'plan_df538656-adf7-4846-8e39-d96d0c362706',
'm_med': 'plan_5f1e6e86-a9d7-4698-b6c1-6ef5935d16b7',
'm_sml': 'plan_8c14d2c0-05fe-4137-912b-1e99b01b871d',
's_big': 'plan_b60a7496-f44d-4f95-b942-c2445958d58a',
's_hug': 'plan_5172896b-9f29-48f6-8820-3d07a04b80c9',
's_med': 'plan_01fd9ba8-2e8f-4a0a-bbf7-aecb4d982780',
's_sml': 'plan_5ad112b9-15ca-4c1d-800d-f1cb2989ee83',
'xl_big': 'plan_ffdd05ce-5f30-4750-94c5-424899d1f89d',
'xl_med': 'plan_e4745876-6013-48fc-9cbf-c7a49be84cb4',
'xl_sml': 'plan_36429877-93c4-4639-83f0-cdc96493caf3',
'xs_big': 'plan_8c379b80-6f2d-4b93-8839-8cb14aee4218',
'xs_med': 'plan_14ab2ba4-4b61-4e4f-8971-f26e7daa4963',
'xs_sml': 'plan_b53983a2-63d3-472d-8c98-f1bdea682912',
'xxl_big': 'plan_e6030ef6-005a-4f77-939d-f1395b02f5d6',
'xxl_sml': 'plan_98370e12-e54a-4dc1-8ee4-4d4f3007f3eb',
'xxx_med': 'plan_8da4539d-198b-48da-9d4e-df4e832085fe'}},
'MySQL': {'id': 'mysql-addon',
'plans': {'dev': 'plan_bf78ef5b-aedd-4024-973a-c2ff45541b88',
'l_big': 'plan_cecdd927-0c2b-4df5-80bf-7a3956b7da45',
'l_med': 'plan_786d19e3-d5ec-43d1-bb99-4bdf51d1708f',
'l_sml': 'plan_65af8c37-a9ca-4f37-8490-c727a9d7fe23',
'm_big': 'plan_d7f7bcff-df64-4326-a8f7-9d112d75bbff',
'm_med': 'plan_ead3f4d7-a920-4b8c-8706-8fa8280d0bcc',
'm_sml': 'plan_97afb4d4-cfeb-4fcb-966f-6072c6c327dc',
's_big': 'plan_a367b384-2942-4226-884c-5211b79aae81',
's_med': 'plan_1940a893-ff6b-4c64-85e7-4955bb3355c0',
's_sml': 'plan_445566dd-b996-456f-96e3-186c29f4bd11',
'xl_big': 'plan_df6d36d3-07a4-4027-a0bb-333240a6b7c8',
'xl_med': 'plan_b6cac148-b1fe-4018-a605-9ecd92b436d1',
'xl_sml': 'plan_85292520-3687-488a-aefc-1884349205e4',
'xs_big': 'plan_01d5d079-e900-49c7-8273-4fb3473eedea',
'xs_med': 'plan_889ddd44-68e1-4473-b152-a1abcaeec5b0',
'xs_sml': 'plan_bde0b41f-77a7-4285-a08c-b8f4d9780fc4',
'xs_tny': 'plan_0ffb4a1e-ac15-47c6-965f-f87ac990d99c',
'xxl_big': 'plan_c26fc00b-abfb-44ec-951d-73ed65892f3e',
'xxl_hug': 'plan_9f7b1d05-36e7-493d-a32f-af0507477af0',
'xxl_med': 'plan_f6108804-9258-41f3-9920-0b5e0a41cb60',
'xxl_sml': 'plan_8c48584e-4d0a-43db-91a1-f2d1a3475fe5',
'xxs_big': 'plan_b7ee59a4-4115-41b8-b735-e44378b95c57',
'xxs_med': 'plan_53d3c47d-ce81-466b-8acb-d5efaa68d27b',
'xxs_sml': 'plan_7ab494e2-c319-4330-8170-35d78738c1ee'}},
'PostgreSQL': {'id': 'postgresql-addon',
'plans': {'dev': 'plan_d2ada71a-aa8e-4ead-8cb9-28314664437e',
'l_big': 'plan_166c1a3f-3e7d-427c-9a2d-adeaf6c30c1c',
'l_gnt': 'plan_957a3cbc-022f-430d-aa6e-3156b8da20c6',
'l_med': 'plan_bfc12b2d-37af-4d5c-98b5-f5ce8856965c',
'l_sml': 'plan_20cf7db8-2687-495f-978d-785a8ac87814',
'm_big': 'plan_55d54396-799b-4e01-92ce-34ad68392e7a',
'm_med': 'plan_ef13e023-b519-4130-bfa0-54e2e0893362',
'm_sml': 'plan_13def017-26d9-469d-88b1-6591cbf4422f',
's_big': 'plan_06303461-b9d1-4418-a352-7be788c17777',
's_hug': 'plan_926643d1-3180-4e6a-bbc0-da828b884f77',
's_med': 'plan_db834b61-21d3-423f-81ac-e8ec5b94a51c',
's_sml': 'plan_f1f39547-be55-4be0-96d0-5140da25c138',
'xl_big': 'plan_1067ae12-c433-497d-a20f-d16e50b932d9',
'xl_gnt': 'plan_fdbaa2cc-3403-451a-b341-0973e04be1d2',
'xl_hug': 'plan_b972af97-96cb-4a5d-b4ff-7b3efb6ff44b',
'xl_med': 'plan_b35727fe-2100-4f7c-82c8-31d9471769ab',
'xl_sml': 'plan_9b4505e7-ac36-4caa-ac7d-9e11b6a26b15',
'xs_big': 'plan_a9cfb2d3-c959-4ab2-8b6f-fcbd07aa4f51',
'xs_med': 'plan_cb267d0a-5a2d-4c5b-9709-ee4d321691e7',
'xs_sml': 'plan_f14478be-b59a-4f64-870c-6887c561492d',
'xs_tny': 'plan_4b988584-adf5-43a5-891b-9ba1d8fe6d5d',
'xxl_big': 'plan_d3131cb3-5d92-4b8c-b3db-30e8be4716af',
'xxl_hug': 'plan_e89e0986-2664-46b1-aaff-305b9c6ec552',
'xxl_med': 'plan_5e33d20a-fe32-4b4f-91f0-b59bf0deae13',
'xxl_sml': 'plan_1c8a4179-78ef-4bdd-bfd7-7f389782b8c6',
'xxs_big': 'plan_810751de-ab47-4bd8-918b-b857f9011050',
'xxs_med': 'plan_9ce0a025-f5bd-4ac4-a5be-e2da37c87583',
'xxs_sml': 'plan_c32d00fb-6c06-48a9-a0a3-9d808937ec68',
'xxxl_big': 'plan_52b00af0-b49f-477b-a2bb-05e6bdf057af',
'xxxl_med': 'plan_9c7779d2-2701-4420-bd8a-3e7b49083847',
'xxxl_sml': 'plan_5601b1c6-9850-4a02-9288-8894670f0d7d'}},
'Pulsar': {'id': 'addon-pulsar',
'plans': {'beta': 'plan_3ad3c5be-5c1e-4dae-bf9a-87120b88fc13'}},
'Redis': {'id': 'redis-addon',
'plans': {'l_mono': 'plan_56579711-5b5c-451c-b274-2662eb528fc1',
'm_mono': 'plan_221bbf5a-30d0-49a9-b539-ce09b8a734a9',
's_mono': 'plan_c62dd71e-15c3-483e-879d-75e4c836e21e',
'xl_mono': 'plan_17f3735a-e1e4-4296-9e4c-e41cf74121c5',
'xxl_mono': 'plan_3bb59326-15d2-404b-be6e-97069710e8dd',
'xxxl_mono': 'plan_3901ff93-bb24-411d-9a5c-be3c625e3dd9',
'xxxxl_mono': 'plan_b43a32be-ada0-49c1-ab3e-db04f9dcfef2'}}}
Not bad đ€
Les versions
On sâattaque aux versions, cette fois-ci, on doit faire plusieurs calls pour chaque addon
def main():
versions = {}
for addon in plans.keys():
addon_response = clever_api.get(f"{api_endpoint}/v4/addon-providers/{addon}")
if addon_response.ok:
addon_response_body = addon_response.json()
if "dedicated" in addon_response_body.keys():
addon_versions = list(addon_response_body['dedicated'].keys())
versions[addon] = addon_versions
else:
pprint.pprint(addon_response)
pprint.pprint(versions)
Bon on a semble-t-il tout ce quâil nous faut.
{
'addon-pulsar': [],
'es-addon': ['7', '8'],
'jenkins': ['LTS'],
'mongodb-addon': ['4.0.3'],
'mysql-addon': ['5.7', '8.0'],
'postgresql-addon': ['12', '15', '11', '13', '10', '14'],
'redis-addon': ['7.0.11']
}
Les régions
Au tour des régions maintenant
MĂȘme combat, on appelle et on transforme.
Jâai dĂ©cidĂ© arbitrairent dâune clef composite, mais faites ce que vous voulez ^^
def main():
zones_response = clever_api.get(f"{api_endpoint}/v4/products/zones")
zones = {(x["country"], x["city"]): x["name"] for x in zones_response.json()}
pprint.pprint(zones)
Qui a pour résultat
{('Australia', 'Sydney'): 'syd',
('Canada', 'Montreal'): 'mtl',
('France', 'North'): 'fr-north-hds',
('France', 'Paris'): 'scw',
('France', 'Roubaix'): 'rbxhds',
('Poland', 'Warsaw'): 'wsw',
('Saudi Arabia', 'Jeddah'): 'jed',
('Singapore', 'Singapore'): 'sgp'}
Bon, on est pas mal du tout đ
Attends ! il est oĂč par
??
Ah oups, la zone de âScalewayâ est aussi Ă ('France', 'Paris')
du coup ça sâĂ©crase.
Cela sera un poil moins bien, mais bon, ça sera plus correct.
def main():
zones_response = clever_api.get(f"{api_endpoint}/v4/products/zones")
zones = {(x["country"], x["city"]+"-"+x["name"]): x["name"] for x in zones_response.json()}
pprint.pprint(zones)
{('Australia', 'Sydney-syd'): 'syd',
('Canada', 'Montreal-mtl'): 'mtl',
('France', 'North-fr-north-hds'): 'fr-north-hds',
('France', 'Paris-clevergrid'): 'clevergrid',
('France', 'Paris-par'): 'par',
('France', 'Paris-scw'): 'scw',
('France', 'Roubaix-rbx'): 'rbx',
('France', 'Roubaix-rbxhds'): 'rbxhds',
('Poland', 'Warsaw-wsw'): 'wsw',
('Saudi Arabia', 'Jeddah-jed'): 'jed',
('Singapore', 'Singapore-sgp'): 'sgp'}
Mieux !
Spawn !
On est prĂȘt Ă spawn notre :
- provider : PG
- version : 15
- nom : toto
- region : (France, Paris)
- plan : XXS Small space (xxs_sml)
Nous devons donc construire
{
"name": "toto",
"region": "par",
"providerId": "postgresql-addon",
"plan": "plan_c32d00fb-6c06-48a9-a0a3-9d808937ec68",
"options": {
"version": "15",
"encryption": "false"
}
}
Ben letâs go !
def main():
create_data = {
"name": "toto",
"region": zones[("France", "Paris-par")],
"providerId": providers["PostgreSQL"]["id"],
"plan": providers["PostgreSQL"]["plans"]["xxs_sml"],
"options" : {
"version" : "15",
"encryption": "false"
}
}
pprint.pprint(create_data)
Ce qui donne bien
{'name': 'toto',
'options': {'encryption': 'false', 'version': '15'},
'plan': 'plan_c32d00fb-6c06-48a9-a0a3-9d808937ec68',
'providerId': 'postgresql-addon',
'region': 'par'}
Letâs go pour la crĂ©ation.
def main():
response = clever_api.post(f"{api_endpoint}/v2/self/addons", json=create_data)
addon_id = response.json()["id"]
pprint.pprint(response.json())
On peut alors récupérer le précieux id
Qui va nous permettre dâenfin atteindre les identifiants de connexions.
def main():
response = clever_api.get(f"{api_endpoint}/v2/self/addons/{addon_id}/env")
pprint.pprint(response.json())
Et voilĂ le travail
[{'name': 'POSTGRESQL_ADDON_VERSION', 'value': '15'},
{'name': 'POSTGRESQL_ADDON_USER', 'value': 'uzek9l75d2qxpuh9eeel'},
{'name': 'POSTGRESQL_ADDON_PASSWORD', 'value': 'xxxxxxx'},
{'name': 'POSTGRESQL_ADDON_DB', 'value': 'bp000eqgpxma5nc0w1lu'},
{'name': 'POSTGRESQL_ADDON_HOST',
'value': 'bp000eqgpxma5nc0w1lu-postgresql.services.clever-cloud.com'},
{'name': 'POSTGRESQL_ADDON_PORT', 'value': '6959'},
{'name': 'POSTGRESQL_ADDON_URI',
'value': 'postgresql://uzek9l75d2qxpuh9eeel:xxxxxxxx@bp000eqgpxma5nc0w1lu-postgresql.services.clever-cloud.com:6959/bp000eqgpxma5nc0w1lu'}]
Plus quâĂ rendre ça plus sexy
def main():
pretty_env = {x["name"] : x["value"] for x in response_env}
pprint.pprint(pretty_env)
Bien mieux ^^
{'POSTGRESQL_ADDON_DB': 'bcxgrphuc6bqapahifm8',
'POSTGRESQL_ADDON_HOST': 'bcxgrphuc6bqapahifm8-postgresql.services.clever-cloud.com',
'POSTGRESQL_ADDON_PASSWORD': 'xxxxxx',
'POSTGRESQL_ADDON_PORT': '6960',
'POSTGRESQL_ADDON_URI': 'postgresql://u5i6w2q70kb7sdbbthjy:xxxxxx@bcxgrphuc6bqapahifm8-postgresql.services.clever-cloud.com:6960/bcxgrphuc6bqapahifm8',
'POSTGRESQL_ADDON_USER': 'u5i6w2q70kb7sdbbthjy',
'POSTGRESQL_ADDON_VERSION': '15'}
Destruction
MĂȘme combat, on fait de lâingĂ©niĂ©rie inverse et on tombe sur le call pour dĂ©truire lâaddon
def main():
response = clever_api.delete(f"{api_endpoint}/v2/self/addons/{addon_id}")
pprint.pprint(response.json())
Résultat
{'id': 318,
'message': 'The server successfully deleted your service',
'type': 'success'}
Code Complet
Code python
import os
from dotenv import load_dotenv
from requests_oauthlib import OAuth1Session
import pprint
load_dotenv()
def main():
consumer_key = os.getenv("consumer_key")
consumer_secret = os.getenv("consumer_secret")
resource_key = os.getenv("resource_key")
resource_secret = os.getenv("resource_secret")
clever_api = OAuth1Session(
client_key=consumer_key,
client_secret=consumer_secret,
resource_owner_key=resource_key,
resource_owner_secret=resource_secret
)
api_endpoint = "https://api.clever-cloud.com"
response = clever_api.get(f"{api_endpoint}/v2/products/addonproviders").json()
providers = {addon["name"]: {
"id": addon["id"],
"plans": {x["slug"]: x["id"] for x in addon["plans"]}} for addon in response
}
versions = {}
for addon in providers.values():
addon_response = clever_api.get(f"{api_endpoint}/v4/addon-providers/{addon['id']}")
if addon_response.ok:
addon_response_body = addon_response.json()
if "dedicated" in addon_response_body.keys():
addon_versions = list(addon_response_body['dedicated'].keys())
versions[addon['id']] = addon_versions
else:
pprint.pprint(addon_response)
zones_response = clever_api.get(f"{api_endpoint}/v4/products/zones")
zones = {(x["country"], x["city"]+"-"+x["name"]): x["name"] for x in zones_response.json()}
create_data = {
"name": "toto",
"region": zones[("France", "Paris-par")],
"providerId": providers["PostgreSQL"]["id"],
"plan": providers["PostgreSQL"]["plans"]["xxs_sml"],
"options" : {
"version" : "15",
"encryption": "false"
}
}
response = clever_api.post(f"{api_endpoint}/v2/self/addons", json=create_data)
addon_id = response.json()['id']
response_env = clever_api.get(f"{api_endpoint}/v2/self/addons/{addon_id}/env").json()
pretty_env = {x["name"] : x["value"] for x in response_env}
pprint.pprint(pretty_env)
response = clever_api.delete(f"{api_endpoint}/v2/self/addons/{addon_id}")
pprint.pprint(response.json())
if __name__ == "__main__":
main()
Conclusion
Python câest trĂšs puissant pour manipuler de la donnĂ©e arbitraire.
Oauth quel enfer.
Je gĂšre un peu mieux le produit que jâaide Ă concevoir ^^â
Lâopen api de clever nâest pas suffisante comme documentation.
Cet article sera un trĂšs bon pense-bĂȘte đ
A+