Exemple #1
0
 def get_repository_info_from_user():
     data = {}
     while True:
         vcs_type = safe_input('VCS type [git, svn, hg, bzr]: ')
         if vcs_type in ['git', 'svn', 'hg', 'bzr']:
             break
         error("'{0}' is not a valid vcs type.".format(vcs_type))
         if not maybe_continue(msg='Try again'):
             return {}
     data['type'] = vcs_type
     while True:
         url = safe_input('VCS url: ')
         if url:
             break
         error("Nothing entered for url.")
         if not maybe_continue(msg='Try again'):
             return {}
     data['url'] = url
     while True:
         version = safe_input('VCS version [commit, tag, branch, etc]: ')
         if version:
             break
         error("Nothing entered for version.")
         if not maybe_continue(msg='Try again'):
             return {}
     data['version'] = version
     return data
Exemple #2
0
def get_repo_uri(repository, distro):
    url = None
    # Fetch the distro file
    distribution_file = get_distribution_file(distro)
    if repository in distribution_file.repositories and \
       distribution_file.repositories[repository].release_repository is not None:
        url = distribution_file.repositories[repository].release_repository.url
    else:
        error("Specified repository '{0}' is not in the distribution file located at '{1}'"
              .format(repository, get_disitrbution_file_url(distro)))
        matches = difflib.get_close_matches(repository, distribution_file.repositories)
        if matches:
            info(fmt("@{yf}Did you mean one of these: '" + "', '".join([m for m in matches]) + "'?"))
    if not url:
        info("Could not determine release repository url for repository '{0}' of distro '{1}'"
             .format(repository, distro))
        info("You can continue the release process by manually specifying the location of the RELEASE repository.")
        info("To be clear this is the url of the RELEASE repository not the upstream repository.")
        try:
            url = safe_input('Release repository url [press enter to abort]: ')
        except (KeyboardInterrupt, EOFError):
            url = None
            info('', use_prefix=False)
        if not url:
            error("No release repository url given, aborting.", exit=True)
        global _user_provided_release_url
        _user_provided_release_url = url
    return url
Exemple #3
0
def get_repo_uri(repository, distro):
    url = None
    # Fetch the distro file
    distribution_file = get_distribution_file(distro)
    if repository in distribution_file.repositories and \
       distribution_file.repositories[repository].release_repository is not None:
        url = distribution_file.repositories[repository].release_repository.url
    else:
        error("Specified repository '{0}' is not in the distribution file located at '{1}'"
              .format(repository, get_disitrbution_file_url(distro)))
        matches = difflib.get_close_matches(repository, distribution_file.repositories)
        if matches:
            info(fmt("@{yf}Did you mean one of these: '" + "', '".join([m for m in matches]) + "'?"))
    if url is None:
        info("Could not determine release repository url for repository '{0}' of distro '{1}'"
             .format(repository, distro))
        info("You can continue the release process by manually specifying the location of the RELEASE repository.")
        info("To be clear this is the url of the RELEASE repository not the upstream repository.")
        info("For release repositories on github, you should provide the `https://` url which should end in `.git`.")
        while True:
            try:
                url = safe_input('Release repository url [press enter to abort]: ')
            except (KeyboardInterrupt, EOFError):
                url = None
                info('', use_prefix=False)
            if not url:
                url = None
                error("No release repository url given, aborting.", exit=True)
                break
            # If github.com address, validate it
            if url is not None and 'github.com' in url:
                valid_url = True
                if not url.endswith('.git') and not url.endswith('.git/'):
                    valid_url = False
                    warning("The release repository url you provided does not end in `.git`.")
                if not url.startswith('https://'):
                    valid_url = False
                    warning("The release repository url you provided is not a `https://` address.")
                if not valid_url:
                    warning("Would you like to enter the address again?")
                    if maybe_continue():
                        url = None
                        continue
                    else:
                        info("Very well, the address '{url}' will be used as is.".format(**locals()))
                        break
            break
        global _user_provided_release_url
        _user_provided_release_url = url
    return url
Exemple #4
0
def get_github_interface():
    # First check to see if the oauth token is stored
    oauth_config_path = os.path.join(os.path.expanduser('~'), '.config', 'bloom')
    config = {}
    if os.path.exists(oauth_config_path):
        with open(oauth_config_path, 'r') as f:
            config = json.loads(f.read())
            token = config.get('oauth_token', None)
            username = config.get('github_user', None)
            if token and username:
                return Github(username, auth=auth_header_from_oauth_token(token), token=token)
    if not os.path.isdir(os.path.dirname(oauth_config_path)):
        os.makedirs(os.path.dirname(oauth_config_path))
    # Ok, now we have to ask for the user name and pass word
    info("")
    warning("Looks like bloom doesn't have an oauth token for you yet.")
    warning("Therefore bloom will require your Github username and password just this once.")
    warning("With your Github username and password bloom will create an oauth token on your behalf.")
    warning("The token will be stored in `~/.config/bloom`.")
    warning("You can delete the token from that file to have a new token generated.")
    warning("Guard this token like a password, because it allows someone/something to act on your behalf.")
    warning("If you need to unauthorize it, remove it from the 'Applications' menu in your Github account page.")
    info("")
    token = None
    while token is None:
        try:
            username = getpass.getuser()
            username = safe_input("Github username [{0}]: ".format(username)) or username
            password = getpass.getpass("Github password (never stored): ")
        except (KeyboardInterrupt, EOFError):
            return None
        if not password:
            error("No password was given, aborting.")
            return None
        gh = Github(username, auth=auth_header_from_basic_auth(username, password))
        try:
            token = gh.create_new_bloom_authorization(update_auth=True)
            with open(oauth_config_path, 'a') as f:
                config.update({'oauth_token': token, 'github_user': username})
                f.write(json.dumps(config))
            info("The token '{token}' was created and stored in the bloom config file: '{oauth_config_path}'"
                 .format(**locals()))
        except GithubException as exc:
            error("{0}".format(exc))
            info("")
            warning("This sometimes fails when the username or password are incorrect, try again?")
            if not maybe_continue():
                return None
    return gh
