Пример #1
0
    def get_grains(self):
        client = SSHClient()
        output = client.cmd(tgt='*',
                            fun='grains.item host \
				hwaddr_interfaces ip4_interfaces')
        olist = sorted(output.keys())
        data = {}
        for o in olist:
            if output[o]['retcode'] == 0:
                out = output[o]['return']
                data[out['host']] = output[o]['return']
        return data
Пример #2
0
def salt_ssh(target, command):
    from salt.client.ssh.client import SSHClient
    client = SSHClient(c_path='/etc/salt/master')
    client.opts['ssh_skip_roster'] = True
    client.opts['raw_shell'] = True
    command = client.cmd(tgt=target, fun=command)
    server = next(iter(command))
    retcode =  command[server]['retcode']
    stdout = command[server]['stdout']
    stderr = command[server]['stderr']
    if retcode == 0:
        return stdout,retcode
    else:
        return stderr,retcode
Пример #3
0
    def get_bonding_opts(self, host, bond):
        # maybe tell it where the python lib is?
        client = SSHClient()
        # can't do hosts without a roster file, gotta build that
        # or figure something out
        mcmd = [
            "grep BONDING_OPTS \
			/etc/sysconfig/network-scripts/ifcfg-%s" % bond
        ]
        output = client.cmd(tgt=host, fun='cmd.run', arg=mcmd)
        olist = sorted(output.keys())
        for o in olist:
            if output[o]['retcode'] == 0:
                out = output[o]['return'].lower()
        return out
Пример #4
0
def paramiko_ssh(target_list, command):
    res = []
    k = RSAKey.from_private_key_file("/root/.ssh/id_rsa")
    ssh_status = False
    c = SSHClient()
    c.set_missing_host_key_policy(AutoAddPolicy())
    for target in target_list:
        try:
            c.connect( hostname = target, username = "******", pkey = k )
            ssh_status = True
        except:
            return ssh_status, ''
        stdin , stdout, stderr = c.exec_command(command)
        res.append(dict(status=ssh_status, stdout=stdout.read().decode('utf8').strip()))
    return res
Пример #5
0
    def get_real_macs(self, host, bond):
        # maybe tell it where the python lib is?
        client = SSHClient()
        # can't do hosts without a roster file, gotta build that
        # or figure something out
        mcmd = ["cat /proc/net/bonding/%s | egrep 'addr|Slave I'" % bond]

        output = client.cmd(tgt=host, fun='cmd.run', arg=mcmd)
        olist = sorted(output.keys())
        realnic = {}
        for o in olist:
            if output[o]['retcode'] == 0:
                out = output[o]['return'].split('\n')
                l = [x.split()[-1] for x in out]
                realmacs = dict(zip(*[iter(l)] * 2))
        return realmacs
Пример #6
0
def push_config(request):
    c = SSHClient()
    if request.is_ajax():
        rst = {}
        regions = Region.objects.all()
        action = request.GET.get('action')

        rst = {}
        for i in regions:
            region = i.region
            roster = ''
            roster_path = './media/salt/region_sls'
            if not os.path.exists(roster_path):
                os.makedirs(roster_path)
            host_list = SaltHost.objects.filter(region=i)
            for h in host_list:
                for u in h.user.all():
                    roster = roster + '''%s-%s:
  host: %s
  port: %s
  user: %s
  passwd: %s
  thin_dir: /home/%s/.salt-thin
  timeout: 30\n''' % (h.ip, u.username, h.ip, h.port, u.username, u.password,
                      u.username)
            with open('%s/roster_%s' % (roster_path, region), 'w') as f:
                f.write(roster)

            if action == 'push':
                r = {}
                ret = c.cmd(tgt='*',
                            fun='state.sls',
                            roster_file='%s/roster_%s' % (roster_path, region),
                            arg=['grains.%s' % region])
                rst = dict(rst, **ret)
            elif action == 'refresh':
                ## refresh grains
                ret = c.cmd(tgt='*',
                            fun='saltutil.sync_grains',
                            roster_file='%s/roster_%s' % (roster_path, region))
                rst = dict(rst, **ret)
            else:
                raise Http404
        return HttpResponse(json.dumps(rst))
