From 931e005ccdfa6280e4578ece1b3dfd02827ef069 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Fri, 10 May 2024 15:52:10 +0200 Subject: [PATCH] Add a `thebacknd create` subcommand. --- lib/thebacknd/__init__.py | 75 ++++++++++++++++++++++ packages/thebacknd/create/__main__.py | 65 +------------------ packages/thebacknd/destroy-all/__main__.py | 2 +- 3 files changed, 78 insertions(+), 64 deletions(-) diff --git a/lib/thebacknd/__init__.py b/lib/thebacknd/__init__.py index 11ddc65..eef4d86 100644 --- a/lib/thebacknd/__init__.py +++ b/lib/thebacknd/__init__.py @@ -4,6 +4,7 @@ import datetime import hmac import hashlib +import json import os import pydo from types import SimpleNamespace @@ -98,12 +99,74 @@ def list_droplets(): r[x["id"]]["debug"] = x return r + +# Find the smallest number (starting from 1) that is not in a given list. +def smallest_missing_number(numbers): + current = 1 + while True: + if current not in numbers: + return current + current += 1 + + +def existing_droplet_numbers(): + xs = do_client.droplets.list(tag_name="thebacknd") + # The names looks like "thebacknd-123". + ns = [int(x["name"].split("-")[-1]) for x in xs["droplets"]] + return ns + + +def create_droplet(nix_toplevel, nix_binary): + numbers = existing_droplet_numbers() + n = smallest_missing_number(numbers) + vm_id = create_vm_id() + + user_data_content = {} + per_vm_secret = create_killcode(vm_id) + user_data_content["vm_id"] = vm_id + user_data_content["vm_killcode"] = per_vm_secret + # doctl serverless functions get thebacknd/destroy-self --url + # TODO Must be automatically discovered. + user_data_content[ + "destroy_url" + ] = "https://faas-ams3-2a2df116.doserverless.co/api/v1/web/fn-85df16d9-63e4-4388-875f-28a44e683171/thebacknd/destroy-self" + + if nix_toplevel: + user_data_content["nix_toplevel"] = nix_toplevel + if nix_binary: + user_data_content["nix_binary"] = nix_binary + user_data_content["nix_cache"] = conf.nix_cache + user_data_content["nix_trusted_key"] = conf.nix_trusted_key + user_data_content["nix_cache_key_id"] = conf.nix_cache_key_id + user_data_content["nix_cache_key_secret"] = conf.nix_cache_key_secret + + droplet_req = { + "name": "thebacknd-{0}".format(n), + "region": conf.vm_region, + "size": conf.vm_size, + "image": conf.vm_image, + "ssh_keys": [ssh_key], + # We pass the file content with user_data instead of + # user_data_file because I don't know how to get content + # back within the VM. With user_data, we can simple query + # the metadata service at + # http://169.254.169.254/metadata/v1/user-data. + "user_data": json.dumps(user_data_content, indent=2), + "tags": ["thebacknd", vm_id], + } + c = do_client.droplets.create(body=droplet_req) + return { + "create": c, + } + + def destroy_all_droplets(): d = do_client.droplets.destroy_by_tag(tag_name="thebacknd") return { "destroy": d, } + def cli(): """ Drive the above functions from the command-line (i.e. locally instead of @@ -121,6 +184,13 @@ def run_list(): xs = list_droplets() pprint.pp(xs) + def run_create(nix_toplevel, nix_binary): + r = create_droplet( + nix_toplevel=nix_toplevel, + nix_binary=nix_binary, + ) + pprint.pp(r) + def run_destroy_all(): r = destroy_all_droplets() pprint.pp(r) @@ -131,6 +201,11 @@ def run_destroy_all(): parser_list = subparsers.add_parser('list', help='List virtual machines') parser_list.set_defaults(func=lambda args: run_list()) + parser_create = subparsers.add_parser('create', help='Create a virtual machine') + parser_create.add_argument("--toplevel", type=str, default=None, help="Specify a toplevel to deploy.") + parser_create.add_argument("--binary", type=str, default=None, help="Specify a binary to run.") + parser_create.set_defaults(func=lambda args: run_create(nix_toplevel=args.toplevel, nix_binary=args.binary)) + parser_destroy_all = subparsers.add_parser('destroy-all', help='Destroy all virtual machines') parser_destroy_all.set_defaults(func=lambda args: run_destroy_all()) diff --git a/packages/thebacknd/create/__main__.py b/packages/thebacknd/create/__main__.py index 9baa7c8..4057529 100644 --- a/packages/thebacknd/create/__main__.py +++ b/packages/thebacknd/create/__main__.py @@ -1,70 +1,9 @@ -import json import thebacknd -# Find the smallest number (starting from 1) that is not in a given list. -def smallest_missing_number(numbers): - current = 1 - while True: - if current not in numbers: - return current - current += 1 - - -def existing_droplet_numbers(): - xs = thebacknd.do_client.droplets.list(tag_name="thebacknd") - # The names looks like "thebacknd-123". - ns = [int(x["name"].split("-")[-1]) for x in xs["droplets"]] - return ns - - -def create_droplet(nix_toplevel, nix_binary): - numbers = existing_droplet_numbers() - n = smallest_missing_number(numbers) - vm_id = thebacknd.create_vm_id() - - user_data_content = {} - per_vm_secret = thebacknd.create_killcode(vm_id) - user_data_content["vm_id"] = vm_id - user_data_content["vm_killcode"] = per_vm_secret - # doctl serverless functions get thebacknd/destroy-self --url - # TODO Must be automatically discovered. - user_data_content[ - "destroy_url" - ] = "https://faas-ams3-2a2df116.doserverless.co/api/v1/web/fn-85df16d9-63e4-4388-875f-28a44e683171/thebacknd/destroy-self" - - if nix_toplevel: - user_data_content["nix_toplevel"] = nix_toplevel - if nix_binary: - user_data_content["nix_binary"] = nix_binary - user_data_content["nix_cache"] = thebacknd.conf.nix_cache - user_data_content["nix_trusted_key"] = thebacknd.conf.nix_trusted_key - user_data_content["nix_cache_key_id"] = thebacknd.conf.nix_cache_key_id - user_data_content["nix_cache_key_secret"] = thebacknd.conf.nix_cache_key_secret - - droplet_req = { - "name": "thebacknd-{0}".format(n), - "region": thebacknd.conf.vm_region, - "size": thebacknd.conf.vm_size, - "image": thebacknd.conf.vm_image, - "ssh_keys": [thebacknd.ssh_key], - # We pass the file content with user_data instead of - # user_data_file because I don't know how to get content - # back within the VM. With user_data, we can simple query - # the metadata service at - # http://169.254.169.254/metadata/v1/user-data. - "user_data": json.dumps(user_data_content, indent=2), - "tags": ["thebacknd", vm_id], - } - r = thebacknd.do_client.droplets.create(body=droplet_req) - return r - - def main(event): - c = create_droplet( + r = thebacknd.create_droplet( nix_toplevel=event.get("nix_toplevel", None), nix_binary=event.get("nix_binary", None), ) - return { - "create": c, - } + return r diff --git a/packages/thebacknd/destroy-all/__main__.py b/packages/thebacknd/destroy-all/__main__.py index 3f6dadf..d488a82 100644 --- a/packages/thebacknd/destroy-all/__main__.py +++ b/packages/thebacknd/destroy-all/__main__.py @@ -1,6 +1,6 @@ import thebacknd -def main(args): +def main(event): r = thebacknd.destroy_all_droplets() return r