Exemple #5
0
def new(track, template=None, copy_track=None, overrides={}):
    """
    Creates a new track

    :param track: Name of the track to create
    :param template: Template to base new track off
    :param copy_track: Track to copy values of,
        if '' then use any availabe track if one exists
    :param overrides: dict of entries to override default values
    """
    tracks_dict = get_tracks_dict_raw()
    if track in tracks_dict['tracks']:
        error("Cannot create track '{0}' beause it exists.".format(track))
        error("Run `git-bloom-config edit {0}` instead.".format(track),
              exit=True)
    track_dict = copy.copy(DEFAULT_TEMPLATE)
    template_dict = copy.copy(config_template[template])
    if copy_track is not None:
        if template is not None:
            error("You cannot specify both a template and track to copy.",
                  exit=True)
        if copy_track == '' and len(tracks_dict['tracks']) != 0:
            copy_track = list(reversed(sorted(
                tracks_dict['tracks'].keys())))[0]
        if copy_track and copy_track not in tracks_dict['tracks']:
            error("Cannot copy a track which does not exist: '{0}'".format(
                copy_track),
                  exit=True)
        if copy_track:
            template_dict = tracks_dict['tracks'][copy_track]
        else:
            template_dict = {}
    for key in template_entry_order:
        if key in template_dict:
            track_dict[key].default = template_dict[key]
        if key in overrides:
            track_dict[key].default = overrides[key]
        if track_dict[key].default == ':{name}':
            track_dict[key].default = track
        ret = safe_input(str(track_dict[key]))
        if ret:
            track_dict[
                key].default = ret  # This type checks against self.values
            if ret in [':{none}', 'None']:
                track_dict[key].default = None
        track_dict[key] = track_dict[key].default
    tracks_dict['tracks'][track] = track_dict
    write_tracks_dict_raw(tracks_dict)
    info("Created '{0}' track.".format(track))
Exemple #6
0
def new(track, template=None, copy_track=None, overrides={}):
    """
    Creates a new track

    :param track: Name of the track to create
    :param template: Template to base new track off
    :param copy_track: Track to copy values of,
        if '' then use any availabe track if one exists
    :param overrides: dict of entries to override default values
    """
    tracks_dict = get_tracks_dict_raw()
    if track in tracks_dict['tracks']:
        error("Cannot create track '{0}' beause it exists.".format(track))
        error("Run `git-bloom-config edit {0}` instead.".format(track),
              exit=True)
    track_dict = copy.copy(DEFAULT_TEMPLATE)
    template_dict = copy.copy(config_template[template])
    if copy_track is not None:
        if template is not None:
            error("You cannot specify both a template and track to copy.",
                  exit=True)
        if copy_track == '' and len(tracks_dict['tracks']) != 0:
            copy_track = tracks_dict['tracks'].keys()[0]
        if copy_track and copy_track not in tracks_dict['tracks']:
            error("Cannot copy a track which does not exist: '{0}'"
                  .format(copy_track), exit=True)
        if copy_track:
            template_dict = tracks_dict['tracks'][copy_track]
        else:
            template_dict = {}
    for key in template_entry_order:
        if key in template_dict:
            track_dict[key].default = template_dict[key]
        if key in overrides:
            track_dict[key].default = overrides[key]
        if track_dict[key].default == ':{name}':
            track_dict[key].default = track
        ret = safe_input(str(track_dict[key]))
        if ret:
            track_dict[key].default = ret  # This type checks against self.values
            if ret in [':{none}', 'None']:
                track_dict[key].default = None
        track_dict[key] = track_dict[key].default
    tracks_dict['tracks'][track] = track_dict
    write_tracks_dict_raw(tracks_dict)
    info("Created '{0}' track.".format(track))
Exemple #7
0
def edit(track):
    tracks_dict = get_tracks_dict_raw()
    if track not in tracks_dict['tracks']:
        error("Track '{0}' does not exist.".format(track), exit=True)
    # Ensure the track is complete
    track_dict = tracks_dict['tracks'][track]
    update_track(track_dict)
    # Prompt for updates
    for key in template_entry_order:
        pe = DEFAULT_TEMPLATE[key]
        pe.default = tracks_dict['tracks'][track][key]
        ret = safe_input(str(pe))
        if ret:
            pe.default = ret  # This type checks against self.values
            if ret in [':{none}', 'None']:
                pe.default = None
        tracks_dict['tracks'][track][key] = pe.default
    tracks_dict['tracks'][track] = track_dict
    info("Saving '{0}' track.".format(track))
    write_tracks_dict_raw(tracks_dict)
