def installed(name,
              pkgs=None,
              dir=None,
              user=None,
              force_reinstall=False,
              registry=None,
              env=None):
    '''
    Verify that the given package is installed and is at the correct version
    (if specified).

    .. code-block:: yaml

        coffee-script:
          npm.installed:
            - user: someuser

        coffee-script@1.0.1:
          npm.installed: []

    name
        The package to install

        .. versionchanged:: 2014.7.2
            This parameter is no longer lowercased by salt so that
            case-sensitive NPM package names will work.

    pkgs
        A list of packages to install with a single npm invocation; specifying
        this argument will ignore the ``name`` argument

        .. versionadded:: 2014.7.0

    dir
        The target directory in which to install the package, or None for
        global installation

    user
        The user to run NPM with

        .. versionadded:: 0.17.0

    registry
        The NPM registry from which to install the package

        .. versionadded:: 2014.7.0

    env
        A list of environment variables to be set prior to execution. The
        format is the same as the :py:func:`cmd.run <salt.states.cmd.run>`.
        state function.

        .. versionadded:: 2014.7.0

    force_reinstall
        Install the package even if it is already installed
    '''
    ret = {'name': name, 'result': None, 'comment': '', 'changes': {}}

    pkg_list = pkgs if pkgs else [name]

    try:
        installed_pkgs = __salt__['npm.list'](dir=dir, runas=user, env=env, depth=0)
    except (CommandNotFoundError, CommandExecutionError) as err:
        ret['result'] = False
        ret['comment'] = 'Error looking up \'{0}\': {1}'.format(name, err)
        return ret
    else:
        installed_pkgs = dict((p, info)
                for p, info in six.iteritems(installed_pkgs))

    pkgs_satisfied = []
    pkgs_to_install = []

    def _pkg_is_installed(pkg, installed_pkgs):
        '''
        Helper function to determine if a package is installed

        This performs more complex comparison than just checking
        keys, such as examining source repos to see if the package
        was installed by a different name from the same repo

        :pkg str: The package to compare
        :installed_pkgs: A dictionary produced by npm list --json
        '''
        if (pkg_name in installed_pkgs and
            'version' in installed_pkgs[pkg_name]):
            return True
        # Check to see if we are trying to install from a URI
        elif '://' in pkg_name:  # TODO Better way?
            for pkg_details in installed_pkgs.values():
                try:
                    pkg_from = pkg_details.get('from', '').split('://')[1]
                    # Catch condition where we may have specified package as
                    # git://github.com/foo/bar but packager describes it as
                    # git://github.com/foo/bar.git in the package
                    if not pkg_from.endswith('.git') and pkg_name.startswith('git://'):
                        pkg_from += '.git'
                    if pkg_name.split('://')[1] == pkg_from:
                        return True
                except IndexError:
                    pass
        return False
    for pkg in pkg_list:
        # Valid:
        #
        # @google-cloud/bigquery@^0.9.6
        # @foobar
        # buffer-equal-constant-time@1.0.1
        # coffee-script
        matches = re.search(r'^(@?[^@\s]+)(?:@(\S+))?', pkg)
        pkg_name, pkg_ver = matches.group(1), matches.group(2) or None

        if force_reinstall is True:
            pkgs_to_install.append(pkg)
            continue
        if not _pkg_is_installed(pkg, installed_pkgs):
            pkgs_to_install.append(pkg)
            continue

        installed_name_ver = '{0}@{1}'.format(pkg_name,
                installed_pkgs[pkg_name]['version'])

        # If given an explicit version check the installed version matches.
        if pkg_ver:
            if installed_pkgs[pkg_name].get('version') != pkg_ver:
                pkgs_to_install.append(pkg)
            else:
                pkgs_satisfied.append(installed_name_ver)

            continue
        else:
            pkgs_satisfied.append(installed_name_ver)
            continue

    if __opts__['test']:
        ret['result'] = None

        comment_msg = []
        if pkgs_to_install:
            comment_msg.append('NPM package(s) \'{0}\' are set to be installed'
                .format(', '.join(pkgs_to_install)))

            ret['changes'] = {'old': [], 'new': pkgs_to_install}

        if pkgs_satisfied:
            comment_msg.append('Package(s) \'{0}\' satisfied by {1}'
                .format(', '.join(pkg_list), ', '.join(pkgs_satisfied)))
            ret['result'] = True

        ret['comment'] = '. '.join(comment_msg)
        return ret

    if not pkgs_to_install:
        ret['result'] = True
        ret['comment'] = ('Package(s) \'{0}\' satisfied by {1}'
                .format(', '.join(pkg_list), ', '.join(pkgs_satisfied)))
        return ret

    try:
        cmd_args = {
            'dir': dir,
            'runas': user,
            'registry': registry,
            'env': env,
            'pkgs': pkg_list,
        }

        call = __salt__['npm.install'](**cmd_args)
    except (CommandNotFoundError, CommandExecutionError) as err:
        ret['result'] = False
        ret['comment'] = 'Error installing \'{0}\': {1}'.format(
                ', '.join(pkg_list), err)
        return ret

    if call and (isinstance(call, list) or isinstance(call, dict)):
        ret['result'] = True
        ret['changes'] = {'old': [], 'new': pkgs_to_install}
        ret['comment'] = 'Package(s) \'{0}\' successfully installed'.format(
                ', '.join(pkgs_to_install))
    else:
        ret['result'] = False
        ret['comment'] = 'Could not install package(s) \'{0}\''.format(
                ', '.join(pkg_list))

    return ret
