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
|
2022-04-07 17:05:19 +03:00
|
|
|
from typing import Optional
|
2022-04-05 23:25:07 +03:00
|
|
|
|
2022-04-07 17:21:11 +03:00
|
|
|
# For spinning wheel
|
|
|
|
from itertools import cycle
|
|
|
|
from time import sleep
|
|
|
|
|
|
|
|
|
2022-04-05 23:25:07 +03:00
|
|
|
app = typer.Typer()
|
|
|
|
servers_app = typer.Typer()
|
|
|
|
app.add_typer(servers_app, name='vds')
|
2022-04-08 15:20:33 +03:00
|
|
|
backups_app = typer.Typer()
|
|
|
|
servers_app.add_typer(backups_app, name='backup')
|
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()
|
|
|
|
|
2022-04-07 17:21:11 +03:00
|
|
|
@staticmethod
|
|
|
|
def get_vds(vds_id):
|
2022-04-07 18:10:44 +03:00
|
|
|
"""
|
|
|
|
Get VDS info
|
|
|
|
"""
|
2022-04-07 17:21:11 +03:00
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
@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)
|
|
|
|
print(result.json())
|
|
|
|
|
2022-04-05 23:25:07 +03:00
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
@servers_app.command("start")
|
2022-04-07 17:05:19 +03:00
|
|
|
def vds_start(vds_id: Optional[int] = typer.Argument(None)):
|
2022-04-07 18:10:44 +03:00
|
|
|
"""
|
|
|
|
Start VDS, show cute spinner until VDS starts
|
|
|
|
"""
|
2022-04-07 17:05:19 +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)
|
|
|
|
|
2022-04-07 17:21:11 +03:00
|
|
|
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")
|
2022-04-07 17:05:19 +03:00
|
|
|
def vds_stop(vds_id: Optional[int] = typer.Argument(None)):
|
2022-04-07 18:10:44 +03:00
|
|
|
"""
|
|
|
|
Stop VDS, show cute spinner until VDS stops
|
|
|
|
"""
|
2022-04-07 17:05:19 +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)
|
|
|
|
|
2022-04-07 17:21:11 +03:00
|
|
|
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)):
|
|
|
|
"""
|
|
|
|
Clone VDS, show cute spinner until VDS stops
|
|
|
|
"""
|
|
|
|
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()
|
2022-04-07 16:11:06 +03:00
|
|
|
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-07 16:11:06 +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-08 22:30:56 +03:00
|
|
|
|
|
|
|
if __name__ == '__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-05 23:25:07 +03:00
|
|
|
reqHeader = {"Authorization": "Bearer " + apikey}
|
|
|
|
app()
|