Exemple #8
0
def edit(track):
    tracks_dict = get_tracks_dict_raw()
    if track not in tracks_dict['tracks']:
        error("Track '{0}' does not exist.".format(track), exit=True)
    # Ensure the track is complete
    track_dict = tracks_dict['tracks'][track]
    update_track(track_dict)
    # Prompt for updates
    for key in template_entry_order:
        pe = DEFAULT_TEMPLATE[key]
        pe.default = tracks_dict['tracks'][track][key]
        ret = safe_input(str(pe))
        if ret:
            pe.default = ret  # This type checks against self.values
            if ret in [':{none}', 'None']:
                pe.default = None
        tracks_dict['tracks'][track][key] = pe.default
    tracks_dict['tracks'][track] = track_dict
    info("Saving '{0}' track.".format(track))
    write_tracks_dict_raw(tracks_dict)
Exemple #9
0
def get_repo_uri(repository, distro):
    url = None
    # Fetch the distro file
    distribution_file = get_distribution_file(distro)
    if repository in distribution_file.repositories and \
       distribution_file.repositories[repository].release_repository is not None:
        url = distribution_file.repositories[repository].release_repository.url
    else:
        error("Specified repository '{0}' is not in the distribution file located at '{1}'"
              .format(repository, get_disitrbution_file_url(distro)))
        matches = difflib.get_close_matches(repository, distribution_file.repositories)
        if matches:
            info(fmt("@{yf}Did you mean one of these: '" + "', '".join([m for m in matches]) + "'?"))
    if url is None:
        info("Could not determine release repository url for repository '{0}' of distro '{1}'"
             .format(repository, distro))
        info("You can continue the release process by manually specifying the location of the RELEASE repository.")
        info("To be clear this is the url of the RELEASE repository not the upstream repository.")
        info("For release repositories on GitHub, you should provide the `https://` url which should end in `.git`.")
        info("Here is the url for a typical release repository on GitHub: https://github.com/ros-gbp/rviz-release.git")
        while True:
            try:
                url = safe_input('Release repository url [press enter to abort]: ')
            except (KeyboardInterrupt, EOFError):
                url = None
                info('', use_prefix=False)
            if not url:
                url = None
                error("No release repository url given, aborting.", exit=True)
                break
            if url is None:
                break
            # If github.com address, validate it
            if not validate_github_url(url, 'release'):
                continue
            break
        global _user_provided_release_url
        _user_provided_release_url = url
    return url
Exemple #10
0
def process_track_settings(track_dict, release_inc_override):
    settings = {}
    settings['name'] = track_dict['name']
    vcs_uri = track_dict['vcs_uri']
    # Is the vcs_uri set?
    if vcs_uri is None or vcs_uri.lower() == ':{none}':
        error("The '{0}' must be set to something other than None.".format(
            DEFAULT_TEMPLATE['vcs_uri'].name),
              exit=True)
    # Is the vcs_type set and valid?
    vcs_type = track_dict['vcs_type']
    vcs_type_prompt = DEFAULT_TEMPLATE['vcs_type']
    if vcs_type is None or vcs_type.lower() not in vcs_type_prompt.values:
        error("The '{0}' cannot be '{1}', valid values are: {2}".format(
            vcs_type_prompt.name, vcs_type, vcs_type_prompt.values),
              exit=True)
    settings['vcs_type'] = vcs_type
    # Is the version set to auto?
    version = track_dict['version']
    track_dict['ros_distro'] = str(track_dict['ros_distro'].lower())
    repo = None
    if version.lower() == ':{auto}':
        # Is the vcs_type either hg, git, or svn?
        if vcs_type not in ['git', 'hg', 'svn']:
            error(
                "Auto detection of version is not supported for '{0}'".format(
                    vcs_type),
                exit=True)
        devel_branch = track_dict['devel_branch']
        try:
            string_types = [str, unicode]
        except NameError:
            string_types = [str]
        if type(devel_branch) in string_types \
           and devel_branch.lower() == ':{none}':
            devel_branch = None
        version, repo = find_version_from_upstream(vcs_uri, vcs_type,
                                                   devel_branch,
                                                   track_dict['ros_distro'])
        if version is None:
            warning("Could not determine the version automatically.")
    if version is None or version == ':{ask}':
        ret = safe_input('What version are you releasing '
                         '(version should normally be MAJOR.MINOR.PATCH)? ')
        if not ret:
            error("You must specify a version to continue.", exit=True)
        version = ret
    settings['version'] = version
    vcs_uri = vcs_uri.replace(':{version}', version)
    settings['vcs_local_uri'] = repo.get_path() if repo else vcs_uri
    # Now that we have a version, template the vcs_uri if needed
    if ':{version}' in vcs_uri:
        vcs_uri = vcs_uri.replace(':{version}', version)
    settings['vcs_uri'] = vcs_uri
    # Is the release tag set to ask
    release_tag = track_dict['release_tag']
    release_tag_prompt = DEFAULT_TEMPLATE['release_tag']
    if release_tag is not None and release_tag == ':{ask}':
        ret = safe_input('What upstream tag should bloom import from? ')
        if not ret:
            error("You must specify a release tag.", exit=True)
        release_tag = ret
    elif release_tag is None or release_tag.lower() == ':{none}':
        if vcs_type not in ['svn', 'tar']:
            error(
                "'{0}' can not be None unless '{1}' is either 'svn' or 'tar'".
                format(release_tag_prompt.name, vcs_type_prompt.name))
        release_tag = ':{none}'
    else:
        release_tag = release_tag.replace(':{version}', version)
    settings['release_tag'] = release_tag
    # Transfer other settings
    settings['devel_branch'] = track_dict['devel_branch']
    settings['patches'] = track_dict['patches'] or ''
    settings['ros_distro'] = track_dict['ros_distro']
    # Release increment
    if 'last_version' in track_dict and track_dict['last_version'] != version:
        next_release_inc = str(1)
    else:
        next_release_inc = str(int(track_dict['release_inc']) + 1)
    settings['release_inc'] = release_inc_override or next_release_inc
    return settings
