← Back to team overview

cloud-init-dev team mailing list archive

[Merge] ~chad.smith/cloud-init:tools/migrate-script into cloud-init:master

 

Chad Smith has proposed merging ~chad.smith/cloud-init:tools/migrate-script into cloud-init:master.

Commit message:
tools: add migrate-lp-user-to-github script to link LP to github

To link a launchpad account name to your github account for licensing
accountability each LP user should publish a merge proposal in launchpad
with their LP account and a matching merge proposal in github using
their github user.

Cloud-init will track these usename maps in ./tools/.lp-to-git-user as
JSON.

Run ./tools/migrate-lp-user-to-github <LP_USERNAME> <GITHUB_USERNAME>
to automatically create merge proposals in launchpad and your github
account.

Requested reviews:
  Server Team CI bot (server-team-bot): continuous-integration
  cloud-init Commiters (cloud-init-dev)

For more details, see:
https://code.launchpad.net/~chad.smith/cloud-init/+git/cloud-init/+merge/375163
-- 
Your team cloud-init Commiters is requested to review the proposed merge of ~chad.smith/cloud-init:tools/migrate-script into cloud-init:master.
diff --git a/tools/.lp-to-git-user b/tools/.lp-to-git-user
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/tools/.lp-to-git-user
@@ -0,0 +1 @@
+{}
diff --git a/tools/migrate-lp-user-to-github b/tools/migrate-lp-user-to-github
new file mode 100755
index 0000000..4fa36b6
--- /dev/null
+++ b/tools/migrate-lp-user-to-github
@@ -0,0 +1,201 @@
+#!/usr/bin/python3
+"""Link your Launchpad user to github, proposing branches to LP and Github"""
+
+from argparse import ArgumentParser
+from launchpadlib.launchpad import Launchpad
+from subprocess import Popen, PIPE
+import os
+import sys
+
+if "avoid-pep8-E402-import-not-top-of-file":
+    _tdir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
+    sys.path.insert(0, _tdir)
+    from cloudinit import util
+
+
+VERBOSITY = 0
+DRYRUN = False
+LP_TO_GIT_USER_FILE=os.path.abspath(
+    os.path.join(os.path.dirname(__file__), '.lp-to-git-user'))
+MIGRATE_USER_BRANCH_NAME='migrate-lp-to-github'
+GITHUB_PULL_URL='https://github.com/cloud-init/cloud-init/compare/master...{github_user}:{branch}'
+
+def error(message):
+    if isinstance(message, bytes):
+        message = message.decode('utf-8')
+    log('ERROR: {error}'.format(error=message))
+    sys.exit(1)
+
+
+def log(message, verbosity=0):
+    '''Print a message to logs when VERBOSITY >= verbosity.'''
+    if VERBOSITY >= verbosity:
+        print(message)
+
+
+def subp(cmd, skip=False):
+    log('$ {command}'.format(command=' '.join(cmd)),
+        verbosity=0 if (DRYRUN or skip) else 1)
+    if skip:
+        return
+    proc = Popen(cmd, stdout=PIPE, stderr=PIPE)
+    out, err = proc.communicate()
+    if proc.returncode:
+        error(err if err else out)
+    return out.decode('utf-8')
+
+
+RE_MERGE_URL_TMPL = (r'https://code.launchpad.net/~(?P<lpuser>[^/]+)/'
+                     r'{project}/\+git/{project}/\+merge/\d+')
+LP_REMOTE_PATH_TMPL = 'git+ssh://{launchpad_user}@git.launchpad.net/~{launchpad_user}/{project}'
+GITHUB_REMOTE_PATH_TMPL = 'git@xxxxxxxxxx:{github_user}/cloud-init.git'
+
+
+# Comment templates
+COMMIT_MSG_TMPL = '''
+lp-to-git-users: adding {gh_username}
+
+Mapped from {lp_username}
+'''
+
+
+def get_parser():
+    parser = ArgumentParser(description=__doc__)
+    parser.add_argument(
+        '--dryrun', required=False, default=False, action='store_true',
+        help=('Run commands and review operation in dryrun mode, '
+              'making not changes.'))
+    parser.add_argument('launchpad_user', help='Your launchpad username.')
+    parser.add_argument('github_user', help='Your github username.')
+    parser.add_argument(
+        '--upstream-branch', required=False, dest='upstream',
+        default='origin/master',
+        help=('The name of remote branch target into which we will merge.'
+              ' Default: origin/master'))
+    parser.add_argument(
+        '-v', '--verbose', required=False, default=False, action='store_true',
+        help=('Print all actions.'))
+    parser.add_argument(
+        '--push-remote', required=False, dest='pushremote',
+        help=('QA-only provide remote name into which you want to push'))
+    return parser
+
+
+def create_publish_branch(upstream, publish_branch):
+    '''Create clean publish branch target in the current git repo.'''
+    branches = subp(['git', 'branch'])
+    upstream_remote, upstream_branch = upstream.split('/', 1)
+    subp(['git', 'checkout', upstream_branch])
+    subp(['git', 'pull'])
+    if publish_branch in branches:
+        subp(['git', 'branch', '-D', publish_branch])
+    subp(['git', 'checkout', upstream, '-b', publish_branch])
+
+
+def add_lp_and_github_remotes(lp_user, gh_user):
+    """Add lp and github remotes if not present.
+
+    @return Tuple with (lp_remote_name, gh_remote_name)
+    """
+    lp_remote = LP_REMOTE_PATH_TMPL.format(
+        launchpad_user=lp_user, project='cloud-init')
+    gh_remote = GITHUB_REMOTE_PATH_TMPL.format(github_user=gh_user)
+    remotes = subp(['git', 'remote', '-v'])
+    lp_remote_name = gh_remote_name = None
+    for remote in remotes.splitlines():
+        if not remote:
+            continue
+        remote_name, remote_url, _operation = remote.split()
+        if lp_remote == remote_url:
+            lp_remote_name = remote_name
+        elif gh_remote == remote_url:
+            gh_remote_name = remote_name
+    if not lp_remote_name:
+        lp_remote_name = 'lp-{}'.format(lp_user)
+        subp(['git', 'remote', 'add', lp_remote_name, lp_remote])
+    subp(['git', 'fetch', lp_remote_name])
+    if not gh_remote_name:
+        gh_remote_name = 'gh-{}'.format(gh_user)
+        subp(['git', 'remote', 'add', gh_remote_name, gh_remote])
+    try:
+        subp(['git', 'fetch', gh_remote_name])
+    except:
+        print("Could not fetch gh_remote: {remote}."
+              "Please create a fork for your github user by clicking 'Fork'"
+              " from {gh_upstream}".format(
+                  remote=gh_remote, gh_upstream=GH_UPSTREAM_URL))
+              sys.exit(1)
+    return (lp_remote_name, gh_remote_name)
+
+
+def create_migration_branch(
+        branch_name, upstream, lp_user, gh_user, commit_msg):
+    """Create an LP to Github migration branch and add lp_user->gh_user."""
+    print(
+        "Creating a migration branch: {} adding your users".format(
+            MIGRATE_USER_BRANCH_NAME))
+    create_publish_branch(upstream, MIGRATE_USER_BRANCH_NAME)
+    lp_to_git_map = {}
+    if os.path.exists(LP_TO_GIT_USER_FILE):
+        with open(LP_TO_GIT_USER_FILE) as stream:
+            lp_to_git_map = util.load_json(stream.read())
+
+    if gh_user in lp_to_git_map.values():
+        raise RuntimeError(
+            "github user '{}' already in {}".format(
+                gh_user, LP_TO_GIT_USER_FILE))
+    if lp_user in lp_to_git_map:
+        raise RuntimeError(
+            "launchpad user '{}' already in {}".format(
+                lp_user, LP_TO_GIT_USER_FILE))
+    lp_to_git_map[lp_user] = gh_user
+    with open(LP_TO_GIT_USER_FILE, 'w') as stream:
+        stream.write(util.json_dumps(lp_to_git_map))
+    subp(['git', 'add', LP_TO_GIT_USER_FILE])
+    commit_file = os.path.join(os.path.dirname(os.getcwd()), 'commit.msg')
+    with open(commit_file, 'wb') as stream:
+        stream.write(commit_msg.encode('utf-8'))
+    subp(['git', 'commit', '--all', '-F', commit_file])
+
+
+def main():
+    global DRYRUN
+    global VERBOSITY
+    parser = get_parser()
+    args = parser.parse_args()
+    DRYRUN = args.dryrun
+    VERBOSITY = 1 if args.verbose else 0
+    lp = Launchpad.login_with(
+        "server-team github-migration tool", 'production', version='devel')
+    lp_remote_name, gh_remote_name = add_lp_and_github_remotes(
+        args.launchpad_user, args.github_user)
+
+    commit_msg = COMMIT_MSG_TMPL.format(
+        gh_username=args.github_user, lp_username=args.launchpad_user)
+    create_migration_branch(
+        MIGRATE_USER_BRANCH_NAME, args.upstream, args.launchpad_user,
+        args.github_user, commit_msg)
+
+    for push_remote in (lp_remote_name, gh_remote_name):
+        subp(['git', 'push', push_remote, MIGRATE_USER_BRANCH_NAME, '--force'])
+
+    # Make merge request on LP
+    master = lp.git_repositories.getByPath(
+        path='cloud-init').getRefByPath(path='master')
+    LP_BRANCH_PATH='~{launchpad_user}/cloud-init/+git/cloud-init'
+    lp_git_repo = lp.git_repositories.getByPath(
+        path=LP_BRANCH_PATH.format(launchpad_user=args.launchpad_user))
+    lp_user_migrate_branch = lp_git_repo.getRefByPath(
+        path='refs/heads/migrate-lp-to-github')
+    lp_user_migrate_branch.createMergeProposal(
+        commit_message=commit_msg, merge_target=master, needs_review=True)
+    print("Merge proposal in launchpad created.\n"
+          "To link your account to github open your browser and"
+          " click 'Create pull request' at the following URL:\n"
+          "{url}".format(url=GITHUB_PULL_URL.format(
+              github_user=args.github_user, branch=MIGRATE_USER_BRANCH_NAME)))
+    return 0
+
+
+if __name__ == '__main__':
+    sys.exit(main())