diff --git a/github_deploy/commands/_constants.py b/github_deploy/commands/_constants.py index 1249c71..a8fb431 100644 --- a/github_deploy/commands/_constants.py +++ b/github_deploy/commands/_constants.py @@ -1,2 +1,2 @@ REPOS_URL = "https://api.github.com/search/repositories?q=org:{org}" -BASE_URL = "https://api.github.com/repos/{repo}/contents/{path}" +FILE_CONTENTS_URL = "https://api.github.com/repos/{repo}/contents/{path}" diff --git a/github_deploy/commands/_repo_utils.py b/github_deploy/commands/_repo_utils.py index 438d970..64b7d55 100644 --- a/github_deploy/commands/_repo_utils.py +++ b/github_deploy/commands/_repo_utils.py @@ -1,9 +1,11 @@ import base64 +import os import aiofiles import asyncclick as click +from aiofiles import os as aiofiles_os -from github_deploy.commands._constants import REPOS_URL, BASE_URL +from github_deploy.commands._constants import REPOS_URL, FILE_CONTENTS_URL from github_deploy.commands._http_utils import get, delete, put from github_deploy.commands._utils import get_headers @@ -29,7 +31,7 @@ async def delete_content( if exists: data["sha"] = current_sha - url = BASE_URL.format(repo=repo, path=dest) + url = FILE_CONTENTS_URL.format(repo=repo, path=dest) async with semaphore: response = await delete( @@ -40,7 +42,7 @@ async def delete_content( async def check_exists(*, session, repo, dest, token, semaphore, skip_missing): - url = BASE_URL.format(repo=repo, path=dest) + url = FILE_CONTENTS_URL.format(repo=repo, path=dest) async with semaphore: response = await get( @@ -62,6 +64,7 @@ async def upload_content( token, semaphore, exists, + only_update, current_sha, current_content ): @@ -73,6 +76,19 @@ async def upload_content( if current_content == base64_content: click.echo("Skipping: Contents are the same.") return + else: + if exists: + click.echo("Storing backup of existing file...") + + dirname, filename = os.path.split(f"{repo}/{dest}") + + await aiofiles_os.makedirs(dirname, exist_ok=True) + + async with aiofiles.open(f"{dirname}/{filename}", mode="wb") as f: + await f.write(base64.b64decode(current_content)) + elif only_update: + click.echo(f"Skipping: only updating existing files.") + return data = { "message": f"Updated {dest}" @@ -83,7 +99,9 @@ async def upload_content( if exists: data["sha"] = current_sha - url = BASE_URL.format(repo=repo, path=dest) + url = FILE_CONTENTS_URL.format(repo=repo, path=dest) + + click.echo(f"Uploading {source} to {repo}/{dest}...") async with semaphore: response = await put( diff --git a/github_deploy/commands/upload.py b/github_deploy/commands/upload.py index 3baf4a3..9d2e32d 100644 --- a/github_deploy/commands/upload.py +++ b/github_deploy/commands/upload.py @@ -8,7 +8,7 @@ async def handle_file_upload( - *, repo, source, dest, overwrite, token, semaphore, session + *, repo, source, dest, overwrite, only_update, token, semaphore, session ): check_exists_response = await check_exists( session=session, @@ -23,19 +23,18 @@ async def handle_file_upload( current_content = check_exists_response.get("content") exists = current_sha is not None - if exists and not overwrite: - return click.style( - "Skipped uploading {source} to {repo}/{path}: Found an existing copy.".format( - source=source, - repo=repo, - path=dest, - ), - fg="blue", - bold=True, - ) - - else: - if exists: + if exists: + if not overwrite: + return click.style( + "Skipped uploading {source} to {repo}/{path}: Found an existing copy.".format( + source=source, + repo=repo, + path=dest, + ), + fg="blue", + bold=True, + ) + else: click.echo( click.style( "Found an existing copy at {repo}/{path} overwriting it's contents...".format( @@ -45,36 +44,32 @@ async def handle_file_upload( ), ) - upload_response = await upload_content( - session=session, - repo=repo, - source=source, - dest=dest, - token=token, - semaphore=semaphore, - exists=exists, - current_sha=current_sha, - current_content=current_content, - ) + upload_response = await upload_content( + session=session, + repo=repo, + source=source, + dest=dest, + token=token, + semaphore=semaphore, + exists=exists, + only_update=only_update, + current_sha=current_sha, + current_content=current_content, + ) - if upload_response: - return click.style( - "Successfully uploaded '{source}' to {repo}/{dest}".format( - source=upload_response["content"]["name"], - repo=repo, - dest=upload_response["content"]["path"], - ), - fg="green", - bold=True, - ) + if upload_response: + return click.style( + "Successfully uploaded '{source}' to {repo}/{dest}".format( + source=upload_response["content"]["name"], + repo=repo, + dest=upload_response["content"]["path"], + ), + fg="green", + bold=True, + ) @click.command() -@click.option( - "--org", - prompt=click.style("Enter your github user/organization", bold=True), - help="The github organization.", -) @click.option( "--token", prompt=click.style("Enter your personal access token", bold=True), @@ -82,6 +77,11 @@ async def handle_file_upload( hide_input=True, envvar="TOKEN", ) +@click.option( + "--org", + prompt=click.style("Enter your github user/organization", bold=True), + help="The github organization.", +) @click.option( "--source", prompt=click.style("Enter path to source file", fg="blue"), @@ -98,16 +98,30 @@ async def handle_file_upload( prompt=click.style( "Should we overwrite existing contents at this path", fg="blue" ), + is_flag=True, + show_default=True, help="Overwrite existing files.", default=False, ) +@click.option( + "--only-update/--no-only-update", + prompt=click.style( + "Should we only update existing files at this path", fg="blue" + ), + is_flag=True, + show_default=True, + help="Only update existing files.", + default=False, +) @click.option( "--private/--no-private", prompt=click.style("Should we Include private repositories", bold=True), + is_flag=True, + show_default=True, help="Upload files to private repositories.", default=True, ) -async def main(org, token, source, dest, overwrite, private): +async def main(org, token, source, dest, overwrite, only_update, private): """Upload a file to all repositories owned by an organization/user.""" # create instance of Semaphore: max concurrent requests. semaphore = asyncio.Semaphore(1000) @@ -171,6 +185,7 @@ async def main(org, token, source, dest, overwrite, private): dest=dest, token=token, overwrite=overwrite, + only_update=only_update, session=session, semaphore=semaphore, )