Exemple #11
0
def get_github_interface(quiet=False):
    def mfa_prompt(oauth_config_path, username):
        """Explain how to create a token for users with Multi-Factor Authentication configured."""
        warning("Receiving 401 when trying to create an oauth token can be caused by the user "
                "having two-factor authentication enabled.")
        warning("If 2FA is enabled, the user will have to create an oauth token manually.")
        warning("A token can be created at https://github.com/settings/tokens")
        warning("The resulting token can be placed in the '{oauth_config_path}' file as such:"
                .format(**locals()))
        info("")
        warning('{{"github_user": "******", "oauth_token": "TOKEN_GOES_HERE"}}'
                .format(**locals()))
        info("")

    global _gh
    if _gh is not None:
        return _gh
    # First check to see if the oauth token is stored
    oauth_config_path = os.path.join(os.path.expanduser('~'), '.config', 'bloom')
    config = {}
    if os.path.exists(oauth_config_path):
        with open(oauth_config_path, 'r') as f:
            config = json.loads(f.read())
            token = config.get('oauth_token', None)
            username = config.get('github_user', None)
            if token and username:
                return Github(username, auth=auth_header_from_oauth_token(token), token=token)
    if not os.path.isdir(os.path.dirname(oauth_config_path)):
        os.makedirs(os.path.dirname(oauth_config_path))
    if quiet:
        return None
    # Ok, now we have to ask for the user name and pass word
    info("")
    warning("Looks like bloom doesn't have an oauth token for you yet.")
    warning("Therefore bloom will require your GitHub username and password just this once.")
    warning("With your GitHub username and password bloom will create an oauth token on your behalf.")
    warning("The token will be stored in `~/.config/bloom`.")
    warning("You can delete the token from that file to have a new token generated.")
    warning("Guard this token like a password, because it allows someone/something to act on your behalf.")
    warning("If you need to unauthorize it, remove it from the 'Applications' menu in your GitHub account page.")
    info("")
    if not maybe_continue('y', "Would you like to create an OAuth token now"):
        return None
    token = None
    while token is None:
        try:
            username = getpass.getuser()
            username = safe_input("GitHub username [{0}]: ".format(username)) or username
            password = getpass.getpass("GitHub password (never stored): ")
        except (KeyboardInterrupt, EOFError):
            return None
        if not password:
            error("No password was given, aborting.")
            return None
        gh = Github(username, auth=auth_header_from_basic_auth(username, password))
        try:
            token = gh.create_new_bloom_authorization(update_auth=True)
            with open(oauth_config_path, 'w') as f:
                config.update({'oauth_token': token, 'github_user': username})
                f.write(json.dumps(config))
            info("The token '{token}' was created and stored in the bloom config file: '{oauth_config_path}'"
                 .format(**locals()))
        except GitHubAuthException as exc:
            error("{0}".format(exc))
            mfa_prompt(oauth_config_path, username)
        except GithubException as exc:
            error("{0}".format(exc))
            info("")
            if hasattr(exc, 'resp') and '{0}'.format(exc.resp.status) in ['401']:
                mfa_prompt(oauth_config_path, username)
            warning("This sometimes fails when the username or password are incorrect, try again?")
            if not maybe_continue():
                return None
    _gh = gh
    return gh