Пример #7
0
    def __attrs_post_init__(self):
        """Do post init."""
        self._client = SSHClient(c_path=str(self.c_path))

        # if self.roster_file is None:
        #     path = USER_SHARED_PILLAR.all_hosts_path(
        #         'roster.sls'
        #     )
        #     if not path.exists():
        #        path = GLUSTERFS_VOLUME_PILLAR_DIR.all_hosts_path(
        #            'roster.sls'
        #        )

        #    if path.exists():
        #        self.roster_file = path

        if self.roster_file:
            logger.debug(f'default roster is set to {self.roster_file}')
            self._def_roster_data = load_yaml(self.roster_file)
Пример #8
0
def job_exec_nginx(host_list, dest_file, bid_list, port, sls, roster_file, desc):
    c = SSHClient()
    data = {'dest_file': dest_file, 'backends': bid_list, 'port': port}
    result_source = c.cmd(tgt=host_list, fun='state.sls', roster_file=roster_file,
                          arg=[sls, 'pillar=%s' % json.dumps(data)], expr_form='list')
    result = []
    keys = result_source.keys()
    keys.sort()
    for i in keys:
        value = result_source[i]
        t_upstream = {}
        t_reload = {}
        ret = 0
        if value['retcode'] != 0:
            ret = 103
        for k, v in value['return'].items():
            keys = k.split('|')
            if keys[-1] == '-replace':
                t_upstream[keys[1]] = {'comment': v['comment'], 'result': v['result']}
            if keys[1] == '-nginx-reload_':
                if v['changes']:
                    if v['changes']['retcode'] != 0:
                        ret = 102 # test failed
                    t_reload = {'comment': v['comment'], 'result': v['result'], 'retcode': v['changes']['retcode'], 'stderr': v['changes']['stderr']}
                else:
                    t_reload = {'comment': v['comment'], 'result': v['result']}
                if v['comment'] == 'onlyif execution failed':
                    ret = 99

        r = {'host': i, 'backends': t_upstream, 'reload': t_reload, 'retcode': value['retcode'], 'ret': ret}
        result.append(r)

        if value['retcode'] == 0 and ret == 0:
            temp = '成功'
        elif ret == 99:
            temp = '失败:配置无任何变更'
        elif ret == 102:
            temp = '失败:配置测试不通过'
        else:
            temp = '失败:未知异常'
        logger.info('{0}nginx服务器{1}后端{2}:{3}{4},返回信息:{5}'.format(desc, i, bid_list, port, temp, r))

    return {'result': result, 'source': result_source}
Пример #9
0
def log_tail(request):
    if request.is_ajax():
        pid = request.POST.get('pid')
        host = request.POST.get('host')
        project = Project.objects.get(pk=pid)
        roster_file = os.path.join(
            BASE_DIR, 'media/salt/project_roster/roster_hostgroup_%s' %
            (project.host_group.id))
        data = {'puser': project.host_group.user, 'dpath': project.path}
        sls = 'log_tail'
        c = SSHClient()
        r = c.cmd(tgt=host,
                  fun='state.sls',
                  roster_file=roster_file,
                  arg=[sls, 'pillar=%s' % json.dumps(data)],
                  expr_form='list')
        ret = ''
        for _, v in r.items():
            for _, v1 in v['return'].items():
                ret = v1['changes']['stdout']
        return JsonResponse({'retcode': 0, 'result': ret})
Пример #10
0
    def create_host_files(self):
        client = SSHClient()
        try:
            output = client.cmd(tgt='*', fun='grains.item fqdn_ip4 fqdn host')
        except:
            raise CommandError(self, "Host target is incorrect.")

        f = open('/etc/salt/roster', 'w')
        h = open('/etc/hosts', 'a')
        for o in output:
            if output[o]['retcode'] == 0:
                name = output[o]['return']['host']
                ip = output[o]['return']['fqdn_ip4'][0]
                fqdn = output[o]['return']['fqdn']
                f.write("%s: %s\n" % (name, ip))
                h.write("%s %s %s\n" % (ip, fqdn, name))
            else:
                msg = "Unable to access %s. " % o
                msg += " via ssh. It will not be included."
                print msg

        f.close()
        h.close()
