From d045d70609b2f44bc841fc1891db652c1639eb5a Mon Sep 17 00:00:00 2001 From: Ahmad Bilal Khalid Date: Wed, 17 Jul 2019 19:58:39 +0500 Subject: [PATCH] much better cli --- .gitignore | 1 + Pipfile | 1 + Pipfile.lock | 10 +++- commands/__init__ | 0 commands/host.py | 30 +++++++++++ commands/image.py | 30 +++++++++++ commands/user.py | 31 +++++++++++ commands/vm.py | 75 ++++++++++++++++++++++++++ helper.py | 15 ++++++ specs.json | 4 ++ ucloud.py | 134 +++++----------------------------------------- 11 files changed, 210 insertions(+), 121 deletions(-) create mode 100644 commands/__init__ create mode 100644 commands/host.py create mode 100644 commands/image.py create mode 100644 commands/user.py create mode 100644 commands/vm.py create mode 100644 helper.py create mode 100644 specs.json diff --git a/.gitignore b/.gitignore index c28386c..bd69026 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ venv/ .idea/ .vscode/ .env +__pycache__/ diff --git a/Pipfile b/Pipfile index 0abd36c..b8badd7 100644 --- a/Pipfile +++ b/Pipfile @@ -9,6 +9,7 @@ verify_ssl = true requests = "*" python-decouple = "*" pyotp = "*" +click = "*" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index a95d37a..3190413 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "e9d4e4efe74bd8386357563e682181c69cbbc66d40fc02931fd0f730ae6053e4" + "sha256": "1f6e0448a1e04906885393b4ff57423363593680a7b6f73a5ed1f514496bb1f3" }, "pipfile-spec": 6, "requires": { @@ -30,6 +30,14 @@ ], "version": "==3.0.4" }, + "click": { + "hashes": [ + "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", + "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" + ], + "index": "pypi", + "version": "==7.0" + }, "idna": { "hashes": [ "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", diff --git a/commands/__init__ b/commands/__init__ new file mode 100644 index 0000000..e69de29 diff --git a/commands/host.py b/commands/host.py new file mode 100644 index 0000000..b15e0b4 --- /dev/null +++ b/commands/host.py @@ -0,0 +1,30 @@ +import click +import json +import requests + +from decouple import config +from helper import OTPCredentials + + +@click.group() +def host(): + pass + + +@host.command("add") +@click.option("--name", required=True) +@click.option("--realm", required=True) +@click.option("--seed", required=True) +@click.option("--specs", required=True) +@click.option("--hostname", required=True) +def add_host(name, realm, seed, specs, hostname): + with open(specs, "r") as specs_f: + specs = json.loads(specs_f.read()) + data = { + **OTPCredentials(name, realm, seed).get_json(), + "specs": specs, + "hostname": hostname, + } + r = requests.post(f"{config('UCLOUD_API_SERVER')}/host/create", json=data) + + print(json.loads(r.content)) diff --git a/commands/image.py b/commands/image.py new file mode 100644 index 0000000..f355a9a --- /dev/null +++ b/commands/image.py @@ -0,0 +1,30 @@ +import click +import json +import requests + +from decouple import config +from helper import OTPCredentials + + +@click.group() +def image(): + pass + + +@image.command("list") +@click.option("--public", is_flag=True) +@click.option("--private", is_flag=True) +def list(public, private): + if public: + r = requests.get(f"{config('UCLOUD_API_SERVER')}/image/list-public") + print(json.loads(r.content)) + + +@image.command("create-from-file") +@click.option("--name", required=True) +@click.option("--uuid", required=True) +@click.option("--image_store_name", required=True) +def create_from_file(name, uuid, image_store_name): + data = {"name": name, "uuid": uuid, "image_store": image_store_name} + r = requests.post(f"{config('UCLOUD_API_SERVER')}/image/create", data) + print(r.content.decode("utf-8")) diff --git a/commands/user.py b/commands/user.py new file mode 100644 index 0000000..433e2e9 --- /dev/null +++ b/commands/user.py @@ -0,0 +1,31 @@ +import click +import json +import requests + +from decouple import config +from helper import OTPCredentials + + +@click.group() +def user(): + pass + + +@user.command("list-files") +@click.option("--name", required=True) +@click.option("--realm", required=True) +@click.option("--seed", required=True) +def list_files(name, realm, seed): + data = OTPCredentials(name, realm, seed).get_json() + r = requests.get(f"{config('UCLOUD_API_SERVER')}/user/files", json=data) + print(json.loads(r.content)) + + +@user.command("list-vms") +@click.option("--name", required=True) +@click.option("--realm", required=True) +@click.option("--seed", required=True) +def list_vms(name, realm, seed): + data = OTPCredentials(name, realm, seed).get_json() + r = requests.get(f"{config('UCLOUD_API_SERVER')}/user/vms", json=data) + print(json.loads(r.content)) diff --git a/commands/vm.py b/commands/vm.py new file mode 100644 index 0000000..7301efd --- /dev/null +++ b/commands/vm.py @@ -0,0 +1,75 @@ +import click +import json +import requests + +from decouple import config +from helper import OTPCredentials + + +def vm_action(action, otp, vmid): + data = {**otp.get_json(), "vmid": vmid} + r = requests.post(f"{config('UCLOUD_API_SERVER')}/vm/{action}", json=data) + return r + + +@click.group() +def vm(): + pass + + +@vm.command("create") +@click.option("--name", required=True) +@click.option("--realm", required=True) +@click.option("--seed", required=True) +@click.option("--specs", required=True) +@click.option("--image_uuid", required=True) +def create(name, realm, seed, specs, image_uuid): + with open(specs, "r") as specs_f: + specs = json.loads(specs_f.read()) + data = { + **OTPCredentials(name, realm, seed).get_json(), + "specs": specs, + "image_uuid": image_uuid, + } + r = requests.post(f"{config('UCLOUD_API_SERVER')}/vm/create", json=data) + print(json.loads(r.content)) + + +@vm.command("start") +@click.option("--name", required=True) +@click.option("--realm", required=True) +@click.option("--seed", required=True) +@click.option("--vmid", required=True) +def start(name, realm, seed, vmid): + r = vm_action("start", OTPCredentials(name, realm, seed), vmid) + print(json.loads(r.content)) + + +@vm.command("suspend") +@click.option("--name", required=True) +@click.option("--realm", required=True) +@click.option("--seed", required=True) +@click.option("--vmid", required=True) +def suspend(name, realm, seed, vmid): + r = vm_action("suspend", OTPCredentials(name, realm, seed), vmid) + print(json.loads(r.content)) + + +@vm.command("resume") +@click.option("--name", required=True) +@click.option("--realm", required=True) +@click.option("--seed", required=True) +@click.option("--vmid", required=True) +def resume(name, realm, seed, vmid): + r = vm_action("resume", OTPCredentials(name, realm, seed), vmid) + print(json.loads(r.content)) + + +@vm.command("shutdown") +@click.option("--name", required=True) +@click.option("--realm", required=True) +@click.option("--seed", required=True) +@click.option("--vmid", required=True) +def shutdown(name, realm, seed, vmid): + r = vm_action("shutdown", OTPCredentials(name, realm, seed), vmid) + print(json.loads(r.content)) diff --git a/helper.py b/helper.py new file mode 100644 index 0000000..9b5444f --- /dev/null +++ b/helper.py @@ -0,0 +1,15 @@ +import click + +from dataclasses import dataclass +from pyotp import TOTP + + +@dataclass +class OTPCredentials: + name: str + realm: str + seed: str + + def get_json(self): + r = {"name": self.name, "realm": self.realm, "token": TOTP(self.seed).now()} + return r diff --git a/specs.json b/specs.json new file mode 100644 index 0000000..589b99f --- /dev/null +++ b/specs.json @@ -0,0 +1,4 @@ +{ + "cpu": 2, + "ram": "2GB" +} diff --git a/ucloud.py b/ucloud.py index 7acbc6c..b0d998d 100644 --- a/ucloud.py +++ b/ucloud.py @@ -1,127 +1,21 @@ import argparse -import requests -import json +import click -from decouple import config -from pyotp import TOTP - -def add_otp(parser): - parser.add_argument("--name", required=True) - parser.add_argument("--realm", required=True) - parser.add_argument("--seed", required=True) - return parser - -def add_vmid(parser): - parser.add_argument("--vmid", required=True) - return parser - -argparser = argparse.ArgumentParser() -subparser = argparser.add_subparsers(dest="command") - -create_image_parser = subparser.add_parser("create-image-from-file") -create_image_parser.add_argument("--uuid", required=True) -create_image_parser.add_argument("--name", required=True) -create_image_parser.add_argument("--image-store", - dest="image_store_name", - required=True) - -images_parser = subparser.add_parser("images") -images_parser.add_argument("--list", - choices=["public", "private"], - required=True) - -user_parser = subparser.add_parser("user") -user_subparser = user_parser.add_subparsers(dest="usercommand") - -user_list_vms_parser = user_subparser.add_parser("list") -user_list_vms_parser.add_argument("--files", action='store_true') -user_list_vms_parser.add_argument("--vms", action='store_true') -add_otp(user_list_vms_parser) - -vm_parser = subparser.add_parser("vm") -vm_subparser = vm_parser.add_subparsers(dest="vmcommand") - -vm_create_parser = vm_subparser.add_parser("create") -add_otp(vm_create_parser) -vm_create_parser.add_argument("--specs_file", required=True) -vm_create_parser.add_argument("--image_uuid", required=True) +from commands.vm import vm +from commands.user import user +from commands.host import host +from commands.image import image -vm_start_parser = vm_subparser.add_parser("start") -vm_suspend_parser = vm_subparser.add_parser("suspend") -vm_resume_parser = vm_subparser.add_parser("resume") -vm_shutdown_parser = vm_subparser.add_parser("shutdown") -vm_status_parser = vm_subparser.add_parser("status") +@click.group() +def entry_point(): + pass -add_vmid(add_otp(vm_start_parser)) -add_vmid(add_otp(vm_suspend_parser)) -add_vmid(add_otp(vm_resume_parser)) -add_vmid(add_otp(vm_shutdown_parser)) -add_vmid(add_otp(vm_status_parser)) -args = argparser.parse_args() +entry_point.add_command(vm) +entry_point.add_command(user) +entry_point.add_command(image) +entry_point.add_command(host) -if args.command == "create-image-from-file": - name = args.name - uuid = args.uuid - image_store_name = args.image_store_name - data = { - "name": args.name, - "uuid": uuid, - "image_store": image_store_name - } - r = requests.post(f"{config('UCLOUD_API_SERVER')}/image/create", data) - print(r.content.decode("utf-8")) - -elif args.command == "images": - if args.list == "public": - r = requests.get(f"{config('UCLOUD_API_SERVER')}/image/list-public") - print(json.loads(r.content)) - -elif args.command == "vm": - if args.vmcommand == "create": - with open(args.specs_file, "r") as specs_f: - specs = json.loads(specs_f.read()) - data = { - "name": args.name, - "realm": args.realm, - "token": TOTP(args.seed).now(), - "specs": specs, - "image_uuid": args.image_uuid - } - r = requests.post(f"{config('UCLOUD_API_SERVER')}/vm/create", - json=data) - print(json.loads(r.content)) - elif args.vmcommand in ["start", "suspend", "resume", - "status", "shutdown"]: - data = { - "name": args.name, - "realm": args.realm, - "token": TOTP(args.seed).now(), - "vmid": args.vmid, - } - if args.vmcommand == "status": - r = requests.get(f"{config('UCLOUD_API_SERVER')}/vm/{args.vmcommand}", json=data) - else: - r = requests.post(f"{config('UCLOUD_API_SERVER')}/vm/{args.vmcommand}", json=data) - print(json.loads(r.content)) - -elif args.command == "user": - if args.usercommand == "list" and args.vms: - data = { - "name": args.name, - "realm": args.realm, - "token": TOTP(args.seed).now() - } - r = requests.get(f"{config('UCLOUD_API_SERVER')}/user/vms", - json=data) - print(json.loads(r.content)) - elif args.usercommand == "list" and args.files: - data = { - "name": args.name, - "realm": args.realm, - "token": TOTP(args.seed).now() - } - r = requests.get(f"{config('UCLOUD_API_SERVER')}/user/files", - json=data) - print(json.loads(r.content)) \ No newline at end of file +if __name__ == "__main__": + entry_point()