Ensure a repository is present

    name
        This is the name of the repository.

    description
        The description of the repository.

    homepage
        The URL with more information about the repository.

    private
        The visiblity of the repository. Note that private repositories require
        a paid GitHub account.

    has_issues
        Whether to enable issues for this repository.

    has_wiki
        Whether to enable the wiki for this repository.

    has_downloads
        Whether to enable downloads for this repository.

    auto_init
        Whether to create an initial commit with an empty README.

    gitignore_template
        The desired language or platform for a .gitignore, e.g "Haskell".

    license_template
        The desired LICENSE template to apply, e.g "mit" or "mozilla".

    teams
        The teams for which this repo should belong to, specified as a dict of
        team name to permission ('pull', 'push' or 'admin').

        .. versionadded:: 2017.7.0

    Example:

    .. code-block:: yaml

        Ensure repo my-repo is present in github:
            github.repo_present:
                - name: 'my-repo'
                - description: 'My very important repository'

    .. versionadded:: 2016.11.0

def repo_present(
        name,
        description=None,
        homepage=None,
        private=None,
        has_issues=None,
        has_wiki=None,
        has_downloads=None,
        auto_init=False,
        gitignore_template=None,
        license_template=None,
        teams=None,
        profile="github",
        **kwargs):
    '''
    Ensure a repository is present

    name
        This is the name of the repository.

    description
        The description of the repository.

    homepage
        The URL with more information about the repository.

    private
        The visiblity of the repository. Note that private repositories require
        a paid GitHub account.

    has_issues
        Whether to enable issues for this repository.

    has_wiki
        Whether to enable the wiki for this repository.

    has_downloads
        Whether to enable downloads for this repository.

    auto_init
        Whether to create an initial commit with an empty README.

    gitignore_template
        The desired language or platform for a .gitignore, e.g "Haskell".

    license_template
        The desired LICENSE template to apply, e.g "mit" or "mozilla".

    teams
        The teams for which this repo should belong to, specified as a dict of
        team name to permission ('pull', 'push' or 'admin').

        .. versionadded:: 2017.7.0

    Example:

    .. code-block:: yaml

        Ensure repo my-repo is present in github:
            github.repo_present:
                - name: 'my-repo'
                - description: 'My very important repository'

    .. versionadded:: 2016.11.0
    '''
    ret = {
        'name': name,
        'changes': {},
        'result': True,
        'comment': ''
    }

    # This is an optimization to cache all repos in the organization up front.
    # The first use of this state will collect all of the repos and save a bunch
    # of API calls for future use.
    __salt__['github.list_repos'](profile=profile)

    try:
        target = __salt__['github.get_repo_info'](name, profile=profile, **kwargs)
    except CommandExecutionError:
        target = None

    given_params = {
        'description': description,
        'homepage': homepage,
        'private': private,
        'has_issues': has_issues,
        'has_wiki': has_wiki,
        'has_downloads': has_downloads,
        'auto_init': auto_init,
        'gitignore_template': gitignore_template,
        'license_template': license_template
    }

    # Keep track of current_teams if we've fetched them after creating a new repo
    current_teams = None

    if target:  # Repo already exists
        # Some params are only valid on repo creation
        ignore_params = ['auto_init', 'gitignore_template', 'license_template']
        parameters = {}
        old_parameters = {}
        for param_name, param_value in six.iteritems(given_params):
            if (param_value is not None and param_name not in ignore_params and
                    target[param_name] is not param_value and
                    target[param_name] != param_value):
                parameters[param_name] = param_value
                old_parameters[param_name] = target[param_name]

        if parameters:
            repo_change = {
                'old': 'Repo properties were {0}'.format(old_parameters),
                'new': 'Repo properties (that changed) are {0}'.format(parameters)
            }
            if __opts__['test']:
                ret['changes']['repo'] = repo_change
                ret['result'] = None
            else:
                result = __salt__['github.edit_repo'](name, profile=profile,
                                                      **parameters)
                if result:
                    ret['changes']['repo'] = repo_change
                else:
                    ret['result'] = False
                    ret['comment'] = 'Failed to update repo properties.'
                    return ret

    else:  # Repo does not exist - it will be created.
        repo_change = {
            'old': None,
            'new': 'Repo {0} has been created'.format(name)
        }
        if __opts__['test']:
            ret['changes']['repo'] = repo_change
            ret['result'] = None
        else:
            add_params = dict(given_params)
            add_params.update(kwargs)
            result = __salt__['github.add_repo'](
                name,
                **add_params
            )

            if not result:
                ret['result'] = False
                ret['comment'] = 'Failed to create repo {0}.'.format(name)
                return ret

            # Turns out that trying to fetch teams for a new repo can 404 immediately
            # after repo creation, so this waits until we can fetch teams successfully
            # before continuing.
            for attempt in range(3):
                time.sleep(1)
                try:
                    current_teams = __salt__['github.get_repo_teams'](
                        name, profile=profile, **kwargs)
                    break
                except CommandExecutionError as e:
                    log.info("Attempt %s to fetch new repo %s failed",
                             attempt,
                             name)

            if current_teams is None:
                ret['result'] = False
                ret['comment'] = 'Failed to verify repo {0} after creation.'.format(name)
                return ret

            ret['changes']['repo'] = repo_change

    if teams is not None:
        if __opts__['test'] and not target:
            # Assume no teams if we're in test mode and the repo doesn't exist
            current_teams = []
        elif current_teams is None:
            current_teams = __salt__['github.get_repo_teams'](name, profile=profile)
        current_team_names = set([t['name'] for t in current_teams])

        # First remove any teams that aren't present
        for team_name in current_team_names:
            if team_name not in teams:
                team_change = {
                    'old': 'Repo {0} is in team {1}'.format(name, team_name),
                    'new': 'Repo {0} is not in team {1}'.format(name, team_name)
                }

                if __opts__['test']:
                    ret['changes'][team_name] = team_change
                    ret['result'] = None
                else:
                    result = __salt__['github.remove_team_repo'](name, team_name,
                                                                 profile=profile)
                    if result:
                        ret['changes'][team_name] = team_change
                    else:
                        ret['result'] = False
                        ret['comment'] = ('Failed to remove repo {0} from team {1}.'
                                          .format(name, team_name))
                        return ret

        # Next add or modify any necessary teams
        for team_name, permission in six.iteritems(teams):
            if team_name not in current_team_names:  # Need to add repo to team
                team_change = {
                    'old': 'Repo {0} is not in team {1}'.format(name, team_name),
                    'new': 'Repo {0} is in team {1}'.format(name, team_name)
                }
                if __opts__['test']:
                    ret['changes'][team_name] = team_change
                    ret['result'] = None
                else:
                    result = __salt__['github.add_team_repo'](name, team_name,
                                                              profile=profile,
                                                              permission=permission)
                    if result:
                        ret['changes'][team_name] = team_change
                    else:
                        ret['result'] = False
                        ret['comment'] = ('Failed to remove repo {0} from team {1}.'
                                          .format(name, team_name))
                        return ret
            else:
                current_permission = (__salt__['github.list_team_repos']
                                      (team_name, profile=profile)
                                      .get(name.lower(), {})
                                      .get('permission'))
                if not current_permission:
                    ret['result'] = False
                    ret['comment'] = ('Failed to determine current permission for team '
                                      '{0} in repo {1}'.format(team_name, name))
                    return ret
                elif current_permission != permission:
                    team_change = {
                        'old': ('Repo {0} in team {1} has permission {2}'
                                .format(name, team_name, current_permission)),
                        'new': ('Repo {0} in team {1} has permission {2}'
                                .format(name, team_name, permission))
                    }
                    if __opts__['test']:
                        ret['changes'][team_name] = team_change
                        ret['result'] = None
                    else:
                        result = __salt__['github.add_team_repo'](name, team_name,
                                                                  profile=profile,
                                                                  permission=permission)
                        if result:
                            ret['changes'][team_name] = team_change
                        else:
                            ret['result'] = False
                            ret['comment'] = ('Failed to set permission on repo {0} from '
                                              'team {1} to {2}.'
                                              .format(name, team_name, permission))
                            return ret
    return ret