Exemple #12
0
def generate_ros_distro_diff(track, repository, distro):
    distribution_dict = get_distribution_file(distro).get_data()
    # Get packages
    packages = get_packages()
    if len(packages) == 0:
        warning("No packages found, will not generate 'package: path' entries for rosdistro.")
    # Get version
    track_dict = get_tracks_dict_raw()['tracks'][track]
    last_version = track_dict['last_version']
    release_inc = track_dict['release_inc']
    version = '{0}-{1}'.format(last_version, release_inc).encode('utf-8')
    # Create a repository if there isn't already one
    if repository not in distribution_dict['repositories']:
        global _user_provided_release_url
        distribution_dict['repositories'][repository] = {}
    # Create a release entry if there isn't already one
    if 'release' not in distribution_dict['repositories'][repository]:
        distribution_dict['repositories'][repository]['release'.encode('utf-8')] = {
            'url'.encode('utf-8'): _user_provided_release_url
        }
    # Update the repository
    repo = distribution_dict['repositories'][repository]['release']
    if 'tags' not in repo:
        repo['tags'.encode('utf-8')] = {}
    repo['tags']['release'.encode('utf-8')] = generate_release_tag(distro)
    repo['version'.encode('utf-8')] = version
    if 'packages' not in repo:
        repo['packages'.encode('utf-8')] = []
    for path, pkg in packages.items():
        if pkg.name not in repo['packages']:
            repo['packages'].append(pkg.name)
    # Remove any missing packages
    packages_being_released = [p.name for p in packages.values()]
    for pkg_name in list(repo['packages']):
        if pkg_name not in packages_being_released:
            repo['packages'].remove(pkg_name)
    repo['packages'].sort()

    def get_repository_info_from_user():
        data = {}
        while True:
            vcs_type = safe_input('VCS type [git, svn, hg, bzr]: ')
            if vcs_type in ['git', 'svn', 'hg', 'bzr']:
                break
            error("'{0}' is not a valid vcs type.".format(vcs_type))
            if not maybe_continue(msg='Try again'):
                return {}
        data['type'] = vcs_type
        while True:
            url = safe_input('VCS url: ')
            if url:
                break
            error("Nothing entered for url.")
            if not maybe_continue(msg='Try again'):
                return {}
        data['url'] = url
        while True:
            version = safe_input('VCS version [commit, tag, branch, etc]: ')
            if version:
                break
            error("Nothing entered for version.")
            if not maybe_continue(msg='Try again'):
                return {}
        data['version'] = version
        return data

    # Ask for doc entry
    if 'BLOOM_DONT_ASK_FOR_DOCS' not in os.environ:
        docs = distribution_dict['repositories'][repository].get('doc', {})
        if not docs and maybe_continue(msg='Would you like to add documentation information for this repository?'):
            info("Please enter your repository information for the doc generation job.")
            info("This information should point to the repository from which documentation should be generated.")
            docs = get_repository_info_from_user()
        distribution_dict['repositories'][repository]['doc'] = docs

    # Ask for source entry
    if 'BLOOM_DONT_ASK_FOR_SOURCE' not in os.environ:
        source = distribution_dict['repositories'][repository].get('source', {})
        if not source and maybe_continue(msg='Would you like to add source information for this repository?'):
            info("Please enter information which points ot the active development branch for this repository.")
            info("This information is used to run continuous integration jobs and for developers to checkout from.")
            source = get_repository_info_from_user()
        distribution_dict['repositories'][repository]['source'] = source

    # Ask for maintainership information
    if 'BLOOM_DONT_ASK_FOR_MAINTENANCE_STATUS' not in os.environ:
        status = distribution_dict['repositories'][repository].get('status', None)
        description = distribution_dict['repositories'][repository].get('status_description', None)
        if status is None and maybe_continue(msg='Would you like to add a maintenance status for this repository?'):
            info("Please enter a maintenance status.")
            info("Valid maintenance statuses:")
            info("- developed: active development is in progress")
            info("- maintained: no new development, but bug fixes and pull requests are addressed")
            info("- end-of-life: should not be used, will disapear at some point")
            while True:
                status = safe_input('Status: ')
                if status in ['developed', 'maintained', 'end-of-life']:
                    break
                error("'{0}' is not a valid status.".format(status))
                if not maybe_continue(msg='Try again'):
                    status = None
                    break
            if status is not None:
                info("You can also enter a status description.")
                info("This is usually reserved for giving a reason when a status is 'end-of-life'.")
                if description is not None:
                    info("Current status description: {0}".format(description))
                description_in = safe_input('Status Description [press Enter for no change]: ')
                if description_in:
                    description = description_in
        if status is not None:
            distribution_dict['repositories'][repository]['status'] = status
            if description is not None:
                distribution_dict['repositories'][repository]['status_description'] = description

    # Do the diff
    distro_file_name = get_relative_distribution_file_path(distro)
    updated_distribution_file = rosdistro.DistributionFile(distro, distribution_dict)
    distro_dump = yaml_from_distribution_file(updated_distribution_file)
    distro_file_raw = load_url_to_file_handle(get_disitrbution_file_url(distro)).read()
    if distro_file_raw != distro_dump:
        # Calculate the diff
        udiff = difflib.unified_diff(distro_file_raw.splitlines(), distro_dump.splitlines(),
                                     fromfile=distro_file_name, tofile=distro_file_name)
        temp_dir = tempfile.mkdtemp()
        udiff_file = os.path.join(temp_dir, repository + '-' + version + '.patch')
        udiff_raw = ''
        info("Unified diff for the ROS distro file located at '{0}':".format(udiff_file))
        for line in udiff:
            if line.startswith('@@'):
                udiff_raw += line
                line = fmt('@{cf}' + sanitize(line))
            if line.startswith('+'):
                if not line.startswith('+++'):
                    line += '\n'
                udiff_raw += line
                line = fmt('@{gf}' + sanitize(line))
            if line.startswith('-'):
                if not line.startswith('---'):
                    line += '\n'
                udiff_raw += line
                line = fmt('@{rf}' + sanitize(line))
            if line.startswith(' '):
                line += '\n'
                udiff_raw += line
            info(line, use_prefix=False, end='')
        # Assert that only this repository is being changed
        distro_file_yaml = yaml.load(distro_file_raw)
        distro_yaml = yaml.load(distro_dump)
        if 'repositories' in distro_file_yaml:
            distro_file_repos = distro_file_yaml['repositories']
            for repo in distro_yaml['repositories']:
                if repo == repository:
                    continue
                if repo not in distro_file_repos or distro_file_repos[repo] != distro_yaml['repositories'][repo]:
                    error("This generated pull request modifies a repository entry other than the one being released.")
                    error("This likely occured because the upstream rosdistro changed during this release.")
                    error("This pull request will abort, please re-run this command with the -p option to try again.",
                          exit=True)
        # Write the diff out to file
        with open(udiff_file, 'w+') as f:
            f.write(udiff_raw)
        # Return the diff
        return updated_distribution_file
    else:
        warning("This release resulted in no changes to the ROS distro file...")
    return None
