twvdscli/twvdscli.py

602 lines
16 KiB
Python
Raw Normal View History

2022-04-05 23:25:07 +03:00
#!/usr/bin/python3
2022-04-07 15:29:49 +03:00
import json
2022-04-07 15:38:11 +03:00
import sys
2022-04-05 23:25:07 +03:00
import requests
import typer
import os
import configparser
import base64
2022-04-07 16:08:28 +03:00
from prettytable import PrettyTable
from typing import Optional
2022-04-05 23:25:07 +03:00
# For spinning wheel
from itertools import cycle
from time import sleep
2022-04-05 23:25:07 +03:00
app = typer.Typer()
2022-04-21 21:32:52 +03:00
dbs_app = typer.Typer()
app.add_typer(dbs_app, name='dbs', help='Control Managed databases')
2022-04-05 23:25:07 +03:00
servers_app = typer.Typer()
2022-04-21 21:11:02 +03:00
app.add_typer(servers_app, name='vds', help='Control VDS Servers, their snapshots and backups')
2022-04-10 03:43:24 +03:00
2022-04-08 15:20:33 +03:00
backups_app = typer.Typer()
2022-04-21 21:11:02 +03:00
servers_app.add_typer(backups_app, name='backup', help='Create/Delete backup')
2022-04-05 23:25:07 +03:00
2022-04-10 03:43:24 +03:00
snapshot_app = typer.Typer()
2022-04-21 21:11:02 +03:00
servers_app.add_typer(snapshot_app, name='snap', help='Create/Rollback/Delete snapshot')
2022-04-10 03:43:24 +03:00
2022-04-22 18:39:34 +03:00
2022-04-21 21:32:52 +03:00
class Dbaas:
"""
DataBases As A Service
"""
def list():
url = 'https://public-api.timeweb.com/api/v1/dbs'
result = requests.get(
url=url,
headers=reqHeader
)
if not result.ok:
return None
return result.json()
2022-04-22 18:39:34 +03:00
def get(db_id):
url = 'https://public-api.timeweb.com/api/v1/dbs/{db_id}'
result = requests.get(
url=url.format(db_id=db_id),
headers=reqHeader
)
if not result.ok:
return None
return result.json()
2022-04-05 23:25:07 +03:00
class Server:
2022-04-07 18:10:44 +03:00
"""
Not a server, but a backend.
Everything that works directly with API is here.
"""
2022-04-05 23:25:07 +03:00
@staticmethod
def get_list():
2022-04-07 18:10:44 +03:00
"""
Get list of VDSs'
"""
2022-04-05 23:25:07 +03:00
url = 'https://public-api.timeweb.com/api/v2/vds'
result = requests.get(
url=url,
headers=reqHeader
)
2022-04-07 15:38:11 +03:00
if not result.ok:
return None
2022-04-05 23:25:07 +03:00
return result.json()
@staticmethod
def get_vds(vds_id):
2022-04-07 18:10:44 +03:00
"""
Get VDS info
"""
url = "https://public-api.timeweb.com/api/v2/vds/{vds_id}".format(vds_id=vds_id)
result = requests.get(
url=url,
headers=reqHeader
)
if not result.ok:
return None
return result.json()
2022-04-05 23:25:07 +03:00
@staticmethod
def start(vds_id):
2022-04-07 18:10:44 +03:00
"""
Start VDS
"""
2022-04-05 23:25:07 +03:00
uri = "https://public-api.timeweb.com/api/v1/vds/{id}/{action}"
result = requests.post(
uri.format(id=vds_id, action='start'),
headers=reqHeader
)
if not result.ok:
return None
else:
return result.json()
@staticmethod
def stop(vds_id):
2022-04-07 18:10:44 +03:00
"""
Stop VDS
"""
2022-04-05 23:25:07 +03:00
uri = "https://public-api.timeweb.com/api/v1/vds/{id}/{action}"
result = requests.post(
uri.format(id=vds_id, action='shutdown'),
headers=reqHeader
)
if not result.ok:
return None
else:
return result.json()
2022-04-07 23:31:30 +03:00
@staticmethod
def clone(vds_id):
"""
Clone VDS
"""
uri = "https://public-api.timeweb.com/api/v1/vds/{id}/{action}"
result = requests.post(
uri.format(id=vds_id, action='clone'),
headers=reqHeader
)
if not result.ok:
return None
else:
return result.json()
2022-04-07 23:46:57 +03:00
@staticmethod
def remove(vds_id):
"""
Remove VDS
"""
uri = "https://public-api.timeweb.com/api/v1/vds/{id}"
result = requests.delete(
uri.format(id=vds_id),
headers=reqHeader
)
if not result.ok:
return None
else:
return result.json()
2022-04-08 15:20:33 +03:00
class Backups:
@staticmethod
def create(vds_id):
disk_id = Server.get_vds(vds_id)['server']['disk_stats']['disk_id']
uri = "https://public-api.timeweb.com/api/v1/backups/vds/{id}/drive/{disk_id}"
result = requests.post(
uri.format(id=vds_id, disk_id=disk_id),
headers=reqHeader
)
if result.ok:
return result.json()
else:
return None
2022-04-10 02:34:26 +03:00
@staticmethod
def list(vds_id):
disk_id = Server.get_vds(vds_id)['server']['disk_stats']['disk_id']
uri = "https://public-api.timeweb.com/api/v1/backups/vds/{id}/drive/{disk_id}"
result = requests.get(
uri.format(id=vds_id, disk_id=disk_id),
headers=reqHeader
)
if result.ok:
return result.json()
else:
return None
2022-04-10 02:49:27 +03:00
@staticmethod
def remove(vds_id, backup_id):
disk_id = Server.get_vds(vds_id)['server']['disk_stats']['disk_id']
uri = "https://public-api.timeweb.com/api/v1/backups/{backup_id}/vds/{id}/drive/{disk_id}"
result = requests.delete(
uri.format(id=vds_id, disk_id=disk_id, backup_id=backup_id),
headers=reqHeader
)
if result.ok:
return result.json()
else:
return None
2022-04-08 15:20:33 +03:00
2022-04-10 03:43:24 +03:00
class Snapshots:
@staticmethod
def get(vds_id):
uri = "https://public-api.timeweb.com/api/v1/restore-points/{vds_id}"
result = requests.get(
uri.format(vds_id=vds_id),
headers=reqHeader
)
if result.ok:
return result.json()
else:
return None
@staticmethod
def create(vds_id):
uri = "https://public-api.timeweb.com/api/v1/restore-points/{vds_id}/create"
result = requests.post(
uri.format(vds_id=vds_id),
headers=reqHeader
)
if result.ok:
return result.json()
else:
return None
@staticmethod
def remove(vds_id):
uri = "https://public-api.timeweb.com/api/v1/restore-points/{vds_id}/commit"
result = requests.post(
uri.format(vds_id=vds_id),
headers=reqHeader
)
if result.ok:
return result.json()
else:
return None
@staticmethod
def restore(vds_id):
uri = "https://public-api.timeweb.com/api/v1/restore-points/{vds_id}/rollback"
result = requests.post(
uri.format(vds_id=vds_id),
headers=reqHeader
)
if result.ok:
return result.json()
else:
return None
@snapshot_app.command("get")
def get_snap(vds_id: Optional[int] = typer.Argument(None)):
"""
Get snapshot
"""
if vds_id is None:
vds_list()
vds_id = input("Enter VDS ID: ")
result = Snapshots.get(vds_id)
if result is None:
print(typer.style("Error", fg=typer.colors.RED))
sys.exit(1)
else:
x = PrettyTable()
x.field_names = ["VDS ID", "ID", "Created" , "Expire"]
x.add_row([vds_id, result['restore_point']['id'], result['restore_point']['created_at'], result['restore_point']['expired_at']])
print(x)
@snapshot_app.command("create")
def create_snap(vds_id: Optional[int] = typer.Argument(None)):
"""
Create snapshot
"""
if vds_id is None:
vds_list()
vds_id = input("Enter VDS ID: ")
result = Snapshots.create(vds_id)
if result is None:
print(typer.style("Error", fg=typer.colors.RED))
sys.exit(1)
else:
print(typer.style("Success", fg=typer.colors.GREEN))
@snapshot_app.command("restore")
def rollback_snap(vds_id: Optional[int] = typer.Argument(None)):
"""
Restore VDS from snapshot
"""
if vds_id is None:
vds_list()
vds_id = input("Enter VDS ID: ")
result = Snapshots.restore(vds_id)
if result is None:
print(typer.style("Error", fg=typer.colors.RED))
sys.exit(1)
else:
print(typer.style("Success", fg=typer.colors.GREEN))
@snapshot_app.command("remove")
def remove_snap(vds_id: Optional[int] = typer.Argument(None)):
"""
Remove snapshot
"""
if vds_id is None:
vds_list()
vds_id = input("Enter VDS ID: ")
result = Snapshots.remove(vds_id)
if result is None:
print(typer.style("Error", fg=typer.colors.RED))
sys.exit(1)
else:
print(typer.style("Success", fg=typer.colors.GREEN))
2022-04-08 15:20:33 +03:00
@backups_app.command("create")
def create_backup(vds_id: Optional[int] = typer.Argument(None)):
"""
Create backup of main disk
"""
if vds_id is None:
vds_list()
vds_id = input("Enter VDS ID: ")
result = Backups.create(vds_id)
if result is None:
print(typer.style("Error", fg=typer.colors.RED))
sys.exit(1)
2022-04-10 02:34:26 +03:00
else:
print(typer.style("Success", fg=typer.colors.GREEN))
@backups_app.command("list")
def list_backup(vds_id: Optional[int] = typer.Argument(None)):
"""
List backups
"""
if vds_id is None:
vds_list()
vds_id = input("Enter VDS ID: ")
result = Backups.list(vds_id)
if result is None:
print(typer.style("Error", fg=typer.colors.RED))
sys.exit(1)
else:
x = PrettyTable()
2022-04-10 02:49:27 +03:00
x.field_names = ["id", "Date", "Size", "Cost", "Mounted", "status"]
2022-04-10 02:34:26 +03:00
for i in result['backups']:
2022-04-10 02:49:27 +03:00
x.add_row([i['id'], i['c_date'], i['drive_size'], i['cost_backup'], i['mounted'], i['status']])
2022-04-10 02:34:26 +03:00
print(x)
2022-04-08 15:20:33 +03:00
2022-04-05 23:25:07 +03:00
2022-04-10 02:49:27 +03:00
@backups_app.command("remove")
def remove_backup(vds_id: Optional[int] = typer.Argument(None), backup_id: int = typer.Option(...)):
"""
Remove backup of main disk
"""
if vds_id is None:
vds_list()
vds_id = input("Enter VDS ID: ")
result = Backups.remove(vds_id, backup_id)
if result is None:
print(typer.style("Error", fg=typer.colors.RED))
sys.exit(1)
else:
print(typer.style("Success", fg=typer.colors.GREEN))
2022-04-07 15:41:50 +03:00
@app.command("balance")
2022-04-05 23:25:07 +03:00
def get_balance():
2022-04-07 18:10:44 +03:00
"""
Show balance and Monthly costs
"""
2022-04-05 23:25:07 +03:00
response = requests.get("https://public-api.timeweb.com/api/v1/accounts/finances", headers=reqHeader)
2022-04-07 15:38:11 +03:00
if not response.ok:
print(typer.style("Error", fg=typer.colors.RED))
sys.exit(1)
2022-04-07 15:41:50 +03:00
else:
2022-04-07 16:08:28 +03:00
x = PrettyTable()
x.field_names = ["Balance", "Monthly cost"]
2022-04-07 15:41:50 +03:00
response = response.json()
2022-04-07 16:08:28 +03:00
x.add_row([response['finances']['balance'], response['finances']['monthly_cost']])
print(x)
2022-04-05 23:25:07 +03:00
2022-04-21 21:32:52 +03:00
@dbs_app.command("list")
def list_dbs():
"""
2022-04-22 18:39:34 +03:00
Show list of DBs
ID, State, Name, IP, local IP, Password, Type
2022-04-21 21:32:52 +03:00
"""
list_of_dbaas = Dbaas.list()
x = PrettyTable()
x.field_names = ['id', 'state', 'name', 'ip', 'local_ip', 'password', 'type']
if list_of_dbaas is None:
print(typer.style("Error", fg=typer.colors.RED))
sys.exit(1)
for i in list_of_dbaas['dbs']:
if i['status'] == 'started':
state = typer.style("Running", fg=typer.colors.GREEN)
2022-04-22 18:39:34 +03:00
# elif i['status'] == 'off':
2022-04-21 21:32:52 +03:00
# state = typer.style('Stopped', fg=typer.colors.RED)
else:
state = i['status']
2022-04-22 18:21:53 +03:00
if i['type'] == 'mysql':
type_of_db = 'MySQL 8'
elif i['type'] == 'mysql5':
type_of_db = 'MySQL 5.7'
elif i['type'] == 'postgres':
type_of_db = 'PostgreSQL 13'
else:
type_of_db = i['type']
2022-04-21 21:32:52 +03:00
x.add_row([
i['id'],
state,
i['name'],
i['ip'],
i['local_ip'],
i['password'],
2022-04-22 18:21:53 +03:00
type_of_db
2022-04-21 21:32:52 +03:00
])
print(x)
2022-04-05 23:25:07 +03:00
@servers_app.command("start")
def vds_start(vds_id: Optional[int] = typer.Argument(None)):
2022-04-07 18:10:44 +03:00
"""
2022-04-21 21:11:02 +03:00
Start VDS
2022-04-07 18:10:44 +03:00
"""
if vds_id is None:
vds_list()
vds_id = input("Enter VDS ID: ")
2022-04-05 23:25:07 +03:00
result = Server.start(vds_id)
2022-04-07 15:38:11 +03:00
if result is None:
print(typer.style("Error", fg=typer.colors.RED))
sys.exit(1)
for frame in cycle(r'-\|/'):
state = Server.get_vds(vds_id)
if state:
if state['server']['status'] == 'on':
print(typer.style("\nRunning", fg=typer.colors.GREEN))
break
print('\r', frame, sep='', end='', flush=True)
sleep(0.1)
2022-04-05 23:25:07 +03:00
@servers_app.command("stop")
def vds_stop(vds_id: Optional[int] = typer.Argument(None)):
2022-04-07 18:10:44 +03:00
"""
2022-04-21 21:11:02 +03:00
Stop VDS
2022-04-07 18:10:44 +03:00
"""
if vds_id is None:
vds_list()
vds_id = input("Enter VDS ID: ")
2022-04-05 23:25:07 +03:00
result = Server.stop(vds_id)
2022-04-07 15:38:11 +03:00
if result is None:
print(typer.style("Error", fg=typer.colors.RED))
sys.exit(1)
for frame in cycle(r'-\|/'):
state = Server.get_vds(vds_id)
if state:
if state['server']['status'] == 'off':
print(typer.style("\nStopped", fg=typer.colors.RED))
break
print('\r', frame, sep='', end='', flush=True)
sleep(0.1)
2022-04-05 23:25:07 +03:00
2022-04-07 23:31:30 +03:00
@servers_app.command("clone")
def vds_clone(vds_id: Optional[int] = typer.Argument(None)):
"""
2022-04-21 21:11:02 +03:00
Clone VDS
2022-04-07 23:31:30 +03:00
"""
if vds_id is None:
vds_list()
vds_id = input("Enter VDS ID: ")
new_vds = Server.clone(vds_id)
if new_vds is None:
print(typer.style("Error", fg=typer.colors.RED))
sys.exit(1)
else:
new_vds = new_vds['server']
for frame in cycle(r'-\|/'):
state = Server.get_vds(new_vds['id'])
if state:
if state['server']['status'] == 'on':
print(typer.style("\nCloned: " + new_vds['configuration']['caption'], fg=typer.colors.GREEN))
break
print('\r', frame, sep='', end='', flush=True)
sleep(0.1)
2022-04-07 23:46:57 +03:00
@servers_app.command("remove")
def vds_remove(vds_id: Optional[int] = typer.Argument(None)):
"""
Remove VDS
"""
if vds_id is None:
vds_list()
vds_id = input("Enter VDS ID: ")
result = Server.remove(vds_id)
if result is None:
print(typer.style("Error", fg=typer.colors.RED))
sys.exit(1)
for frame in cycle(r'-\|/'):
state = Server.get_vds(vds_id)
if not state.get('server'):
print(typer.style("\nDeleted", fg=typer.colors.RED))
break
print('\r', frame, sep='', end='', flush=True)
sleep(0.1)
2022-04-05 23:25:07 +03:00
@servers_app.command("list")
def vds_list():
2022-04-07 18:10:44 +03:00
"""
Show list of VDSes
ID, State, Name, IP, CPUs, Ram, Disk
"""
2022-04-05 23:25:07 +03:00
list_of_servers = Server.get_list()
x = PrettyTable()
x.field_names = ['id', 'state', 'name', 'ip', 'cpus', 'ram', 'disk']
2022-04-07 15:38:11 +03:00
if list_of_servers is None:
print(typer.style("Error", fg=typer.colors.RED))
sys.exit(1)
2022-04-05 23:25:07 +03:00
for i in list_of_servers['servers']:
if i['status'] == 'on':
state = typer.style("Running", fg=typer.colors.GREEN)
elif i['status'] == 'off':
state = typer.style('Stopped', fg=typer.colors.RED)
else:
state = i['status']
2022-04-09 00:44:59 +03:00
x.add_row([
i['id'],
state,
i['name'],
i['ip'],
i['configuration']['cpu'],
i['configuration']['ram'],
i['configuration']['disk_size']
])
2022-04-07 23:31:50 +03:00
print(x)
2022-04-05 23:25:07 +03:00
2022-04-07 16:00:14 +03:00
def auth(based):
2022-04-05 23:25:07 +03:00
"""
2022-04-07 18:10:44 +03:00
Get access token based on base64'ed login:password
2022-04-05 23:25:07 +03:00
"""
2022-04-07 16:00:14 +03:00
headers = {"Authorization": "Basic " + based}
2022-04-05 23:25:07 +03:00
result = requests.post(
'https://public-api.timeweb.com/api/v2/auth',
json=dict(refresh_token="string"),
headers=headers
)
if not result.ok:
return None
else:
result = result.content.decode('utf-8')
result = json.loads(result)
result = result['access_token']
return result
def get_api_key():
2022-04-07 18:10:44 +03:00
"""
Load base64'ed login:pass and get access token
"""
2022-04-05 23:25:07 +03:00
config = configparser.ConfigParser()
config.read(os.path.join(os.getenv('HOME'), '.config', 'twvdscli.ini'))
2022-04-07 16:00:14 +03:00
based = config.get('api', 'key', fallback=None)
if based is None:
2022-04-05 23:25:07 +03:00
login = input("Enter login ")
passwd = input("Enter passwd ")
2022-04-07 16:00:14 +03:00
based = base64.b64encode(str(login + ":" + passwd).encode('utf-8'))
based = based.decode('utf-8')
2022-04-05 23:25:07 +03:00
config.add_section('api')
2022-04-07 16:00:14 +03:00
config.set('api', 'key', based)
2022-04-05 23:25:07 +03:00
with open(os.path.join(os.getenv('HOME'), '.config', 'twvdscli.ini'), 'w') as configfile:
config.write(configfile)
2022-04-07 16:00:14 +03:00
result = auth(based)
2022-04-05 23:25:07 +03:00
return result
2022-04-09 00:43:00 +03:00
2022-04-08 21:27:45 +03:00
def main():
2022-04-05 23:25:07 +03:00
apikey = get_api_key()
2022-04-07 16:00:14 +03:00
if apikey is None:
print(typer.style("Auth Error", fg=typer.colors.RED))
sys.exit(1)
2022-04-09 00:43:00 +03:00
reqHeader['Authorization'] += apikey
2022-04-05 23:25:07 +03:00
app()
2022-04-08 21:27:45 +03:00
2022-04-09 00:43:00 +03:00
2022-04-08 21:27:45 +03:00
if __name__ == '__main__':
2022-04-09 00:43:00 +03:00
reqHeader = {"Authorization": "Bearer "}
2022-04-08 21:27:45 +03:00
main()