mirror of
https://github.com/josegonzalez/python-github-backup.git
synced 2025-12-06 16:38:03 +01:00
Merge pull request #216 from Ondkloss/feature/fine_grained
Add support for fine-grained tokens (continued)
This commit is contained in:
30
README.rst
30
README.rst
@@ -29,16 +29,17 @@ Usage
|
|||||||
|
|
||||||
CLI Usage is as follows::
|
CLI Usage is as follows::
|
||||||
|
|
||||||
github-backup [-h] [-u USERNAME] [-p PASSWORD] [-t TOKEN] [--as-app]
|
github-backup [-h] [-u USERNAME] [-p PASSWORD] [-t TOKEN_CLASSIC]
|
||||||
[-o OUTPUT_DIRECTORY] [-l LOG_LEVEL] [-i] [--starred]
|
[-f TOKEN_FINE] [--as-app] [-o OUTPUT_DIRECTORY]
|
||||||
[--all-starred] [--watched] [--followers] [--following]
|
[-l LOG_LEVEL] [-i] [--starred] [--all-starred]
|
||||||
[--all] [--issues] [--issue-comments] [--issue-events]
|
[--watched] [--followers] [--following] [--all] [--issues]
|
||||||
[--pulls] [--pull-comments] [--pull-commits]
|
[--issue-comments] [--issue-events] [--pulls]
|
||||||
[--pull-details] [--labels] [--hooks] [--milestones]
|
[--pull-comments] [--pull-commits] [--pull-details]
|
||||||
[--repositories] [--bare] [--lfs] [--wikis] [--gists]
|
[--labels] [--hooks] [--milestones] [--repositories]
|
||||||
[--starred-gists] [--skip-archived] [--skip-existing]
|
[--bare] [--lfs] [--wikis] [--gists] [--starred-gists]
|
||||||
[-L [LANGUAGES ...]] [-N NAME_REGEX] [-H GITHUB_HOST]
|
[--skip-archived] [--skip-existing] [-L [LANGUAGES ...]]
|
||||||
[-O] [-R REPOSITORY] [-P] [-F] [--prefer-ssh] [-v]
|
[-N NAME_REGEX] [-H GITHUB_HOST] [-O] [-R REPOSITORY]
|
||||||
|
[-P] [-F] [--prefer-ssh] [-v]
|
||||||
[--keychain-name OSX_KEYCHAIN_ITEM_NAME]
|
[--keychain-name OSX_KEYCHAIN_ITEM_NAME]
|
||||||
[--keychain-account OSX_KEYCHAIN_ITEM_ACCOUNT]
|
[--keychain-account OSX_KEYCHAIN_ITEM_ACCOUNT]
|
||||||
[--releases] [--assets] [--exclude [REPOSITORY [REPOSITORY ...]]
|
[--releases] [--assets] [--exclude [REPOSITORY [REPOSITORY ...]]
|
||||||
@@ -57,7 +58,10 @@ CLI Usage is as follows::
|
|||||||
-p PASSWORD, --password PASSWORD
|
-p PASSWORD, --password PASSWORD
|
||||||
password for basic auth. If a username is given but
|
password for basic auth. If a username is given but
|
||||||
not a password, the password will be prompted for.
|
not a password, the password will be prompted for.
|
||||||
-t TOKEN, --token TOKEN
|
-f TOKEN_FINE, --token-fine TOKEN_FINE
|
||||||
|
fine-grained personal access token or path to token
|
||||||
|
(file://...)
|
||||||
|
-t TOKEN_CLASSIC, --token TOKEN_CLASSIC
|
||||||
personal access, OAuth, or JSON Web token, or path to
|
personal access, OAuth, or JSON Web token, or path to
|
||||||
token (file://...)
|
token (file://...)
|
||||||
--as-app authenticate as github app instead of as a user.
|
--as-app authenticate as github app instead of as a user.
|
||||||
@@ -163,13 +167,13 @@ Backup all repositories, including private ones::
|
|||||||
export ACCESS_TOKEN=SOME-GITHUB-TOKEN
|
export ACCESS_TOKEN=SOME-GITHUB-TOKEN
|
||||||
github-backup WhiteHouse --token $ACCESS_TOKEN --organization --output-directory /tmp/white-house --repositories --private
|
github-backup WhiteHouse --token $ACCESS_TOKEN --organization --output-directory /tmp/white-house --repositories --private
|
||||||
|
|
||||||
Backup a single organization repository with everything else (wiki, pull requests, comments, issues etc)::
|
Use a fine-grained access token to backup a single organization repository with everything else (wiki, pull requests, comments, issues etc)::
|
||||||
|
|
||||||
export ACCESS_TOKEN=SOME-GITHUB-TOKEN
|
export ACCESS_TOKEN=SOME-GITHUB-TOKEN
|
||||||
ORGANIZATION=docker
|
ORGANIZATION=docker
|
||||||
REPO=cli
|
REPO=cli
|
||||||
# e.g. git@github.com:docker/cli.git
|
# e.g. git@github.com:docker/cli.git
|
||||||
github-backup $ORGANIZATION -P -t $ACCESS_TOKEN -o . --all -O -R $REPO
|
github-backup $ORGANIZATION -P -f $ACCESS_TOKEN -o . --all -O -R $REPO
|
||||||
|
|
||||||
Testing
|
Testing
|
||||||
=======
|
=======
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ except ImportError:
|
|||||||
VERSION = "unknown"
|
VERSION = "unknown"
|
||||||
|
|
||||||
FNULL = open(os.devnull, "w")
|
FNULL = open(os.devnull, "w")
|
||||||
|
FILE_URI_PREFIX = "file://"
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -126,9 +127,15 @@ def parse_args(args=None):
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-t",
|
"-t",
|
||||||
"--token",
|
"--token",
|
||||||
dest="token",
|
dest="token_classic",
|
||||||
help="personal access, OAuth, or JSON Web token, or path to token (file://...)",
|
help="personal access, OAuth, or JSON Web token, or path to token (file://...)",
|
||||||
) # noqa
|
) # noqa
|
||||||
|
parser.add_argument(
|
||||||
|
"-f",
|
||||||
|
"--token-fine",
|
||||||
|
dest="token_fine",
|
||||||
|
help="fine-grained personal access token (github_pat_....), or path to token (file://...)",
|
||||||
|
) # noqa
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--as-app",
|
"--as-app",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@@ -431,18 +438,27 @@ def get_auth(args, encode=True, for_git_cli=False):
|
|||||||
raise Exception(
|
raise Exception(
|
||||||
"You must specify both name and account fields for osx keychain password items"
|
"You must specify both name and account fields for osx keychain password items"
|
||||||
)
|
)
|
||||||
elif args.token:
|
elif args.token_fine:
|
||||||
_path_specifier = "file://"
|
if args.token_fine.startswith(FILE_URI_PREFIX):
|
||||||
if args.token.startswith(_path_specifier):
|
args.token_fine = read_file_contents(args.token_fine)
|
||||||
path_specifier_len = len(_path_specifier)
|
|
||||||
args.token = open(args.token[path_specifier_len:], "rt").readline().strip()
|
if args.token_fine.startswith("github_pat_"):
|
||||||
|
auth = args.token_fine
|
||||||
|
else:
|
||||||
|
raise Exception(
|
||||||
|
"Fine-grained token supplied does not look like a GitHub PAT"
|
||||||
|
)
|
||||||
|
elif args.token_classic:
|
||||||
|
if args.token_classic.startswith(FILE_URI_PREFIX):
|
||||||
|
args.token_classic = read_file_contents(args.token_classic)
|
||||||
|
|
||||||
if not args.as_app:
|
if not args.as_app:
|
||||||
auth = args.token + ":" + "x-oauth-basic"
|
auth = args.token_classic + ":" + "x-oauth-basic"
|
||||||
else:
|
else:
|
||||||
if not for_git_cli:
|
if not for_git_cli:
|
||||||
auth = args.token
|
auth = args.token_classic
|
||||||
else:
|
else:
|
||||||
auth = "x-access-token:" + args.token
|
auth = "x-access-token:" + args.token_classic
|
||||||
elif args.username:
|
elif args.username:
|
||||||
if not args.password:
|
if not args.password:
|
||||||
args.password = getpass.getpass()
|
args.password = getpass.getpass()
|
||||||
@@ -457,7 +473,7 @@ def get_auth(args, encode=True, for_git_cli=False):
|
|||||||
if not auth:
|
if not auth:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if not encode:
|
if not encode or args.token_fine is not None:
|
||||||
return auth
|
return auth
|
||||||
|
|
||||||
return base64.b64encode(auth.encode("ascii"))
|
return base64.b64encode(auth.encode("ascii"))
|
||||||
@@ -481,6 +497,10 @@ def get_github_host(args):
|
|||||||
return host
|
return host
|
||||||
|
|
||||||
|
|
||||||
|
def read_file_contents(file_uri):
|
||||||
|
return open(file_uri[len(FILE_URI_PREFIX) :], "rt").readline().strip()
|
||||||
|
|
||||||
|
|
||||||
def get_github_repo_url(args, repository):
|
def get_github_repo_url(args, repository):
|
||||||
if repository.get("is_gist"):
|
if repository.get("is_gist"):
|
||||||
if args.prefer_ssh:
|
if args.prefer_ssh:
|
||||||
@@ -503,7 +523,7 @@ def get_github_repo_url(args, repository):
|
|||||||
auth = get_auth(args, encode=False, for_git_cli=True)
|
auth = get_auth(args, encode=False, for_git_cli=True)
|
||||||
if auth:
|
if auth:
|
||||||
repo_url = "https://{0}@{1}/{2}/{3}.git".format(
|
repo_url = "https://{0}@{1}/{2}/{3}.git".format(
|
||||||
auth,
|
auth if args.token_fine is None else "oauth2:" + auth,
|
||||||
get_github_host(args),
|
get_github_host(args),
|
||||||
repository["owner"]["login"],
|
repository["owner"]["login"],
|
||||||
repository["name"],
|
repository["name"],
|
||||||
@@ -523,7 +543,13 @@ def retrieve_data_gen(args, template, query_args=None, single_request=False):
|
|||||||
while True:
|
while True:
|
||||||
page = page + 1
|
page = page + 1
|
||||||
request = _construct_request(
|
request = _construct_request(
|
||||||
per_page, page, query_args, template, auth, as_app=args.as_app
|
per_page,
|
||||||
|
page,
|
||||||
|
query_args,
|
||||||
|
template,
|
||||||
|
auth,
|
||||||
|
as_app=args.as_app,
|
||||||
|
fine=True if args.token_fine is not None else False,
|
||||||
) # noqa
|
) # noqa
|
||||||
r, errors = _get_response(request, auth, template)
|
r, errors = _get_response(request, auth, template)
|
||||||
|
|
||||||
@@ -559,7 +585,13 @@ def retrieve_data_gen(args, template, query_args=None, single_request=False):
|
|||||||
retries += 1
|
retries += 1
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
request = _construct_request(
|
request = _construct_request(
|
||||||
per_page, page, query_args, template, auth, as_app=args.as_app
|
per_page,
|
||||||
|
page,
|
||||||
|
query_args,
|
||||||
|
template,
|
||||||
|
auth,
|
||||||
|
as_app=args.as_app,
|
||||||
|
fine=True if args.token_fine is not None else False,
|
||||||
) # noqa
|
) # noqa
|
||||||
r, errors = _get_response(request, auth, template)
|
r, errors = _get_response(request, auth, template)
|
||||||
|
|
||||||
@@ -643,17 +675,23 @@ def _get_response(request, auth, template):
|
|||||||
return r, errors
|
return r, errors
|
||||||
|
|
||||||
|
|
||||||
def _construct_request(per_page, page, query_args, template, auth, as_app=None):
|
def _construct_request(
|
||||||
|
per_page, page, query_args, template, auth, as_app=None, fine=False
|
||||||
|
):
|
||||||
querystring = urlencode(
|
querystring = urlencode(
|
||||||
dict(
|
dict(
|
||||||
list({"per_page": per_page, "page": page}.items()) + list(query_args.items())
|
list({"per_page": per_page, "page": page}.items())
|
||||||
|
+ list(query_args.items())
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
request = Request(template + "?" + querystring)
|
request = Request(template + "?" + querystring)
|
||||||
if auth is not None:
|
if auth is not None:
|
||||||
if not as_app:
|
if not as_app:
|
||||||
request.add_header("Authorization", "Basic ".encode("ascii") + auth)
|
if fine:
|
||||||
|
request.add_header("Authorization", "token " + auth)
|
||||||
|
else:
|
||||||
|
request.add_header("Authorization", "Basic ".encode("ascii") + auth)
|
||||||
else:
|
else:
|
||||||
auth = auth.encode("ascii")
|
auth = auth.encode("ascii")
|
||||||
request.add_header("Authorization", "token ".encode("ascii") + auth)
|
request.add_header("Authorization", "token ".encode("ascii") + auth)
|
||||||
|
|||||||
Reference in New Issue
Block a user