Exemple #13
0
def process_track_settings(track_dict, release_inc_override):
    settings = {}
    settings['name'] = track_dict['name']
    vcs_uri = track_dict['vcs_uri']
    # Is the vcs_uri set?
    if vcs_uri is None or vcs_uri.lower() == ':{none}':
        error("The '{0}' must be set to something other than None."
              .format(DEFAULT_TEMPLATE['vcs_uri'].name),
              exit=True)
    # Is the vcs_type set and valid?
    vcs_type = track_dict['vcs_type']
    vcs_type_prompt = DEFAULT_TEMPLATE['vcs_type']
    if vcs_type is None or vcs_type.lower() not in vcs_type_prompt.values:
        error("The '{0}' cannot be '{1}', valid values are: {2}"
              .format(vcs_type_prompt.name, vcs_type, vcs_type_prompt.values),
              exit=True)
    settings['vcs_type'] = vcs_type
    # Is the version set to auto?
    version = track_dict['version']
    track_dict['ros_distro'] = str(track_dict['ros_distro'].lower())
    repo = None
    if version.lower() == ':{auto}':
        # Is the vcs_type either hg, git, or svn?
        if vcs_type not in ['git', 'hg', 'svn']:
            error("Auto detection of version is not supported for '{0}'"
                  .format(vcs_type), exit=True)
        devel_branch = track_dict['devel_branch']
        try:
            string_types = [str, unicode]
        except NameError:
            string_types = [str]
        if type(devel_branch) in string_types \
           and devel_branch.lower() == ':{none}':
            devel_branch = None
        version, repo = find_version_from_upstream(vcs_uri,
                                                   vcs_type,
                                                   devel_branch,
                                                   track_dict['ros_distro'])
        if version is None:
            warning("Could not determine the version automatically.")
    if version is None or version == ':{ask}':
        ret = safe_input('What version are you releasing '
                         '(version should normally be MAJOR.MINOR.PATCH)? ')
        if not ret:
            error("You must specify a version to continue.", exit=True)
        version = ret
    settings['version'] = version
    vcs_uri = vcs_uri.replace(':{version}', version)
    settings['vcs_local_uri'] = repo.get_path() if repo else vcs_uri
    # Now that we have a version, template the vcs_uri if needed
    if ':{version}' in vcs_uri:
        vcs_uri = vcs_uri.replace(':{version}', version)
    settings['vcs_uri'] = vcs_uri
    # Is the release tag set to ask
    release_tag = track_dict['release_tag']
    release_tag_prompt = DEFAULT_TEMPLATE['release_tag']
    if release_tag is not None and release_tag == ':{ask}':
        ret = safe_input('What upstream tag should bloom import from? ')
        if not ret:
            error("You must specify a release tag.", exit=True)
        release_tag = ret
    elif release_tag is None or release_tag.lower() == ':{none}':
        if vcs_type not in ['svn', 'tar']:
            error("'{0}' can not be None unless '{1}' is either 'svn' or 'tar'"
                  .format(release_tag_prompt.name, vcs_type_prompt.name))
        release_tag = ':{none}'
    else:
        release_tag = release_tag.replace(':{version}', version)
    settings['release_tag'] = release_tag
    # Transfer other settings
    settings['devel_branch'] = track_dict['devel_branch']
    settings['patches'] = track_dict['patches'] or ''
    settings['ros_distro'] = track_dict['ros_distro']
    # Release increment
    if 'last_version' in track_dict and track_dict['last_version'] != version:
        next_release_inc = str(0)
    else:
        next_release_inc = str(int(track_dict['release_inc']) + 1)
    settings['release_inc'] = release_inc_override or next_release_inc
    return settings