def installed ( name , pkgs = None , dir = None , user = None , force_reinstall = False , registry = None , env = None ) : ret = { 'name' : name , 'result' : None , 'comment' : '' , 'changes' : { } } pkg_list = pkgs if pkgs else [ name ] try : installed_pkgs = __salt__ [ 'npm.list' ] ( dir = dir , runas = user , env = env , depth = 0 ) except ( CommandNotFoundError , CommandExecutionError ) as err : ret [ 'result' ] = False ret [ 'comment' ] = 'Error looking up \'{0}\': {1}' . format ( name , err ) return ret else : installed_pkgs = dict ( ( p , info ) for p , info in six . iteritems ( installed_pkgs ) ) pkgs_satisfied = [ ] pkgs_to_install = [ ] def _pkg_is_installed ( pkg , installed_pkgs ) : if ( pkg_name in installed_pkgs and 'version' in installed_pkgs [ pkg_name ] ) : return True elif '://' in pkg_name : for pkg_details in installed_pkgs . values ( ) : try : pkg_from = pkg_details . get ( 'from' , '' ) . split ( '://' ) [ 1 ] if not pkg_from . endswith ( '.git' ) and pkg_name . startswith ( 'git://' ) : pkg_from += '.git' if pkg_name . split ( '://' ) [ 1 ] == pkg_from : return True except IndexError : pass return False for pkg in pkg_list : matches = re . search ( r'^(@?[^@\s]+)(?:@(\S+))?' , pkg ) pkg_name , pkg_ver = matches . group ( 1 ) , matches . group ( 2 ) or None if force_reinstall is True : pkgs_to_install . append ( pkg ) continue if not _pkg_is_installed ( pkg , installed_pkgs ) : pkgs_to_install . append ( pkg ) continue installed_name_ver = '{0}@{1}' . format ( pkg_name , installed_pkgs [ pkg_name ] [ 'version' ] ) if pkg_ver : if installed_pkgs [ pkg_name ] . get ( 'version' ) != pkg_ver : pkgs_to_install . append ( pkg ) else : pkgs_satisfied . append ( installed_name_ver ) continue else : pkgs_satisfied . append ( installed_name_ver ) continue if __opts__ [ 'test' ] : ret [ 'result' ] = None comment_msg = [ ] if pkgs_to_install : comment_msg . append ( 'NPM package(s) \'{0}\' are set to be installed' . format ( ', ' . join ( pkgs_to_install ) ) ) ret [ 'changes' ] = { 'old' : [ ] , 'new' : pkgs_to_install } if pkgs_satisfied : comment_msg . append ( 'Package(s) \'{0}\' satisfied by {1}' . format ( ', ' . join ( pkg_list ) , ', ' . join ( pkgs_satisfied ) ) ) ret [ 'result' ] = True ret [ 'comment' ] = '. ' . join ( comment_msg ) return ret if not pkgs_to_install : ret [ 'result' ] = True ret [ 'comment' ] = ( 'Package(s) \'{0}\' satisfied by {1}' . format ( ', ' . join ( pkg_list ) , ', ' . join ( pkgs_satisfied ) ) ) return ret try : cmd_args = { 'dir' : dir , 'runas' : user , 'registry' : registry , 'env' : env , 'pkgs' : pkg_list , } call = __salt__ [ 'npm.install' ] ( ** cmd_args ) except ( CommandNotFoundError , CommandExecutionError ) as err : ret [ 'result' ] = False ret [ 'comment' ] = 'Error installing \'{0}\': {1}' . format ( ', ' . join ( pkg_list ) , err ) return ret if call and ( isinstance ( call , list ) or isinstance ( call , dict ) ) : ret [ 'result' ] = True ret [ 'changes' ] = { 'old' : [ ] , 'new' : pkgs_to_install } ret [ 'comment' ] = 'Package(s) \'{0}\' successfully installed' . format ( ', ' . join ( pkgs_to_install ) ) else : ret [ 'result' ] = False ret [ 'comment' ] = 'Could not install package(s) \'{0}\'' . format ( ', ' . join ( pkg_list ) ) return ret
Verify that the given package is installed and is at the correct version
    (if specified).

    .. code-block:: yaml

        coffee-script:
          npm.installed:
            - user: someuser

        coffee-script@1.0.1:
          npm.installed: []

    name
        The package to install

        .. versionchanged:: 2014.7.2
            This parameter is no longer lowercased by salt so that
            case-sensitive NPM package names will work.

    pkgs
        A list of packages to install with a single npm invocation; specifying
        this argument will ignore the ``name`` argument

        .. versionadded:: 2014.7.0

    dir
        The target directory in which to install the package, or None for
        global installation

    user
        The user to run NPM with

        .. versionadded:: 0.17.0

    registry
        The NPM registry from which to install the package

        .. versionadded:: 2014.7.0

    env
        A list of environment variables to be set prior to execution. The
        format is the same as the :py:func:`cmd.run <salt.states.cmd.run>`.
        state function.

        .. versionadded:: 2014.7.0

    force_reinstall
        Install the package even if it is already installed