Пример #11
0
    def post(self, id):
        username = self.get_secure_cookie('username')
        permission = permissiondict[username]['permission']
        global SALTRET
        global ecount
        SALTRET = 'Just init value'

        if id == 'server_initialization':
            PACKAGE = self.get_argument('package')
            PACKAGE_LINE = [
                i.split() for i in PACKAGE.encode('utf-8').split('\r\n')
            ]
            logger.debug('%s, PACKAGE_LINE: %s', username, PACKAGE_LINE)
            SALTCMD = 'Host Initialization'
            SALT_FUN = 'host.init'

            ecount = 0
            SALTRET = []
            SALTRET.append('')
            ## 需要加入主机名和IP地址不重复验证
            ## 日后再加
            for ELMENT in PACKAGE_LINE:
                j = ' '.join(ELMENT)
                if len(ELMENT) < 2:
                    ecount += 1
                    SALTRET.append({j: 1})
                else:
                    SALTRET.append({j: 0})
            logger.debug('%s, ecount: %s SALTRET: %s', username, ecount,
                         SALTRET)
            if ecount > 0:
                SALTRET[0] = '下列标红的行所提供之信息不完整,请修正后重新提交: '
                self.render('result.html',
                            SALTCOMMAND=SALTCMD,
                            ECOUNT=ecount,
                            SALTRESULT=SALTRET,
                            FLAGID=id,
                            MENUDICT=menudict,
                            SALTFUNCTION=SALT_FUN,
                            PERMISSION=permission)

            else:
                ret_usertype = 0
                PACKAGE_DICT = {}
                HOSTNAME_DICT = {}
                ROSTER_CONF = '.roster_' + str(time.time())
                for USER in ['root', 'ubuntu']:
                    if USER == 'root':
                        for ELMENT in PACKAGE_LINE:
                            if len(ELMENT) == 3:
                                PASS = ELMENT[-1]
                            else:
                                PASS = '******'
                            PACKAGE_DICT[ELMENT[1]] = {
                                'host': ELMENT[0],
                                'user': USER,
                                'passwd': PASS,
                                'port': 22
                            }
                            HOSTNAME_DICT[ELMENT[0]] = ELMENT[1]
                            PACKAGE_YAML = yaml.dump(PACKAGE_DICT)
                            logger.debug('%s, PACKAGE_YAML: %s', username,
                                         PACKAGE_YAML)
                            ROSTER_FD = open(ROSTER_CONF, 'w')
                            ROSTER_FD.write(PACKAGE_YAML)
                            ROSTER_FD.close()
                    elif USER == 'ubuntu':
                        for hosty in retb:
                            if retb[hosty] == 0:
                                PACKAGE_DICT.pop(hosty)
                            elif retb[hosty] == 1:
                                PACKAGE_DICT[hosty]['user'] = '******'

                    logger.debug('%s, PACKAGE_DICT: %s', username,
                                 PACKAGE_DICT)
                    TARGET = ','.join([i for i in HOSTNAME_DICT.values()])

                    ## 验证ssh的用户密码是否正确
                    SALTSSH_RETFILE = '.saltsshret_' + str(time.time())

                    retb = LoginVirifi(PACKAGE_DICT)
                    logger.debug('%s, The result of LoginVirifi: %s', username,
                                 retb)
                    retc = sum(retb.values())
                    if retc == 0:
                        ret_usertype = ret_usertype - 1
                        logger.debug(
                            '%s, All host LoginVirifi success,ret_usertype: %s',
                            username, ret_usertype)
                        break
                    else:
                        ret_usertype = 1
                        logger.debug(
                            '%s, All or part of host LoginVirifi fail,ret_usertype: %s',
                            username, ret_usertype)
                        continue

                ## 验证用户为ubuntu时,修改root密码与ubuntu用户密码相同
                ## ubuntu 用户修改root 密码失败暂未做处理
                if ret_usertype == 1:
                    ecount = -1
                    SALTRET = []
                    SALTRET.append('下列标红的服务器ssh登录失败,请修正后重新提交:')
                    for j in PACKAGE_LINE:
                        k = ' '.join(j)
                        if j[1] in retb.keys():
                            SALTRET.append({k: 1})
                        else:
                            SALTRET.append({k: 0})
                    logger.info('%s, ecount: %s SALTRET: %s', username, ecount,
                                SALTRET)
                    self.render('result.html',
                                SALTCOMMAND=SALTCMD,
                                ECOUNT=ecount,
                                SALTRESULT=SALTRET,
                                FLAGID=id,
                                MENUDICT=menudict,
                                SALTFUNCTION=SALT_FUN,
                                PERMISSION=permission)
                else:
                    #SALT_FUN = 'state.sls'
                    self.render('result.html',
                                SALTCOMMAND=SALTCMD,
                                ECOUNT=ecount,
                                SALTRESULT=SALTRET,
                                FLAGID=id,
                                MENUDICT=menudict,
                                SALTFUNCTION=SALT_FUN,
                                PERMISSION=permission)

                    ## 验证用户为ubuntu时,修改root密码与ubuntu用户密码相同
                    ## ubuntu 用户修改root 密码失败暂未做处理
                    if ret_usertype == 0:
                        retd = ChangePasswd(PACKAGE_DICT)
                        logger.debug('%s, The result of ChangePasswd: %s',
                                     username, retd)
                        rete = sum(retd.values())

    ## host init
                    client = SSHClient()
                    logger.debug(
                        "%s, client.cmd\(tgt=%s,fun='state.sls', arg=['inithost'],roster_file=%s,expr_form=\'list\',kwarg={'pillar':%s,}\)",
                        username, TARGET, ROSTER_CONF, HOSTNAME_DICT)
                    # rand_thin_dir=True or -W is for fixing the salt-ssh problem when minion is python2.7 and master is python2.6 can cause error below:
                    # 'AttributeError: 'module' object has no attribute 'fromstringlist
                    # refer https://github.com/saltstack/salt/issues/26584
                    RET = client.cmd(tgt=TARGET,
                                     fun='state.sls',
                                     arg=['inithost'],
                                     roster_file=ROSTER_CONF,
                                     expr_form='list',
                                     ignore_host_keys=True,
                                     rand_thin_dir=True,
                                     kwarg={'pillar': HOSTNAME_DICT})
                    logger.debug('%s, ecount: %d RET: %s', username, ecount,
                                 RET)
                    SALTRET = ret_process(RET, dtype='init')
                    logger.info('%s, SALTRET: %s', username, SALTRET)
