This script requires GITHUB_USERNAME and the GITHUB_PASSWORD env variables to be set and lets users with sufficient privileges initiate a transfer from a GitHub organization to another by specifying two arguments, for example: ./github-org-transfer.py oldorg/repo neworg/repo Change-Id: I2383d256958c028efe81b235ff8641d131bbb3a7
		
			
				
	
	
		
			131 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			131 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
#!/usr/bin/env python
 | 
						|
# Copyright 2019 Red Hat
 | 
						|
#
 | 
						|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
						|
# not use this file except in compliance with the License. You may obtain
 | 
						|
# a copy of the License at
 | 
						|
#
 | 
						|
#      http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
#
 | 
						|
# Unless required by applicable law or agreed to in writing, software
 | 
						|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
						|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
						|
# License for the specific language governing permissions and limitations
 | 
						|
# under the License.
 | 
						|
#
 | 
						|
 | 
						|
# Script that automates the transfer of a repository from an organization to
 | 
						|
# another on GitHub.
 | 
						|
# Relevant docs:
 | 
						|
#  - https://developer.github.com/v3/orgs/members/#get-your-organization-membership
 | 
						|
#  - https://developer.github.com/v3/repos/#get
 | 
						|
#  - https://developer.github.com/v3/repos/#transfer-a-repository
 | 
						|
 | 
						|
import argparse
 | 
						|
import requests
 | 
						|
import json
 | 
						|
import os
 | 
						|
import sys
 | 
						|
 | 
						|
GITHUB_API = "https://api.github.com"
 | 
						|
GITHUB_USERNAME = os.environ.get("GITHUB_USERNAME", None)
 | 
						|
 | 
						|
# The password can be the account's password or a personal access token created
 | 
						|
# at https://github.com/settings/tokens
 | 
						|
# TODO: Add support for two factor authentication by passing the "x-github-otp"
 | 
						|
#       header. See: https://developer.github.com/v3/auth/#working-with-two-factor-authentication
 | 
						|
GITHUB_PASSWORD = os.environ.get("GITHUB_PASSWORD", None)
 | 
						|
 | 
						|
 | 
						|
def get_args():
 | 
						|
    parser = argparse.ArgumentParser()
 | 
						|
    parser.add_argument("src_repo", help="Source org and repo (ex: oldorg/repo)")
 | 
						|
    parser.add_argument("dst_repo", help="Destination org and repo (ex: neworg/repo)")
 | 
						|
    parser.add_argument("--dry-run", action="store_true",
 | 
						|
        help="Check repositories and privileges but don't intiate any transfers."
 | 
						|
    )
 | 
						|
    args = parser.parse_args()
 | 
						|
    return args
 | 
						|
 | 
						|
 | 
						|
def is_organization_admin(session, repository):
 | 
						|
    """
 | 
						|
    Returns true if the current user has admin privileges for the repository's
 | 
						|
    organization.
 | 
						|
    """
 | 
						|
    org = repository.split("/")[0]
 | 
						|
    memberships = session.get("%s/user/memberships/orgs/%s" % (GITHUB_API, org))
 | 
						|
    if memberships.status_code == 404:
 | 
						|
        return False
 | 
						|
    elif memberships.status_code != 200:
 | 
						|
        raise Exception("Could not retrieve organization memberships: %s" % json.dumps(memberships.json(), indent=2))
 | 
						|
 | 
						|
    role = memberships.json()["role"]
 | 
						|
    if role == "admin":
 | 
						|
        return True
 | 
						|
    return False
 | 
						|
 | 
						|
 | 
						|
def main():
 | 
						|
    args = get_args()
 | 
						|
 | 
						|
    if GITHUB_USERNAME is None or GITHUB_PASSWORD is None:
 | 
						|
        raise Exception("Environment variables GITHUB_USERNAME and GITHUB_PASSWORD must be set.")
 | 
						|
 | 
						|
    session = requests.Session()
 | 
						|
    session.auth = (GITHUB_USERNAME, GITHUB_PASSWORD)
 | 
						|
    session.headers.update({
 | 
						|
        "Content-Type": "application/json",
 | 
						|
        "Accept": "application/vnd.github.nightshade-preview+json"
 | 
						|
    })
 | 
						|
 | 
						|
    # Ensure we have sufficient privileges to initiate the transfer
 | 
						|
    for repository in [args.src_repo, args.dst_repo]:
 | 
						|
        if not is_organization_admin(session, repository):
 | 
						|
            raise Exception("Insufficient privileges for %s or it is a personal namespace" % repository)
 | 
						|
 | 
						|
    # Get source repository
 | 
						|
    src_repo = session.get("%s/repos/%s" % (GITHUB_API, args.src_repo))
 | 
						|
    # The source repository should exist. Raise an exception if it doesn't.
 | 
						|
    src_repo.raise_for_status()
 | 
						|
    src_repo = src_repo.json()
 | 
						|
 | 
						|
    # Check if the provided source repo name matches the GitHub repo name.
 | 
						|
    # This is important because GitHub will have a different "full_name" if a
 | 
						|
    # repo has been moved in the past. For example, if "oldorg/repo" had been
 | 
						|
    # moved to "neworg/repo" in the past, querying "oldorg/repo" would yield
 | 
						|
    # the full_name "neworg/repo" instead of the expected "oldorg/repo".
 | 
						|
    if args.src_repo != src_repo["full_name"]:
 | 
						|
        if src_repo["full_name"] == args.dst_repo:
 | 
						|
            print("Nothing to do: repository has already been moved.")
 | 
						|
            sys.exit(0)
 | 
						|
        else:
 | 
						|
            raise Exception("Source repository exists but as %s" % src_repo["full_name"])
 | 
						|
 | 
						|
    # Get destination repository
 | 
						|
    dst_repo = session.get("%s/repos/%s" % (GITHUB_API, args.dst_repo))
 | 
						|
    # The destination repository shouldn't exist. If it does, try to be helpful about it.
 | 
						|
    if dst_repo.status_code == 200:
 | 
						|
        dst_repo = dst_repo.json()
 | 
						|
        if dst_repo["full_name"] == args.dst_repo:
 | 
						|
            print("Nothing to do: repository has already been moved.")
 | 
						|
            sys.exit(0)
 | 
						|
        else:
 | 
						|
            raise Exception("Destination repository exists but as %s" % dst_repo["full_name"])
 | 
						|
 | 
						|
    # Initiate transfer request
 | 
						|
    payload = {
 | 
						|
        "new_owner": args.dst_repo.split('/')[0]
 | 
						|
    }
 | 
						|
 | 
						|
    if not args.dry_run:
 | 
						|
        data = session.post("%s/repos/%s/transfer" % (GITHUB_API, args.src_repo), data=json.dumps(payload))
 | 
						|
        if data.status_code != 202:
 | 
						|
            raise Exception("Failed to request transfer: %s" % json.dumps(data.json(), indent=2))
 | 
						|
        print("Sent transfer request for %s to %s." % (args.src_repo, args.dst_repo))
 | 
						|
    else:
 | 
						|
        print("Not requesting transfer (dry-run enabled)")
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    main()
 |