Exemple #14
0
def open_pull_request(track, repository, distro, ssh_pull_request):
    # Get the diff
    distribution_file = get_distribution_file(distro)
    if repository in distribution_file.repositories and \
       distribution_file.repositories[repository].release_repository is not None:
        orig_version = distribution_file.repositories[repository].release_repository.version
    else:
        orig_version = None
    updated_distribution_file = generate_ros_distro_diff(track, repository, distro)
    if updated_distribution_file is None:
        # There were no changes, no pull request required
        return None
    version = updated_distribution_file.repositories[repository].release_repository.version
    updated_distro_file_yaml = yaml_from_distribution_file(updated_distribution_file)
    # Determine if the distro file is hosted on github...
    gh_org, gh_repo, gh_branch, gh_path = get_gh_info(get_disitrbution_file_url(distro))
    if None in [gh_org, gh_repo, gh_branch, gh_path]:
        warning("Automated pull request only available via github.com")
        return
    # Get the github user name
    gh_username = None
    bloom_user_path = os.path.join(os.path.expanduser('~'), '.bloom_user')
    if os.path.exists(bloom_user_path):
        with open(bloom_user_path, 'r') as f:
            gh_username = f.read().strip()
    gh_username = gh_username or getpass.getuser()
    response = safe_input("github user name [{0}]: ".format(gh_username))
    if response:
        gh_username = response
        info("Would you like bloom to store your github user name (~/.bloom_user)?")
        if maybe_continue():
            with open(bloom_user_path, 'w') as f:
                f.write(gh_username)
        else:
            with open(bloom_user_path, 'w') as f:
                f.write(' ')
            warning("If you want to have bloom store it in the future remove the ~/.bloom_user file.")
    # Get the github password
    gh_password = getpass.getpass("github password (This is not stored):")
    if not gh_password or not gh_username:
        error("Either the github username or github password is not set.")
        warning("Skipping the pull request...")
        return
    # Check for fork
    info(fmt("@{bf}@!==> @|@!Checking for rosdistro fork on github..."))
    gh_user_repos = fetch_github_api('https://api.github.com/users/{0}/repos'.format(gh_username), use_pagination=True)
    if gh_user_repos is None:
        error("Failed to get a list of repositories for user: '******'".format(gh_username))
        warning("Skipping the pull request...")
        return
    if 'rosdistro' not in [x['name'] for x in gh_user_repos if 'name' in x]:
        warning("Github user '{0}' does not have a fork ".format(gh_username) +
                "of the {0}:{1} repository, create one?".format(gh_org, gh_repo))
        if not maybe_continue():
            warning("Skipping the pull request...")
            return
        # Create a fork
        create_fork(gh_org, gh_repo, gh_username, gh_password)
    # Clone the fork
    info(fmt("@{bf}@!==> @|@!" + "Cloning {0}/{1}...".format(gh_username, gh_repo)))
    temp_dir = tempfile.mkdtemp()
    new_branch = None
    title = "{0}: {1} in '{2}' [bloom]".format(repository, version, gh_path)
    body = """\
Increasing version of package(s) in repository `{0}`:
- previous version: `{1}`
- new version: `{2}`
- distro file: `{3}`
- bloom version: `{4}`
""".format(repository, orig_version or 'null', version, gh_path, bloom.__version__)
    with change_directory(temp_dir):
        def _my_run(cmd):
            info(fmt("@{bf}@!==> @|@!" + str(cmd)))
            # out = check_output(cmd, stderr=subprocess.STDOUT, shell=True)
            out = None
            from subprocess import call
            call(cmd, shell=True)
            if out:
                info(out, use_prefix=False)
        if ssh_pull_request:
            rosdistro_git_fork = '[email protected]/{0}/{1}.git'.format(gh_username, gh_repo)
        else:
            rosdistro_git_fork = 'https://github.com/{0}/{1}.git'.format(gh_username, gh_repo)
        _my_run('git clone {0}'.format(rosdistro_git_fork))
        with change_directory(gh_repo):
            _my_run('git remote add bloom https://github.com/{0}/{1}.git'.format(gh_org, gh_repo))
            _my_run('git remote update')
            _my_run('git fetch')
            track_branches()
            branches = get_branches()
            new_branch = 'bloom-{repository}-{count}'
            count = 0
            while new_branch.format(repository=repository, count=count) in branches:
                count += 1
            new_branch = new_branch.format(repository=repository, count=count)
            # Final check
            info(fmt("@{cf}Pull Request Title: @{yf}" + title))
            info(fmt("@{cf}Pull Request Body : \n@{yf}" + body))
            msg = fmt("@!Open a @|@{cf}pull request@| @!@{kf}from@| @!'@|@!@{bf}" +
                      "{gh_username}/{gh_repo}:{new_branch}".format(**locals()) +
                      "@|@!' @!@{kf}into@| @!'@|@!@{bf}" +
                      "{gh_org}/{gh_repo}:{gh_branch}".format(**locals()) +
                      "@|@!'?")
            info(msg)
            if not maybe_continue():
                warning("Skipping the pull request...")
                return
            _my_run('git checkout -b {0} bloom/{1}'.format(new_branch, gh_branch))
            with open('{0}'.format(gh_path), 'w') as f:
                info(fmt("@{bf}@!==> @|@!Writing new distribution file: ") + str(gh_path))
                f.write(updated_distro_file_yaml)
            _my_run('git add {0}'.format(gh_path))
            _my_run('git commit -m "{0}"'.format(title))
            _my_run('git push origin {0}'.format(new_branch))
    # Open the pull request
    return create_pull_request(gh_org, gh_repo, gh_username, gh_password, gh_branch, new_branch, title, body)