Пример #12
0
 def __attrs_post_init__(self):
     self._client = SSHClient(c_path=str(self.c_path))
Пример #13
0
def process_info(project, hosts):
    roster_file = os.path.join(
        BASE_DIR, 'media/salt/project_roster/roster_hostgroup_%s' %
        (project.host_group.id))
    c = SSHClient()
    if project.container == 0:
        ## 显示长用户名
        rst = c.cmd(
            hosts,
            'cmd.run', [
                'ps -o ruser=LONGUSERNAME12 -eo pid,ppid,pcpu,pmem,rss,lstart,etime,cmd|grep config.file=/home/%s/%s/conf|grep -v grep|awk \'{print $1,$2,$3,$4,$5,$6,$8,$9,$10,$12}\''
                % (project.host_group.user, project.path)
            ],
            roster_file=roster_file,
            expr_form='list')

    else:
        logger.info('Process Collect')
        rst = c.cmd(
            hosts,
            'cmd.run', [
                'if [ -f /home/%s/%s/RUNNING_PID ];then ps -o ruser=LONGUSERNAME12 -eo pid,ppid,pcpu,pmem,rss,lstart,etime,cmd|grep `cat /home/%s/%s/RUNNING_PID`|grep -v grep|awk \'{print $1,$2,$3,$4,$5,$6,$8,$9,$10,$12}\';else exit 1;fi'
                % (project.host_group.user, project.path,
                   project.host_group.user, project.path)
            ],
            roster_file=roster_file,
            expr_form='list')
    logger.info('Result: %s' % rst)
    result = {}
    for k, v in rst.items():
        try:
            plist = ProcessList.objects.get(tag='%s-%s' % (project.id, k))
        except:
            plist = ProcessList()
            plist.tag = '%s-%s' % (project.id, k)
        plist.project = project
        plist.host = SaltHost.objects.get(ip=k)
        if v['retcode'] == 0 and v['return']:
            rlist = v['return'].split(' ')
            t = 0
            if "-" in rlist[9]:
                t = t + int(rlist[9].split("-")[0]) * 24 * 3600
                d = rlist[9].split("-")[1]
            else:
                d = rlist[9].split("-")[0]
            d = d.split(":")
            if len(d) == 3:
                t = t + int(d[0]) * 3600 + int(d[1]) * 60 + int(d[2])
            else:
                t = t + int(d[0]) * 60 + int(d[1])
            plist.process_user = rlist[0]
            plist.process_pid = rlist[1]
            plist.process_ppid = rlist[2]
            plist.process_cpu_per = rlist[3]
            plist.process_mem_per = rlist[4]
            plist.process_rmem = rlist[5]
            plist.process_start = '%s%s-%s' % (rlist[6], rlist[7], rlist[8])
            plist.process_etime = t
            r = "%s %s %s %s %s %s %s%s-%s %s" % (
                rlist[0], rlist[1], rlist[2], rlist[3], rlist[4], rlist[5],
                rlist[6], rlist[7], rlist[8], t)
        else:
            r = "None None None None None None None None"
            plist.process_user = None
            plist.process_pid = None
            plist.process_ppid = None
            plist.process_cpu_per = None
            plist.process_mem_per = None
            plist.process_rmem = None
            plist.process_start = None
            plist.process_etime = None
        plist.save()
        plist = ProcessList.objects.get(tag='%s-%s' % (project.id, k))
        result[plist.pk] = r

    return result
