From a5eb43d65c93c0c1a7190171455c4fd8e196ab42 Mon Sep 17 00:00:00 2001 From: Zev Averbach Date: Fri, 31 Mar 2023 22:47:06 +0200 Subject: [PATCH] broke things into modules, fixed a number of things. --- publify/api.py | 132 ++++++++++++++++ publify/file_doers.py | 28 ++++ publify/publify.py | 357 +++++++++++++----------------------------- 3 files changed, 267 insertions(+), 250 deletions(-) create mode 100644 publify/api.py create mode 100644 publify/file_doers.py diff --git a/publify/api.py b/publify/api.py new file mode 100644 index 0000000..393b02b --- /dev/null +++ b/publify/api.py @@ -0,0 +1,132 @@ +import os +import pathlib as pl +import requests +import sys + +from .file_doers import make_a_zip_file, make_sure_theres_a_nested_folder_and_index_html + +try: + NETLIFY_TOKEN = os.environ["NETLIFY_TOKEN"] +except KeyError: + print("Please set the environment variable NETLIFY_TOKEN") + sys.exit(1) + +NETLIFY_DOMAINS = os.getenv("NETLIFY_DOMAINS") or "" +if NETLIFY_DOMAINS: + NETLIFY_DOMAINS = NETLIFY_DOMAINS.split(",") + +AUTH_HEADER = {"Authorization": f"Bearer {NETLIFY_TOKEN}"} + + +class NoResult(Exception): + pass + + +class DomainInUse(Exception): + pass + + +class TooManyResults(Exception): + pass + + +def deploy_page_to_netlify(dirpath: pl.Path, custom_domain: str | None = None) -> None: + make_sure_theres_a_nested_folder_and_index_html(dirpath) + + URL = "https://api.netlify.com/api/v1/sites" + headers = { + "Content-Type": "application/zip", + } | AUTH_HEADER + zip_file = make_a_zip_file(dirpath) + response = requests.post( + URL, + data=zip_file.read_bytes(), + headers=headers, + ) + zip_file.unlink() + if not response.ok: + raise Exception("something went wrong") + rj = response.json() + print("the site is published: " + rj["url"]) + if custom_domain is not None: + check_that_custom_domain_is_not_in_use(custom_domain) + set_to_custom_domain(rj["id"], custom_domain) + + +def remove_custom_domain(site_id: str) -> None: + URL = f"https://app.netlify.com/access-control/bb-api/api/v1/sites/{site_id}" + response = requests.put( + URL, + json={"custom_domain": None}, + headers=AUTH_HEADER, + ) + if not response.ok: + print(response.reason) + raise Exception("something went wrong with removing the custom domain") + + +def get_all_sites(): + URL = "https://api.netlify.com/api/v1/sites" + response = requests.get(URL, headers=AUTH_HEADER) + if not response.ok: + raise Exception("something went wrong") + return response.json() + + +def delete_site(site_id: str) -> None: + URL = f"https://api.netlify.com/api/v1/sites/{site_id}" + response = requests.delete(URL, headers=AUTH_HEADER) + if not response.ok: + raise Exception("something went wrong") + + +def set_to_custom_domain(site_id: str, custom_domain: str) -> None: + URL = f"https://app.netlify.com/access-control/bb-api/api/v1/sites/{site_id}" + response = requests.put( + URL, + json={"custom_domain": custom_domain}, + headers=AUTH_HEADER, + ) + if not response.ok: + print(response.reason) + raise Exception("something went wrong with setting the custom domain") + print(f"the site is published at {custom_domain}.") + + +def get_site_id_from_netlify_domain(domain: str) -> tuple[str, str]: + orig_domain = domain + if not domain.startswith("http://"): + domain = f"http://{domain}" + candidate = None + for site in get_all_sites(): + if site["url"].startswith(domain): + if candidate is not None: + raise TooManyResults( + f"too many results for partial domain '{orig_domain}'" + ) + candidate = site["id"], site["url"] + if candidate is None: + raise NoResult(f"no result for partial domain '{orig_domain}'") + return candidate + + +def get_site_id_from_custom_domain(custom_domain: str) -> tuple[str, str]: + for site in get_all_sites(): + if site["custom_domain"] == custom_domain: + return site["id"], site["custom_domain"] + raise NoResult + + +def check_that_custom_domain_is_not_in_use(custom_domain: str) -> None: + candidate = None + for site in get_all_sites(): + if site["custom_domain"] is not None and site["custom_domain"].startswith( + custom_domain + ): + if candidate is not None: + raise TooManyResults( + f"too many results for partial domain '{custom_domain}', it's ambiguous" + ) + candidate = site["custom_domain"] + if candidate is not None: + raise DomainInUse(f"'{candidate}' is already in use") diff --git a/publify/file_doers.py b/publify/file_doers.py new file mode 100644 index 0000000..fc55cfb --- /dev/null +++ b/publify/file_doers.py @@ -0,0 +1,28 @@ +import pathlib as pl +import shutil +import uuid + + +class NoNestedFolder(Exception): + pass + + +class NoIndexHtml(Exception): + pass + + +def make_a_zip_file(dirpath: pl.Path) -> pl.Path: + + zipfile_filename = str(uuid.uuid4()) + shutil.make_archive(zipfile_filename, "zip", dirpath) + return pl.Path(zipfile_filename + ".zip") + + +def make_sure_theres_a_nested_folder_and_index_html(dirpath: pl.Path) -> None: + if "folder" not in [d.name for d in list(dirpath.iterdir())]: + raise NoNestedFolder( + "Please create a folder in the root of the site's directory, and put all the files in there." + ) + + if "index.html" not in [i.name for i in list((dirpath / "folder").iterdir())]: + raise NoIndexHtml diff --git a/publify/publify.py b/publify/publify.py index 123e163..55bd163 100644 --- a/publify/publify.py +++ b/publify/publify.py @@ -1,25 +1,36 @@ -import os import pathlib as pl -import shutil import sys -from time import sleep -import uuid -import requests +from .api import ( + check_that_custom_domain_is_not_in_use, + get_site_id_from_custom_domain, + get_site_id_from_netlify_domain, + remove_custom_domain, + set_to_custom_domain, + get_all_sites, + delete_site, + deploy_page_to_netlify, + NoResult, + NETLIFY_DOMAINS, + DomainInUse, + TooManyResults, +) -try: - NETLIFY_TOKEN = os.environ["NETLIFY_TOKEN"] -except KeyError: - print("Please set the environment variable NETLIFY_TOKEN") - sys.exit(1) -AUTH_HEADER = {"Authorization": f"Bearer {NETLIFY_TOKEN}"} -NETLIFY_DOMAINS = os.getenv("NETLIFY_DOMAINS") or "" -if NETLIFY_DOMAINS: - NETLIFY_DOMAINS = NETLIFY_DOMAINS.split(",") +from .file_doers import NoNestedFolder -class DomainInUse(Exception): - pass +def cli_display_help() -> None: + print() + print( + """Usage: + pub help + pub [] -------------------------------->deploy a site + pub list ------------------------------->list all sites + pub custom -------------------------->set a custom domain + pub remove-custom ----------------------->remove a custom domain + pub delete/remove -------------------------------->delete a site + """ + ) class InvalidArguments(Exception): @@ -30,119 +41,40 @@ class NoCustomDomains(Exception): pass -class NoNestedFolder(Exception): - pass - - -class NoIndexHtml(Exception): - pass - - -class NoResult(Exception): - pass - - -def make_a_zip_file(dirpath: pl.Path) -> pl.Path: - - zipfile_filename = str(uuid.uuid4()) - shutil.make_archive(zipfile_filename, "zip", dirpath) - return pl.Path(zipfile_filename + ".zip") - - -def make_sure_theres_a_nested_folder_and_index_html(dirpath: pl.Path) -> None: - if "folder" not in [d.name for d in list(dirpath.iterdir())]: - raise NoNestedFolder( - "Please create a folder in the root of the site's directory, and put all the files in there." - ) - - if "index.html" not in [i.name for i in list((dirpath / "folder").iterdir())]: - raise NoIndexHtml - - -def deploy_page_to_netlify(dirpath: pl.Path, custom_domain: str | None = None) -> None: - make_sure_theres_a_nested_folder_and_index_html(dirpath) - - URL = "https://api.netlify.com/api/v1/sites" - headers = { - "Content-Type": "application/zip", - } | AUTH_HEADER - zip_file = make_a_zip_file(dirpath) - response = requests.post( - URL, - data=zip_file.read_bytes(), - headers=headers, - ) - zip_file.unlink() - if not response.ok: - raise Exception("something went wrong") - rj = response.json() - print("the site is published: " + rj["url"]) +def cli_deploy_site(root_dir: str, custom_domain: str | None) -> None: + """ + Deploy a folder of web pages to Netlify + """ if custom_domain is not None: - check_that_custom_domain_is_not_in_use(custom_domain) - set_to_custom_domain(rj["id"], custom_domain) + if len(NETLIFY_DOMAINS) == 0: + raise NoCustomDomains( + "No custom domains configured in NETLIFY_DOMAINS, and a custom domain was provided" + ) + else: + check_that_custom_domain_is_not_in_use(custom_domain) + + if ( + custom_domain is not None + and len(NETLIFY_DOMAINS) == 1 + and custom_domain.count(".") == 0 + ): + custom_domain = f"{custom_domain}.{NETLIFY_DOMAINS[0]}" + deploy_page_to_netlify(pl.Path(root_dir), custom_domain) -def remove_custom_domain(site_id: str) -> None: - URL = f"https://app.netlify.com/access-control/bb-api/api/v1/sites/{site_id}" - response = requests.put( - URL, - json={"custom_domain": None}, - headers=AUTH_HEADER, - ) - if not response.ok: - print(response.reason) - raise Exception("something went wrong with removing the custom domain") - - -def get_site_id_from_netlify_domain(domain: str) -> str: - URL = "https://api.netlify.com/api/v1/sites" - response = requests.get(URL, headers=AUTH_HEADER) - if not response.ok: - raise Exception("something went wrong") - rj = response.json() - if not domain.startswith("http://"): - domain = f"http://{domain}" - print(f"trying to find {domain}") - for site in rj: - if site["url"] == domain: - return site["id"] - raise NoResult - - -def get_site_id_from_custom_domain(custom_domain: str) -> str: - URL = "https://api.netlify.com/api/v1/sites" - response = requests.get(URL, headers=AUTH_HEADER) - if not response.ok: - raise Exception("something went wrong") - rj = response.json() - for site in rj: - if site["custom_domain"] == custom_domain: - return site["id"] - raise NoResult - - -def check_that_custom_domain_is_not_in_use(custom_domain: str) -> None: - URL = "https://api.netlify.com/api/v1/sites" - response = requests.get(URL, headers=AUTH_HEADER) - if not response.ok: - raise Exception("something went wrong") - rj = response.json() - for site in rj: - if site["custom_domain"] == custom_domain: - raise DomainInUse(f"{custom_domain} is already in use") - - -def set_to_custom_domain(site_id: str, custom_domain: str) -> None: - URL = f"https://app.netlify.com/access-control/bb-api/api/v1/sites/{site_id}" - response = requests.put( - URL, - json={"custom_domain": custom_domain}, - headers=AUTH_HEADER, - ) - if not response.ok: - print(response.reason) - raise Exception("something went wrong with setting the custom domain") - print(f"the site is published at {custom_domain}.") +def cli_set_custom_domain(custom_domain: str, domain: str) -> None: + """ + Assign a custom domain to an already deployed site on Netlify + """ + check_that_custom_domain_is_not_in_use(custom_domain) + try: + site_id, _ = get_site_id_from_netlify_domain(domain) + except NoResult: + print(f"No site found with domain '{domain}'") + return + if len(NETLIFY_DOMAINS) == 1 and custom_domain.count(".") == 0: + custom_domain = f"{custom_domain}.{NETLIFY_DOMAINS[0]}" + set_to_custom_domain(site_id, custom_domain) def cli_remove_custom_domain() -> None: @@ -153,42 +85,18 @@ def cli_remove_custom_domain() -> None: print("Please set the environment variable NETLIFY_DOMAINS") raise NoCustomDomains try: - custom_domain = sys.argv[sys.argv.index("--remove-custom-domain") + 1] - except IndexError: - print("Please provide a domain") - return + custom_domain = sys.argv[sys.argv.index("remove-custom") + 1] + except (ValueError, IndexError): + try: + custom_domain = sys.argv[sys.argv.index("delete-custom") + 1] + except IndexError: + print("Please provide a domain") + return if len(NETLIFY_DOMAINS) == 1 and custom_domain.count(".") == 0: custom_domain = f"{custom_domain}.{NETLIFY_DOMAINS[0]}" - site_id = get_site_id_from_custom_domain(custom_domain) + site_id, full_custom_domain = get_site_id_from_custom_domain(custom_domain) remove_custom_domain(site_id) - print(f"{custom_domain} was removed") - - -def cli_set_custom_domain() -> None: - """ - Assign a custom domain to an already deployed site on Netlify - """ - try: - custom_domain = sys.argv[sys.argv.index("--custom-domain") + 1] - except IndexError: - print("Please provide a --custom-domain") - return - check_that_custom_domain_is_not_in_use(custom_domain) - try: - domain = sys.argv[sys.argv.index("--domain") + 1] - except IndexError: - print("Please provide a domain") - return - if domain.count(".") == 0: - domain = f"{domain}.netlify.app" - try: - site_id = get_site_id_from_netlify_domain(domain) - except NoResult: - print(f"No site found with domain '{domain}'") - return - if len(NETLIFY_DOMAINS) == 1 and custom_domain.count(".") == 0: - custom_domain = f"{custom_domain}.{NETLIFY_DOMAINS[0]}" - set_to_custom_domain(site_id, custom_domain) + print(f"'{full_custom_domain}' was removed") def cli_delete_site() -> None: @@ -196,123 +104,60 @@ def cli_delete_site() -> None: Delete a site from Netlify """ try: - domain = sys.argv[sys.argv.index("--delete-site") + 1] + domain = sys.argv[sys.argv.index("delete") + 1] except (ValueError, IndexError): try: - domain = sys.argv[sys.argv.index("--remove-site") + 1] + domain = sys.argv[sys.argv.index("remove") + 1] except IndexError: print("Please provide a domain") return - netlify_domain = domain - if netlify_domain.count(".") == 0: - netlify_domain = f"{domain}.netlify.app" + got = get_site_id_from_netlify_domain(domain) try: - site_id = get_site_id_from_netlify_domain(netlify_domain) + site_id, full_domain = got + except ValueError: + print(got) + raise + except NoResult: if len(NETLIFY_DOMAINS) == 0: raise NoCustomDomains( "No custom domains configured in NETLIFY_DOMAINS, and couldn't find a site with that domain" ) - print(f"No site found with domain '{domain}', trying custom domains") if domain.count(".") == 0 and len(NETLIFY_DOMAINS) == 1: domain = f"{domain}.{NETLIFY_DOMAINS[0]}" try: - site_id = get_site_id_from_custom_domain(domain) + site_id, full_domain = get_site_id_from_custom_domain(domain) except NoResult: print(f"No site found with custom domain '{domain}'") return delete_site(site_id) - print(f"site {domain} was deleted") - - -def delete_site(site_id: str) -> None: - URL = f"https://api.netlify.com/api/v1/sites/{site_id}" - response = requests.delete(URL, headers=AUTH_HEADER) - if not response.ok: - raise Exception("something went wrong") - - -def display_help() -> None: - print( - """Usage: - pub --help - pub --root-dir --custom-domain - pub --list-sites - pub --custom-domain --domain - pub --remove-custom-domain - pub --delete-site - """ - ) - - -def deploy_site() -> None: - """ - Deploy a folder of web pages to Netlify - """ - custom_domain = None - if "--custom-domain" in sys.argv: - if len(NETLIFY_DOMAINS) == 0: - raise NoCustomDomains( - "No custom domains configured in NETLIFY_DOMAINS, and --custom-domain was provided" - ) - try: - custom_domain = sys.argv[sys.argv.index("--custom-domain") + 1] - except IndexError: - print("Please provide a --custom-domain") - return - else: - check_that_custom_domain_is_not_in_use(custom_domain) - else: - args = sys.argv[1:].copy() - args.remove("--root_dir") - if args: - raise InvalidArguments(", ".join(args)) - if "--root-dir" not in sys.argv: - return display_help() - try: - root_dir = pl.Path(sys.argv[sys.argv.index("--root-dir") + 1]) - except IndexError: - print("Please provide a root directory") - return - - if ( - custom_domain is not None - and len(NETLIFY_DOMAINS) == 1 - and custom_domain.count(".") == 0 - ): - custom_domain = f"{custom_domain}.{NETLIFY_DOMAINS[0]}" - deploy_page_to_netlify(root_dir, custom_domain) + print(f"site '{full_domain}' was deleted") def cli_list_sites() -> None: """ List all sites on Netlify """ - URL = "https://api.netlify.com/api/v1/sites" - response = requests.get(URL, headers=AUTH_HEADER) - if not response.ok: - raise Exception("something went wrong") - rj = response.json() - + sites = get_all_sites() print("sites without custom domains:") - for site in rj: + for site in sites: if site["custom_domain"] is None: print(f"{site['name']}: {site['url']}") print() print("sites with custom domains:") - for site in rj: + for site in sites: if site["custom_domain"] is not None: print(f"{site['name']}: {site['url']}") def main() -> None: - if "--help" in sys.argv or len(sys.argv) == 1: - return display_help() - elif "--list-sites" in sys.argv: + if "help" in sys.argv or len(sys.argv) == 1: + return cli_display_help() + if "list" in sys.argv: return cli_list_sites() - elif "--remove-custom-domain" in sys.argv: + if "remove-custom" in sys.argv or "delete-custom" in sys.argv: try: cli_remove_custom_domain() except NoResult: @@ -320,29 +165,41 @@ def main() -> None: except NoCustomDomains: print("No custom domains configured in NETLIFY_DOMAINS") return - elif "--delete-site" in sys.argv or "--remove-site" in sys.argv: + if "delete" in sys.argv or "remove" in sys.argv: try: cli_delete_site() - except NoCustomDomains as e: + except (NoCustomDomains, NoResult) as e: print(str(e)) return - elif "--custom-domain" in sys.argv and "--root-dir" not in sys.argv: + if "custom" in sys.argv: if len(NETLIFY_DOMAINS) == 0: print("No custom domains configured in NETLIFY_DOMAINS") return - if "--domain" not in sys.argv: - print("Please supply a --domain") + args = sys.argv[2:] + try: + custom_domain, domain = args + except ValueError: + print("Please provide a custom domain and domain") return try: - cli_set_custom_domain() - except DomainInUse as e: + cli_set_custom_domain(custom_domain, domain) + except (DomainInUse, TooManyResults) as e: print(str(e)) return + + args = sys.argv[1:] + if len(args) == 1: + root_dir = args[0] + custom_domain = None + elif len(args) == 2: + root_dir, custom_domain = args else: - try: - deploy_site() - except (NoCustomDomains, DomainInUse, NoNestedFolder) as e: - print(str(e)) + print("please provide a root directory, and optionally a custom domain") + return + try: + cli_deploy_site(root_dir, custom_domain) + except (NoCustomDomains, DomainInUse, NoNestedFolder, TooManyResults) as e: + print(str(e)) if __name__ == "__main__":