Exemple #15
0
def get_github_interface(quiet=False):
    def mfa_prompt(oauth_config_path, username):
        """Explain how to create a token for users with Multi-Factor Authentication configured."""
        warning(
            "Receiving 401 when trying to create an oauth token can be caused by the user "
            "having two-factor authentication enabled.")
        warning(
            "If 2FA is enabled, the user will have to create an oauth token manually."
        )
        warning("A token can be created at https://github.com/settings/tokens")
        warning(
            "The resulting token can be placed in the '{oauth_config_path}' file as such:"
            .format(**locals()))
        info("")
        warning(
            '{{"github_user": "******", "oauth_token": "TOKEN_GOES_HERE"}}'
            .format(**locals()))
        info("")

    global _gh
    if _gh is not None:
        return _gh
    # First check to see if the oauth token is stored
    oauth_config_path = os.path.join(os.path.expanduser('~'), '.config',
                                     'bloom')
    config = {}
    if os.path.exists(oauth_config_path):
        with open(oauth_config_path, 'r') as f:
            config = json.loads(f.read())
            token = config.get('oauth_token', None)
            username = config.get('github_user', None)
            if token and username:
                return Github(username,
                              auth=auth_header_from_token(username, token),
                              token=token)
    if not os.path.isdir(os.path.dirname(oauth_config_path)):
        os.makedirs(os.path.dirname(oauth_config_path))
    if quiet:
        return None
    # Ok, now we have to ask for the user name and pass word
    info("")
    warning("Looks like bloom doesn't have an oauth token for you yet.")
    warning(
        "You can create a token by visiting https://github.com/settings/tokens in your browser."
    )
    warning(
        "For bloom to work the token must have at least `public_repo` scope.")
    warning(
        "If you want bloom to be able to automatically update your fork of ros/rosdistro (recommended)"
    )
    warning("then you must also enable the workflow scope for the token.")
    warning(
        "If you need to unauthorize it, remove it from the 'Tokens' menu in your GitHub account settings."
    )
    info("")
    if not maybe_continue('y', 'Would you like to enter an access token now'):
        return None
    token = None
    while token is None:
        try:
            username = getpass.getuser()
            username = safe_input(
                "GitHub username [{0}]: ".format(username)) or username
            token = getpass.getpass("GitHub access token: ").strip()
        except (KeyboardInterrupt, EOFError):
            return None
        if not token:
            error("No token was given, aborting.")
            return None
        gh = Github(username, auth=auth_header_from_token(username, token))
        try:
            gh.check_token_validity(username, token, update_auth=True)
            with open(oauth_config_path, 'w') as f:
                config.update({'oauth_token': token, 'github_user': username})
                f.write(json.dumps(config))
            info(
                "The token '{token}' was created and stored in the bloom config file: '{oauth_config_path}'"
                .format(**locals()))
        except GitHubAuthException as exc:
            error("{0}".format(exc))
            mfa_prompt(oauth_config_path, username)
        except GithubException as exc:
            error("{0}".format(exc))
            info("")
            if hasattr(exc, 'resp') and '{0}'.format(
                    exc.resp.status) in ['401']:
                mfa_prompt(oauth_config_path, username)
            warning(
                "This sometimes fails when the username or password are incorrect, try again?"
            )
            if not maybe_continue():
                return None
    _gh = gh
    return gh
Exemple #16
0
def get_github_interface(quiet=False):
    def mfa_prompt(oauth_config_path, username):
        """Explain how to create a token for users with Multi-Factor Authentication configured."""
        warning(
            "Receiving 401 when trying to create an oauth token can be caused by the user "
            "having two-factor authentication enabled.")
        warning(
            "If 2FA is enabled, the user will have to create an oauth token manually."
        )
        warning("A token can be created at https://github.com/settings/tokens")
        warning(
            "The resulting token can be placed in the '{oauth_config_path}' file as such:"
            .format(**locals()))
        info("")
        warning(
            '{{"github_user": "******", "oauth_token": "TOKEN_GOES_HERE"}}'
            .format(**locals()))
        info("")

    global _gh
    if _gh is not None:
        return _gh
    # First check to see if the oauth token is stored
    oauth_config_path = os.path.join(os.path.expanduser('~'), '.config',
                                     'bloom')
    config = {}
    if os.path.exists(oauth_config_path):
        with open(oauth_config_path, 'r') as f:
            config = json.loads(f.read())
            token = config.get('oauth_token', None)
            username = config.get('github_user', None)
            if token and username:
                return Github(username,
                              auth=auth_header_from_oauth_token(token),
                              token=token)
    if not os.path.isdir(os.path.dirname(oauth_config_path)):
        os.makedirs(os.path.dirname(oauth_config_path))
    if quiet:
        return None
    # Ok, now we have to ask for the user name and pass word
    info("")
    warning("Looks like bloom doesn't have an oauth token for you yet.")
    warning(
        "Therefore bloom will require your GitHub username and password just this once."
    )
    warning(
        "With your GitHub username and password bloom will create an oauth token on your behalf."
    )
    warning("The token will be stored in `~/.config/bloom`.")
    warning(
        "You can delete the token from that file to have a new token generated."
    )
    warning(
        "Guard this token like a password, because it allows someone/something to act on your behalf."
    )
    warning(
        "If you need to unauthorize it, remove it from the 'Applications' menu in your GitHub account page."
    )
    info("")
    if not maybe_continue('y', "Would you like to create an OAuth token now"):
        return None
    token = None
    while token is None:
        try:
            username = getpass.getuser()
            username = safe_input(
                "GitHub username [{0}]: ".format(username)) or username
            password = getpass.getpass("GitHub password (never stored): ")
        except (KeyboardInterrupt, EOFError):
            return None
        if not password:
            error("No password was given, aborting.")
            return None
        gh = Github(username,
                    auth=auth_header_from_basic_auth(username, password))
        try:
            token = gh.create_new_bloom_authorization(update_auth=True)
            with open(oauth_config_path, 'w') as f:
                config.update({'oauth_token': token, 'github_user': username})
                f.write(json.dumps(config))
            info(
                "The token '{token}' was created and stored in the bloom config file: '{oauth_config_path}'"
                .format(**locals()))
        except GitHubAuthException as exc:
            error("{0}".format(exc))
            mfa_prompt(oauth_config_path, username)
        except GithubException as exc:
            error("{0}".format(exc))
            info("")
            if hasattr(exc, 'resp') and '{0}'.format(
                    exc.resp.status) in ['401']:
                mfa_prompt(oauth_config_path, username)
            warning(
                "This sometimes fails when the username or password are incorrect, try again?"
            )
            if not maybe_continue():
                return None
    _gh = gh
    return gh