Пример #14
0
def project_config(request, template_name, pid=None):
    page_name = u'配置文件'
    content_sls = ''
    content_config = ''

    project = Project.objects.get(pk=pid)
    config_path = './media/salt/config/%s-%s' % (project.id, project.path)
    config_list = [
        i['name']
        for i in ConfigList.objects.filter(project=project).values('name')
    ]
    project_id = (project.path).replace('.', '-')
    roster_file = './media/salt/project_roster/roster_hostgroup_%s' % (
        project.host_group.id)
    # 过滤禁用主机
    host_list = project.host_group.hosts.filter(status=True)
    regions = Region.objects.all()
    rst = {'retcode': 0}
    if request.is_ajax():
        if request.method == 'POST':
            action = request.POST.get('action')
            if action == 'update':
                filename = request.POST.get('config')
                content_config = request.POST.get('content_config')
                content_sls = request.POST.get('content_sls')
                config_path = './media/salt/config/%s-%s' % (project.id,
                                                             project.path)
                file = filename.split('.')[0]
                ## 备份原文件
                shutil.copy('%s/%s.jinja' % (config_path, filename),
                            '%s/%s.jinja.bakup' % (config_path, filename))
                shutil.copy('%s/%s.ini' % (config_path, filename),
                            '%s/%s.ini.bakup' % (config_path, filename))

                try:
                    with open('%s/%s.ini' % (config_path, filename), 'w') as f:
                        f.write(content_sls)
                except:
                    content_config = 'File %s/%s not exists.' % (config_path,
                                                                 filename)

                try:
                    with open('%s/%s.jinja' % (config_path, filename),
                              'w') as f:
                        f.write(content_config)
                except:
                    content_config = 'File %s/%s not exists.' % (config_path,
                                                                 filename)

                return HttpResponse(json.dumps('ok'))

            if action == 'release':
                hosts = request.POST.get('hosts')
                filename = request.POST.get('config')
                #### test ####
                path = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
                # 重命名备份文件
                shutil.copy('%s/%s.jinja.bakup' % (config_path, filename),
                            '%s/%s.jinja.%s' % (config_path, filename, path))
                shutil.copy('%s/%s.ini.bakup' % (config_path, filename),
                            '%s/%s.ini.%s' % (config_path, filename, path))
                c = SSHClient()

                cfg = SomsParse()
                cfg.read(os.path.join(config_path, '{}.ini'.format(filename)))
                data = cfg.as_dict()
                data['puser'] = project.host_group.user
                data['dpath'] = project.path
                data['dtime'] = path
                data['pub_path'] = project.path
                data['filename'] = filename
                data['ctype'] = project.container
                data['project'] = project.id

                rst_source = c.cmd(
                    tgt=hosts,
                    fun='state.sls',
                    roster_file=roster_file,
                    arg=['job_config',
                         'pillar=%s' % json.dumps(data)],
                    expr_form='list')
                #### end #####
                rst = result_handle_config(rst_source)

                # 创建更新列表
                cbak = ConfigBackup()
                cbak.name = ConfigList.objects.filter(project=project).get(
                    name=filename)
                cbak.path = path
                cbak.content = rst
                cbak.save()

                # 记录操作日志
                ConfigLog.objects.create(user=username_auth(request),
                                         project=project.name,
                                         config=filename,
                                         action_ip=user_ip(request),
                                         content=rst,
                                         source_content=rst_source,
                                         msg_type=False)
                return HttpResponse(json.dumps(rst))

            if action == 'rollback':
                hosts = request.POST.get('hosts')
                filename = request.POST.get('config')
                version = request.POST.get('config_ver')
                # 还原对应版本文件
                shutil.copy(
                    '%s/%s.jinja.%s' % (config_path, filename, version),
                    '%s/%s.jinja' % (config_path, filename))
                c = SSHClient()
                data = {
                    'puser': project.host_group.user,
                    'project': project.id,
                    'dpath': project.path,
                    'dtime': version,
                    'pub_path': project.path,
                    'filename': filename,
                    'ctype': project.container
                }
                rst_source = c.cmd(tgt=hosts,
                                   fun='state.sls',
                                   roster_file=roster_file,
                                   arg=[
                                       'job_config_rollback',
                                       'pillar=%s' % json.dumps(data)
                                   ],
                                   expr_form='list')
                #### end #####
                rst = result_handle_config(rst_source)
                ConfigLog.objects.create(user=username_auth(request),
                                         project=project.name,
                                         config=filename,
                                         action_ip=user_ip(request),
                                         content=rst,
                                         source_content=rst_source,
                                         msg_type=True)
                return HttpResponse(json.dumps(rst))

        filename = request.GET.get('config')

        config_path = os.path.join(
            BASE_DIR, './media/salt/config/%s-%s' % (project.id, project.path))
        if not os.path.exists(config_path):
            os.makedirs(config_path)

        file = filename.split('.')[0]
        action = request.GET.get('action')
        if action == 'get':
            try:
                with open('%s/%s.ini' % (config_path, filename), 'r') as f:
                    content_sls = f.read()
            except:
                content_sls = 'File %s/%s not exists, created?' % (config_path,
                                                                   filename)
                rst['retcode'] = 1
            try:
                with open('%s/%s.jinja' % (config_path, filename), 'r') as f:
                    content_config = f.read()
            except:
                content_config = 'File %s/%s not exists, created?' % (
                    config_path, filename)
                rst['retcode'] = 1
        else:
            config = ConfigParser.RawConfigParser()
            region_query = Region.objects.all()
            if len(region_query) == 0:
                rst['retcode'] = 3
                return JsonResponse(rst)
            for i in region_query:
                config.add_section(i.region)
                config.set(i.region, 'name', i.name)
                config.set(i.region, 'region', i.region)
            with open(os.path.join(config_path, '%s.ini' % filename),
                      'w') as f:
                config.write(f)

            try:
                with open('%s/%s.jinja' % (config_path, filename), 'r') as f:
                    content_config = f.read()
            except:
                with open('%s/%s.jinja' % (config_path, filename), 'w') as f:
                    f.write(
                        "{% set region = grains['region'] %}\n#key不存在时报错:pillar[region]['key']\n"
                        +
                        "#key不存在时使用默认值:salt['pillar.get'](region + ':key', 'default')"
                    )
            # 记录文件
            clist = ConfigList()
            clist.name = filename
            clist.project = project
            clist.tag = '%s-%s' % (filename, project.id)
            clist.save()

            rst['retcode'] = 2

        rst['sls'] = content_sls
        rst['config'] = content_config

        return HttpResponse(json.dumps(rst))

    return render(
        request, template_name, {
            'page_name': page_name,
            'pid': pid,
            'all_hosts': host_list,
            'all_regions': regions,
            'project': project,
            'project_id': project_id,
            'config_list': config_list,
            'nav_tag': 'project_list'
        })
Пример #15
0
 def _client_init(self):
     self._client = SSHClient(c_path=str(self.c_path))
Пример #16
0
- :mod:`napalm proxy minion <salt.proxy.napalm>`

.. versionadded:: TBD
.. versionchanged:: TBD
'''

from __future__ import absolute_import

import salt.client
import re
import json
import salt.config
from salt.client.ssh.client import SSHClient

client = SSHClient()
local = salt.client.LocalClient()
master = salt.client.Caller()

__virtualname__ = 'nuts'


def __virtual__():
    return __virtualname__


# ----------------------------------------------------------------------------------------------------------------------
# helper functions -- will not be exported
# ----------------------------------------------------------------------------------------------------------------------